Suck Hunt is a little demo game which uses the 'MinTile'
software tilemap (or hardware on SMS) for graphics, ChibiTracks
for music and presents a little fighting game with an AI CPU
player. It runs on CPC / MSX2 / Spectrum NEXT and SAM coupe |
![]() |
Here is our game screen. The background is 32x20 tiles. We'll split up the background map into 4x4 tile supertiles. The supertilemap will therefore have 8x5 entries (40 supertiles) The actual map will use one nibble per supertile definition - meaning 16 supertiles max. This means each 32x20 level background uses a supertile map of just 20 bytes |
![]() |
Here are the tile patterns we've defined |
![]() |
We combine these into supertile definitions. The Supertile definitions are also compressed - each 16 tile supertile is stored in 8 bytes. this is achieved by storing each tile as 2 bit definition, with a 4 bit tile lookup.... This means we can only use 4 unique tiles in each supertile. |
![]() |
Here is the supertile map that defines the green play area. The first byte is the height of the supertile map (it must be the full width of the screen - 8 supertiles) The next two bytes are a pointer to the supertile definitions. The remaining bytes are the supertile numbers, one nibble per supertile |
![]() |
The actual supertile definitions each use 8 bytes. The first 4 bytes are the Index/LookUp Table of the 4 tile pattern numbers that are used in this supertile. The next 4 bytes are the 16 tiles that make up this 4x4 supertile... Each byte is one line of the supertile, pairs of bits define which Index entry in the LUT define the pattern number for that tile. |
![]() |
LoadSuperTileMapNibble will
'decompress' the supertilemap into the tilemap. IXL is the First tile pattern num (pattern offset) - this is used to skip the font for the background graphics HL is the Source Supertilemap HL' is the Destination Tilemap in RAM - this is used to actually draw the screen. With MinTile the RAM tilemap is 36 tiles wide... this is to allow a 4 tile 'scrollable' area We load in each byte, and split it's two nibbles, processing them with CopyOneSupertile4ByteLut |
![]() |
A is the Supertile Definition Num IY is the address of the Source Supertile definitions HL' is the Destination Tilemap RAM IXL is the offset tile num (in pattern data) Each supertile definition is 8 bytes, so we first multiply A by 8, and add it to the source definitions in IY - storing the result in DE The first 4 bytes are the Index/LUT of tile numbers The second 4 bytes are the 2 bit lookup entries - each byte is one line of the 4x4 supertile. We next shift into the supertile numbers, by setting bit 2 of DE We load a byte into C - each tile is defined by 2 bits so this is a whole line. we read 2 bits at a time, and read the actual tilenumber from the index, and put this in VRAM in HL after each line of the supertile, we move down in the tilemap vram by adding 32 (32+4 = 36... the width of the tilemap in VRAM) |
![]() |
Here we use the supertile code to draw the player life bars at
the top of the screen |
![]() |
Lesson
CF2 - Titlescreen and GameStart Lets take a look at the 'compressed' titlescreen code, and the game init routine |
![]() |
See
ChibiFighter
folder
|
![]() |
Titlescreen
Lets draw our title screen! | ![]() |
At the start of the game routine, we INIT the AI vars (in ram), we
do this by copying from the template (Maybe ROM) We set the 'songtimer' - this is for the CPC interrupt handler, it's not actually needed on other systems. We start the first song for the title screen. |
![]() |
We need to draw the 'ChibiFighter' logo onto the tilemap
(TileCache) The tilemap for the logo is up to 32x13... but we've used some crude compression. Many of the tiles are 0 (black) - so lets 'compress them' The tilemap only uses <128 tiles, so the top bit of the tilenumber is free. If a line has a large string of zero tiles we'll store it a a 1 byte 'skip X tiles' where the top bit is 1... %1nnnnnnn where n is the tiles to skip. if the line has ended we will end with a %11111111 (255) byte and skip to the next line. This is very simple 'compression' but greatly reduces the tilemap size. |
![]() |
Here is the tilemap data |
![]() |
After the graphic we show the 'Hyper futile' text, and wait for
fire. |
![]() |
New Game
'NewBattle' is executed at the start of each new opponent battle. The first game we reset the OpponentNum to 0 We reset the sprites from the template. |
![]() |
The ResetRound function clears all the level and player vars back
to their defaults. |
![]() |
The next sequence is rather long and annoying! Really it just loads up the sprite data for the player and cpu characters, positions their sprites and shows the VS message. GetPlayerSettings loads in the defaults for a character into sprite IX GetSubSettings loads in the defaults for the 'sub sprite' (Fireball atack) AnimateSprite updates the sprite animation, and sets the size and pattern settings from the default sprite number RepaintScreen draws the background tilemap (and text) DrawPlayers draws the sprite characters ![]() On The SMS it also loads in the 128 tiles for the player / Character into pattern data (overwriting the title screen patterns) |
![]() |
The Getplayer settings functions load in the various parameters of
the player / enemy The enemy sprite also defines the background and level music. |
![]() |
Here are the contents of part of the playerdefs table. |
![]() |
New Round
Battle commences until someone wins two rounds. This means there can be up to 3 rounds (if there are no draws). Before each round we show a "Round 1" "Fight" type message. First we need to initialize the background and reposition the player sprite. Then we show the text. We pause before text messages. ![]() After this message, we're ready to start gameplay! |
![]() |
Lesson
CF3 - MainLoop and Round End Lets take a look at the main game loop, and the code that handles the end of a round |
![]() |
See
ChibiFighter
folder
|
![]() |
Main Loop
The first task is to update
the 'ticks' - this is used to control timed animation events. Next we read in keypresses from the joystick. |
![]() |
We want to update our two players. We use two routines. UpdateFighter handles the input. AnimateSprite updates the graphics bases on scripted sequences - we'll see details of these later. We use the same routines for the human and CPU, but the ReadAI routine is used to control the CPU - it returns a virtual 'joystick keypress' by the computer, so we can easily switch to Human VS Human, or even CPU vs CPU! |
![]() |
Next we check if the round time limit has been reached. |
![]() |
If it hasn't we update the time. The counter shows 60 ticks, but there is a second hidden timer RoundTime2, which counts the time until the point to reduce the visible ticks. |
![]() |
Next we repaint the tilemap with RepaintScreen,
and draw the sprites with DrawPlayers |
![]() |
Next we handle the collision detection. As well as the main sprites, we have 2 sub sprites for the fireball attack. We check if there has been a collision between a player's punches/kicks and fireballs. |
![]() |
Collision detection
First we check the opponents life. If the opponents life has reached zero, the player has won. |
![]() |
Next we check the life of the object - a fireball which is
offscreen will have a life of zero. We also check if the attack is <0 a negative value is a block... an attack of zero is no attack (just standing around!) |
![]() |
Next we check the distance of the attacker from the opponent - if
the attacker is too far away, the attack missed. We also check if the opponent is blocking (attack <0 - bit 7 nonzero) - if so the attack missed. In these cases the attack missed, so we clear the attack to 0 |
![]() ![]() |
The attack has HIT!... so we give the attacker a point. and redraw the scores. | ![]() |
We move the amount of 'attack' of the attacker to the 'hurt' of the opponent - this will drain the injured parties life over the next few ticks. | ![]() |
Round End
If the time runs out we work out which player has the most life
left and run PlayerWins with IX pointing to the winner. If both players have the same lif, it's a draw! |
![]() |
The winner has been decided - we add 1 to their wins, and show
their name to the screen. |
![]() |
We now check how many wins each player has If the CPU has 2 wins the game is over. If the HUMAN has 2 wins, it's time for the next challenger. Otherwise it's just another round. |
![]() |
If the human has lost we show a simple gameover screen. |
![]() |
Lesson
CF4 - Drawing routines While the graphics are handled by 'Mintile' (My tilemap code), we have to do some setup to get it working with our code. |
![]() |
See
ChibiFighter
folder
|
![]() |
DrawPlayers
Mintile uses the same code for the tilemap as the sprites. We need to use selfmodifying code to change the way the tilemap works to switch it to sprite mode! On some systems we are running from ROM, so instead of selfmodifying, we use 'TileClear' The ChibiFighter Graphics are 4 color bitmaps, even on 16 color systems, we use a 'Tint', effectively selecting one of 4x four color palettes |
![]() |
We run the DrawSprite routines to show the CPU and Human players |
![]() |
Next we draw the fireballs to the screen, but only if the objects
are 'alive' |
![]() |
Font code
Character drawing also goes through 'MinTile' The 'Mintile routine uses a 32x24 tilecache - which keeps track of updates which are needed to the the onscreen tilemap. A 'Zero' in the cache means 'skip the tile'... any other value is a tile change. Once the tile is written to the screen, it's reset to zero in the cache. |
![]() |
Screen Drawing
Repaint screen is a general update of the screen. This sets up the MinTile code to draw the tilemap (zeroing the cache after drawing) the 'CLS' routine will transfer the cache to the screen. |
![]() |
Blackout screen wipes the screen to the empty tile (1) This clears both the cache, and the background tilemap, it's used for Gameover and the title screen. |
![]() |
DrawScreen is a general refresh of the background objects. First we redraw the Life bar area at the top of the screen. Then we transfer the entire background tilemap into the cache - this forces MinTile to do a complete redraw. |
![]() |
We've only drawn the empty bar so far, we need to draw the filled
life bars and player names. We do this for the human and CPU player |
![]() |
We draw the other text objects, Time and players score. |
![]() |
The Draw time prints 1 BCD byte to the screen (0-60) PrintPlayerName will show the character name, and also shows the 'wins' The round ends with 2 wins, so we only show 0 or 1 icon. |
![]() |
The life bars move towards the center of the screen They are made up of 3 parts. A filled part, based on the top 4 bits of the life A half filled part, based on bit 3 of the life, which may or may not be drawn. A blank part, which is the remainder of the bar. ![]() |
![]() |
The Animators list
Our animator routine supports up to 15 animation types 1-15. Each has a pointer to the script in a lookup table. Animator 0 is 'no animator' - a static sprite! |
![]() |
The first byte of an animator is the 'speed' of the animation
(0=fastest 1=every other frame 3=every 1/4th frame etc) There can be up to 16 lines in the script each line takes 4 bytes. The first byte is the animation command, the 3 other bytes are parameters relating to that command |
![]() |
We define symbols for the possible animation commands, There is a lookup table for the code which performs the animation action. |
![]() |
Animation handling
Our ObjectAnimator routine is executed with IX pointing to the
sprite to be animated, and A containing the animator byte. The animator byte is in the format: TTTTAAAA ... T=Tick / A=Animator meaning a maximum of 16 animators, and 16 lines per animator script (ticks) |
![]() |
GetAnimatorMempos backs the animator byte up into B, and loads the
pointer to the current animation script into HL |
![]() |
The top nibble of the animator byte is the current tick - the line
of the script we need to run, We effectively multiply this nibble by 4, and add this offset to the current animator script pos. We get the command number - the first of the 4 bytes of the line of the script, and execute it via the 'VectorJump' subroutine. This uses ex (sp),hl to effectively jump to HL, while preserving the old value of HL which was pushed onto the stack. |
![]() |
VectorLookup will read a 16 bit word from the table of addresses
HL A is used as the entry number, and the result is returned in HL |
![]() |
Animation Commands
At the end of each script we need to update the current tick (line
number) of the script. |
![]() ![]() |
The Sprite option changes the current graphic. The first parameter is the sprite number. The second byte is a flag to force a repaint of the tilemap - Mintile has trouble with half tile moves of complex shaped sprites and this can help reduce glitches The Third parameter is the direction of the sprite - 0 leaves it unchanged, 1 faces Right (0 in the flags) 2 faces Left (1 in the flags) |
![]() |
Move will make an immediate relative 8 bit move to the sprite's
X,Y,Z pos X is for moving left and right, Y is for jumping, Z is for moving 'In and Out' of the screen (like TMNT or Renegade) If the player is facing left DoXFlip_IX will reverse the X move. |
![]() |
Attack will set the attack, and optionally a sprite. This is used for punching or blocking. The sprite for the 'punch' will be set and a >0 attack will be set... or <0 for a block! |
![]() |
Loop is used for the walking animation, which will continue until we stop moving. | ![]() |
Spawn is used to spawn the sub object (fireball) The pointer to the sub object is loaded in from the SubSprite pointer, and the new object is initialized from the settings array passed in the parameter. |
![]() ![]() |
![]() |
The animator code supports complex condition
based loops, and executions to subroutines, but they were not
needed for this game. The unused action types can also be re-written to do anything you want! |
UpdateFighter - Actions!
At the start of the routine we
check if the player is 'hurt' If the player is injured, they are on the floor, and they cannot move of their own volition. We check the Animator is set to 6 (the injury animator) |
![]() |
The D register contains the bits of the joystick in the format
%4321RLDU We can jump with Fire 2, but We may not have a fire 2! Fire 2 is a 'synonym' for Down+Fire1 which will also jump! |
![]() |
We don't want the player to be able to punch or block forever, so
we have a 'timeout' between Fires! It will only decrease once Fire is released, and stops the player punching again too quickly |
![]() |
We buffer 8 recent keypresses for special moves. |
![]() |
Next we check if fire is pressed, If it is we check the timeout. The player isn't allowed to hold down fire, the timeout stops this, and clears the keypress if the player hit fire recently |
![]() |
We have a special move... Fireball! The sequence to perform a fireball is Down, Down-Right, Right+Fire (or the reverse if facing left) |
![]() |
CmpSeq will compare recent keypressses to DE We handle the direction the player is facing via DoXJoyFlip_IX which will handle the flipping of Left/Right if pressed |
![]() |
Next we check if the player has tried to jump - this only works if
the player is on the ground (Ypos=152) If the player can jump we set Spr_Ymove - this will decrease the Ypos over time until Spr_Ymove=0 |
![]() |
We check the down direction... If down+Fire are pressed we block |
![]() |
Fire on it's own will punch Fire+ Left or Right will kick |
![]() |
If an action was decided we set it now! |
![]() |
UpdateFighter - Movements!
If no directions we pressed, we reduce the timeout on the buffer
(used for special moves) We clear the buffer when the timeout hits zero. |
![]() |
If the player is moving left/right AND is on the ground AND it's
not already set, we need to set the animator to 1 If the player has stopped moving and was walking we set the animator to 0 |
![]() |
Next we check up and down keys. These move in and out of the screen |
![]() |
We check Left and right, and set bit 0 of the flags accordingly Flags=1 will flip the sprite - making it face left. |
![]() |
Next we check the players position The player can be 'pushed' offscreen by an attack, but will slide back in over the next few frames. We the run the AnimateSprite routine which will handle other movements. |
![]() |
Lesson
CF7 - Animate Sprite The AnimateSprite routine handles automatic movements outside of player control. |
![]() |
See
ChibiFighter
folder
|
![]() |
AnimateSprite
Before we do anything we check the objects life, An object with a life of zero does not need updating! |
![]() |
W next load in the pointer to the animation script command list. If the sprites animator isn't zero, we run the Animator script routines, and update the current animator number. |
![]() |
We need to update the position! We need to factor in three things! Firstly the 16 bit XposW,YposW and ZposW are the center point, so we need to subtract half the width hand height for the final drawing positions Spr_Xpos and Spr_Ypos Second we need to update the draw Ypos position based on the 'depth' Zpos Third we need to back up the old width and height - as the new sprite graphic may change them! |
![]() |
Before we draw it The sprite number may have changed, so we load
in the info for the current image from the SpriteInfo table. |
![]() |
We now process the sprite moves, If the sprite is attempting to
jump we move it up the screen. If not, and the sprite is above the ground (Ypos<152) we move it closer to the ground. |
![]() |
We now process any moves being applied to the X position | ![]() |
Finally we run the 'remove and zero' routines. The 'remove sprite routine takes the sprite off the screen - redrawing the tilemap underneath. the 'Zero' routine attempts to reduce flicker, by stopping the redraw of parts which will be filled by the new sprite - unfortunately it's not 100% accurate when sprites move a 1/2 tile! |
![]() |
Maths!
The Random Number Generator used by ChibiFighter is a fairly crude
which uses a 16 bit seed. |
![]() |
Collision detection for Punches and Kicks uses the position of two
objects We're going to check if object IX is in range of IY, Where IX is the attacker, and IY is the victim! The range is fixed &0C10 - 12x16 units. |
![]() |
The range checking routine is the one we've used before. | ![]() |
Lesson
CF8 - CPU AI The AI routine makes a decision on the computers actions. These are passed to the main routine as a simulated 'Keypress' Lets take a peek! |
![]() |
See
ChibiFighter
folder
|
![]() |
AI Routines
The possible move sequences for the CPU are stored in a table 'Strategy List'. Each move sequence is 8 bytes. The first byte is a delay between each direction. the rest of the bytes are Keypresses (when bit 7=1) or a 'sequence change' for chaining sequences (when bit 7=0) Strategy list format: Speed,Keypress,Keypress,Keypress Keypress %1XXXXXXX = Keypress match (%--21RLDU) Strategy %0XXXXXXX = Jump to new strategy |
![]() |
ReadAI will return a keypress in
the Accumulator. If the Timeout for the last keypress has not reached zero,we just return the same value. |
![]() |
We load in the current strategy. Zero means the CPU is still thinking what to do |
![]() |
Each Strategy uses 8 bytes, we calculate the offset to the current
strategy and load in the Delay time. We then shift to the current 'tick' - ant therefore the new keypress. In theory we can chain strategies by setting the top bit of the strategy byte to 0, but actually this was never used in the final game. |
![]() |
Deciding a new strategy We combine D and E to decide our final strategy, We use random data to make the cpu unpredictable, we store this in B We compare the player (IY) and CPU (IX) position to decide what to do. |
![]() |
We compare the two characters, and see if they are at the same Z
position on the ground. If they are not, we move the CPU closer to the player |
![]() |
If the Z position is the same We consider if the CPU should jump, there is effectively a 1/4 chance the CPU will decide to jump. | ![]() |
We now decide if the CPU needs to move Left or Right to get closer to the player. | ![]() |
If the Player is far away, we may decide to throw a fireball,
otherwise we'll punch or kick! Which we do is random!... The direction of the punch or kick is based on the D register |
![]() |
There is a very small chance of a fireball attack, just 1 in 16. | ![]() |
At the end we set the new AI Strategy | ![]() |
When the CPU is thinking, the
CPU will block for a while and think. We slow down the CPU to make it dumber to give the human more chance to retaliate. |
![]() |
![]() |
Lesson
CF9 - Amstrad CPC code The Tile drawing and sound routines are platform specific. Lets take a look at the CPC version. |
![]() |
See
ChibiFighter
folder
|
![]() |
Sound Routines
Music and SFX are performed by ChibiTracks. In addition to the song Chibitracks needs various includes to work. |
![]() |
We install a jump to the IM2 interrupt handler at address
&0038 |
![]() |
Our music is designed to play back at 50hz, but there are 6
interrupts per screen redraw on the CPC, We use a 'SongTimer', and only update the sound every 1 in 6 interrupts. |
![]() |
Before our music can play, we must set the song base and call
StartSong |
![]() |
We can effect SFX by forcibly playing instruments on one of the
channels. This uses the function 'InstPlaySFX' which executes 'CommonSFXModule' CommonSFXModule, if defined is also used by ChibiTracker for the same purpose. |
![]() |
Tile Drawing Routines
The MinTile shared routine provides calculation and tilemap
planning, however we need to do the actual job of 'Drawing' in the
platform code. The code can horizontally flip tiles, on the Amstrad CPC we use a 256 byte lookup table, to 'pre-calculate the flipping of the 4 pixels in a Mode 1 byte |
![]() |
The LUT needs to be byte aligned, so &8100 or &FE00 would be fine, but &FE01 would not! | ![]() |
We have a 'GetScreenPos' function , which calculates the VRAM
destination for the sprite objects. |
![]() |
DoStrip will draw one horizontal strip of tiles. The job is defined by the following registers HL = VRAM Dest BC' = Tilemap DE' = Pattern data IYL = TileMap Width The tilemap will either be the TileCache (for the background) or a mini tilemap (for sprites) If using the tile cache we zero the tiles after they are drawn we use stack misuse to speed up loading the pattern data |
![]() |
DoStripRev has essentially the same function, however it
horizontally flips the pattern data via the lookup table. |
![]() ![]() |
The rest of the
Tilemap code is a multiplatform module called 'MinTile' We'll look at that soon in a separate series! |
![]() |
![]() |
Lesson
CF10 - Sam Coupe code The Tile drawing and sound routines are platform specific. Lets take a look at the SAM version. |
![]() |
See
ChibiFighter
folder
|
![]() |
Sound Routines
Music and SFX are performed by ChibiTracks. In addition to the song Chibitracks needs various includes to work. |
![]() |
We're going to use IM1 for interrupts, which means we need RAM at
memory address &0038. We load our program in at memory address &8000, but we page the bank into the &0000-7FFF rage, and use the &8000+ range for Vram |
![]() |
We install a jump to the IM2 interrupt handler at address
&0038 |
![]() |
The ChibiTracks update runs in the interrupt handler, it
uses AF,BC,DE,HL and IX only |
![]() |
Before our music can play, we must set the song base and call
StartSong |
![]() |
We can effect SFX by forcibly playing instruments on one of the
channels. This uses the function 'InstPlaySFX' which executes 'CommonSFXModule' CommonSFXModule, if defined is also used by ChibiTracker for the same purpose. |
![]() |
Tile Drawing Routines
The MinTile shared routine provides calculation and tilemap planning, however we need to do the actual job of 'Drawing' in the platform code.
We have a 'GetScreenPos' function , which calculates the VRAM
destination for the sprite objects. |
![]() |
DoStrip will draw one horizontal strip of tiles. The job is defined by the following registers HL = VRAM Dest BC' = Tilemap DE' = Pattern data IYL = TileMap Width The tilemap will either be the TileCache (for the background) or a mini tilemap (for sprites) If using the tile cache we zero the tiles after they are drawn We use 4 color pattern data, and convert it up to 16 color. Each byte of the pattern data contains color info for 4 pixels (2bpp = 4 color). A 'Tint' is used to select one of 4x four color palettes. We load the bits into C, and shift them into 2 screen bytes (4 pixels). If the source byte is zero, we just write directly, to save a little bit shifting time! We have to do this twice for each 8 pixel (2 byte) wide tile. |
![]() ![]() |
DoStripRev has essentially the same function, however
the bitshifts are reversed to swap the pairs of pixels |
![]() ![]() ![]() |
The rest of the
Tilemap code is a multiplatform module called 'MinTile' We'll look at that soon in a separate series! |
![]() |
![]() |
Lesson
CF11 - SMS code The Tile drawing and sound routines are platform specific. Lets take a look at the SMS version. |
![]() |
See
ChibiFighter
folder
|
![]() |
Sound Routines
Music and SFX are performed by ChibiTracks. In addition to the song Chibitracks needs various includes to work. |
![]() |
We're going to use IM1 for interrupts, we need to put a Jump at memory address &0038 in the header | ![]() |
We need to set bit 5 of VDP Register 1 to enable the Vblank
interrupt |
![]() |
The ChibiTracks update runs in the interrupt handler, it
uses AF,BC,DE,HL and IX only On the SMS we need to read in the VDP control port &BF to clear the interrupt - we don't actually need to do anything with the read data! |
![]() |
Before our music can play, we must set the song base and call
StartSong |
![]() |
We can effect SFX by forcibly playing instruments on one of the
channels. This uses the function 'InstPlaySFX' which executes 'CommonSFXModule' CommonSFXModule, if defined is also used by ChibiTracker for the same purpose. |
![]() |
Tile Drawing Routines
The MinTile shared routine provides calculation and tilemap planning, however we need to do the actual job of 'Drawing' in the platform code.
Our Tilemap data is actually 4 color - 2 bitplanes. We 'Tint' the 4 color data, by using IX as a constant value for the tip 2 bitplanes - effectively giving us 4 palettes of 4 colors. |
![]() |
We define the 4x 4 color palettes, two for our background, and two for our players | ![]() |
The tile loading routines define IX with the top two bitplane
values to select the 'palette' |
![]() |
We have a 'GetScreenPos' function , which calculates the VRAM
destination for the sprite objects. |
![]() |
DoStrip will draw one horizontal strip of tiles. The job is defined by the following registers HL = VRAM Dest BC' = Tilemap DE' = Pattern data IYL = TileMap Width The tilemap will either be the TileCache (for the background) or a mini tilemap (for sprites) If using the tile cache we zero the tiles after they are drawn We transfer the tile numbers into the tilemap - each uses the 2 byte tile number (with attributes) |
![]() |
DoStripRev has essentially the same function, however
the X-flip bit is set in the attributes |
![]() |
The rest of the
Tilemap code is a multiplatform module called 'MinTile' We'll look at that soon in a separate series! |
![]() |
![]() |
Lesson
CF12 - Spectrum Next code The Tile drawing and sound routines are platform specific. Lets take a look at the ZXN version. |
![]() |
See
ChibiFighter
folder
|
![]() |
Sound Routines
Music and SFX are performed by ChibiTracks. In addition to the song Chibitracks needs various includes to work. |
![]() |
We're going to use IM1 for interrupts, We need to put a Jump at memory address &0038, but on the spectrum this is ROM. We can page in RAM at the &0000-1FFF range with NextReg &50 |
![]() |
The ChibiTracks update runs in the interrupt handler, it
uses AF,BC,DE,HL and IX only |
![]() |
Before our music can play, we must set the song base and call
StartSong |
![]() |
We can effect SFX by forcibly playing instruments on one of the
channels. This uses the function 'InstPlaySFX' which executes 'CommonSFXModule' CommonSFXModule, if defined is also used by ChibiTracker for the same purpose. |
![]() |
Tile Drawing Routines
The MinTile shared routine provides calculation and tilemap planning, however we need to do the actual job of 'Drawing' in the platform code.
We use a 1 byte per color palette We select the palette with reg &40, and send the values with &41 |
![]() |
Although the screen is 256 color, We use a total of 16 colors We define the 4x 4 color palettes, two for our background, and two for our players |
![]() |
We use a 'TileTint' to select one of the 4 palettes |
![]() |
We have a 'GetScreenPos' function , which calculates the VRAM
destination for the sprite objects. We page in the screen VRAM into the &2000-3FFF range. |
![]() |
DoStrip will draw
one horizontal strip of tiles. The job is defined by the following registers HL = VRAM Dest BC' = Tilemap DE' = Pattern data IYL = TileMap Width The tilemap will either be the TileCache (for the background) or a mini tilemap (for sprites) If using the tile cache we zero the tiles after they are drawn The source bitmap data is 4 color, so we multiply the tile number by 16 to get the pattern data address |
![]() |
We read in a byte from the DE source. We take 2 bits from this, shift them in position and OR in the tint from B We do this 4 times (4 pixels) before reading in a second byte. After 2 bytes we've done a full line. |
![]() |
We repeat for 8 lines, then move across one tile and repeat for
the rest of the strup |
![]() |
DoStripRev has
essentially the same function, however we draw the pixels from right
to left, reversing the pattern bitmap |
![]() ![]() |
The rest of the
Tilemap code is a multiplatform module called 'MinTile' We'll look at that soon in a separate series! |
![]() |
![]() |
Lesson
CF13 - MSX2 code The Tile drawing and sound routines are platform specific. Lets take a look at the MSX2 version. |
![]() |
See
ChibiFighter
folder
|
![]() |
Sound Routines
Music and SFX are performed by ChibiTracks. In addition to the song Chibitracks needs various includes to work. |
![]() |
We're going to use IM1 for interrupts, By default on the MSX2, the low area is ROM, so &0038 is redirected to the firmware interrupt handler, however we can redirect this by patching a jump to our own interrupt hander at address &FD9A |
![]() |
We also need to set bit 5 of Mode Register 1 to enable the VBlank interrupt | ![]() |
The ChibiTracks update runs in the interrupt handler, it
uses AF,BC,DE,HL and IX only When we return we pass control back to the firmware interrupt handler, so we do not enable interrupts before returning |
![]() |
Before our music can play, we must set the song base and call
StartSong |
![]() |
We can effect SFX by forcibly playing instruments on one of the
channels. This uses the function 'InstPlaySFX' which executes 'CommonSFXModule' CommonSFXModule, if defined is also used by ChibiTracker for the same purpose. |
![]() |
Tile Drawing Routines
The MinTile shared routine provides calculation and tilemap planning, however we need to do the actual job of 'Drawing' in the platform code.
We need to transfer our tile patterns to the hidden area of VRAM
(Lines 256+) For sprites we need to store a normal copy, and an Xflipped copy. |
![]() |
We're running in 16 color mode, but our source patterns are only 4
color We define the 4x 4 color palettes, two for our background, and two for our players |
![]() |
We send our tile patterns to Vram, applying a 'Tint' to select one
of the 4 colors There are two versions 'SendTiles' for background graphics, and 'SendTilesDuo' which sends a regular tile and Xflips the tile. |
![]() |
We transfer the 16 bytes of the source pattern. we
use a HMMC command to send the data. Each pattern byte contains 4 pixels. We have formatted the source data to allow it to easily be converted. We mask the first two pixels and OR in our tint, sending them to the VDP. We then load the data again, bit shift it 2 pixels, and mask the second two pixels. |
![]() |
SendTilesDUO will define the pattern, once normally,
and once x-flipped 128 Y-lines down Each line of our tile pattern is 2 bytes, we move to the right of the tile, and rotate the bytes of the source data to Xflip them |
![]() |
DoStrip will draw
one horizontal strip of tiles. The job is defined by the following registers HL = X,Y pos in VRAM BC' = Tilemap DE' = Pattern data IYL = TileMap Width The tilemap will either be the TileCache (for the background) or a mini tilemap (for sprites) If using the tile cache we zero the tiles after they are drawn We calculate the source pos in VRAM for the tile we want to transfer and use HMMM to write the tile to the screen. |
![]() |
We repeat for 8 lines, then move across one tile and repeat for
the rest of the strup |
![]() |
DoStripRev has
essentially the same function, however we use the flipped version of
the tiles |
![]() |
The rest of the
Tilemap code is a multiplatform module called 'MinTile' We'll look at that soon in a separate series! |
![]() |