return to ChibiAkumas.com Choose a Color Scheme: Dark
Print Mode


Learn Multi platform Z80 Assembly Programming... With Vampires!
Chibi Akumas Technical Documentation

Overview
This series is designed to provide an educational insight into the programming of the ChibiAkumas game... it is intended for those who want to know what programming an 8-bit game is like, or are looking to modify the ChibiAkumas game....

In this series, we'll look at the 'Akuyou' game engine, how it works, what design choices were made, and why... and how to alter the game and it's graphics

Chibiakumas is open source, and you are totally welcome to modify it and release you're own version... A great way (if not the best way) to learn assembly is to take some-one elses code, and modify it to do new things and learn step by step, using existing code to help you!
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.




Screen Co-ordinates
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... memory is 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!

In 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 jumps in the X axis

Unfortunately 8 bit programming means coping with such limitations... and their unexpected consequences!
Text Co-Ordinates
Text co-ordinates 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

Getting Text on the screen
Lets get some text on the screen using the Akuyou game engine!

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!
On 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!

Recompiling the core

The Akuyou Core handles Game play, graphics and input...
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 - Movements
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 Movements (0-127)
Basic moves are defined when the top bit 7 is 0... the remaining  bits are in the format:
-DYYYXXX
 7  6  5  4  3  2  1  0
- D Y Y Y X X X

Where:
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

Advanced Movements (128-255)
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
Name Bits Bit Meanings
mveBackground %1100SSSS S= Speed
mveSeeker_P1 %100001SS S= Speed
mveSeeker_P2 %100100SS S= Speed
mveSeeker %100010SS S= Speed
mveWave %1010DSPP D = Depth bit, S= Speed, PP Position
mveCustom1 %1111XXXX X=Data passed to subroutine (variable?)
mveCustom2 %1110XXXX X=Data passed to subroutine (variable?)
mveCustom3 %1101XXXX X=Data passed to subroutine (variable?)
mveCustom4 %1011XXXX X=Data passed to subroutine (variable?)
Custom 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!

Lets give it a go!

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!
This 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 File Formats
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!


Getting started with AkuSprite Editor

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.
AkuSprite 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 problem! 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 reimport them!

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 Part II - 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!






The Data Block:
The 'Data Block' is a bank of &700 bytes that defines all the attributes of bullets and objects in the game (also saved object settings bank 1)
The organization of the block is a little weird, it's designed to allow movement through the array by changing the Low byte, and looking at a different variable of the same object by changing the High byte

Addr &00 &40 &80 &C0 &FF
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

VBlock+&0700




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:
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..
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)
Drawing the Stars...

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!
The 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!

Collision Detection...
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)
Addr &00 &40 &80 &C0 &FF
VB+&0000 Star Y Star Y Star Y Star Y
VB+&0100 Star X Star X Star X Star X
VB+&0200 Star Move Star Move Star Move Star Move
VB+&0300 Object Y Object Animator Player Stars Y Player Stars Y
VB+&0400 Object X Object Size Player Stars X Player Stars X
VB+&0500 Object Move Object Program Player Stars Move Player Stars Move
VB+&0600 Object Sprite Object Life Obj Settings -15 x 8 bytes

VB+&0700





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
Order of operations
Each object is handled with the following procedure:
    1.  Work out the players hitbox
    2. Find the next living object
    3. Read in it's data from memory
    4. Execute object animator
    4. See if object is in players hitbox
    5. Check object's life, and age if needed
    6. See if a player bullet has hit the object and kill object if needed
    7. Move the object
    8. Save the objects new settings back to memory
    8. Make the object shoot (if progam requires it)
    9. Draw object sprite to screen
    10. Repeat for next object

Work out the players hitbox
To save time in the loop, we 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

Find the next living object
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)

Read in it's data from memory

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!

Execute the object animator
Animators handle complex movement and fire patterns, we'll look at them later in these tutorials!

See if object is in players hitbox

We compare the object position to the player hitbox, we also check if the player is alive!
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
Not all 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.

Check object's life, and age if needed
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.

See if a player bullet has hit the object and kill object if needed

We need to check if any player 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)


Move the object

We move the object with DoMoves... we looked at that here

Save the objects new settings back to memory

We need to store the new object settings back to the memory

Effectively this is the reverse of stage 3

Make the object shoot (if progam requires it)
the "Program' of an object typically defines how it shoots...
so this is the point where we will make the object fire a burst...
Some special programs do other things!
Special programs 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 CPC)
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!


Draw object sprite to screen
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!
Repeat for next object
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!
Chibiakumas game 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.

Accessing the data is done in one of two ways -
In the CORE - many of the data elements are accessed by direct lables
Outside the core - (and in the core in places) we have to access the data by requesting one of the few pointers accessible via the Akuyou calls - and use offsets - for example:

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
Because 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



-20 unused



-19 unused



-18 unused



-17 unused


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

CPCVer 0 -1 detected system
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
Player_Array P1_P00 100 0 Y pos

P1_P01 32 1 X pos

P1_P02 0 2 shoot delay

P1_P03 2 3 smartbombs

P1_P04 0 4 drones (0/1/2)

P1_P05 60 5 continues

P1_P06 0 6 drone pos

P1_P07 7 7 Invincibility

P1_P08 0 8 Player SpriteNum

P1_P09 3 9 Lives

P1_P10 100 10 Burst Fire (Xfire)

P1_P11 %00000100 11 Fire Speed

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

P1_P15 &67 15 Fire Direction

Player_Array2
P2_P00
150 16 Y pos

P2_P01 32 17 X pos

P2_P02 0 18 shoot delay

P2_P03 2 19 smartbombs

P2_P04 0 20 drones (0/1/2)

P2_P05 60 21 continues

P2_P06 0 22 drone pos

P2_P07 7 23 Invincibility

P2_P08 0 24 Player SpriteNum

P2_P09 3 25 Lives

P2_P10 100 26 Burst Fire (Xfire)

P2_P11 %00000100 27 Fire Speed

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

P2_P15 &67 31 Fire Direction

KeyMap2 0 %11101111,&09 Pause (Bits-Row)


2 %10111111,&08 Fire-3 (Bits-Row)


4 %11111011,&00 Fire-2 (Bits-Row)


6 %11101111,&00 Fire-1 (Bits-Row)


8 %11110111,&01 Right (Bits-Row)


10 %10111111,&01 Left (Bits-Row)


12 %11011111,&01 Down (Bits-Row)


14 %10111111,&02 Up (Bits-Row)

KeyMap 0 %11101111,&09 Pause (Bits-Row)


2 %10111111,&07 Fire-3 (Bits-Row)


4 %11111011,&09 Fire-2 (Bits-Row)


6 %11111110,&09 Fire-1 (Bits-Row)


8 %11111011,&07 Right (Bits-Row)


10 %11011111,&07 Left (Bits-Row)


12 %11111101,&07 Down (Bits-Row)


14 %11110111,&07 Up (Bits-Row)
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!





Technically 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.


lets take a look at a super simple command!

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

Lets see how setting 14 was defined!

We're going to define the settings at Tick 0 - at the start of the level... and we need to use evtMultipleCommands to define 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!

Lets take a look at a special command!

We have a command at Tick 1... evtCallAddress tells the core to call a memory address in the level code... the label is 'SetScrollDown'... 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)

We've only 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!
Here are the full set of commands that are possible in the Event Stream... next time we're going to look at the details of how the event stream is processed

Symbol ByteData Extra Parameters Details
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
evtReprogramPlusPalette %11110001 w1
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
evtReprogramSmartBombed %11111100 w1

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
evtSetAnimator 142 b1
set animator to b1... 0 means no animator
evtSetAnimatorPointers 143 w1
set address of array of animators to w1
evtSetLevelSpeed 140 b1
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
evtSetObjectSize 141 b1
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)
evtSettingsBank_Save %10010000+15 b1
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





Set Scroll

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.

LevelTime

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

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)

MoreEventsDec
This is an internal function to handle the EvtMultipleCommands function... it handles the event count, and starts the repeat

Event_Stream
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.

Event_GetEventsNow
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

VectorJump

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

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!


MultipleEvents
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


Event_CoreReprogram_PowerupSprites

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!





'Move' Reprogrammers

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

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!

Call

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!

Clear Powerups

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

Object Strip

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'

Object column

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?

Multi-Object

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!!!

AddObject

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

DoSettingsLoad

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.

Event_CoreSaveLoadSettings_Save

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.

ResetPowerup

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)



Visit www.ChibiAkumas.com to get my games and their source code! | Support me on patreon