From 0c9b0ad7938e2ba7e994574b4947e25c82440b8c Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Mon, 23 Mar 2026 23:59:26 +0100 Subject: Implement the "take off" animation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the animation that is done after clearing a stage. Moreover, and for the first time since I started development, now we can move into the next level as intended from the game's design. Signed-off-by: Miquel Sabaté Solà --- src/background.s | 54 ++++++++++ src/driver.s | 303 +++++++++++++++++++++++++++++++++++++++++++++++++++---- src/interrupts.s | 10 ++ src/items.s | 143 ++++++++++++++++++++++---- src/over.s | 48 +-------- 5 files changed, 470 insertions(+), 88 deletions(-) (limited to 'src') diff --git a/src/background.s b/src/background.s index 0facde6..369def9 100644 --- a/src/background.s +++ b/src/background.s @@ -107,4 +107,58 @@ ;; End of the list. .byte $FF + + ;; Clear out the shuttle from the background. + ;; + ;; NOTE: this should only be called from NMI code. + .proc clear_shuttle + ;; The low part of the rocket. + bit PPU::m_status + ldx #$2B + stx PPU::m_address + ldx #$15 + stx PPU::m_address + sta PPU::m_data + sta PPU::m_data + + ;; High part of the rocket. + bit PPU::m_status + ldy #$2A + sty PPU::m_address + ldx #$75 + stx PPU::m_address + sta PPU::m_data + sta PPU::m_data + + bit PPU::m_status + sty PPU::m_address + ldx #$95 + stx PPU::m_address + sta PPU::m_data + sta PPU::m_data + + bit PPU::m_status + sty PPU::m_address + ldx #$B5 + stx PPU::m_address + sta PPU::m_data + sta PPU::m_data + + ;; Middle part of the rocket. + bit PPU::m_status + sty PPU::m_address + ldx #$D5 + stx PPU::m_address + sta PPU::m_data + sta PPU::m_data + + bit PPU::m_status + sty PPU::m_address + ldx #$F5 + stx PPU::m_address + sta PPU::m_data + sta PPU::m_data + + rts + .endproc .endscope diff --git a/src/driver.s b/src/driver.s index ab63b5f..3372f9a 100644 --- a/src/driver.s +++ b/src/driver.s @@ -57,11 +57,14 @@ ;; Bitmap of various boolean values lumped together. ;; - ;; |SP-- ----| + ;; |SP-- -MDT| ;; | - ;; |- S: whether sprites have already been moved out in the + ;; |- S: whether Sprites have already been moved out in the ;; | 'move_sprites_out' situation. - ;; |- P: whether the pause message on the HUD has to be toggled. + ;; |- P: whether the Pause message on the HUD has to be toggled. + ;; |- M: the shuttle should Move. Only used coupled with T. + ;; |- D: the taking off animation should be moving downwards. + ;; |- T: the rocket is Taking off. zp_flags = $38 ;; Initialization routine that is to be called before enabling NMIs back for @@ -171,6 +174,16 @@ .endproc .proc update + ;; Are we in the shuttle transition? + lda Driver::zp_flags + and #$01 + beq @check_player_timer + + ;; Yes! Then just handle the shuttle animation and move into sprite + ;; cycling. + JAL Driver::handle_shuttle + + @check_player_timer: ;; If the player timer is over, jump to the game immediately. Otherwise ;; decrement the counter. lda zp_player_timer @@ -301,8 +314,21 @@ ;; over with the game screen. lda Globals::zp_flags and #$10 + bne @player_got_toasted + + ;; Nope, the player is just fine. Just an extra check: do we have all + ;; shuttle parts? + lda Items::zp_collected + cmp #9 + bne @sprite_cycling + + ;; Yes, we do! Then we check if the player is colliding with the shuttle + ;; platform. If so it's time to blast off + jsr Items::player_in_shuttle beq @sprite_cycling + JAL Driver::init_take_off + @player_got_toasted: ;; Invalidate bullets and enemies if we haven't already. bit Driver::zp_flags bmi @check_explosions @@ -338,25 +364,10 @@ ;; Invalidate items, which were skipped on move_sprites_out() on purpose ;; to keep them after each death. But since we are about to go to the ;; title screen, now they are no longer useful. - lda #$FF - ldx #0 - ldy #Items::POOL_CAPACITY - @items_reset_loop: - sta Items::zp_pool_base, x - NEXT_ITEM_INDEX_X - dey - bne @items_reset_loop + jsr Items::invalidate_all @reset_timers: - ;; Reset the player's timer to enter the game screen again. - lda #PLAYER_TIMER_VALUE - sta zp_player_timer - - ;; Restart the blinking animation. - lda #BLINKING_TIME - sta Driver::zp_blink_timer - lda #$80 - sta Driver::zp_blink_status + jsr Driver::reset_timers @sprite_cycling: __fallthrough__ sprite_cycling @@ -644,6 +655,258 @@ .endproc .endif + ;; Reset all timers which are relevant for entering a new screen. + .proc reset_timers + ;; Reset the player's timer to enter the game screen again. + lda #PLAYER_TIMER_VALUE + sta zp_player_timer + + ;; Restart the blinking animation. + lda #BLINKING_TIME + sta Driver::zp_blink_timer + lda #$80 + sta Driver::zp_blink_status + + rts + .endproc + + ;; Initialize the "take off" animation. From this point forward the + ;; Drivers::update() function will no longer go through its normal route + ;; and it will just call Drivers::handle_shuttle(). + ;; + ;; Hence, this function sets/unsets all the relevant flags, and sets + ;; 'OAM::m_sprites' to only contain the sprites for the animation. The 'ppu' + ;; and 'shuttle' flags will also be touched so any background elements from + ;; the shuttle are also cleared out. + .proc init_take_off + ;;; + ;; Manually create all 12 sprites that make up the shuttle in the take + ;; off animation. This is seemingly a lot of code, but it's just sprite + ;; initialization over and over. + + ;; Y screen coordinates. + lda #Background::GROUND_Y_COORD - 48 + sta OAM::m_sprites + sta OAM::m_sprites + 4 + + lda #Background::GROUND_Y_COORD - 40 + sta OAM::m_sprites + 8 + sta OAM::m_sprites + 12 + + lda #Background::GROUND_Y_COORD - 32 + sta OAM::m_sprites + 16 + sta OAM::m_sprites + 20 + + lda #Background::GROUND_Y_COORD - 24 + sta OAM::m_sprites + 24 + sta OAM::m_sprites + 28 + + lda #Background::GROUND_Y_COORD - 16 + sta OAM::m_sprites + 32 + sta OAM::m_sprites + 36 + + lda #Background::GROUND_Y_COORD - 8 + sta OAM::m_sprites + 40 + sta OAM::m_sprites + 44 + + ;; Tile IDs + lda #$04 + sta OAM::m_sprites + 1 + lda #$05 + sta OAM::m_sprites + 5 + + lda #$14 + sta OAM::m_sprites + 9 + lda #$15 + sta OAM::m_sprites + 13 + + lda #$06 + sta OAM::m_sprites + 17 + lda #$07 + sta OAM::m_sprites + 21 + + lda #$16 + sta OAM::m_sprites + 25 + lda #$17 + sta OAM::m_sprites + 29 + + lda #$08 + sta OAM::m_sprites + 33 + lda #$09 + sta OAM::m_sprites + 37 + + lda #$42 + sta OAM::m_sprites + 41 + lda #$43 + sta OAM::m_sprites + 45 + + ;; Zero out attributes + lda #0 + sta OAM::m_sprites + 2 + sta OAM::m_sprites + 6 + sta OAM::m_sprites + 10 + sta OAM::m_sprites + 14 + sta OAM::m_sprites + 18 + sta OAM::m_sprites + 22 + sta OAM::m_sprites + 26 + sta OAM::m_sprites + 30 + sta OAM::m_sprites + 34 + sta OAM::m_sprites + 38 + sta OAM::m_sprites + 42 + sta OAM::m_sprites + 46 + + ;; X screen coordinates. + lda #Items::DROPPING_SCREEN_X + sta OAM::m_sprites + 3 + sta OAM::m_sprites + 11 + sta OAM::m_sprites + 19 + sta OAM::m_sprites + 27 + sta OAM::m_sprites + 35 + sta OAM::m_sprites + 43 + + lda #Items::DROPPING_SCREEN_X + 8 + sta OAM::m_sprites + 7 + sta OAM::m_sprites + 15 + sta OAM::m_sprites + 23 + sta OAM::m_sprites + 31 + sta OAM::m_sprites + 39 + sta OAM::m_sprites + 47 + + ;;; + ;; Clear out the rest of the sprites. Note that this is done manually + ;; and not via the rest of helper functions because it's faster and it + ;; touches 'OAM::m_sprites' directly. + + ldx #(12 * 4) ; NOTE: 12 sprites from the shuttle. + lda #$FF + @clear_loop: + sta OAM::m_sprites, x + inx + inx + inx + inx + bne @clear_loop + + ;;; + ;; Flags and stuff. + + ;; Enable the 'T' flag. That is, we signal to the Driver::update() + ;; function that the "take off" animation is going on and it should call + ;; Driver::handle_shuttle() instead of going the regular route. + ;; + ;; NOTE: all other flags are cleared out on purpose as they are no + ;; longer relevant. + lda #1 + sta Driver::zp_flags + + ;; Force the shuttle to be removed from the background (see interrupt.s + ;; for the specific handling for this). + lda #0 + sta Items::zp_collected + + ;; Enable the 'ppu' and the 'shuttle' flags. This, coupled with the + ;; previous zeroing out of 'Items::zp_collected', makes the background + ;; shuttle disappear in favor of the animated meta-sprite. + lda Globals::zp_flags + ora #%01100000 + sta Globals::zp_flags + + rts + .endproc + + ;; Handle the "take off" animation from the shuttle. That is, move it + ;; upwards/downwards depending on the 'D' bit, and check for "collisions" on + ;; certain spots where the animation should flip or be over. + .proc handle_shuttle + ;; Move the shuttle every other frame. + lda Driver::zp_flags + eor #$04 + sta Driver::zp_flags + and #$04 + beq @end + + ;; Move all sprites from the shuttle up/down depending on the 'D' flag. + ldx #0 + @loop: + ;; To always check whether the 'D' flag is set on each sprite is + ;; admittedly not the most performant thing to do. But it's easy and + ;; this function is literally the only thing that will be done + ;; computing-wise, so whatever... + lda Driver::zp_flags + and #$02 + beq @up + inc OAM::m_sprites, x + jmp @next + @up: + dec OAM::m_sprites, x + + @next: + ;; The rocket is made up of 12 sprites, and each one takes 4 bytes on + ;; OAM space. + inx + inx + inx + inx + cpx #(12 * 4) + bne @loop + + ;; Is the shuttle at a limit when it should either flip the 'D' bit or + ;; declare the animation to be over? + lda Driver::zp_flags + and #$02 + lsr + tax + lda limits, x + cmp OAM::m_sprites + bne @end + + ;; Flip the 'D' bit. If doing so results on a zero bit, then we know we + ;; are back at the ground and hence we should stop the + ;; animation. Otherwise we should store the result so we move downwards + ;; next time. + lda Driver::zp_flags + eor #$02 + tax + and #$02 + bne @set + + ;; The animation is over. Reset the flags to the expected 'S' one. Not + ;; that we care too much about it, but at least we will be consistent + ;; with player's death and other scenarios like that. + lda #$80 + sta Driver::zp_flags + + ;; Increase the level :) + inc Globals::zp_level + lda Globals::zp_level + and #%00000111 + sta Globals::zp_level_kind + + ;; Just like we did in Drivers::switch(), we re-initialize some things + ;; like timers and the items. Note that re-setting the timers will force + ;; the Drivers::update() function to re-initialize most things + ;; (e.g. enemies). + jsr Driver::reset_timers + jsr Items::init_level + + ;; Enable the 'ppu' and the 'shuttle' flags, so the shuttle is back into + ;; the background. + lda Globals::zp_flags + ora #%01100000 + sta Globals::zp_flags + + rts + + @set: + stx Driver::zp_flags + + @end: + rts + + limits: + .byte Background::UPPER_MARGIN_Y_COORD, Background::GROUND_Y_COORD - 48 + .endproc + ;; Toggle the "Paused" message from the (not quite) HUD. ;; ;; NOTE: only call this function from NMI code. diff --git a/src/interrupts.s b/src/interrupts.s index 2c78099..6c7e75b 100644 --- a/src/interrupts.s +++ b/src/interrupts.s @@ -103,8 +103,18 @@ tax and #%00100000 beq @game_status + + ;; Yes! Then, if there are no collected items, we just clear out the shuttle + ;; entirely (i.e. it's the shuttle take off animation), otherwise we go into + ;; the usual route. + lda Items::zp_collected + bne @do_update_shuttle + jsr Background::clear_shuttle + jmp @unset_shuttle_flag +@do_update_shuttle: jsr Items::update_shuttle +@unset_shuttle_flag: ;; And unset the flag. lda Globals::zp_flags and #%11011111 diff --git a/src/items.s b/src/items.s index de0a28e..947e96e 100644 --- a/src/items.s +++ b/src/items.s @@ -91,6 +91,10 @@ ;; Coordinate where the dropping of items takes place. DROPPING_SCREEN_X = $A8 + ;; Coordinates where the player is allowed to enter into the shuttle to take + ;; off. + ENTER_SCREEN_Y = $A8 + ;; Y screen coordinates in order for various parts to be considered as ;; "collected". MID_SHUTTLE_Y = $A7 @@ -866,6 +870,35 @@ rts .endproc + ;; Sets 1 to the 'a' register if the player is "entering" the shuttle, 0 + ;; otherwise. + ;; + ;; NOTE: this function does not check on whether that makes sense (e.g. is + ;; the player allowed to do it?). That's up to the caller to decide. + .proc player_in_shuttle + lda Items::zp_player_screen_x + + cmp #DROPPING_SCREEN_X - 8 + bcs @maybe + jmp @no + @maybe: + cmp #DROPPING_SCREEN_X + 8 + bcc @check_vertical + jmp @no + + @check_vertical: + lda Items::zp_player_screen_y + cmp #ENTER_SCREEN_Y + bcc @no + + @yes: + lda #1 + rts + @no: + lda #0 + rts + .endproc + ;; Let go the item from the player if there is one being grabbed. .proc let_go_on_death ;; First of all, we need do check if the player was actually holding an @@ -938,30 +971,28 @@ rts .endproc + ;; Invalidate all items from the screen. + .proc invalidate_all + lda #$FF + ldx #0 + ldy #Items::POOL_CAPACITY + + @items_reset_loop: + sta Items::zp_pool_base, x + + NEXT_ITEM_INDEX_X + dey + bne @items_reset_loop + + rts + .endproc + ;; Prepare the background scenary for items. Namely, the rocket parts which ;; belong to the background. ;; ;; NOTE: this has to be called with the PPU disabled. .proc prepare_background_scene - ;; The low part of the rocket. - bit PPU::m_status - lda #$2A - sta PPU::m_address - lda #$F5 - sta PPU::m_address - ldx #$0C - stx PPU::m_data - inx - stx PPU::m_data - - lda #$2B - sta PPU::m_address - lda #$15 - sta PPU::m_address - inx - stx PPU::m_data - inx - stx PPU::m_data + jsr draw_low_part_shuttle lda Globals::zp_level_kind beq @end @@ -974,7 +1005,12 @@ rts .endproc - ;; Update the background scenary for the shuttle. + ;; Update the background scenery for the shuttle. This has to take into + ;; account not just the background elements, but also the attributes for + ;; each case to account for the fuel getting in. It also handles the basic + ;; case of having just the low part as we might come from a previous level + ;; which has "dirtied" out the attributes. All in all, the implementation is + ;; not the sexiest thing ever, but it gets the job done :) ;; ;; NOTE: this has to be called with the PPU disabled. .proc update_shuttle @@ -1001,7 +1037,7 @@ sta PPU::m_address lda #%10101010 sta PPU::m_data - bne @end + jmp @end @half_high_middle: lda Items::zp_collected @@ -1014,7 +1050,7 @@ sta PPU::m_address lda #%10100010 sta PPU::m_data - bne @end + jmp @end @low_middle: lda Items::zp_collected @@ -1053,15 +1089,51 @@ sta PPU::m_address lda #%10101010 sta PPU::m_data + bne @end @just_top: cmp #3 bcc @just_middle jsr draw_high_part_shuttle + ;; Set the attributes to the default one just in case we are switching + ;; from a previous level. + bit PPU::m_status + lda #$2B + sta PPU::m_address + lda #$E5 + sta PPU::m_address + lda #0 + sta PPU::m_data + @just_middle: jsr draw_middle_part_shuttle + ;; Set the attributes to the default one just in case we are switching + ;; from a previous level. + bit PPU::m_status + lda #$2B + sta PPU::m_address + lda #$ED + sta PPU::m_address + lda #0 + sta PPU::m_data + + ;; NOTE: just in case we move into the next level and we need to + ;; reconstruct the low part of the shuttle after the "take off" + ;; animation cleared it away. + jsr draw_low_part_shuttle + + ;; Set the attributes to the default one just in case we are switching + ;; from a previous level. + bit PPU::m_status + lda #$2B + sta PPU::m_address + lda #$F5 + sta PPU::m_address + lda #0 + sta PPU::m_data + @end: rts .endproc @@ -1119,4 +1191,31 @@ rts .endproc + + ;; Update the background scenary to show the low part of the shuttle. + ;; + ;; NOTE: this has to be called with the PPU disabled. + .proc draw_low_part_shuttle + ;; The low part of the rocket. + bit PPU::m_status + lda #$2A + sta PPU::m_address + lda #$F5 + sta PPU::m_address + ldx #$0C + stx PPU::m_data + inx + stx PPU::m_data + + lda #$2B + sta PPU::m_address + lda #$15 + sta PPU::m_address + inx + stx PPU::m_data + inx + stx PPU::m_data + + rts + .endproc .endscope diff --git a/src/over.s b/src/over.s index e5855e3..ab13d76 100644 --- a/src/over.s +++ b/src/over.s @@ -152,52 +152,8 @@ dex bne @clear_ground_loop - ;; The low part of the rocket. - bit PPU::m_status - ldx #$2B - stx PPU::m_address - ldx #$15 - stx PPU::m_address - sta PPU::m_data - sta PPU::m_data - - ;; High part of the rocket. - bit PPU::m_status - ldy #$2A - sty PPU::m_address - ldx #$75 - stx PPU::m_address - sta PPU::m_data - sta PPU::m_data - - bit PPU::m_status - sty PPU::m_address - ldx #$95 - stx PPU::m_address - sta PPU::m_data - sta PPU::m_data - - bit PPU::m_status - sty PPU::m_address - ldx #$B5 - stx PPU::m_address - sta PPU::m_data - sta PPU::m_data - - ;; Middle part of the rocket. - bit PPU::m_status - sty PPU::m_address - ldx #$D5 - stx PPU::m_address - sta PPU::m_data - sta PPU::m_data - - bit PPU::m_status - sty PPU::m_address - ldx #$F5 - stx PPU::m_address - sta PPU::m_data - sta PPU::m_data + ;; Clear the shuttle from the background. + jsr Background::clear_shuttle rts .endproc -- cgit v1.2.3