aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-03-03 23:19:53 +0100
committerMiquel Sabaté Solà <mssola@mssola.com>2026-03-03 23:19:53 +0100
commit196f2ac744fa8e0b6ce2da555e01178d04bfb322 (patch)
tree196c630b875c30a6deb6c0728d5b679b297438f7 /src
parentd71a38f3223dd90ad59b706321de87d60c0ed666 (diff)
downloadjetpac.nes-196f2ac744fa8e0b6ce2da555e01178d04bfb322.tar.gz
jetpac.nes-196f2ac744fa8e0b6ce2da555e01178d04bfb322.zip
Let enemies die whenever they touch a bullet
Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
Diffstat (limited to 'src')
-rw-r--r--src/bullets.s59
-rw-r--r--src/enemies.s85
-rw-r--r--src/jetpac.s2
3 files changed, 128 insertions, 18 deletions
diff --git a/src/bullets.s b/src/bullets.s
index 1dd98e0..cdbcafc 100644
--- a/src/bullets.s
+++ b/src/bullets.s
@@ -1,5 +1,14 @@
.segment "CODE"
+;; Assuming that the 'x' register indexes a bullet on its pool, increment the
+;; register as many times as to point to the next one. Bound checking is not
+;; performed, it's up to the caller to implement that.
+.macro NEXT_BULLET_INDEX_X
+ inx
+ inx
+ inx
+.endmacro
+
;; Function and variables which deal with the pool of bullets that the
;; `driver.s` will use in order to render and deal with bullets on screen.
.scope Bullets
@@ -68,9 +77,7 @@
lda #$FF
@pool_init_loop:
sta zp_bullets_pool_base, x
- inx
- inx
- inx
+ NEXT_BULLET_INDEX_X
dey
bne @pool_init_loop
@@ -119,9 +126,7 @@
;; are over the total size in memory, we have to roll the `x` back to
;; zero. This is possible because the loop starts at
;; `zp_last_allocated_index`, which is not necessarily 0.
- inx
- inx
- inx
+ NEXT_BULLET_INDEX_X
cpx #BULLETS_POOL_CAPACITY_BYTES
bne @find_free_bullet_bucket
ldx #0
@@ -190,9 +195,7 @@
bne @move_active_bullet
;; No, go for the next one.
- inx
- inx
- inx
+ NEXT_BULLET_INDEX_X
jmp @move_loop
@move_active_bullet:
@@ -213,14 +216,16 @@
;; are done checking for bullets. In this case, if this was the last
;; bullet active, return early.
dec zp_bullets_pool_size
- beq @end
+ bne @decrease_y
+ jmp @end
+ @decrease_y:
dey
- beq @end
+ bne @next_iteration
+ jmp @end
+ @next_iteration:
;; We still have active bullets to move, go to the next iteration.
- inx
- inx
- inx
+ NEXT_BULLET_INDEX_X
bne @move_loop
@do_move_active_bullet:
@@ -282,8 +287,29 @@
inx
jmp @move_loop
+ ;; Enemy collision for this bullet. It's actually easier/faster to just
+ ;; unroll the loop.
@check_enemy_collision:
- ;; TODO
+ lda #Enemies::ENEMY_0_IDX
+ sta Enemies::zp_pool_index
+ jsr Enemies::collides
+ beq @enemy_1
+ jsr Enemies::bite_the_dust
+ jmp @save_bullet_move
+ @enemy_1:
+ lda #Enemies::ENEMY_1_IDX
+ sta Enemies::zp_pool_index
+ jsr Enemies::collides
+ beq @enemy_2
+ jsr Enemies::bite_the_dust
+ jmp @save_bullet_move
+ @enemy_2:
+ lda #Enemies::ENEMY_2_IDX
+ sta Enemies::zp_pool_index
+ jsr Enemies::collides
+ beq @save_bullet_move
+ jsr Enemies::bite_the_dust
+ __fallthrough__ @save_bullet_move
@save_bullet_move:
;; Restore back the old value from the 'x' register.
@@ -299,7 +325,8 @@
sta zp_bullets_pool_base, x
inx
dey
- bne @move_loop
+ beq @end
+ jmp @move_loop
@end:
rts
diff --git a/src/enemies.s b/src/enemies.s
index b6bfdd2..5acadc5 100644
--- a/src/enemies.s
+++ b/src/enemies.s
@@ -20,6 +20,11 @@
;; The capacity of the enemies pool in bytes.
ENEMIES_POOL_CAPACITY_BYTES = ENEMIES_POOL_CAPACITY * 4
+ ;; Indeces where each enemy definition starts on the pool.
+ ENEMY_0_IDX = 0
+ ENEMY_1_IDX = 4
+ ENEMY_2_IDX = 8
+
;; Initial X coordinates for enemies depending on if they appear on the
;; left/right edge of the screen.
ENEMIES_INITIAL_X = $F0
@@ -69,6 +74,18 @@
;; useful for different waves with the same algorithm but different speeds.
zp_enemy_arg = $D5
+ ;; Checking for collision with bullets is actually way faster if after an
+ ;; update we save tile coordinates for each enemy. For this, we only need to
+ ;; save the tile coordinates, but we actually span 4 bytes per enemy. That's
+ ;; because of padding: we are re-using the 'Enemies::zp_pool_index' variable
+ ;; to index both the pool and this buffer. Hence, identifying an enemy by
+ ;; 'zp_pool_index' works in both buffers. This is extremely useful so
+ ;; bullets don't have to work out two different indeces for two different
+ ;; structures. Yes, this also means that we are wasting away 6 bytes of RAM,
+ ;; but we can work with that.
+ CURRENT_TILES_BYTES = ENEMIES_POOL_CAPACITY * 4
+ zp_current_tiles = $F0 ; asan:reserve CURRENT_TILES_BYTES
+
;; Values for the counter of enemies that fall.
;;
;; NOTE: values for this have to fit into a nibble.
@@ -95,7 +112,18 @@
asl
sta zp_enemy_tiles
- ;; And set the movement function for this type.
+ ;; Initialize the tiles buffer by marking it as invalid. Note that we
+ ;; only initialize those positions that are actually needed. That is,
+ ;; the padding is left untouched as we don't care.
+ lda #$FF
+ sta Enemies::zp_current_tiles
+ sta Enemies::zp_current_tiles + 1
+ sta Enemies::zp_current_tiles + 4
+ sta Enemies::zp_current_tiles + 5
+ sta Enemies::zp_current_tiles + 8
+ sta Enemies::zp_current_tiles + 9
+
+ ;; Set the movement function for this type.
lda movement_lo, x
sta zp_enemy_movement_fn
lda movement_hi, x
@@ -290,6 +318,18 @@
;; Restore the value from the 'x' register.
ldx Enemies::zp_pool_index
+ ;; Save the current tile coordinates for this enemy.
+ lda Enemies::zp_enemies_pool_base + 1, x
+ lsr
+ lsr
+ lsr
+ sta Enemies::zp_current_tiles, x
+ lda Enemies::zp_enemies_pool_base + 2, x
+ lsr
+ lsr
+ lsr
+ sta Enemies::zp_current_tiles + 1, x
+
;; TODO: collision with player
@increase_index_next:
@@ -405,6 +445,47 @@
rts
.endproc
+ ;; Given a tile coordinate via 'Globals::zp_arg0' (Y) and 'Globals::zp_arg1'
+ ;; (X), and an enemy pointed by the 'Enemies::zp_pool_index' index, set to
+ ;; 'a' if the given coordinate collides with the referenced enemy.
+ ;;
+ ;; NOTE: the 'x' register is being touched. Everything else is left
+ ;; untouched.
+ .proc collides
+ ;; Fetch the Y tile coordinate. If it's not valid return early.
+ ldx Enemies::zp_pool_index
+ lda Enemies::zp_current_tiles, x
+ cmp #$FF
+ beq @no
+
+ ;; Check for the Y tile coordinate. If it's not the same on either the
+ ;; upper or the bottom parts of the enemy, then it's a no.
+ cmp Globals::zp_arg0
+ beq @check_x
+ clc
+ adc #1
+ cmp Globals::zp_arg0
+ bne @no
+
+ @check_x:
+ ;; If the Y tile coordinate checks out, let's narrow it down to the X
+ ;; coordinate.
+ lda Enemies::zp_current_tiles + 1, x
+ cmp Globals::zp_arg1
+ beq @yes
+ clc
+ adc #1
+ cmp Globals::zp_arg1
+ bne @no
+
+ @yes:
+ lda #1
+ rts
+ @no:
+ lda #0
+ rts
+ .endproc
+
;; The enemy has been set to dust, remove it.
.proc bite_the_dust
dec Enemies::zp_enemies_pool_size
@@ -416,6 +497,8 @@
;; Invalidate this enemy.
lda #$FF
sta Enemies::zp_enemies_pool_base, x
+ sta Enemies::zp_current_tiles, x
+ sta Enemies::zp_current_tiles + 1, x
stx Globals::zp_tmp0
sty Globals::zp_tmp1
diff --git a/src/jetpac.s b/src/jetpac.s
index 89cd4eb..b6b44e2 100644
--- a/src/jetpac.s
+++ b/src/jetpac.s
@@ -43,8 +43,8 @@
.include "prng.s"
.include "explosions.s"
.include "player.s"
-.include "bullets.s"
.include "enemies.s"
+.include "bullets.s"
.include "title.s"
.include "driver.s"
.include "interrupts.s"