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
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
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! |
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. |
Basic moves are
defined when the top bit 7 is 0... the remaining bits are in
the format: -DYYYXXX
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 |
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 |
|
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! |
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! |
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. |
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! |
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! |
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
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! |
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 |
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! |
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. |
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 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
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! |
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! |
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! |
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) |
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. |
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
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! |
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! |
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 |
MoreEventsDec This is an internal function to handle the EvtMultipleCommands function... it handles the event count, and starts the repeat |
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 |
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! |
Moveswitch Moveswitch is the start of a wide variety of commands, the low nibble is used with a lookuptable for a vector jump. |
Clear Powerups After each level you will lose your powerups, This function resets the player to 'no powerup' state |
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) |
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!!! |
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! |
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. |
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 |
Player Handler 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 |
Control Reading 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. |
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. |
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 |
DrawUI IconLoop 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 |
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, |
DrawUI Dual 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 |
Player?DrawUI 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). |
Cheat Mode! 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) |
The Background as it appears onscreen! |
The Code that Draws it! (Click to enlarge) |
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 |
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! |
Solidfill is almost the same, it has no read in procedure, and all the pushes are DE |