aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-03-23 23:59:26 +0100
committerMiquel Sabaté Solà <mssola@mssola.com>2026-03-23 23:59:26 +0100
commit0c9b0ad7938e2ba7e994574b4947e25c82440b8c (patch)
treecb6721fc9689104463b5a71ca02303299486af9e /src
parentfb23cf51040f06bfcfbaf318d7b452d76ffbedfb (diff)
downloadjetpac.nes-0c9b0ad7938e2ba7e994574b4947e25c82440b8c.tar.gz
jetpac.nes-0c9b0ad7938e2ba7e994574b4947e25c82440b8c.zip
Implement the "take off" animation
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à <mssola@mssola.com>
Diffstat (limited to 'src')
-rw-r--r--src/background.s54
-rw-r--r--src/driver.s303
-rw-r--r--src/interrupts.s10
-rw-r--r--src/items.s143
-rw-r--r--src/over.s48
5 files changed, 470 insertions, 88 deletions
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