From 0c5af85cc0cb9bb39a3a72548e735b92daf0a28f Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Wed, 4 Mar 2026 22:53:35 +0100 Subject: Add collision with the player MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows for explosions to run after making the player to disappear, and it re-runs the entering scene timer. Signed-off-by: Miquel Sabaté Solà --- src/driver.s | 89 ++++++++++++++++++++++++++++++++++++++++----- src/enemies.s | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/explosions.s | 14 ++++++-- src/player.s | 32 +++++++++++++++++ 4 files changed, 228 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/driver.s b/src/driver.s index 8d6ee52..8a0d452 100644 --- a/src/driver.s +++ b/src/driver.s @@ -2,12 +2,18 @@ .scope Driver ;; Timer for the player to be able to pick up the joypad upon entering the - ;; game. + ;; game (either when transitioning from the title or when losing a life). ;; ;; NOTE: this memory address is shared with `zp_title_timer`, as they can ;; never conflict with each other. zp_player_timer = $30 ; asan:ignore - PLAYER_TIMER_VALUE = HZ * 2 + PLAYER_TIMER_FULL_VALUE = HZ * 3 + PLAYER_TIMER_DEV_VALUE = HZ / 2 + .ifdef PARTIAL + PLAYER_TIMER_VALUE = PLAYER_TIMER_DEV_VALUE + .else + PLAYER_TIMER_VALUE = PLAYER_TIMER_FULL_VALUE + .endif .ifdef PAL ;; Frame counter which resets every 5 frames. @@ -32,6 +38,12 @@ ;; Same as `zp_next_bullet_cycle` but for enemies. zp_next_enemy_cycle = $37 + ;; Whether sprites have already been moved out in the 'move_sprites_out' + ;; situation. It's probably a waste of resources to spend a full byte for + ;; this, but I didn't see where to put it either, and we still have plenty + ;; of RAM left. + zp_moved_out = $38 + ;; Switch from the title screen to the main screen. Note that this function ;; is to be called with the PPU disabled. If that's not the case, then it ;; will set the proper values to disable it on the next `nmi` call and set @@ -65,11 +77,7 @@ sta PPU::zp_mask ;; Setup the player timer. - .ifdef PARTIAL - lda #1 - .else - lda #PLAYER_TIMER_VALUE - .endif + lda #PLAYER_TIMER_VALUE sta zp_player_timer ;; Mark the state of the game as "game". That is, the player has @@ -82,14 +90,48 @@ rts .endproc + ;; Move enemies and bullets out of the screen. This is done by setting the + ;; 'inactive' state for each object. + .proc move_sprites_out + ldx #0 + lda #$FF + + ;; Invalidate all enemies. + ldy #Enemies::ENEMIES_POOL_CAPACITY + @enemies_reset_loop: + sta Enemies::zp_enemies_pool_base, x + NEXT_ENEMY_INDEX_X + dey + bne @enemies_reset_loop + + ;; Invalidate all bullets. + ldy #Bullets::BULLETS_POOL_CAPACITY + @bullets_reset_loop: + sta Bullets::zp_bullets_pool_base, x + NEXT_BULLET_INDEX_X + dey + bne @bullets_reset_loop + + ;; Set that we have done this operation so it's not done in future + ;; cycles. + lda #1 + sta Driver::zp_moved_out + + rts + .endproc + .proc update + ;; If the player timer is over, jump to the game immediately. Otherwise + ;; decrement the counter. lda zp_player_timer beq @game dec zp_player_timer beq @load_player + ;; TODO: items falling down. ;; TODO: blinking of the selected player (every HZ count?). + rts @load_player: @@ -98,15 +140,22 @@ jsr Enemies::init jsr Explosions::init - ;; Initialize pause timer. + ;; Initialize pause timer and whether sprites have been moved out of the + ;; screen. lda #0 sta zp_pause_timer + sta Driver::zp_moved_out ;; Initialize variables for sprite cycling. sta zp_next_bullet_cycle sta zp_next_enemy_cycle @game: + ;; Has the player died? + lda Globals::zp_flags + and #$10 + bne @do_minimal_update + ;; Check if the player is toggling the `pause` state. lda #(Joypad::BUTTON_START | Joypad::BUTTON_SELECT) and Joypad::zp_buttons1 @@ -158,10 +207,32 @@ jsr Player::update jsr Bullets::update jsr Enemies::update + @do_minimal_update: jsr Explosions::update - ;; TODO: check if player has died + ;; Has the player died? If it is dead, then we need to remove all + ;; sprites except for objects and explosions, and whenever + ;; explosions/items are done moving we can set the timer again to start + ;; over with the game screen. + lda Globals::zp_flags + and #$10 + beq @sprite_cycling + + ;; Invalidate bullets and enemies if we haven't already. + lda Driver::zp_moved_out + bne @check_explosions + jsr move_sprites_out + + @check_explosions: + ;; Are there still active explosions? + lda Explosions::zp_active + bne @sprite_cycling + + ;; Nope! Then set the player's timer. + lda #PLAYER_TIMER_VALUE + sta zp_player_timer + @sprite_cycling: __fallthrough__ sprite_cycling .endproc diff --git a/src/enemies.s b/src/enemies.s index 5acadc5..0279958 100644 --- a/src/enemies.s +++ b/src/enemies.s @@ -86,6 +86,15 @@ CURRENT_TILES_BYTES = ENEMIES_POOL_CAPACITY * 4 zp_current_tiles = $F0 ; asan:reserve CURRENT_TILES_BYTES + ;; Cached values for the tile coordinates from the player. This is set + ;; before enemy update, and it's then used during collision check for each + ;; enemy. + zp_player_tile_left = $FC + zp_player_tile_right = $FD + zp_player_tile_top = $FE + zp_player_tile_waist = $FF + zp_player_tile_bottom = $CF + ;; Values for the counter of enemies that fall. ;; ;; NOTE: values for this have to fit into a nibble. @@ -234,6 +243,45 @@ .proc update ldx #0 + ;; Save the player's tile coordinates now as it will be useful/faster + ;; for collision checking with each enemy. + lda Player::zp_screen_y + tay + lsr + lsr + lsr + sta Enemies::zp_player_tile_top + tya + clc + adc #Player::PLAYER_WAIST + lsr + lsr + lsr + sta Enemies::zp_player_tile_waist + tya + clc + adc #Player::PLAYER_HEIGHT + lsr + lsr + lsr + sta Enemies::zp_player_tile_bottom + + lda Player::zp_screen_x + tay + clc + adc #Player::LEFT_OFFSET + lsr + lsr + lsr + sta Enemies::zp_player_tile_left + tya + clc + adc #(Player::PLAYER_WIDTH / 2) + lsr + lsr + lsr + sta Enemies::zp_player_tile_right + ;; The loop index will be moved out of the 'y' register since movement ;; handlers might need to use it. Note that we loop over all the pool ;; instead of just deciding on active ones. This is just to give dead @@ -330,7 +378,14 @@ lsr sta Enemies::zp_current_tiles + 1, x - ;; TODO: collision with player + ;; Does this enemy collide with the player? + jsr Enemies::collides_with_player + beq @increase_index_next + + ;; Ooops, the player just kicked the bucket! Call the handlers for the + ;; enemy and the player and return early. + jsr Enemies::bite_the_dust + JAL Player::die_bart_die @increase_index_next: ;; Move the 'x' register to the current enemy for this iteration. @@ -486,12 +541,59 @@ rts .endproc + ;; Sets 'a' to 1 if the current enemy collides with the player, 0 otherwise. + .proc collides_with_player + ;; Top left/right are done only once because the top of the player is + ;; just the head, which in anchored to one side. Hence, depending on + ;; where the player is heading, we will check for top left or right. + bit Player::zp_state + bvs @set_left + lda Enemies::zp_player_tile_right + bne @store_top + @set_left: + lda Enemies::zp_player_tile_left + @store_top: + sta Globals::zp_arg1 + lda Enemies::zp_player_tile_top + sta Globals::zp_arg0 + jsr collides + bne @end + + ;; Waist left + lda Enemies::zp_player_tile_left + sta Globals::zp_arg1 + lda Enemies::zp_player_tile_waist + sta Globals::zp_arg0 + jsr collides + bne @end + + ;; Bottom left + lda Enemies::zp_player_tile_bottom + sta Globals::zp_arg0 + jsr collides + bne @end + + ;; Waist right + lda Enemies::zp_player_tile_right + sta Globals::zp_arg1 + lda Enemies::zp_player_tile_waist + sta Globals::zp_arg0 + jsr collides + bne @end + + ;; Bottom right + lda Enemies::zp_player_tile_bottom + sta Globals::zp_arg0 + jsr collides + + @end: + rts + .endproc + ;; The enemy has been set to dust, remove it. .proc bite_the_dust dec Enemies::zp_enemies_pool_size - ;; TODO: this assumes we are coming from within Enemies always. What - ;; about impacting bullets? ldx Enemies::zp_pool_index ;; Invalidate this enemy. diff --git a/src/explosions.s b/src/explosions.s index 6e04668..58a700e 100644 --- a/src/explosions.s +++ b/src/explosions.s @@ -31,19 +31,22 @@ ;; 3. X coordinate. zp_pool_base = $70 ; asan:reserve EXPLOSIONS_POOL_CAPACITY_BYTES + ;; Number of active explosions at the moment. + zp_active = $7C + ;; The amount of time each explosion frame will take. FRAME_TIME = HZ / 20 ;; Initialize the pool of explosions for the game. .proc init lda #0 + sta Explosions::zp_active + ldx #0 ldy #EXPLOSIONS_POOL_CAPACITY - @loop: sta Explosions::zp_pool_base, x NEXT_EXPLOSION_INDEX_X - dey bne @loop @@ -73,6 +76,9 @@ sta Explosions::zp_pool_base + 1, x lda Globals::zp_arg3 sta Explosions::zp_pool_base + 2, x + + ;; Increase the number of active explosions and quit. + inc Explosions::zp_active rts @next: @@ -122,7 +128,9 @@ bne @next @explosion_done: - ;; We are actually done. Invalidate the explosion. + ;; We are actually done. Decrement the number of active explosions and + ;; invalidate this one. + dec Explosions::zp_active ldy #0 __fallthrough__ @set_and_next diff --git a/src/player.s b/src/player.s index df2ee68..a821f0e 100644 --- a/src/player.s +++ b/src/player.s @@ -114,6 +114,11 @@ ;; Initialize the player's sprite. Note that for the sprite to look ;; correctly on screen you still need to call `Player::update` afterwards. .proc init + ;; Make sure that the 'dead' bit from the global flags is zeroed out. + lda Globals::zp_flags + and #%11101111 + sta Globals::zp_flags + ;; Initial state. lda #%01000100 sta zp_state @@ -822,4 +827,31 @@ rts .endproc + + ;; That's just german for "the Bart, the". + .proc die_bart_die + ;; TODO: dec lifes + + ;; Move the player's sprites out of the screen. + ldx #0 + lda #$FF + sta OAM::m_sprites, x + sta OAM::m_sprites + 4, x + sta OAM::m_sprites + 8, x + sta OAM::m_sprites + 12, x + sta OAM::m_sprites + 16, x + sta OAM::m_sprites + 20, x + + ;; Set the player as dead. + lda #$10 + ora Globals::zp_flags + sta Globals::zp_flags + + ;; Create an explosion. + lda Player::zp_screen_y + sta Globals::zp_arg2 + lda Player::zp_screen_x + sta Globals::zp_arg3 + JAL Explosions::create + .endproc .endscope -- cgit v1.2.3