Well as this forum is quite silent, I'll start commenting out some internals about this project:
Chapter 1: Choosing the tile size
With a modern PC a 2D game such as Skool Daze, could be done by having the graphic of the School in memory and using a big backbuffer with the whole screen area. Sprites will be drawn there and a technique such as “dirty-rectangles” will be used to update the areas of the screen which need to be redrawn by simply dumping them from that backbuffer. This is not possible in an 8-bit computer of the old era, due to memory and speed constraints.
A tile-based game divides graphical data into small regions, called tiles, which are then rendered into the screen forming the playing area. This is an excellent way to minimize memory usage, as we will store just the identifiers of the tiles for a given position, instead of the whole graphic, as long as the graphic of the tiles is repeated enough times throughout the playing area. One can have a tile to represent a chair, or a section of a door or a wall or a tree…, and then repeat the graphic many times without actually repeating the graphical data.
This can be done in isometric games (as Space:1999) as well as in 2D games, such as it is done in Skool Daze.
Usually tiles are of a fixed size (though this is not strictly necessary) and a few big tiles will use little memory, but produce a quite repetitive play area, while a lot of small tiles will use more memory, but probably much richer graphics. Due to the Oric’s funky display, the tile width will be a multiple of 6 pixels (to use an integer number of bytes). Also to optimize the drawing a good tile height could be 6 pixels. This way we can use a 6x6 pixel tile, which is square and also can be rendered quite fast with a code such as the next one. Let the zero page variable tmp be a pointer to the screen position where we want to dump a tile graphic (which is stored as a set of 6 consecutive bytes starting at address labeled “backbuffer”:
Code: Select all
ldy #0
lda backbuffer
sta (tmp),y
ldy #40
lda backbuffer+1
sta (tmp),y
...
lda backbuffer+4
sta (tmp),y
ldy #200
lda backbuffer+5
sta (tmp),y
Of course what we will have in “backbuffer” is not just the background graphic – it will include any sprite portion that overlaps with that tile (more on that later). You can write more compact code, of course, but we will need all these drawing routines to
blaze.
So there you are. Just design your graphics as composition of tiles (the more they are re-used, the lower the memory needs) and store the tile number (or identification) in the map, for instance as an array: char map[ntiles_x, ntiles_y] (that will give you as much as 255 different tiles). More on efficient storing and accessing will come.
The problem comes when trying to port an existing game. Most 8-bit computers have 8-pixels per byte of graphical data (such as the speccy), so expect those games to use 8x8 or 16x16 tiles (32x32 for the classical Filmation games, such as Knight Lore). And what does that mean, you may ask? Problems. That is what it means.
The playing area in Skool Daze is 768x168 (a total of 16128 bytes, way too much!), divided into a grid of 96x21 (2016 bytes) tiles of 8x8 pixels. Though we can split this image in tiles of 6x6, resulting in a grid of 128x28 (3584 bytes), tile borders will not match walls, floors and objects as you expect. So either you redesign graphics completely or you’ll have problems. And it can be even worse. A character is made of a grid of 3x4 tiles (24x32 pixels). The width is again a multiple of 6, but the height isn’t. Try to redesign such small graphics so they fit into 24x30… and even if the result is good, you’ll have other problems. The character position now is not in the second column, but half in the 2nd and half in the 3rd. And also you will have to update and move (and store) more data, so everything will be slower and the data size will also be bigger.
So what did I do? As I am not good at drawing graphics and I also wanted the final game to look as Skool Daze, I decided to use 6x8 tiles and made small tweaks to the school background graphic to make walls, stairs, etc. match tile borders. You can see the results in the pics of my first post: the characters are the same and the school is nearly the same (though internally I had to break my head to solve several problems).
Dumping a tile therefore becomes:
Code: Select all
ldy #0
lda backbuffer
sta (tmp),y
ldy #40
lda backbuffer+1
sta (tmp),y
...
ldy #240
lda backbuffer+6
sta (tmp),y
; Unfortunately, there is one scan left
lda tmp
clc
adc #240
bcc nocarry
inc tmp+1
nocarry
sta tmp
ldy #40
lda backbuffer+7
sta (tmp),y
The whole process to update a tile (tx,ty) in screen involves a little more than just this. The backbuffer is used to avoid flickering and there are three main steps:
1- Find the tile graphical data from the tile id of the map (map(tx,ty)) and dump into the backbuffer. The pointer to the data is put in the lda instruction with self-modifying code to make it faster (smc_udgp+1,+2).
Code: Select all
ldx #7
loop
smc_udgp
lda $1235,x
sta backbuffer,x
dex
bpl loop
2- For each sprite see if any of its tiles is at the tx,ty position and, if so, get pointers to both the graphic and mask of that tile and dump it into the backbuffer. Again self-modifying code is used to alter the addresses at smc_mask_p+1,+2 and smc_graphic_p+1+2 to gain some cycles:
Code: Select all
ldy #7
loopcopy
lda backbuffer,y
#ifdef AIC_SUPPORT
bpl ScreenNoInverse
eor #63
ScreenNoInverse
#endif
smc_mask_p
and $1234,y
smc_graphic_p
ora $1234,y
sta backbuffer,y
dey
bpl loopcopy
In both cases the tilecode zero means “blank” and is special-cased. The code between the #ifdef AIC_SUPPORT #endif directives is the trick posted by Twilighte to support AIC mode coloring…
3- Dump the backbuffer at the screen position, as explained before.
And this is all basically. Quite fast in the end… this is something mandatory if you want to be able to move many characters in a big playing area at the same time.
Of course it is vital to be able to get the pointer to the graphic data as fast as possible for a given tile position, which means an efficient storage of the map in memory. Also, to avoid running out of memory a backbuffer of just one tile (8 bytes) will be used, and only tiles which need to be updated will be at each frame, and just once of course. But that will be explained on a subsequent post.
Don't hesitate on commenting... you can have a look at the sources in the repository here (file engine.s):
http://miniserve.defence-force.org/svn/ ... skooldaze/
Cheers.