diff options
| author | Miquel Sabaté Solà <mssola@mssola.com> | 2026-03-03 23:19:53 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <mssola@mssola.com> | 2026-03-03 23:19:53 +0100 |
| commit | 196f2ac744fa8e0b6ce2da555e01178d04bfb322 (patch) | |
| tree | 196c630b875c30a6deb6c0728d5b679b297438f7 | |
| parent | d71a38f3223dd90ad59b706321de87d60c0ed666 (diff) | |
| download | jetpac.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>
| -rw-r--r-- | .nasm/memory.txt | 3 | ||||
| -rw-r--r-- | .nasm/segments.txt | 2 | ||||
| -rw-r--r-- | src/bullets.s | 59 | ||||
| -rw-r--r-- | src/enemies.s | 85 | ||||
| -rw-r--r-- | src/jetpac.s | 2 |
5 files changed, 131 insertions, 20 deletions
diff --git a/.nasm/memory.txt b/.nasm/memory.txt index 3e1e687..c52be55 100644 --- a/.nasm/memory.txt +++ b/.nasm/memory.txt @@ -42,6 +42,7 @@ $E0: zp_bullets_pool_size $E1: zp_last_allocated_index $E2: zp_current_bullet_y $E3: zp_current_bullet_x +$F0-$FB: zp_current_tiles $0200-$02FF: m_sprites $2000: m_control $2001: m_mask @@ -56,4 +57,4 @@ $4016: m_joypad1 $4017: m_frame_counter --- Summary (in bytes) --- -- Internal RAM: 354/2048 (17.29%) +- Internal RAM: 366/2048 (17.87%) diff --git a/.nasm/segments.txt b/.nasm/segments.txt index b3a3e94..ab31677 100644 --- a/.nasm/segments.txt +++ b/.nasm/segments.txt @@ -1,4 +1,4 @@ - HEADER: 16/16 (100%) -- ROM0: 5733/32762 (17.50%) +- ROM0: 5854/32762 (17.87%) - ROMV: 6/6 (100%) - ROM2: 8192/8192 (100%) 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" |
