MinTile is a simple Tilemap demo it was used to create the Z80
based 'ChibiFighter' Game. While Mintile is designed for a tilemap, small tile grids can be used to simulate 'sprites', and supports X flip with these sprites. Where hardware tile support is available (eg SMS) it will be used, where it is not (eg CPC) sofware tiles will be used, however both work in the exact same way to allow a game to be ported more easily |
![]() |
MinTile is a simple tile
drawing routine, it's designed to work in the same way on all
systems, whether they have hardware tilemap or not! It provides 256 patterns per tilemap, and X-flip for sprites, but not Y-flip A future version called MaxTile is planned to expand the functionality. |
![]() |
![]() |
We actually have
two background tilemaps! The main one is 36 tiles wide (32 + 4 for scrolling) and stores the current background tiles The other is the Cache, which is 32 tiles wide, a Zero tile is an unchanged tile which doesn't need drawing. |
A2 points to the tilemap Cache, the Tile cache should be 32 tiles wide A5 points to the Tile Pattern Data, this needs to be correct for the platform specific drawing routine We shift these into the shadow registers, and point A6 to the start of the screen VRAM, We set D7 to the height of the tilemap - assumed to be 24 for a normal sized 256x192 screen. |
![]() |
The DoStrip routine draws a sequence of D7 tiles to vram
destination A6 This is a platform specific routine which will draw the single 8 pixel tall horizontal strip |
![]() |
When DoStrip returns, HL will point to the end of a
line, however we may need to make a correction to HL to point it
to the following line before we continue. This is platform specific, as how the screen memory layout works varies on each system |
![]() |
We repeat until all 24 horizontal strips are drawn to the screen |
![]() |
Mintile has no real sprite
support, however a small tilemap can be drawn to the screen at an
irregular offset to simulate a sprite DrawTilemap will perform the draw |
![]() |
A2 will point to the tilemap A5 should point to the Tile Pattern data D3 is the tilemap width to draw D6 is tilemap height to draw D7 is the uncropped Tilemap Width A6 is the Vram Dest We have two strip drawing routines. DoStrip Will draw normally. DoStripRev Will draw the tilemap in reverse, moving backwards through the lines of thetilemap, and flipping the pattern data. |
![]() |
We restored A6, so the VRAM destination is pointing
to the start of the tilemap. We need do move down 8 pixels to do the next line - how we do this depends on the platform. |
![]() |
After the line has been drawn, we move down one line in the
tilemap. If the 'sprite' are partially offscreen, we may have cropped it, so we add the width of the whole tilemap before continuing. |
![]() |
DrawTilemapRev Will enable the alternative tilemap drawing routine, before switching the function back on completion. | ![]() |
Lesson
MinTile2 - Sprites As mentioned, Mintile can use small tilemaps to simulate sprites. However before we can draw a tile sprite, we need to handle cropping, and maybe flipping |
![]() |
V1_MinimalTile.asm
|
![]() |
Sprites
Before we actually try to draw a sprite we first check if it's
'flagged' The flag marks that a sprite has actually changed, so needs drawing back to the screen. |
![]() |
When using Drawsprite, A4 should should point to the object with
the sprite parameters. We load the sprites attributes in. Co-ordinates are in Logical units (2 pixels per 1 Logical unit) We load the address of the bitmap pattern data, on some systems we need an alternative address for flipped data |
![]() |
We need to crop the sprite object so it can go partially
offscreen. If the sprite is entirely offscreen, the carry will be set and we just return |
![]() |
We need to calculate our VRAM destination with platform specific
function GetScreenPos We draw the sprite to the screen, flipped if required by the flags. |
![]() |
Cropping
Our cropping routine will work out the X,Y pos in bytes, and width
and height + any skipped tiles from the source data registers D1,D4 is the X,Y co-ordinate in logical units registers D3,D6 is the Width,Height in logical units register A2 is the address of the source bitmap data. The drawing routines can't actually support flipping very well, so we actually flip the X co-ordinate before performing the crop if Xflip is enabled |
![]() |
First we zero D2,D5 - they are used for temp values Ok... lets crop the top of the sprite... First we remove the ypos of the first visible pixel from the draw ypos (D4)... if the result is greater than zero, then nothing is off the screen at the top. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the height of our sprite (D6), if the amount to crop is not less than the height then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the top, we store this in D5 and set the new 'draw position' to Ypos (D4) =0 |
![]() |
Next we do the same for the bottom, We add the height to the Ypos, and subtract the height of the logical screen, if it's over the screen height (greater than zero) we need to crop again - the result is the amount to crop We AND with %11111100 to convert to a tile count |
![]() |
We've calculated the top (D5) and bottom (D2) crop... we now use these to calculate the new height of the sprite (D6). | ![]() |
We then skip over any tiles in the source tilemap (A2) based on
the number of lines of tiles we need to remove from the top. |
![]() |
Now we do the same for the X axis First we remove the xpos of the first visible tile from the draw Xpos (D1) ... if the result is greater than zero, then nothing is off the screen at the left. if the result is less than zero we need to crop... we convert the negative to a positive and compare to the width of our sprite (D3), if the amount to crop is not less than the width then the sprite is completely offscreen. Anything else is the number of lines we need to remove from the left, we store this in E and set the new 'draw position' to the correct partial tile offset for the draw position, via an EOR and AND |
![]() |
Next we do the same for the right, We add the width (H) to the Xpos, and subtract the width of the logical screen, if it's over the screen width (greater than zero) we need to crop again - the result is the amount to crop from the right (D2) |
![]() |
We've calculated the left and right crop... If this is zero,
there's no horizontal crop Otherwise We need to crop, so we calculate the new width in D3. |
![]() |
We need to alter the start Tile to compensate for any left hand
clipping We clear the carry to record that the crop resulted in something to draw! |
![]() |
If the sprite is completely offscreen, there's no point trying to draw, so we set the carry and return | ![]() |
![]() |
MinTile has
to crop to a whole number of 8x8 pixel tiles. This is because the draw routines are hard coded to an 8x8 limit for efficiency. |
Lesson
MinTile3 - Sprites Redraw When a sprite moves we need to redraw it! We need to work out what was under the sprite, and update it the cache |
![]() |
V1_MinimalTile.asm
|
![]() |
Before we do anything with the sprite we load in the previous
position and size (the one we need to redraw with the background) We round to a number of tiles. |
![]() |
To work out the correct area We crop the sprite, then convert the
size and position to a number of tiles. |
![]() |
We need to update the Tile Cache with the tiles that need
redrawing. ShiftTilemap will shift the position in the cache so A3 points to the first tile under the sprite. Tilemap2 contains the tilemap for the background, we add the scroll Offset (the leftmost visible tile), and shift the tilemap to get the top left tile under our sprite. |
![]() |
Ok! We have our source tilemap pos in A3, and our destination in
A2 We now copy all the tiles under our sprite into the Cache for redraw next time. |
![]() |
Helper functions
ShiftTilemap will shift tilemap
A2 to (X,Y) position D1,D4 the width of tilemap DE should be loaded into D7 |
![]() |
DoCropLogicalToTile converts an
X,Y position and a Width,Height from logical units to tiles. There is 2 pixels per logical unit there is 8 pixels per tile So we just divide by 4! We set the Z flag if the result is zero (nothing to do) |
![]() |
It's not related to sprites, but ChangeScroll
is another little routine. When we want to scroll the background, we need to update the Cache with the tiles for the new tilemap position. However, assuming there are repeating areas in the tilemap and we move in whole tiles, there may be no need to draw anything! - The new tile, and the old tile may be exactly the same! ChangeScroll checks this, only updating where the tile for the new scroll position is different. |
![]() |
![]() |
As we have no
double buffer, Redrawing the sprite causes it to flicker. We can reduce this, by working out what tiles will be covered by the new sprite position, and not redrawing those, but we'll cover that next time! |
ZeroSpriteInCache
First we check the flag to see if we actually need to redraw
anything. |
![]() |
If we are offset by a half tile, the edges will need to be
redrawn, In this case we increase the X/Y pos by 1/2 tile, and decrease the Width/Height by 1/2 tile. |
![]() |
We need to load in our Tilemap address (of our sprite) Next we need to perform the Docrop routine to work out the area of the sprite actually onscreen. We convert our co-ordinates to tile numbers |
![]() |
We need to offset the cache, so we're pointing DE to the address of the tile under the topleft corner of the sprite. |
![]() |
If we're X-flipped we need to use self modifying code (or
variables if running from ROM) These switch how the 'Zero' routine works, reversing the order we move through the sprite tilemap. |
![]() |
We now need to actually perform the Zero. For the range decided, we load in each tilenumber of the sprite, and if it's not zero (something to draw), we zero the entry in the cache for the matching area. This ensures that anything which will be covered by the new sprite will not have it's background redrawn, reducing the flicker. |
![]() |
The Zeroing
routine is rather crude, it doesn't do a great job of coping with
sprites with 'holes' in them, causing graphical glitches. The planned successor MaxTile will have a 'Smarter' zeroing routine which will stop this! |
![]() |