| Lesson Aku1
- Screen Co-ordinates and Text Drawing
Before we can really start looking at the code, we need to understand the basics of how the game was designed.
The first thing we'll look at is how graphics and screen co-ordinates work within the ChibiAkumas 'Akuyou' Game engine.
|Chibi akumas was designed for the Amstrad CPC
'Mode 1', which has a
320x200 screen, with 4 bits per pixel... this means there are 80 bytes
across, and 200 lines of bytes down.
Because the 'Bullet hell' style I wanted for the game would require a huge amount of on screen action, I knew I had to always aim to be as fast as possible!... the Z80 is an 8 bit CPU, so is strong only at numbers from 0-255
Also, I needed some way of handling objects that were partially on screen (Sprite clipping), so I needed the game to use XY co-ordinates that did not go above 255, and mapped well onto the CPC screen.
I decided that the 320x200 CPC screen would be treated as 160x200 giving 2 x co-ordinates per byte (while most sprites only move in single bytes, this allowed bullets to use the left or right half of one byte for smoother movement)
As I had already decided my sprites would typically be 24x24 pixels in size, I then added a 24 pixel border on all sides which would be the 'off screen zone' for sprite clipping.
This gave a virtual screen of 208x248, with a central visible window of 160x200... (24,24)-(184,224) is visible
Unfortunately, this did not account for the MSX & spectrum's smaller screen, I needed to alter the visible area - but keep the virtual one the same, so I removed 8 pixels from the sky... as more objects are ground based than sky - so fewer objects to reposition
I also removed 16 pixels from either side of the visible screen. so on those systems V1.666 has a visible screen of (40,32)-(168,224)
|On an 8 bit system...
always limited... by keeping XY co-ordinates to 0-255 we only need 2
bytes per object... so 256 bullets will need 512 bytes... if we allowed
the x co-ordinate to go up to 320... it would have been 768 bytes!!!
Memory isn't the only problem... 8 bit CPU's don't like working in 16 bit - so keeping everything under 256 saved speed AND memory!
processing power terms the Turbo-R version of ChibiAkumas COULD have
had a lot smoother movement, however the co-ordinate system meant it
was impossible for objects to move in less than 2 pixels
the X axis
Unfortunately 8 bit programming means coping with such limitations... and their unexpected consequences!
work differently, they are based on the CPC's firmware co-ordinate
of 0-39 columns, and 0-24 rows
As much of the games text is centered, I removed 4 columns from each side, and the bottom row, so on the MSX and spectrum visible text co-ordinates are now from 4-35 across 0-23 down
for future games that use a new game engine, I will probably resize the CPC screen to 256x192 so that all the systems use the same screen size (It also allows the CPC to use the faster INC L instead of INC HL in sprite commands)... it was something I considered during the development of the of the original game, but it seemed backwards as the game was a horizontal shooter, and I did not want the game to be accused of being a 'Speccy Port'x
|Lets get some text on the screen using the Akuyou game
First we need to select our font... 2 is the normal font... 1 is the mini font (half width)
Next we need to define how many characters to show...(used for the 'boss text where the letters type themselves)... this is stored in register I... but the speccy can't do I registers - so we use a macro 'LDIA' which will work on all systems
Next we set our text position using HL - 0400 is the top left on the MSX & Speccy, and slightly inset on the CPC
Finally we point BC to our &80 terminated text string... and the text will show on screen!
the Speccy we need to use Interrupt Mode 2, so the I register is in
use.... the LDIA macro uses a memory address to simulate the I register
on the spectrum, and the real register on other systems...
It was probably a mistake to use the I register at all.. but it seemed like a good idea at the time!
|The Akuyou Core handles Game play, graphics and
The Bootstrap handles level loading, continue and game over, and the redefine keys functions...
Whenever we change Bootstrap.asm or the core files, we need to recompile.
To do this, Load Build.ASM... enable ONE (and only one) of the compiler symbols... BuildCPC BuildENT.... BuildMSX or BuildZX and compile
the core will be rebuilt for the system selected.
| Lesson Aku2
As well as a position, stars and objects all need a movement direction... once again, for efficiency this had to be a single byte.
Generally speaking Movements are the same for stars and objects, however the 'advanced' movements (above 128) are only available to objects
It should be noted that 'stars' (bullets) are handled by the same code for enemies and players.
|Basic moves are
defined when the top bit 7 is 0... the remaining bits are in
X is horizontal move from -4 to +3
Y is the vertical move from -4 to +3
D is the 'speed doubler' which will increase the move speed
|When the first bit
of the move
byte is 1 then the move is an 'advanced' move... note these only work
with objects, they cannot be used by Stars... (the stars use a simpler
version of the code)
'Background moves' are for background objects (like the castle in Chibiakumas' they move far slower than any other object in the game, and move in the same direction as the scroll.
'Seekers' target a player 'mveSeeker' will target one of the living players (alternates each time)... this is used by coins, and certain enemies
'Wave' is a wave movement used by episode 1 - Note this is obsolete and is not supported by the latest game engine - the 'Animators' function added with EP2 is far more advanced and completely replaces this function
'Custom moves' are also mostly useless now.. they effectively passed all the object parameters to a piece of level code to handle the movement - but again most (or all) of this can now be done far better (and with less debugging) by animators!
We'll cover animators in a later lesson
Moves and Waves are made irrelevent by the 'Animators'... these were
added in EP2, and allow scripted 'timed' changes to movement direction,
animation and enemy firing...
They're very flexible, and need much less debugging than 'Custom Moves' that had to be written in ASM code!
We'll cover 'Animators' in a later lesson!
|We use this in the
'Event Stream'... a series of bytes in the level code that define
objects that appear in the level...
We'll look at the 'event stream' properly later, but lets have a quick look!... take a look at 'Level_Single.asm"
An event needs a time - in this example, we've put 2 commands at time '10'
First we turn off animators, by setting the animator to 0...
We've created an Enemy that can take 1 hit
It uses 2 frames of animation with sprite 15
It's X position is the middle of the screen ... remember! the screen is 160 visible units wide, with a hidden border of 24 - so 80+24 is the middle!
it's Y position is slightly off middle... remember! the screen is 200 visible units tall, with a hidden border of 24
The Move is &23... look at the 'Basic movements' chart above -try some other byte values from &00-&7F... and see what happens!
little example was just something to get you started...
There is far more to discuss on the 'Event Stream' and 'Animators' - but it's too much for now, we'll come back to them later!
| Lesson Aku3
- Sprite Basics
We had a look at creating an object last week, this week lets change the sprite into something new!
We'll have a look at the file formats, and the basics of the sprite editor!
|Binary Sprite file format (CPC/ENT/ZX/SAM)
Akusprite files that have been saved for a platform such as the CPC will have a header... this header has 6 bytes per sprite... the length of the header can vary, it just needs to be big enough to hold all the sprites
Byte 1 is the height of the sprite data in lines
Byte 2 is the Width of the sprite in bytes
Byte 3 is the Y offset... a 16 pixel sprite may have 4 blank lines at the top... in this case the sprite would have a height of 12 and an offset of 4... this is to save memory
Byte 4 is the Settings... the top bit (7) defines if the sprite is transparent (slow) or PSET (fast)... bit 5 defines if the sprite changes or accepts the background color on the speccy... other bits also select the transparent color (bytemask) on cpc
Bits 5 and 6 define the offset to the bitmap data of the sprite in the file... in this case the sprite starts at &0100... this is defined in the spriteeditor by 'SpriteDataOffset' in the Spritelist tab
These 6 bytes will be repeated for the next sprite, and so on
The bitmap data of the sprites appears after the header
|Because of the way
the VDP works MSX files have the sprite data in a RLE bitmap... the
format of the header is different, and uses 10 bytes per sprite
Byte 1 is the height of the sprite data in lines
Byte 2 is the Width of the sprite in bytes
Byte 3 is the Y offset... a 16 pixel sprite may have 4 blank lines at the top... in this case the sprite would have a height of 12 and an offset of 4... this is to save memory
Byte 4 is the Settings... the top bit (7) defines if the sprite is transparent (slow) or PSET (fast)... bit 5 defines if the sprite changes or accepts the background color on the speccy... other bits also select the transparent color (bytemask) on cpc
Bytes 5 and 6 are the X position of the sprite data in the RLE bitmap in pixels
Bytes 7 and 8 are the Y position of the sprite data in the RLE bitmap in pixels
Byte 9 is the Width of the sprite in pixels (0 means 256 pixels wide)
Byte 10 is the Height of the sprite in pixels (0 means 256 pixels wide)
As the bitmap data is in an RLE file, there is no bitmap data following the header - it is in a separate file
The Sprite RLE is shown to the right ->
Whenever you save the sprites for MSX, the tilemap is saved in the clipboard, so you can check it!
|AkuSprite Editor is still under heavy development, so
it may look different to what you see here, but hopefully the
functionality will be the same or better!
AkuSprite is a sprite editor which supports up to 8 banks of up to 64 sprites!
Sprites have up to 16 colors... and 'viewer filters' can be used to temporarrily reduce the color depth (without altering the original 16 color data)... This can be used for preview - and also for export
for systems like the spectrum, 'Color attributes' can be painted onto the sprite - these are stored separately from the '16 color pixel data'
a 17th 'transparent' color is supported - this color is converted to whatever is appropriate depending on export system
By default sprites are up to 256x256, but there is a 512x512 mode - which is intended for creating CPC screens (which are 320x200)
Akusprite can save to many formats, such as CPC .SCR... compressed RLE, bitmap sprites, raw bitmap (headerless native screen bytes)... it can also export ASM code for color palettes...
Auksprite formats are used by ChibiAkumas and in my Z80 tutorials - it is, and will continue to be my graphics tool in all future development - and as it's open source, you can add anything you want to it!
While sprites can be exported to various formats, the native save format is .TXT - and is essentially CSV format - so you can even edit the sprites in the file with notepad or excel!
|Lets take a look at
the basics of the screen!
We have the Primary 16 color palette - this is used to draw our sprite.
We have the seconday palette - this is used for applying block color attributes for systems like the spectrum and c64
The tool strip has the drawing tools and other main functions
The main drawing area is for painting - there are also sub-tabs for view options, sprite list, and notes (free-text comments)
The settings panel has sprite and tool settings - also Zoom and palette reconfiguration options
The preview panel shows the current sprite at pixel size, has sprite and bank selection, and summary info on the current sprite
|Pixel Paint allows 16 color drawing of dots onto the sprite|
|ZX Paint will allow color attributes to be applied to the sprite|
|Color swap will swap a color for another... this can
work on 8x8 blocks, or the whole sprite
It is used for converting 16 color sprites to 4 color (or vice-versa) and for converting sprites to 2 color
|These tools all have options in the tool settings panel|
|A sprite file can only have ONE set of color attributes, it is not possible to have AppleII color attributes and ZX attributes in the same file... you'll need to save two versions of the file.|
is really just a basic pixel editor for small sprites - but there are
options for importing and exporting sprites to other programs!
Chibiakumas was drawn on Krita - a free photoshop like app which is totally awesome!
|Need more power, no
AkuSprite editor is designed to allow you to transfer a sprite to a
better application, and import back once the sprite is finished!
Copy the sprite to the clipboard, edit it in the program of your choice, and paste it back!
|If you want to do
all the work in an external app, you can export all the sprites as a
set of PNG files using the "BMP MAP" function... edit them, and
Note, this only works for the 16 color data... you will have to apply color attributes in the sprite editor.
Each bank is stored in a separate file... and a MAP file defines the areas used by the sprites...
If you re-import the sprites, the editor looks at the MAP - finds the color of each sprite, and works out the area of each sprite - then it imports the sprites using those areas...
The 16 colors are re-imported from Bank0 - so you can re-create all the basic sprite info JUST from the set of PNG files!
|the _MAP file defines the position of the sprites... 64
special colors are used to define the areas of each sprite... a map of
these colors is held at the bottom of the _MAP file..
the 16 colors used by the sprites are also in the _BANK0 file - make sure you use the correct colors, or the sprites may not re-import correctly!
| Lesson Aku3
- The Return!
Watch the video of the use of the sprite editor to actually create a sprite!
| Lesson Aku4
- The Star Array!
We looked recently at moves, and how to create an object... lets look now at how the 'Star Array' draws and handles the bullets of the player and enemies!
|VBlock+&0000||Star array Y||Star array Y||Star array Y||Star array Y|
|VBlock+&0100||Star array X||Star array X||Star array X||Star array X|
|VBlock+&0200||Star array Move||Star array Move||Star array Move||Star array Move|
|VBlock+&0300||Object Array Y||Object Array Animator||Player Stars Y||Player Stars Y|
|VBlock+&0400||Object Array X||Object Array Size||Player Stars X||Player Stars X|
|VBlock+&0500||Object Array Move||Object Array Program||Player Stars Move||Player Stars Move|
|VBlock+&0600||Object Array Sprite||Object Array Life||Obj Saved Settings -15 entries
x 8 bytes
|The Object Array saved settings is used by
the Event stream for 'template' enemies... there is actually a second
bank of saved settings, but it's RAM is in the level block - because
there wasn't another 128 bytes of spare ram in the core!
|The 'star array'
is the normal bullet routine that
handles the players bullets, the enemy bullets - and also the particles
on the Turbo-R version.
Each bullet has 1 byte 3 variables, a Y position, an X position and a Move (See Lesson Aku2 for the move info)
The data is byte aligned for a 256 byte array, so if the star array started at &0100, then bullet 3's Y co-ordinate would be &0102, it's X co-ordinate would be &0202 and the move would be at &0302 - this allows the code to move through the array memory more efficiently
A Y-position of 0 means that star is not in use, so this is used to find empty slots for new stars, and decide which stars need drawing. The star array remembers how big the in use array is, and wont try to draw more than needed, to save a little time
Enemy bullets may hit the player, so when drawing the enemy bullets, the players location 'hit box' is pre-calculated and inserted into the conditions by 'self modifying code' to reduce the amount of time during the star loop - for the player loop this hit code is also disabled by self modifying code...
Player bullets hitting enemies is handled during the 'object drawing loop' which draws enemies and background objects.
Stars are added to the array, either during the object drawing code for enemy bullets, or the player handler for player fire.
The Star loop will scan through star data, and find any living stars (where Y>0)
Once one is found its data is read in, and a simpler version of DoMoves is executed (to save time)
When it comes to drawing the stars, each system is somewhat different, so we'll just look at the CPC version!
We read in the screen memory, and work out the location of the star... we read in 2 concecutive addresses, one into HL, and one into DE - because our star is 2x2 - so we need 2 line addresses, and this is faster than calling GetNextLine
Now we work out if our star is on the LEFT or RIGH of the pixel, by checking Bit 0 of A (when we started the function)
Now we read in the byte that is currently on the screen, and blank out the half used by the star - replacing that half with our star color..
We then write the same data to the line below... WITHOUT reading in what was there before, this may corrupt two pixels - but we saved some time!
The MSX version (non V9K) also directly addresses the memory via the VDP's Ram access commands... because the V9K is so fast, the V9K version uses sprites for it's bullets!
Spectrum Star drawing code is similar to the Amstrad, however Speccy
stars are 6x6 - and because there are 8 pixels per byte on the speccy
not 2 like the CPC, there are 4 branches for drawing the star in each
possible X positionon the spectrum!
The Enterprise uses the CPC code - because both systems have the same screen-byte layout!
We need to do collision detection to see if the stars have hurt the player,
We work out the player position (and hence the 'hitbox' of the player) in advance and pump it in using Self modifying code, this is quicker than looking at the data in the player array each time.
If the bullet is within the players hit box, we call the Player_Hit _Injure routine to hurt the player
| Lesson Aku5
- The Object Array!
It's time to get serious! Lets see how the Object array works!
The object array draws enemy and background sprites, it also handles collision with the player, and player bullets
Lets take a look at how it works!
|Lets look again at the Data block!
The object array only uses the first 128 bytes of a bank of memory - this was to reduce the memory footprints, and also allows the code to move through the data more quickly, by setting bit 6 of the HL address pointer to jump to the second half of the data
Y = Y position (0=dead object)
X = X position
Move = byte Move code (see Above)
Sprite = Sprite Number BBSSSSSS S= sprite number (0-63) B=number of banks (1/2/4)
Animator = Animation script (we'll cover these later)
Size = Size of sprite (in pixels)
Program = Fire program (We'll cover these later)
Life = BPLLLLLL B=bullets hurt object (otherwise timed) P= Hurts Player L= Life (0-63)
|Boss enemies are often multiple objects!
Zombichu was made up of 3 objects - each 24*96 strips
The 'Angler Grinder' was made up of dozens of 24x24 size objects - it all depends on the shape of the final sprite!
Essentially lots of small sprites are faster than one big one - because 'blank' areas slow down the sprite routines
|To save time in the
first work out the 'hit box' of the player, we can then just compare
the objects position to these hitbox co-ordinates to see if the object
has hurt the player - we do this with self modifying code.
This may need to be done again if an object is an different size... originally all objects were 24x24, but with EP2 and V1.666 they can be various sizes, this will mean recalculation of the collision detection
|We scan through the
object data looking for an object with a Y co-ordinate above zero...
this marks a living object.
We keep A as Zero, so we can just do CP C to check if C=0
Because we're only looking at Y we just need to INC L to move to the next object in the array to save time (rather than INC HL)
|When we find an
object we need
to process we read it's data into the registrers, Because the data is
held in two 'columns' we set Bit 6 half-way through the read, and read
back the other way.
We'll also need to work out what sprite bank to show... (not shown in source screenshot)
Working out the bank is complex, and hardware specific... it's not shown here!
|Animators handle complex movement and fire patterns, we'll look at them later in these tutorials!|
|We compare the
object position to the player hitbox, we also check if the player is
If the player is hit, we will run the handler for that event... we need to check if the object has been removed after the 'hit' because powerups will disappear after they have been hit, and want no more processing
objects hurt the player - Coins and powerup have the top bit of their
life set... and do collision dectection - but won't decrease the
players life when the 'hit' occurs... they use 'Program 3' which tells
the system they are a
powerup - which powerup is decided by their sprite number.
|All objects have a life... an immortal object will not hurt the player (byte 0) ... objects which do hurt the player (bit 6) either age through bullet strikes, or onscreen time (bit 7)... Life is 0-63... if an object reaches zero, then it's dead.|
|We need to check if
bullets are in the hitbox of the object - to do this we need to process
all the player bullets, so we need to back up many of our registers, so
we can continue object processing later.
If the object was hit, we need to do something!
We use self modifying code after the scan to handle the hit (we don't allow more than one hit per frame)
|We move the object with DoMoves... we looked at that here|
|We need to store
the new object settings back to the memory
Effectively this is the reverse of stage 3
|the "Program' of an object typically defines how it
so this is the point where we will make the object fire a burst...
Some special programs do other things!
can make the sprite use an alternate bank, or switch banks for every 1
x unit (For 2 pixel shifts of background objects on 4 pixel per byte
They also define 'powerups' and special objects.. they can even be defined as a 'seekpoint' which sucks the player in - this is used to make the player fly to the edge of the screen at the end of a level!
|Now we've worked out the new position we can draw the
sprite to screen
We need to set the following parameters to do this:
SprShow_BankAddr - Address of the sprite bank
SprShow_SprNum - Sprite number
SprShow_X - X position in bytes
SprShow_Y - Y position in lines
We also need to bank in the sprite data before calling ShowSprite
|We've not shown the 'Bank Selection code' here - it uses the current game tick and the top two bits of the sprite number... if you want to see it, please download the sourcecode - or watch the video!|
|The whole procedure
is repeated for the next object!
When the last object is done, control returns to the level loop
|We've only looked at the basics here, we'll
cover things like the ShowSprite routine and Animators in later lessons!
Please be patient! The Object array is very complex, as it does all the onscreen enemies!
| Lesson Aku6
- Settings Data
We've recently started looking at objects and stars - and these start to touch on the players settings...
Now it's a good time to start looking at the settings block - where the players position, lives score and other settings are saved, along with the controls and scores
These settings are saved to disk (into Settings.V02), and loaded back when the game starts again - so the highscores, and game settings are preserved.
|When the game loads, the Settings file is
loaded OVER the settings in the Core...
This means if you corrupt your settings some how, you can just delete the file and the game will reset to defaults.... but you'll lose your highscore!
play settings and current player vars are saved in a block of
data defined in 'Core.asm'
there are a few unused bytes, but the purpose of other bytes can only be changed if you know what you're doing...
Some functions like CPCver and Multiplay config have different purposes on other systems, but most variables work the same in all cases.
|Lets say we want to
reposition the players in the level code.
We cannot use the 'Quick Lables' because they are only usable within the core.
What we do is use the 'Akuyou_Player_GetPlayerVars' command to request 'Player_Array' ... this will be returned in IY... we then use the offsets to update the Y and X pos of the player
Due to lack of core memory, there is no direct command to get player 2's data, but we can add 'Player_Separator' which will offset IY to point to the second player...
|When it comes to system variables - Like CpcVer - these are offset in negative numbers from the Player_Array... so we can get the detected system using IY-1|
|There is a special
case for getting the Highscore data - GetHighScore will request the
position of the highscore,
If for some reason we want player scores, we could subtract 16 for Player1's score, or subtract 8 to get Player2's score
the Core and Bootstrap are always recompiled together, we can use the
Quick labels... however the Level code is not recompiled at the same
time - so the levels have to use the 'Akuyou' jumpblock to get to the
variables... that said, it shouldn't be a problem, as the Level's
shouldn't really need the data!
|Label (accessable in levels)||Quick Label (core only)||Byte||Offset||Purpose|
|1||-16||GameOptions (xxxxxxxS) Screen shake|
|0||-15||playmode 0 normal / 128 - 4Direction|
|ContinueMode||0||-14||Do players share one continue stock?|
|SmartbombsReset||3||-13||SmartbombsReset – Smartbombs on new continue|
|ContinuesReset||60||-12||Continues when starting a new game|
|GameDifficulty||0||-11||Game difficulty (enemy Fire Speed 0= normal, 1=easy, 2=hard) +128 = heaven mode , +64 = star Speedup|
|0||-10||Achievements (WPx54321) (W=Won P=Played)|
|MultiplayConfig||0||-9||Joy Config (xxxxxxFM) M=Multiplay/Kempson F=Swap Fire 1/2|
|TurboMode||0||-8||------XX = Turbo mode [/////NoInsults/NoBackground/NoRaster/NoMusic]|
|LivePlayers||1||-7||Number of players currently active in the game|
|TimerTicks||0||-6||used for benchmarking|
|BlockHeavyPageFlippedColors||64||-5||allow/stop color change each frame for ‘inbetween colors’ on CPC 64=off 255/0=on|
|BlockPageFlippedColors||0||-4||Disable Plus sprite flicker 0/255=on 64=off|
|ScreenBuffer_ActiveScreen||&80||-3||Drawing screen buffer|
|ScreenBuffer_VisibleScreen||&C0||-2||Visible screen buffer|
CPC 0 =464 , 128=128 ; 129 = 128 plus ; 192 = 128 plus with 512k; 193 = 128 plus with 512k pos -1
MSX 1=V9990 4=turbo R
ZX 0=TAP 1=TRD 2=DSK 128= 128k ;192 = +3 or black +2
|P1_P10||100||10||Burst Fire (Xfire)|
|P1_P12||0||12||Player num (0=1, 128=2)|
|P1_P13||0||13||Points to add to player - used to make score 'roll up'|
|P1_P14||0||14||Player Shoot Power|
|P2_P10||100||26||Burst Fire (Xfire)|
|P2_P12||0||28||Player num (0=1, 128=2)|
|P2_P13||0||29||Points to add to player - used to make score 'roll up'|
|P2_P14||0||30||Player Shoot Power|
|KeyboardScanner_KeyPresses||(10-12 bytes)||Buffer for keys|
|Player_ScoreBytes||-16||0,0,0,0,0,0,0,0||Score (BCD – in reverse order)|
|Player_ScoreBytes2||-8||0,0,0,0,0,0,0,0||Score (BCD – in reverse order)|
|HighScoreBytes||0||0,0,0,0,0,0,0,0||Score (BCD – in reverse order)|
| Lesson Aku7
- The Event Stream Basics!
The Event stream is essentially the level data - the background objects, enemies and timed events that will occur within the level.
Creating a 'grid' style map of data would use up too much memory - and unnessasary seeing as we only scroll in one direction - so the objects arrive based on 'timed events'
We'll start by looking at some level code from ChibiAkumas, and see how it works!
an Tick only has one event...but evtMultipleCommands allows us to have
a group of commands at a single tick...
evtMultipleCommands... like many other '+V' commands can only take a parameter 0-15 - this is because the resulting command is a single byte - and the bottom nibble is split out as a parameter.
|The first byte is 15... this means the
event will happen at Tick 15... The level starts at tick 0 - and the
tick increases - when it gets to 255 it will go back to 0 so there is
no limit to the length of your level
evtMultipleCommands+5 is the second byte... evtMultipleCommands is defined as %01110000... the '+5 part' tells the core that there are 5 commands at this time
You should see 'EventStreamDefinitions.asm' for details of all the defined symbols.
evtSettingsBank_Load tells the core we want to load some predefined enemy settings... +14 tells the core that we want to load bank 14
evtSingleSprite tells the system to create a sprite... +7 tells the core to create the sprite at row 7 (or column 7 if level is vertical)... the enemy settings were taken from bank 14
|We're going to define the settings at Tick 0
- at the start of the level... and we need to use evtMultipleCommands
6 events at this time
evtSetProgMoveLife takes 3 bytes... the first is the program (prgNothing - so the sprite won't shoot)... the second is the move (&23 - slow move left)... the third is the life (lifEnemy+15... and enemy that can be shot with a life of 15)
evtSetSprite sets the sprite of the object... TwoFrameSprite+25 means sprite 25, with 2 frames of animation
evtSetAnimator sets the animator script... we're using animator 10
evtAddToForeground means the object will be added to the end of the object array... so it will appear at the front
evtSetObjectSize sets the object hitzone... 64 means the object is 64x64 pixels - all objects must be square
our last command evtSettingsBank_Save saves all these settings to bank 14... when we load these - we will be ready to create an enemy with all these attributes!
|We have a command at Tick 1... evtCallAddress
tells the core to call a memory address in the level code... the label
the ASM code at this address MUST NOT change any registers except A!
There is also a second command at Tick 1...evtJumpToNewTime will set the position in the event stream to the labeled address 'FadeIn'... and the current tick is set to 0... note the first tick at FadeIn must be more than the new current tick (so 1 or more)
covered a few commands here, but it will become more clear later when
we look at the code that makes the event stream work, and when we look
at the level code itself!...
Please be patient, and just consider this an initial overview!
|evtAddToBackground||134||Add oject to foreground (front of object array)|
|evtAddToForeground||135||Add oject to foreground (back of object array)|
|evtBurstSprite||14||Add an object to the burst position|
|evtCallAddress||137||w1||Call a memory address w1... make sure you don't change any registers (other than A)|
|evtJumpToNewTime||136||w1,b2||Change event stream position to w1 , and levetime to b2... time in b2 must be lower than first event at w1|
|evtMultipleCommands||%01110000+V||Multiple commands... V commands will follow|
|evtReprogram_PowerupSprites||%11110110||Define the sprite numbers of the power up objects and coin to b1,b2,b3,b4|
|evtReprogramCustomMove1||%11110100||w1||Define Custom Move handler1 to call w1 each object move|
|evtReprogramCustomMove2||%11110101||w1||Define Custom Move handler2 to call w1 each object move|
|evtReprogramCustomMove3||%11110111||w1||Define Custom Move handler3 to call w1 each object move|
|evtReprogramCustomMove4||%11111000||w1||Define Custom Move handler4 to call w1 each object move|
|evtReprogramCustomPlayerHitter||%11111011||w1||Define Custom hit handler for players as call to w1 - used for steaks in Alchemy level of ep2|
|evtReprogramCustomProg1||%11111001||w1||Define Custom Programmer handler1 to call w1 each program tick (custom fire patterns)|
|evtReprogramCustomProg2||%11111010||w1||Define Custom Programmer handler2 to call w1 each program tick (custom fire patterns)|
|evtReprogramHitHandler||%11110010||w1||Define Custom hit handler as call to w1, used for boss battles|
|evtReprogramObjectBurstPosition||%11111101||b1,b2||Set Burst Animation position to (b1,b2)... used for nuke blasts in Ep2|
|evtReprogramObjectFullCustomMoves||%11111110||w1||All Move events call to w1|
|evtReprogramPalette||%11110000||b1,b2 ……||Reprogram the CPC palette - no effect on other systems – b2 bytes into offset b1|
||Reprogram the CPC PLUS palette|
|evtReprogramShotToDeath||%11110011||w1||Define Custom destroy object event as call to w1, used for nuke satellite, and lasers in Ep2 Tech Noir level|
|evtReprogramSmartBombSpecial||%11111111||w1||Smart bomb event calls to w1... used by omega array to wipe omega stars|
|evtResetPowerup||139||Take away the player powerups... how mean!|
|evtSaveLstObjToAdd||138||w1||Save the memory position of last added object in the object array to memory location w1... used for boss sprites|
||set animator to b1... 0 means no animator|
||set address of array of animators to w1|
||Change the speed of the object array to b1... %00000100 is default.. .%00000010 is faster|
|evtSetLife||130||b1||set Life to b1|
|evtSetMove||131||b1||set Move to b1|
|evtSetMoveLife||128||b1,b2||Set Move to b1, Set Life to b2|
||set Object sprite size to b1... default is 24|
|evtSetProg||129||b1||Set Prog to b1|
|evtSetProgMoveLife||132||b1,b2,b3||Set prog to b1, Set move to b2, set life to b3|
|evtSetSprite||133||b1||set sprite to b1|
|evtSettingsBank_Load||%10010000+V||Load settings from bank V (V=0-14)|
||Save settings to bank b1|
|evtSettingsBankEXT_Load||%10110000+V||Load ExtraBank settings from bank V|
|evtSettingsBankEXT_Save||%10110000+15||b1||Save ExtraBank settings to bank b1|
|evtSingleSprite||0+V||Single sprite... multiple options depending on V|
|0+0||b1,b2,b3||add one object...sprite b1.. at pos (b2,b3)|
|0+1||b1||Add one sprite to pos b1 Far right (sprite predefined)|
|0+(2-13)||add one 24 pixel object far right X=160+24 Y=v*16 -8 (sprite predefined)|
|evtStarburt||%01000000||b1,b2||0100xxxx X Y = (64) add stars to b1,b2 (X,Y) (pattern xxxx) - is this ever used???|
|evtTileSprite||48+V||b1,b2,b3...||add V objects... all on column b1 starting at row b2.. Spaced b3 apart vertically|
| Lesson Aku8
- The Event Stream Code - Part 1
Lets start looking at the Event Stream code - and see what the modules that process the event stream actually do
The ChibiAkumas Game engine allows for scrolling in 4 directions - however some of the elements of the Event Stream are programmed to make objects appear automatically at the Far Right... this is to reduce the amount of bytes in the definition of the events.
SetScroll allows the Event stream to be reconfigured for a different scrolling direction... This is done with Selfmodifying code to change the way the needed elements of the event steam code work.
Note, this is not an element in the event stream, but rather a CALL from the jump block...it was intended to be executed at the start of a new level, when the scroll is not the typical Right.
SetLevelTime is change the position in the Event Stream, it is passed a label and new time, the event stream will proccess the command following the label next.
GetLevelTime is a Core command, it is used by some functions like the boss battles for special events.
Event_StreamInit is a Core command which needs to be executed during the Init routine of the level
it needs to be passed the address of the event stream in HL... and a 128 byte block in DE for the 2nd bank of settings (used to define an extra 15 enemy types - if the level uses them)
This is an internal function to handle the EvtMultipleCommands function... it handles the event count, and starts the repeat
Event_Stream is the main call for the handling of the event stream... we don't update the event stream every tick, so we need to check if this tick is one we want to update.
Now we increase the current LevelTime, and see if the next tick occurs at this time... if it's not, then we return.
This function reads in and processes the command of the event...
Note there is a dirty trick to save space at the start... in most cases,whatever the command, we will run Event_LoadNextEvt... we put this on the top of the stack... so we can run it with a 1 byte RET... rather than a 3 byte Jump!
Also note we load IY with a special command... RST6 will effectively call IY... this also allows us to read in a byte from
The Bottom nibble of the command is a parameter... the top nibble is the command type... we split these into two registers... and use VectorJump to call the command for the top nibble... we have push the current HL value (so it's preserved when the call happens), and load HL with the address of the vector array
The Vector array is a bank of pointers to the commands.
The VectorJump will use the VectorLookup command to read in an HL word from the address... based on an offset of A (in bytepairs)
We then restore HL (meaning all registers except A are intact)
Finally we jump to the address we got from the Vector Array!
StarBurst is a function to make a group of stars appear at a location onscreen (not from an enemy)... this was the first command I added to the game... but I'm not sure if ChibiAkumas ever actually uses this!
note the RET at the end... because we pushed Event_LoadNextEvt - it will run that command when it finishes!
This function will set the number of commands at a single timepoint... this is done to save a few bytes when we need a lot of commands with a single timepoint
|Event Stream Reprogrammers
We have a variety of 're-program' commands... each reads in a single byte and saves it to a location... this selfmods othere parameters in the core
we put the destination in DE, and use a common copy command to do the job.
To save space, we use RST6...which jumps to IY... the jump we have in IY loads A from (HL) and INCs HL
for ProgramMoveLifeSwitch and MoveLifeSwitch we write in two extra bytes into to other locations
Power ups in the game engine all use the same 'Program' so when it comes to identifying a Coin or a Drone we do it by sprite number...
Unfortunately.. .the sprite number of, say, the coin,are not the same on all levels! Therefore, the level needs to tell the coin which sprite number is each powerup
This command will read in the sprite numbers and modify the code accordingly
| Lesson Aku9
- The Event Stream Code - Part 2
Since it does so much work, The event stream is huge! Lets take a look at more of it's code!
In the same way as we reprogrammed single bytes before, in some cases we use Self Modifying code to reprogram byte pairs - in most cases this is used to change the addresses of moves - but also reprogrammes other calls and jumps to alter the functionality of the core.
The address we write to depends on what's being modified, it's address is loaded into DE
Two bytes are read in to BC - and they are written to the address in DE... then we move to the next event!
|CoreReprogram - Palette
This is used by the CPC for the 'rasterswitching' (the color definitions that allow for more than 4 colors onscreen)
Reporgramming the raster palette is pretty 'hardcore' - we load in a offset and a bytecount... the Eventstream then loads the bytes directly into the raster color data...
In case you haven't guessed you have to be very carefuly with the definition - if you make any mistakes, you'll corrupt the color definitions, or program ram itself... and if you have fewer or more bytes of color data than you specified in the bytecount, the rest of your event stream will be invalid!
We'll look at how to create a valid definition in a later lesson!
Moveswitch is the start of a wide variety of commands, the low nibble is used with a lookuptable for a vector jump.
|Save Last object to Address
This command is used in the event stream to save the address of the position in the object array of the last created object.
This is used by boss battles, to allow the level code to make special changes to an object, such as reprogramming its sprite, or anything really!
This is a simple function, it will call the address specified... essentially it is used to execute some code within the level data at a certain time during the event stream...
However beware! You must not change ANY registers except A! it's probably better to use selfmodifying code to set a flag in the level loop, and do anything you need to there - rather than risk the event stream code getting messed up!
After each level you will lose your powerups, This function resets the player to 'no powerup' state
|Change Stream Time
The Eventstream is basically linear, with time progressing forwards, and bytes read in a consecutive order - it is however possible to perform loops by specifying a label and a new 'Time'
The first event after the label must be HIGHER than the new specified time - if they are equal, then the event wouldn't occur for a whole 255 ticks!
|Add Front / Add Back
These commands change the position that the next object will be added to the object array... effectively adding it as a Foreground object (enemy) or Background object (scenary)
|Change Stream Speed
There may be times when the default tick speed is not suitable for your needs, and you need events to occur faster in the event stream - you can change the speed with this function
The default is: %00000100
An object stip is a set of objects - all on a single column (or row if a vertical level) with a common 'spacing' between them - it can be used to create objects that are made up of multiple sprites, such as spikes on the cave level, or parts of super large enemies like the 'angler grinder'
This defines Multiple objects on the same Column, with different X Co-ordinates
I'm not sure if this was ever actually used in the final game?
Multiple objects all defined with their own X,Y at the same time point...
Again... I'm not sure if this was ever actually used in the final game!!!
|'One Object' Variants
When the top 4 bits of the command are all 0 - the bottom 4 bits define the type of command - so %0000xxxx
Values between 2-13 define the object as appearing on an evenly spaced Row (or Column on vertical levels)... there is also an option for an object with a defined 'Y' on the far right, or a ' Burst object' (appearing at the prefdefined burst point.
Finally all other objects (%00000000) have a defined Sprite, X, Y
| Lesson Aku10
- The Event Stream Code - Part 3
Since it does so much work, The event stream is huge! Lets take a look at the last of it's code!
|Load Next Event
This is a core part of the eventstream, and handles the 'multiple events' function - we never actually call this directly.
I'm not sure why it's positioned in the middle of the 'Object' code!!!
The actual AddObject routine is pretty complex!
First we configure the routine depending if we're adding to the foreground or background - if we're adding to the foreground we add to the back of the list... if we're adding to the background then we add to the front!
We then scan through the object array until we find an empty slot...
When we find one, we store all the predefined settings of the new object into the position in the object array (usually loaded from a 'settings' bank)
On the Spectrum we align the Y pos to a multiple of 8 - this solves any 'color clash' problems
There is also a 'Player Check'... if 2 players are playing the life of the enemy is doubled!
|SaveLoadSettings - INIT
The Save/Load Settings command start from the same point - a 'bank number' of 15 defines a SAVE
if we're doing a SAVE - then the bank number is then loaded from the next byte with the RST6 command
The address of that bank is calculated, by multiplying the bank number by 8
The actual saving or loading is done by separate commands
This is used by the previous command and does the actual job of loading the data from the bank - and writing it into the Object creation routine using self-modifying code.
The save routine will read each of the bytes from the positions and write them into the bank - of course this has to be done in the same order as the load.
This procedure is called at the start of each new level - it takes the players powerups, and resets the player bullet color back to the defaults.
The procedure is run twice, once for player 1 and once for player 2 (though some of the code is common to both)
| Lesson Aku11
- Player Driver
The Player driver handles the players movement, firing and the drawing of the player sprites, Its long and complex, but of course it's very important!
|Interfaces to get Core Vars
These calls are used by the code to provide pointers to core variable arrays so they can be manipulated by the level code
When one of the 2 players dies, an 'onscreen continue' appears ingame, allowing the player to jump back in
If both players die, then the game is paused and the 'Continue' screen is shown (which is held in the bootstrap)
The continue will occur if the player presses their fire button, if they do, the credits will be reduced, and the Lives and Smartbombs of the player will be reset
If the players are both dead, we need to get the bootstrap to show the continue screen...
First we need to backup our interrupt handler, then we call the bootstrap - if execution comes back, then the player has continued.
On the MSX we need to restore the palette (because the continue screen has it's own)
We need to restore our interrupt handler back to the ingame one..
Finally we need to restore the double screen-buffer
This is the true start of the Player Driver,
First we count the number of living players - this is done by selfmodifying code that converts NOP's to INC statements, if both players are dead we run the Players_Dead command described above
Next we process the control input, reading in the keys and converting them to UDLRF bits.
We're going to start by processing the 1st playetr, so we check the Player1 lives to see if the player is alive.
|Player 1 Init
We need to load in the correct addresses for the sprite data depending on the system (and memory config on 464)
the actual handling of the player is done by Player_HandlerOne - this is the common code used for both players
|Player 2 Init
We need to do the same for player 2
Also on the CPC+ we need to handle the swapping of the two drone sprites
|Player Handler One
The actual player handler is executed twice, once for each player.
The first part of the code uses self-modifying code to set the sprite numbers and banks for that player.
Next we check if the player has pressed the pause button, and pause the game if they have
Next we check if the player is allowed to fire (or if they fired to recently)... we also uopdate the drone position
Our Keypresses are in IXL - and we now need to test the bits of this byte to see what keypresses need to be actioned
Fire presses are handled by calls to "SetFireDir" functions - these are selfmodified if the game is in '4 direction mode' (where the player can also shoot up and down)
Directional movement is handled by checking if the player is offscreen, and if they are not, moving by the 'Move Speed' (the move speed is slower when fire is held)
The last check is for the smart bomb, if the smartbomb button is pressed, and the player has remaining smartbombs, one will be used and the DoSmartBomb function called.
Once we've finished reading we need to update the current sprite and firedirection.
if the player is currently firing we need to create a bullet
We also need to save tehe current player position, and work out what sprite to use for the player based on the current tick
We now need to draw the drones (if the player has any)...
Self modifying code uis used to reprogram this module if the player is shooting Up or Down, and the are alternative sprite drawing routines for the CPC+
|Starting Drawing the Player
We've ve dealt with the drones, so now we need to start drawing the player sprite.
On the spectrum we have two versions of the player sprite, so we can effect a 4 pixel shift of the player.
We need to flash the player if the player is invincible.
Also, the CPC+ needs special sprite drawing routines
|Drawing the Plus Sprite
We're going to draw the CPC+ Sprite,
We need to convert the regular sprite co-ordinates to be correct for the CPC+,
The player is made up of 4 16x16 sprites, so we need to run the Plus_Setsprite routine 4 times
|Non Plus ShowSprite
We now need to show the sprite on all the regular systems, once we've done that ,we can return to the main level loop
| Lesson Aku12
- Player Driver Part 2
The Player driver has various modules that are needed for the fire modes... lets take a look!
|Fire Functions - Drone reposition and Burst
There are various fire functions - many are to support the 4 direction fire mode.
SetDronePos is used to position the drones, this is altered by selfmodifying code to support UD mode.
Fire_Onburst is used to fire a single burst, but protects all the major registers.
Player Handler_Xfire is the 'Burst mode' achieved by pressing BOTH fire buttons... this fire mode has two options, if the burst counter is greater than 50, then a larger burst will occur.
The player fire function handle the normal fire... the routine is quite complex, as bullets need to fire from all drones,
We also need to check if the player is even allowed to fire, based on the fire speed limit.
The bullet to add is loaded in from IY+12 - which is altered as the player changes direction
|Set Fire Directions
As part of the 4 direction fire mode, we need the ability to reprogram the various modules that handle firing and drawing of drones and the like.
There are 4 modules that set the fire direction - and these all end at a common module that sets the final direction.
In 4 direction mode one button 'Locks' the fire to the direction the player is facing, and the other fires in the same direction as last time.
Of course, the smartbomb clears all stars from the screen - and replaces all enemies with coins!
The MemoryFlushLDIR command is used to wipe the star array.
Then we need to process the 'Object array.. we have to check the life of each object to see if it hurts the player
Note, there is a special exception for objects with a life of 255 - this is used for boss hit targets
Then we need to check the 'program' - as programs <16 are not affected by the smartbomb
When a 'Viable' object is found, it is replaced with a coin, and on the V9k a particle burst occurs.
we loop around tre whole object array, finally we make the smartbomb sound, and set the screen to flash.
This module handles the collision of the player with another object in the object array, it's important to understand that this one module handles both players, and 'PlayerHitMempointer' will point to the player array of the player that was hit
First we check the program - if it's not a powerup, or 16+2 (a custom hitter) then we consider this to be an 'injuring object' and run the Injure routines
Othewise we've hit a powerup! we need to detect which powerup (by sprite),
The various power up routines are all pretty similar, they need to check if the player is already max'ed out, and if not update the powerup by changin it's vars or self-modifying code-
There's also a SFX routine for the powerups.
|Player Hit Injure
if the player has been hit, we need to reduce their life count and set them as invincible
If the player has run out of lives, then we need to start the 'onscreen Continue' message timer - this shows "Continue?" at the top of the screen for a few seconds while the game plays in two player mode.
We also need to make the 'Hurt' SFX
| Lesson Aku13
- Player UI
The Player UI shows the Icons, Scores and 'Burst count' onscreen - it also handles the 'rolling up' of the score...
Unfortunately, because it supports the CPC+, there are two versions of parts of the code.
The 'Plus sprite drawing routines will set the 'icons' to ON or OFF depending on how many Lives/Smartbombs the player has...
Note, the Plus Icons are 'Raster flipped' doubling their appearence onscreen (and exceeding the 16 plus sprites per screen limit)... therefore their status is modified in the 'PlusSprite_Config' array.
SetSpriteIfNonZero is a routine that simplifies the procedure, it allows for a set of 3 sprites to be set on or off depending on the Accumulator
The non CPC+ (and other machines) use the software sprite routines,
they count the number of icons in B, and use ShowSPriteDirect - a simpler spriteroutine that cannot do clipping
|Draw UI - Player Plus Sprites!
The start of 'Draw UI' is a bit misleading... we need to do a 'swap' of the two drone sprites on the CPC+, but couldn't really do it in the main player code... instead we do it here!
we also run the two Player DrawUI routines to handle the drawing of onscreen UI and scores...
It's also important to note the 'DoContinue' routines - these are entry points for if the player is dead and we just need to show continue info.
|Dead Player Continue Messages
If one of the players is dead, we need to show the 'Continue message' on their side of the screen, along with the number of continues that player has,
This routine is called twice - once for each player.
This routine sets up the sprite drawing routines with the correct sptite data and positions, and executes the loops mentioned previously that draw the icons
This is a pair of routines that init the various parameters of the common drawing routines and start them up.
This includes sprite positions and direction (as icons are drawn right-left for player 2).
|Player DrawUI Plus As well!
This routine is common to both Plus, and non plus machines... hence the name!
This routine draws the score onscreen, as well as the single byte 'Burst counter'
it also starts the 'rollup' of the score... because the 'score to add' is only as single byte, multiple rollups are done if the score to add is over 30 - so it doesn't 'fill up'!
Did you notice the RET command had a 'self-modifying' label on it?
This is the Cheat mode! this will be changed to a Nop if the cheat mode is on!
What does the cheat mode do? it gives player 1 full powerups and infinite continues...
It was designed for debugging...
How do you turn the cheat mode on? press 6-F6-6 on the credits screen (or 696 on the Speccy)
This routine adds the 'score to add' to the players score - one unit at at time..
it also handles the awarding of 'Burst power' - 10 units of which are added each 100 points
thois routine is pretty crude... and could probably be done better with DAA - but when I wrote it, this was my first time using Binary Coded Decimal, and I didn't understand the DAA command!
| Lesson Aku14
- Background Drawing on the CPC/Speccy - Part 1
Chibiakumas uses a special routine to draw the background, it's specially coded for each level, and is held in the Level area - it uses a set of predefined routines to fill areas with 'Gradients' 'Filled areas' and 'Tile Area'... the reason for this specialisation is that the background had to be as fast as possible, as it had a huge area to fill and no time to fill it!
Because its screen works differently, The MSX has a separate routine we'll look at another time
|The Background as it appears onscreen!
||The Code that Draws it! (Click to enlarge)
|When we start the routine, HL must point to the right hand side of the top line of the screen
The first area of the screen is a gradient... we need a gradient map with the bit combinations to make the colored area in DE... a line count in B and a bitmask in C - this bitmask alters the scroll rate - and the top bit can invert the scroll direction
The first two bytes of the gradient definition are the initial bytes (they are altenated each line)... and extra byte is added at each specified line offset to form an alternating gradient...
|When we want to fill an area with tiles, we need byte aligned sprite data, of 8 bytes wide (32 pixels on CPC, 64 on Speccy)
We get the memory address by tile number by using GetSpritePos
The 'QuadSprite' function draws the sprites to screen... B specifies the number of lines.
|We need to fill the area between the clouds and the buildings as fast as possible.
BackgroundSolidFill is the fastest way... it uses the bytes in DE as a solid fill pattern.... and fills B lines
|Drawing the buildings is essentially the same as drawing the clouds, however rather than use GetSpriteMempos, we just add the size of the previous sprite to DE, to quckly calculate the start of the cloud sprite|
|Filling the area between the ground and the sky is pretty unremarkable - except we have to calculate the 'space' unused by all our other fill commands.|
|The bottom gradient is basically the same as the top... note the bitmask is all 1's in C - this makes the bottom gradient scroll faster, giving a paralax effect|
|The last strip of tiles is 8 pixels tall, and draws the floor of the level|
|While we can scroll gradient
areas with the C paramater, we have to actually bitshift the data in
tile areas, we do this after the background is drawn using the
The bitshifter takes the address of the sprite in HL, the lines in C, and the width in B (always 8)... A is the 'mask' of the scroll speed (the same as the gradient areas)...
Note the 'inc h' before scrolling the floor tiles ... the bitshifter relies on being byte aligned but cannot go over a 256 byte boundary... as the two 32 pixel x 8 pixel tiles make 256 bytes, we have to manually INC H at this point
Note, blank areas cannot have any scrolling - because they are blank!
Once bitshifting is done we do the 'Spectrum color' which colors the background on the speccy.
| Lesson Aku15
- Background Drawing on the CPC/Speccy - Part 2: QuadSprite and SolidFill
Lets take a look at how the Tilemap routine and Fill routine get their speed!
|We need to use the Stack pointer for datawriting, and we need to read in from HL... all this takes a bit of setup!
Note, we do a single INC of the stackpointer on the speccy... this is so the destination address is not byte aligned, as it would result in the the line possibly being at &xx00 - and a dec L would not work correctly, so we keep HL low by one!
|Once we've got everything ready, we need to read in the 8 bytes that make up a line, we load into IX, BC,DE and HL...|
|We transfer IX to AF in the first push, as AF is faster!
all our following PUSHES use AF... we need to do enough Push groups to fill the line - note the line is 32 bytes on speccy and 80 on ENT and CPC
|Once the line is complete, we restore the stack pointer - so interrupts get a chance to run!
We then calculate the new line position, this is very platform specific!
|We repeat the whole procedure, calculate the next source data pos, and start again to read in the next line of data|
|Solidfill is almost the same, it has no read in procedure, and all the pushes are DE|
|The bitshifter will pixel shift each of the rows in the tile...
The parameters passed are A - which is the bitmask for the scroll time - it's ANDed with the ticks, and for any line which is a 1 a single pixel scroll occurs.
If we're NOT doing a bitshift, we skip all the lines in the tile
If we ARE doing a bit shift then we need to shift all the bits in the bytes of the lines - this is a little tricky on the CPC/ENT as we need to keep the colors in the right place
Of course this command has the effect of scrolling the tile areas - and allows for a paralax effect, where the ground moves fast, and the sky moves slow.
This version handles Right-> Left scrolling... there is an alternate version for REVERSE bitshifting.