Every object has a type field ( 0x5 ) that selects a tick routine. The tick routine for a bomb power-up is shown below (link to video). This object simply moves down the screen. If it collides with the player, the player receives an additional bomb unless they are already at the max.
TickBombPU
LD A,(IY +0xd ) ; ycoord LSB
ADD A,0x20 ; i.e. 1 in in 8.5 fixed point
LD (IY +0xd ),A
JR NC,LAB_ram_c327 ; no overflow, otherwise ..
INC (IY +0xe ) ; inc MSB
LAB_ram_c327
BIT 0x7 ,(IY +0x14 ) ; check for player collision
JR Z,LAB_ram_c347 ; jp there's no collision ...
LD IX ,(CurrentPlayerPtr) ; get player state ptr
LD A,(IX +0x9 ) ; get the number of bombs
CP 0x7 ; >= 7 ?
JR NC,LAB_ram_c34d ; ... then player has max bombs
INC (IX +0x9 ) ; inc the player's bombs
LD HL ,DirtyFlags ; set "status" dirty flag
SET 0x2 ,( HL => DirtyFlags )
LD A,0x5 ; index 5 into score table
CALL AwardPoints ; add to score
JR LAB_ram_c34d ; .. and done
LAB_ram_c347
LD A,(IY +0xe ) ; didn't collide - get ycoord
CP 0x1b ; check off screen
RET C ; if less still visible return
LAB_ram_c34d
LD HL ,BombPUSpawned ; ... dec bomb spawned flag
DEC (HL => BombPUSpawned )
LD HL ,FreeObjectCount ; inc number of free objects
INC (HL => FreeObjectCount )
CALL DecWaveNumberNoAward ; remove the object
RET
The routine is quite compact; this is due to keeping the collision logic outside of the object tick functions and deferring the score updates via dirty flags that are read outside the ISR. Note that we are working in 8.5 fixed point, which leads to the confusing addition of 32 ( 0x20 ) to increment the y position by 1. Anyone who was looked at lots of zx spectrum assembly is pretty much hard-wired to think that an addition of 32 "must" be advancing by one character row, which is not the case here.
The object is removed by the code below LAB_ram_c34d. This updates a bomb spawned flag, which is used along with other flags in logic that chooses what power-up the player should be supplied with next. The object is then returned to the free list in DecWaveNumberNoAward. For objects which are part of waves, this would also update the wave instance, but would not generate a power-up award. This is normally called when an object is removed for reasons ( e.g. leaving the screen ) other than the player destroying it.
For objects with more complex behaviour, flying shark uses a simple script/pattern system. Each object in a wave can be assigned a pattern id which drives its behaviour. The planes that appear at the start ( and throughout ) stage 1 use this system.
EnemyPlane0
db 22h ; Orientation = OrientFlag ? 16 : 16
; AddVelocity
db 83h ; Repeat 3
db 28h ; SteerTowardsPlayer
; AddVelocity
db 90h ; Repeat 16
db 10h ; AddVelocity
db 38h ; OrientFlag = CoinFlip
db 81h ; Repeat = Random(32)
db 10h ; Add Velocity
db 86h ; Repeat 6
db 5Bh ; Orientation += OrientFlag ? 3 : -3
; AddVelocity
db C0h ; Repeat 64
db 10h ; Add Velocity
db 20h ; Set Finished
db 0h ; ZeroVelocity
The planes initially point down the screen. Orientations are in the range 0-63, with 0 degrees facing along the positive x-axis. A value of 16 is a rotation of 90 degrees They then steer towards the player for 3 frames, then travel in a straight line for 16 frames. The orientation flag is then set to a random value. The planes travel straight for a further random number of frames, then rotate nearly 90 degrees to either the left or right ( determined by the randomly set orientation flag ) and fly straight for a further 64 frames. The objects are then removed.
This script covers most of the 13 opcodes. Many scripts are much simpler than this one. E.g. when an enemy plane is killed, it sometimes goes into a death spiral. The script for this is simply:
EnemyPlaneDying
db 88h ; Repeat 8
db 5Bh ; Orientation += Flag[7] ? 3: -3
db 20h ; AddVelocity
This is not a full blown language by any stretch, but it does allow the game to be authored at a higher level of abstraction that z80 assembly.
Assembly at https://github.com/tomgrove/FlyingSharkBlog. The routine TickObjects handles all the object ticking.
Commenti