From aeb1f47f51a2c000d2e995b9df7ac8658bec29d9 Mon Sep 17 00:00:00 2001 From: Jeremy Penner Date: Fri, 18 Jan 2019 23:03:02 -0500 Subject: [PATCH] implementing sprites drawn overtop of tilemap --- testbed.c | 188 +++++++++++++++++++++++++++++++++++++++++++--------- testbed.exe | Bin 38897 -> 64086 bytes 2 files changed, 156 insertions(+), 32 deletions(-) diff --git a/testbed.c b/testbed.c index 00c53d0..5d37102 100755 --- a/testbed.c +++ b/testbed.c @@ -21,6 +21,7 @@ #define setAllPlanes() outport(REG_TS, 0x0f02) #define VID ((volatile char far *)MK_FP(0xa000, 0)) +#define WVID ((volatile int far *)MK_FP(0xa000, 0)) void vid_cleanup() { setTextMode(); @@ -377,7 +378,7 @@ int tifLoadEGA(FILE *f, TifImageMeta_t meta, unsigned int vidOffset, int maxY, u return y; } -int tifLoad(FILE *f, TifImageMeta_t meta, unsigned int *planeBuf, int maxY, int yRepeat) { +int tifLoad(FILE *f, TifImageMeta_t meta, unsigned int *planeBuf, int maxY, int yRepeat, int planes) { int istrip; int irow; int ipixelpair; @@ -389,8 +390,9 @@ int tifLoad(FILE *f, TifImageMeta_t meta, unsigned int *planeBuf, int maxY, int unsigned int *g = b + planeStride; unsigned int *r = g + planeStride; unsigned int *i = r + planeStride; + unsigned int *m = i + planeStride; - if (meta.width > MAX_WIDTH || (meta.width % 16) != 0) { + if (meta.width > MAX_WIDTH || (meta.width % 16) != 0 || planes < 4 || planes > 5) { return 0; } @@ -413,11 +415,17 @@ int tifLoad(FILE *f, TifImageMeta_t meta, unsigned int *planeBuf, int maxY, int *++g = gpair; *++r = rpair; *++i = ipair; + if (planes == 5) { + *++m = bpair & gpair & rpair & ipair; + } } else { *b |= bpair << shift; *g |= gpair << shift; *r |= rpair << shift; *i |= ipair << shift; + if (planes == 5) { + *m |= (bpair & gpair & rpair & ipair) << shift; + } } } y++; @@ -425,10 +433,10 @@ int tifLoad(FILE *f, TifImageMeta_t meta, unsigned int *planeBuf, int maxY, int return y; } if (y % yRepeat == 0) { - b += planeStride * 3; - g += planeStride * 3; - r += planeStride * 3; - i += planeStride * 3; + b += planeStride * (planes - 1); + g += planeStride * (planes - 1); + r += planeStride * (planes - 1); + i += planeStride * (planes - 1); } } } @@ -436,12 +444,11 @@ int tifLoad(FILE *f, TifImageMeta_t meta, unsigned int *planeBuf, int maxY, int } /*** T I L E S ***/ -void prepareEgaMemCopy() { - setAllPlanes(); - setWriteMode(1); -} -#define PAGE_STRIDE 42 +#define PAGE_TILES_W 21 +#define PAGE_TILES_H 13 +#define PAGE_TILES_COUNT (PAGE_TILES_H * PAGE_TILES_W) +#define PAGE_STRIDE (PAGE_TILES_W << 1) void tile_init() { setLogicalWidth(PAGE_STRIDE >> 1); @@ -456,19 +463,33 @@ void blitTile(unsigned int offsetFrom, unsigned int offsetTo) { } } +#define D_NOTHING 0x80 +#define D_BGTILE 0x81 +#define isBufIndex(d) (!((d) & 0x80)) + +#define NUM_BUFFERS 32 +#define nextBufferIndex(i) ((i + 1) % 32) +#define BUF_WSTRIDE 16 +#define BUF_WSIZE (BUF_WSTRIDE * 4) typedef struct { unsigned int w; unsigned int h; int scrollX; int scrollY; unsigned int pageOffset[2]; - unsigned char currentPage; + unsigned char dirty[2][PAGE_TILES_COUNT]; unsigned int tilesOffset; unsigned int *memTiles; unsigned char *map; + unsigned int buffer[NUM_BUFFERS][BUF_WSIZE]; + unsigned int bufferOffset[NUM_BUFFERS]; + unsigned char currentPage; + unsigned char nextBuffer; + unsigned char firstBuffer; } TiledScreen_t; -TiledScreen_t screen = { 0, 0, 0, 0, { 0x0400, 0x2620 }, 0, 0, NULL, NULL }; +TiledScreen_t screen = { 0, 0, 0, 0, { 0x0400, 0x2620 }, 0, 0, NULL, NULL, + 0, 0, 0, 0, 0 }; void loadTiles(unsigned int tilesOffset, unsigned int *memTiles) { screen.tilesOffset = tilesOffset; @@ -481,11 +502,85 @@ void loadMap(unsigned char *map, unsigned int w, unsigned int h) { screen.h = h; } +int prepareBuffer(int pageX, int pageY) { + unsigned char *dirty = &screen.dirty[screen.currentPage][pageX + (pageY * PAGE_TILES_W)]; + if (!isBufIndex(*dirty)) { + unsigned int startX = screen.scrollX >> 4; + unsigned int startY = screen.scrollY >> 4; + unsigned char tile = screen.map[startX + pageX + ((startY + pageY) * screen.w)]; + unsigned char ibuffer = screen.nextBuffer; + screen.nextBuffer = nextBufferIndex(screen.nextBuffer); + *dirty = ibuffer; + memcpy(screen.buffer[ibuffer], &screen.memTiles[tile * BUF_WSIZE], BUF_WSIZE); + screen.bufferOffset[ibuffer] = screen.pageOffset[screen.currentPage] + + (pageX << 1) + (pageY * PAGE_STRIDE); + } + return *dirty; +} + +void drawSpriteToBuf(unsigned int *sprite, int pageX, int pageY, int shift, int yStart) { + unsigned int *buf, *mask; + int y, h, plane; + if (pageX < 0 || pageY < 0 || + pageX >= PAGE_TILES_W || pageY >= PAGE_TILES_H || + shift >= 16 || shift <= -16 || + yStart <= -16 || yStart >= 16) { + return; + } + + buf = screen.buffer[prepareBuffer(pageX, pageY)]; +/* if (yStart < 0) { + sprite = &sprite[-yStart]; + h = yStart + 16; + } else { + buf = &buf[yStart]; + h = 16 - yStart; + } + mask = &sprite[16 * 4]; + if (shift < 0) { + shift = -shift; + for (plane = 0; plane < 4; plane ++) { + for (y = 0; y < h; y ++) { + buf[y] = (buf[y] & ~(mask[y] << shift)) | (sprite[y] << shift); + } + sprite += BUF_WSTRIDE; + buf += BUF_WSTRIDE; + } + } else { + for (plane = 0; plane < 4; plane ++) { + for (y = 0; y < h; y ++) { + buf[y] = (buf[y] & ~(mask[y] >> shift)) | (sprite[y] >> shift); + } + sprite += BUF_WSTRIDE; + buf += BUF_WSTRIDE; + } + }*/ +} + +void drawSprite(unsigned int *sprite, int x, int y) { + unsigned int startX = screen.scrollX >> 4; + unsigned int startY = screen.scrollY >> 4; + int pageX = (x - startX) >> 4; + int pageY = (y - startY) >> 4; + int pageOffsetX = x - (pageX << 4); + int pageOffsetY = y - (pageY << 4); + + drawSpriteToBuf(sprite, pageX, pageY, pageOffsetX, pageOffsetY); + drawSpriteToBuf(sprite, pageX + 1, pageY, pageOffsetX - 16, pageOffsetY); + drawSpriteToBuf(sprite, pageX, pageY + 1, pageOffsetX, pageOffsetY - 16); + drawSpriteToBuf(sprite, pageX + 1, pageY + 1, pageOffsetX - 16, pageOffsetY - 16); +} + void scroll(int x, int y) { + unsigned int startX = screen.scrollX >> 4; + unsigned int startY = screen.scrollY >> 4; x = min(max(x, 0), (screen.w << 4) - 320); y = min(max(y, 0), (screen.h << 4) - 176); screen.scrollX = x; screen.scrollY = y; + if (startX != (x >> 4) || startY != (y >> 4)) { + memset(screen.dirty, D_BGTILE, 2 * PAGE_TILES_COUNT); + } } void drawScreen() { @@ -495,27 +590,51 @@ void drawScreen() { unsigned int offsetY = screen.scrollY - (startY << 4); unsigned int drawOffset = screen.pageOffset[screen.currentPage]; unsigned int scrollOffset = drawOffset + (offsetX >> 3) + (offsetY * PAGE_STRIDE); - unsigned int x, y; + unsigned char *dirty = screen.dirty[screen.currentPage]; + unsigned int x, y, di, plane; - prepareEgaMemCopy(); + setAllPlanes(); + setWriteMode(1); - for (y = startY; y < startY + 13; y ++) { - for (x = startX; x < startX + 21; x ++) { - blitTile(screen.tilesOffset + screen.map[x + (y * screen.w)], drawOffset); + di = 0; + for (y = startY; y < startY + PAGE_TILES_H; y ++) { + for (x = startX; x < startX + PAGE_TILES_W; x ++) { + if (dirty[di++] == D_BGTILE) { + blitTile(screen.tilesOffset + screen.map[x + (y * screen.w)], drawOffset); + } drawOffset += 2; } drawOffset += PAGE_STRIDE * 15; } - + setWriteMode(0); + for(plane = 0; plane < 4; plane ++) { + setPlane(plane); + for (di = screen.firstBuffer; di != screen.nextBuffer; di = nextBufferIndex(di)) { + drawOffset = screen.bufferOffset[di] >> 1; + for (y = 0; y < 16; y ++) { + WVID[drawOffset] = screen.buffer[di][y + (BUF_WSTRIDE * plane)]; + drawOffset += PAGE_STRIDE >> 1; + } + } + } + setAllPlanes(); setDisplayOffset(scrollOffset); screen.currentPage ^= 1; + screen.firstBuffer = screen.nextBuffer; + for (di = 0; di < PAGE_TILES_COUNT; di ++) { + dirty[di] = isBufIndex(dirty[di]) ? D_BGTILE : D_NOTHING; + } } /*** S C R A T C H ***/ - -unsigned int tiles[2*16 * 128]; +#define NUM_TILES 128 +#define NUM_SPRITES 64 #define OFF_TILES 0x4840 +unsigned int tiles[NUM_TILES][4*16]; +unsigned int sprites[NUM_SPRITES][5*16]; + + unsigned char map[10000]; void fillMap() { @@ -546,8 +665,13 @@ void game_init() { f = fopen("TILES.TIF", "rb"); meta = tifLoadMeta(f); - tifLoadEGA(f, meta, OFF_TILES, 256, 16); - tifLoad(f, meta, tiles, 128 * 16, 16); + tifLoadEGA(f, meta, OFF_TILES, NUM_TILES * 16, 16); + tifLoad(f, meta, tiles, NUM_TILES * 16, 16, 4); + fclose(f); + + f = fopen("SPRITE.TIF", "rb"); + meta = tifLoadMeta(f); + tifLoad(f, meta, sprites, NUM_SPRITES * 16, 16, 5); fclose(f); setSplitScreen(351); @@ -558,8 +682,8 @@ void game_init() { } int main() { - int x; - int y; + int x = 100; + int y = 100; int z = 0; game_init(); @@ -567,13 +691,13 @@ int main() { while (!keyPressed(K_ESC)) { - x = screen.scrollX; - y = screen.scrollY; - if (keyPressed(K_LEFT)) x -= 8; - if (keyPressed(K_RIGHT)) x += 8; - if (keyPressed(K_UP)) y -= 8; - if (keyPressed(K_DOWN)) y += 8; - scroll(x, y); + if (keyPressed(K_LEFT)) x -= 1; + if (keyPressed(K_RIGHT)) x += 1; + if (keyPressed(K_UP)) y -= 1; + if (keyPressed(K_DOWN)) y += 1; + scroll(x + 152, y + 90); + drawSprite(sprites[0], 50, 50); + drawSprite(sprites[1], x, y); drawScreen(); z++; diff --git a/testbed.exe b/testbed.exe index ad63d16c3bced37db13e733b087c3907020096c5..ffcb4e3e9ad30da4ecd78c0c7836f307d3ad5d84 100755 GIT binary patch delta 10043 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