[4.5.9] Player's Death Animation, Checkpoints and Game Over screen (metroVania module)

dale_coop

Moderator
Staff member
Death animation, checkpoints and Game Over screen

GameOver.gif

I'll explain a simple way of implementing this.


Context:
Your player is hurt and has no health left, instead of restarting the screen, we'll display his death animation. And if he still has lives, we'll send him back to the last checkpoint (if there is no checkpoint, he'll be respawned at the beginning of the game). If there are no lives left, we'll go to a dedicated Game Over screen.

Note:
I'm going to use the MetroVania module here (because it's the most widely used), but this tutorial should work with any module.




0) BACKUP YOUR FILES!
First thing first, make a backup of your NESmaker folder (copy/paste or right clic, send to Compress Zip Folder). Like that if anything goes wrong you can restaure your NESmaker files.



1) DEATH ANIMATION

Edit the Player object, and make any "death" animation for it:

2023-08-22 14_47_16-NES MAKER 4.5.9 - Version_ 0x176 - MetroVaniaTutorial.MST.png
For that tutorial, I will just make a 2 frames blinking Player ("death right" and "death "left" animations).

Then, in the Object Detail, assigned those death animations to a new "Death" animation type:

2023-08-22 14_48_32-Monster Animation Info.png

Now, we'll use the ACTION STEP 6 as the Player's "Death" state.

Select the Action Step 6, set the "Death" animation type, an animation speed, also set the "ignore gravity" flag and the EndAction to "GoToContinue" with a timer value of "4" (use any value you want here, it will be the duration of the death animation):

2023-08-22 14_50_26-Monster Animation Info.png
The "GoToContinue" will warp the Player to the last checkpoint.



2) CHECKPOINT TILE

Open the "Project Settings > Project Labels", and select a tile type that is not used and rename it as "Checkpoint".
For exemple here, I will rename the "Tile 13" to "Checkpoint":

2023-08-22 16_39_16-Project Settings.png

Now in the "Project Settings > Script Settings", under "TileTypes", select the "Tile 13" element (because I chose to use the "tile 13" as my checkpoint just before).
Then, on the right side panel, navigate into the "BASE_4_5\Game\MOD_PlatformerBase\Tiles" folder and double-click on the "checkpoint_CheckpointBase.asm" to assign that script to the "Tile 13" element you have selected:

2023-08-22 16_44_45-Project Settings.png

The checkpoint tiles should now work at this point. You can place checkpoint tiles at the beginning of each level of your game (at the spawn/warp in position).

Important note aout checkpoint tiles: if you place a checkpoint tile somewhere in a scrolling level (not on the first screen of the scrolling level), you need to set the Screen Infos exactly identical as the first screen of that scrolling level. Because when your player will respawn on that Checkpoint, that screen will become the first screen (loaded) of the scrolling section.



3) HANDLE PLAYER DEATH

We need to make sure that when the player dies, it plays the death animation (action step 6) and not just restart the screen (like it does currently).

In the "Project Settings > Script Settings" dialog, under Common, edit the "Handle Hurt Player" script (hurtPlayer_MetroidVania.asm):

2023-08-22 15_48_16-Project Settings.png

Around the beginning of the script, find the:
Code:
    GetActionStep temp
    CMP #$07
    BNE +canHurtPlayer
        JMP +skipHurt
    +canHurtPlayer
Replace the "07" by a "06", like this:

2023-08-22 15_49_08-● hurtPlayer_PlatformBase.asm - Visual Studio Code.png


Now, from line 32, you should see this:
Code:
        +healthBelowZero
           
            JMP +playerHealthIsZero
        +notDeadYet      
        ChangeActionStep player1_object, #$07

Replace those lines with:
Code:
        +healthBelowZero:
            ;; player is dead, we play the death state:
            ChangeActionStep player1_object, #$06
            ;; stop the scrolling :
            LDA scrollByte
            AND #%00111110
            ORA #%00000010
            STA scrollByte
            ;; and disables the inputs:
            LDA #$FF
            STA gameState          
            JMP +continue
        +notDeadYet:
            ;; player is hurt, we play the hurt state:
            ChangeActionStep player1_object, #$07
        +continue:
Your code should now look like:

2023-08-23 09_06_05-hurtPlayer_PlatformBase.asm - Visual Studio Code.png

Finally, search for the "+playerHealthIsZero" (around line 88)...
and comment out (or remove all the code) between "+playerHealthIsZero" and "+skipHurt", like that:

2023-08-22 15_55_48-● hurtPlayer_PlatformBase.asm - Visual Studio Code.png
 
Last edited:

dale_coop

Moderator
Staff member
4) GAME OVER SCREEN

In your Overworld, create a screen that will be your Game Over.
For example here, I chose the last screen of my Overworld (Y:15, X:15):
2023-08-22 16_14_23-NES MAKER 4.5.9 - Version_ 0x176 - MetroVaniaTutorial.MST.png

And I design a very simple "GAME OVER" with tiles, and I don't forget to set a Game State that will be specific to my game over screen (a special screen):

2023-08-22 16_11_32-NES MAKER 4.5.9 - Version_ 0x176 - MetroVaniaTutorial.MST.png
(I set a different game state, because all the input scripts are assigned per game state, and don't want any inputs for that sceeen, except a RESET when I will press the Start button maybe)

In the screen Info, I set the "Hide sprites", "Hide HUD" and also the "Edge for scroll" flags (I don't want scrolling or see any sprites or hud on my Game Over screen). And I also set the "Warp in Screen Location (X,Y)" coords:

2023-08-22 16_12_27-Screen Details.png
(I choose anywhere around the center of the screen).

And I place SOLID collisions all around the screen, to avoid my (invisible) Player object to escape from that screen.

2023-08-22 16_22_36-NES MAKER 4.5.9 - Version_ 0x176 - MetroVaniaTutorial.MST.png



5) NO MORE LIVES WARP TO GAME OVER

The last step will be to modify the "GoToContinue" to take in consideration the lives.
Edit the "EndTimerScripts.asm" script that is located in your "GameEngineData\Routines\BASE_4_5\Game\Subroutines\" folder.

2023-08-22 16_27_49-Subroutines.png

In the "EndTimerScripts.asm", search for the "goToContinue_action:" section (around line 160).
Between that line and the "RTS" line 181 (before the ";;; 10 = Restart Screen"), replace with that piece of code:
Code:
    ;; if no more lives, warp to GAME OVER:
    LDA myLives
    BEQ +notMoreLives
        DEC myLives
        BEQ +notMoreLives
        BMI +notMoreLives
            ;; player still have lives, warp to last checkpoint:
            LDA continueMap
            STA warpToMap
          
            LDA continueScreen
            STA warpToScreen
            LDA #$02
            STA screenTransitionType
            JMP +doWarpToContinue
    +notMoreLives:
            ;; no more lives:
            LDA #$00              ;; 00: Overworld, 01: Underworld
            STA warpToMap

            LDA #$FF              ;; screen Y:15, X:15 (our GAME OVER screen)
            STA warpToScreen         
            LDA #$01
            STA screenTransitionType

    +doWarpToContinue: 
        LDA myMaxHealth
        STA myHealth 
        WarpToScreen warpToMap, warpToScreen, screenTransitionType
        ChangeActionStep player1_object, #$00
        LDX player1_object
        LDA #$00000000
        STA Object_direction,x
Your code should now look like:

2023-08-23 09_52_59-TimerEndScripts.asm - Visual Studio Code.png



6) RESET ON GAME OVER SCREEN

Last thing, when the GAME OVER is displayed, we need a way to reset the game.

In your Scripts > Input Scripts, adds the "SimpleReset.asm" script (from the "BASE_4_5\Game\inputScripts" folder):
2023-08-23 09_14_02-NES MAKER 4.5.9 - Version_ 0x176 - MetroVaniaTutorial.MST.png

And in the Input Editor, Assign that "SimpleReset.asm" script to the press START button for the "GameState-3" target (the same game state we assigned our Game Over to):
2023-08-23 09_16_31-NES MAKER 4.5.9 - Version_ 0x176 - MetroVaniaTutorial.MST.png



Voilà, now you should be able to respawn to the checkpoint and if no more lives be warped to the Game Over screen.
I've tried to keep it simple with minimal alterations of the stock code. Let me know if you encounter any diffculties.
 
Last edited:

TalkingCat

Member
Thanks Dale, this is a very useful article that will allow beginners to start creating faster) I had to spend a lot of time to implement these scenarios to collect information bit by bit from the forum.
 

dale_coop

Moderator
Staff member
Thanks Dale, this is a very useful article that will allow beginners to start creating faster) I had to spend a lot of time to implement these scenarios to collect information bit by bit from the forum.
Yes, it's a regular request. So I decided to write this simple tutorial.
I should have written it a couple of years ago. But lack of time delayed me (...there are always more urgent things to do >_<).
 

offparkway

Active member
Death animation, checkpoints and Game Over screen

View attachment 7451

I'll explain a simple way of implementing this.


Context:
Your player is hurt and has no health left, instead of restarting the screen, we'll display his death animation. And if he still has lives, we'll send him back to the last checkpoint (if there is no checkpoint, he'll be respawned at the beginning of the game). If there are no lives left, we'll go to a dedicated Game Over screen.

Note:
I'm going to use the MetroVania module here (because it's the most widely used), but this tutorial should work with any module.




0) BACKUP YOUR FILES!
First thing first, make a backup of your NESmaker folder (copy/paste or right clic, send to Compress Zip Folder). Like that if anything goes wrong you can restaure your NESmaker files.



1) DEATH ANIMATION

Edit the Player object, and make any "death" animation for it:

View attachment 7452
For that tutorial, I will just make a 2 frames blinking Player ("death right" and "death "left" animations).

Then, in the Object Detail, assigned those death animations to a new "Death" animation type:

View attachment 7453

Now, we'll use the ACTION STEP 6 as the Player's "Death" state.

Select the Action Step 6, set the "Death" animation type, an animation speed, also set the "ignore gravity" flag and the EndAction to "GoToContinue" with a timer value of "4" (use any value you want here, it will be the duration of the death animation):

View attachment 7455
The "GoToContinue" will warp the Player to the last checkpoint.



2) CHECKPOINT TILE

Open the "Project Settings > Project Labels", and select a tile type that is not used and rename it as "Checkpoint".
For exemple here, I will rename the "Tile 13" to "Checkpoint":

View attachment 7456

Now in the "Project Settings > Script Settings", under "TileTypes", select the "Tile 13" element (because I chose to use the "tile 13" as my checkpoint just before).
Then, on the right side panel, navigate into the "BASE_4_5\Game\MOD_PlatformerBase\Tiles" folder and double-click on the "checkpoint_CheckpointBase.asm" to assign that script to the "Tile 13" element you have selected:

View attachment 7457

The checkpoint tiles should now work at this point. You can place checkpoint tiles at the beginning of each level of your game (at the spawn/warp in position).

Important note aout checkpoint tiles: if you place a checkpoint tile somewhere in a scrolling level (not on the first screen of the scrolling level), you need to set the Screen Infos exactly identical as the first screen of that scrolling level. Because when your player will respawn on that Checkpoint, that screen will become the first screen (loaded) of the scrolling section.



3) HANDLE PLAYER DEATH

We need to make sure that when the player dies, it plays the death animation (action step 6) and not just restart the screen (like it does currently).

In the "Project Settings > Script Settings" dialog, under Common, edit the "Handle Hurt Player" script (hurtPlayer_MetroidVania.asm):

View attachment 7459

Around the beginning of the script, find the:
Code:
    GetActionStep temp
    CMP #$07
    BNE +canHurtPlayer
        JMP +skipHurt
    +canHurtPlayer
Replace the "07" by a "06", like this:

View attachment 7460


Now, from line 32, you should see this:
Code:
        +healthBelowZero
 
            JMP +playerHealthIsZero
        +notDeadYet
        ChangeActionStep player1_object, #$07

Replace those lines with:
Code:
        +healthBelowZero:
            ;; player is dead, we play the death state:
            ChangeActionStep player1_object, #$06
            ;; stop the scrolling :
            LDA scrollByte
            AND #%00111110
            ORA #%00000010
            STA scrollByte
            ;; and disables the inputs:
            LDA #$FF
            STA gameState
            JMP +continue
        +notDeadYet:
            ;; player is hurt, we play the hurt state:
            ChangeActionStep player1_object, #$07
        +continue:
Your code should now look like:

View attachment 7461

Finally, search for the "+playerHealthIsZero" (around line 88)...
and comment out (or remove all the code) between "+playerHealthIsZero" and "+skipHurt", like that:

View attachment 7462
Hey @dale_coop !

I'm working in the Adventure Module, and I'm having a tough time getting this to work. I originally tried setting everything up like my old (functioning) games, but it doesn't seem to translate.

So... I followed your instructions (within the Adventure Module) and I can't seem to get the player to respawn at the checkpoint tile. It just restarts at the locationwhere the player died (the death animation works though).

I've tried the Adventure Checkpoint as well as the Platformer Checkpoint tiles. Same result. The checkpoint tile changes so it is registering collision with the player, but it will not ever acknowledge the checkpoint warp location and just restarts the screen where the player died.

(FYI, in this game I'm not using a game over screen, or "lives". When health = 0, restart at checkpoint)

...I also just noted a couple of random instances where when my player walks to the next screen, it takes me to a completely unrelated screen. I can't replicate the problem, but it's happened a couple of times. Not sure if that's related to the checkpoint and some sort of currentNametable issue or something.
 
Last edited:

dale_coop

Moderator
Staff member
Can you share your modified scripts (Handle Hurt Player, TimerEndScript, and Checkpoint tile script) ?
 

offparkway

Active member
Timer End Script:

Code:
;;; 09 = Go To Continue
goToContinue_action:
    LDA continueMap
    STA warpMap
    
    LDA continueScreen
    STA warpToScreen
    
     LDA myMaxHealth
        STA myHealth 
        WarpToScreen warpToMap, warpToScreen, #$02     
        ChangeActionStep player1_object, #$00
        LDX player1_object
        LDA #$00000000
        STA Object_direction,x
    RTS

Code:
    ;;;;;;;;;;;;;;;;;; Presumes there is a variable called myLives defined in user variables.
    ;;;;;;;;;;;;;;;;;; You could also create your own variable for this.

    LDA gameHandler
    AND #%10000000
    BEQ +canHurtPlayer
        JMP +skipHurt

+invincibleCheck

    LDA Object_vulnerability,x ;; loads vulnerability byte for player object
    AND #%00000100  ;; checks if "Action Step Flag 02" is checked, assumes this is your invincible toggle;;
    BEQ +canHurtPlayer ;; if not invincible, can be hurt
    JMP +skipHurt ;; if invincible, skip hurting; we "jump" to the skipHurt part of the

+canHurtPlayer:
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;;;;; is the monster below our feet?
    ;;;;;;;;;; and are we moving downward?

+doHurtPlayer   
    TXA
    STA temp
    GetActionStep temp
    CMP #$06 ;;; death state
    BNE +canHurtPlayer
        JMP +skipHurt
+canHurtPlayer

     LDA myInvincibleTimer
        BEQ +
        JMP +skipHurt
    +
        LDA #50 ;;;length of time to be invincible
        STA myInvincibleTimer


    ;;; will presume there is a variable myHealth
    ;;; and that player hurt state is action state 7.
    GetActionStep player1_object
    CMP #$07 ;; hurt state.
    BEQ +notAlreadyInHurtState
        DEC myHealth
        
        BMI +healthBelowZero
        BEQ +healthBelowZero
            JMP +notDeadYet
        +healthBelowZero:
                    ;; player is dead, we play the death state:
            ChangeActionStep player1_object, #$06
            ;; stop the scrolling :
            ;LDA scrollByte
            ;AND #%00111110
            ;ORA #%00000010
            ;STA scrollByte
            ;; and disables the inputs:
            LDA #$FF
            STA gameState         
            JMP +continue
        +notDeadYet:
            ;; player is hurt, we play the hurt state:
            ChangeActionStep player1_object, #$07
        +continue:
            ;; recoil
            LDA #$00
            STA Object_h_speed_hi,x
            STA Object_h_speed_lo,x
            STA Object_v_speed_hi,x
            STA Object_v_speed_lo,x
            LDA xPrev
            STA Object_x_hi,x
            LDA yPrev
            STA Object_y_hi,x
    +notAlreadyInHurtState
        LDA Object_x_hi,x
        CLC
        ADC self_center_x
        STA tempA
        LDA Object_y_hi,x
        CLC
        ADC self_center_y
        STA tempB
        TXA
        PHA
            LDX otherObject
            LDA Object_x_hi,x
            CLC
            ADC other_center_x
            STA tempC
            LDA Object_y_hi,x
            CLC
            ADC other_center_y
            STA tempD
        PLA
        TAX
    
        ;;; RECOIL L/R
            ;+recoilHor
                LDA tempA
                CMP tempC
                BCS +recoilRight
                    LDA Object_direction,x
                    AND #%00001111
                    ORA #%10000000
                    STA Object_direction,x
                    JMP +skipHurt

                +recoilRight
                    LDA Object_direction,x
                    AND #%00001111
                    ORA #%11000000
                    STA Object_direction,x
                    JMP +skipHurt
    
+playerHealthIsZero:

    ;LDA continueMap
    ;STA warpMap
    
    ;LDA continueX
    ;STA newX
    ;LDA continueY
    ;STA newY
    
    ;LDA continueScreen
    ;STA warpToScreen
    ;STA camScreen
    

    ;LDA myMaxHealth
    ;STA myHealth
    
    
    
    ;WarpToScreen warpToMap, warpToScreen, #$02
        ;; arg0 = warp to map.  0= map1.  1= map2.
        ;; arg1 = screen to warp to.
        ;; arg2 = screen transition type - most likely use 1 here.
            ;; 1 = warp, where it observes the warp in position for the player.

    
+skipHurt

...and the checkpoint tile that should just be default:

Code:
;;; sets a new player continue checkpoint.
    CPX player1_object
    BNE +skip
    LDA currentNametable
    STA continueScreen
    LDA Object_x_hi,x
    STA continueX
    LDA Object_y_hi,x
    STA continueY
    
+skip

I also tried the platformer checkpoint tile as well.
 

dale_coop

Moderator
Staff member
In your TimerEndscript, it should be "STA warpToMap" (instead of "STA warpMap).

In your Handle Hurt Player, you could try adding:
Code:
LDA gameState
CMP #$FF
BNE +continue
    JMP +skipHurt
+continue

Try those 2 small modifications... but I don't see anything that could cause the issue you're experimenting.
If you can't fix, we can continue in private (maybe you can send me a copy of your nesmaker project files)
 

offparkway

Active member
In your TimerEndscript, it should be "STA warpToMap" (instead of "STA warpMap).

In your Handle Hurt Player, you could try adding:
Code:
LDA gameState
CMP #$FF
BNE +continue
    JMP +skipHurt
+continue

Try those 2 small modifications... but I don't see anything that could cause the issue you're experimenting.
If you can't fix, we can continue in private (maybe you can send me a copy of your nesmaker project files)
I wonder if warpToMap will do it. I honestly didn’t realize there were two different variables like that. Will give it a shot in a little while and let you know! Thanks Dale.
 

offparkway

Active member
In your TimerEndscript, it should be "STA warpToMap" (instead of "STA warpMap).

In your Handle Hurt Player, you could try adding:
Code:
LDA gameState
CMP #$FF
BNE +continue
    JMP +skipHurt
+continue

Try those 2 small modifications... but I don't see anything that could cause the issue you're experimenting.
If you can't fix, we can continue in private (maybe you can send me a copy of your nesmaker project files)
...negative. Changed to warpToMap, and put the gameState check near the beginning of my hurtPlayer script, and still the same result: the player just respawns where he died, even though I walked over a bunch of checkpoint tiles on a previous screen.
 

dale_coop

Moderator
Staff member
That's weird. It should. Something is definitely not working correctly in your project.
Make sure the Action Step 6 is set to "Go to Continue" (and not "restart screen" ;))
 

offparkway

Active member
That's weird. It should. Something is definitely not working correctly in your project.
Make sure the Action Step 6 is set to "Go to Continue" (and not "restart screen" ;))
Haha, I used to make fun of those simple solutions until I realized that it’s super easy to overlook. I’ll check on it after breakfast.
 

offparkway

Active member
That's weird. It should. Something is definitely not working correctly in your project.
Make sure the Action Step 6 is set to "Go to Continue" (and not "restart screen" ;))
... "dead, stopMoving, GoToContinue, Loop" is the death state order of actions. Should be good. I also checked to make sure all the timer end actions were labeled properly (they are). so you see why I'm stumped!



@dale_coop OK, it definitely has something to do with the player object colliding with the checkpoint tile. I had the checkpoint tile check for a monster instead, and when a monster walks over the checkpoint tile, it activates. then when my player dies, i respawn where the monster touched the checkpoint tile!

But when I change the CPX back to look for player1_object, the checkpoint tile ignores the coordinates again. However the tile does recognize the player, because it changes the tile graphic/type when I walk over it. So the checkpoint tile "sees" the player, but seems to ignore the currentNametable stuff.
 
Last edited:

offparkway

Active member
the checkpoint script you shared earlier is the one you are using? It’s the full script?
Yeah that was the whole thing. I’ve added & deleted lines since posting it, trying to get it to work. But it’s just the stock checkpoint tile for the adventure base. I tried the platformer one too, but same result.

@dale_coop right now it looks like this. But it seems to work just fine when a monster touches the tile. Just not when player1_object touches it!

Code:
;;; sets a new player continue checkpoint.


    CPX player1_object
    BEQ +doCheckpoint
        JMP +skip
+doCheckpoint

    LDA currentNametable  ;;Object_screen,x
    STA continueScreen

    LDA Object_x_hi,x
    STA continueX
    LDA Object_y_hi,x
    STA continueY
    ;ChangeTileAtCollision #$20, #$00 ;; change to tile zero (make disappear), collision type 0
+skip
 
Last edited:

dale_coop

Moderator
Staff member
hmmmm
Your player object is a normal player object? Or you use character switching / selection ?

else send me a copy of your NESmaker files, I will check the issue.
 

offparkway

Active member
hmmmm
Your player object is a normal player object? Or you use character switching / selection ?

else send me a copy of your NESmaker files, I will check the issue.
I do in fact have a player/switching mechanic when you press select. But the issue happens either way. One of my other games can swap players as well, but the checkpoints operate just fine in that game.
 
Top Bottom