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. |
DE points to the tilemap Cache, the Tile cache should be 32 tiles wide HL 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 HL to the start of the screen VRAM, We set IXL to the height of the tilemap - assumed to be 24 for a normal sized 256x192 screen. |
![]() |
The DoStrip routine draws a sequence of IYL tiles to vram
destination HL 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 |
![]() |
DE will point to the tilemap DE' should point to the Tile Pattern data IXH is the tilemap width to draw IXL is tilemap height to draw IYH is the uncropped Tilemap Width HL 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. The routine is selected via self modifying code on systems where the code runs from RAM, and using the StripRoutine flag on systems running from ROM |
![]() |
We restored HL, 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 IX 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) MinTile isn't great as partial movements (Alignments not on an 8x8 grid) LowResX and LowResY can be used to clamp the co-ordinates to an 8x8 grid to resolve these issues. |
![]() |
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 B,C is the X,Y co-ordinate in logical units registers H,L is the Width,Height in logical units register IY 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 D,E - 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 (C)... 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 (L), 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 E and set the new 'draw position' to Ypos (C) =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 (E) and bottom (D) crop... we now use these to calculate the new height of the sprite (L). | ![]() |
We then skip over any tiles in the source (IY) 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 (B) ... 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 (H), 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 XOR 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 (D) |
![]() |
We've calculated the left and right crop... We use these
to calculate the bytes to skip after each line of our sprite
(SpriteHClip) If this is zero, there's no horizontal crop We need to crop, so we calculate the new width in H. |
![]() |
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 HL 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 HL, and our destination in
DE We now copy all the tiles under our sprite into the Cache for redraw next time. |
![]() |
Helper functions
ShiftTilemap will shift tilemap
DE to (X,Y) position BC the width of tilemap DE should be loaded into IYH |
![]() |
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. |
![]() |
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 load in our Tilemap address (of our sprite) we also 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! |
![]() |