MinTile
- Minimal Software/Hardware Tilemap
Introduction to the MinTile
Series...
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 |
 |
Lesson
MinTile1 - Basic Tilemap Drawing
The CLS routine is the basic screen redraw function.
This redraws all changed parts of the Tilemap, after which any
sprites will need to be redrawn. |
 |
V1_MinimalTile.asm
|
|
 |
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.
|
CLS to redraw the map
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
|
|
DrawTilemap for irregular sized maps
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
Zero page entry B,C is the X,Y co-ordinate in logical units
Zero page entry H,L is the Width,Height in logical units
Zero page entry 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
If the sprite is completely offscreen, there's no point trying to
draw, so we set the carry and return
|
|
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! |
|
 |
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
|
|
 |
Sprite Removal
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 add one tile if supporting half tile moves
|
 |
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!
|

Lesson
MinTile4 - Zeroing Sprites in Cache
When we move a sprite we redraw the background before drawing the
new sprite.
However, because we don't have a double buffer this causes flicker.
To reduce the flicker we zero the tiles which do not need redrawing,
as the new sprite position will overlap them... here we go!
|
 |
V1_MinimalTile.asm
|
|
 |
ZeroSpriteInCache
First we check the flag to see if we actually need to redraw
anything.
|
 |
We load our position and size
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 - If there's nothing
left, we return
|
|
We need to load in our width, and transfer the Sprite Tilemap
address to HL
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! |
 |
| |
Buy my Assembly programming book on Amazon in Print or Kindle!



Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
Buy my Assembly programming book on Amazon in Print or Kindle!



Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
|