[4.5.6] Brawler Death Animations

AllDarnDavey

Active member
brawlerDeath.gif

This tutorial will go over adding custom death animations to players and enemies in the brawler module.

The first step is to make a custom death animations for your enemy, and assign that animation to action06 using these settings:
MonsterDieSettings.PNG

Assign a new copy of hurtMonster_BralerBase with a different name like hurtMonster_BrawlerBase_deathAnim.asm. Give it the code below:

Code:
LDA gameHandler
    AND #%10000000
    BEQ +canHurtPlayer
        JMP +skipHurt
+canHurtPlayer:
    TXA
    STA temp
    GetActionStep temp  ;;skip if already in either death or hurt state
    CMP #$06 ;; hurt state.
    BCC +notAlreadyInHurtState
        JMP +skipHurt
    +notAlreadyInHurtState

         LDA Object_health,x
         SEC
         SBC #$01
         BEQ +healthBelowZero
         BMI +healthBelowZero
            STA Object_health,x
            ChangeActionStep temp, #$07
            JMP +skipHurt
        +healthBelowZero
            ;;;; if this is set to a right edge
            ;;;; flip the bit so that scrolling can continue
            ;;;; if all bad guys are gone.
            CountObjects #%00001000
            CMP #$02
            BCC +notZeroCount
                LDA scrollByte
                ORA #%00000010
                STA scrollByte
                ;;; if there are no more monsters left, we want to disable
                ;;; the edge check for scrolling.
                LDA ScreenFlags00
                AND #%11101111
                STA ScreenFlags00
                ChangeActionStep temp, #$06
           
        +notZeroCount:
        ChangeActionStep temp, #$06
+skipHurt

That'll make action06 the enemy death animation, which will delete the enemy once the death animation is over.
 
Last edited:

AllDarnDavey

Active member
Now for the custom player death animations... this is a bit more complex, but the initial steps are very similar.

First make a custom player death animation and assign it to action06 with these settings:
PlayerDieSettings.PNG

Assign your hurtPlayer_BrawlerBase script a new one with a different name like hurtPlayer_BrawlerBase_deathAnim.asm with the code below:
Code:
LDA gameHandler
    AND #%10000000
    BEQ +canHurtPlayer
        JMP +skipHurt
+canHurtPlayer:

    GetActionStep player1_object
    CMP #$06 ;; hurt state.
    BCC +notAlreadyInHurtState
        JMP +skipHurt
    +notAlreadyInHurtState

         DEC myHealth
       
         BMI +healthBelowZero
         BEQ +healthBelowZero
             JMP +notDeadYet
         +healthBelowZero
             JMP +playerHealthIsZero
         +notDeadYet
;         UpdateHudElement #$02
         ChangeActionStep player1_object, #$07
         JMP +skipHurt

+playerHealthIsZero:
    ChangeActionStep player1_object, #$06
    Dec myLives
    LDA myLives
    BNE +skipHurt
        JMP RESET ;; game over.

+skipHurt

This will make the player play Action06 when his HP hits zero, then GoToContinue. We'll need to update the GoToContinue in the TimerEndScripts to do all the continue code that hurtPlayer used to do.

Give your TimerEndScripts a new name like TimerEndScripts_deathAnim.asm, and replace the GoToContinue section with this version:
Code:
;;; 09 = Go To Continue
goToContinue_action:
    LDA continueMap
    STA warpMap
   
    LDA continueScreen
    STA currentNametable
   
    ;LDX player1_object
    ;STA Object_screen,x
   
    ;LDA #$02 ;; this is continue type warp.
    ;STA screenTransitionType ;; is of warp type
    WarpToScreen warpToMap, continueScreen, #$02
   
    LDA gameHandler
    ORA #%10000000
    STA gameHandler ;; this will set the next game loop to update the screen.
    LDA myMaxHealth ;; reset health
    STA myHealth  
    ChangeActionStep player1_object, #$00  ;;set to idle
    LDX player1_object  ;;force face right after spawn
    LDA #$00000010
    STA Object_direction,x
    RTS
DO NOT replace the entire file, just the goToContinue_action at about line 159. We need to update it to run a few things the hurtPlayer script used to run, but cannot now because we're playing a death animation instead.

Now, if we're changing our TimerEndScripts file, we need to update Bank1C to point to our new file. Assign a new copy of the Bank1C file with a new name like Bank1C_deathAnim.asm and make sure line7 points to your new TimerEndScripts file location and name.

Bank1C_deathAnim.asm -- Line 07
Code:
.include ROOT\Game\MOD_MyScripts\Subroutines\TimerEndScripts_deathAnim.asm  ;;your path to TimerEndScript maybe different, change to your file
 
Last edited:

AllDarnDavey

Active member
Now that'll work if your standing still while the player is playing the death animation, but there's still one major issue... if you move or punch before the death animation is done playing it'll force you into another action state, and that will cause a bunch of issues.

We need to change our input scripts to ignore inputs when playing the death animation, most of them already ignore inputs if in the hurt state, we just need to make it ignore both hurt and death.

If you look at some of the scripts above you might notice they've been updated to ignore if the action state is 06 or higher, rather than just ignoring action 07. This'll make it ignore inputs if we're playing either the hurt or death animations. But it does mean updating nearly ALL our inputs slightly. As always make a copy of the original files and give them a new name with _deathAnim at the end, and assign them.

moveRight_deathAnim.asm
Code:
;;;;
    TXA
    STA temp ;; assumes the object we want to move is in x.
  GetActionStep temp
    CMP #$06
    BCC +notHurt
        RTS
    +notHurt
    
        StartMoving temp, #RIGHT
        TXA
        STA temp ;; assumes the object we want to move is in x.
        ChangeFacingDirection temp, #FACE_RIGHT
        
    RTS

moveLeft_deathAnim.asm
Code:
;;;;
    
    TXA
    STA temp ;; assumes the object we want to move is in x.
  GetActionStep temp
    CMP #$06
    BCC +notHurt
        RTS
    +notHurt
  
        StartMoving temp, #LEFT
        TXA
        STA temp ;; assumes the object we want to move is in x.
        ChangeFacingDirection temp, #FACE_LEFT

    RTS

moveUp_bralwer_deathAnim.asm
Code:
;;;;
    TXA
    STA temp ;; assumes the object we want to move is in x.
  GetActionStep temp
    CMP #$06
    BCC +notHurt
        RTS
    +notHurt
        StartMoving temp, #UP

    RTS

moveDown_bralwer_deathAnim.asm
Code:
;;;;   
    TXA
    STA temp ;; assumes the object we want to move is in x.
  GetActionStep temp
    CMP #$06
    BCC +notHurt
        RTS
    +notHurt
        StartMoving temp, #DOWN

    RTS

stopMoving_deathAnim.asm
Code:
;;;;
    TXA
    STA temp ;; assumes the object we want to move is in x.
   GetActionStep temp
    CMP #$06
    BCC +notHurt
        RTS
    +notHurt   
    StopMoving temp, #$FF, #$00
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    RTS

changeActiontoMoving_deathAnim.asm
Code:
TXA
    STA temp ;; assumes the object we want to move is in x.
 GetActionStep temp
    CMP #$06
    BCC +notHurt
        RTS
    +notHurt
    CMP #$02
    BEQ +skip ;; don't change to walk if we are punching.
    CMP #$01
    BEQ +alreadyInWalkState
        ChangeActionStep temp, #$01 ;; assumes that "walk" is in action 1
            ;arg0 = what object?
            ;arg1 = what behavior?
    +alreadyInWalkState
    +skip
    RTS

changeActionToStop_BrawlerBase_deathAnim.asm
Code:
TXA
    STA temp ;; assumes the object we want to move is in x.
    ;;; It is possible that we are pressing multiple directions, and let go of one of them.
    ;;; If this happens, we would have our behavior change to stop, even though we'd continue to move.
    ;;; What we need to do is checkt o see if the relevant dpad buttons are pressed.  If any buttons
    ;;; are pressed that would counter the change to a stop action upon release, we need to skip the
    ;;; change to stop action. 
     GetActionStep temp
    CMP #$06
    BCC +notHurt
        RTS
    +notHurt
    CMP #$02
    BNE +notAttacking
        RTS
    +notAttacking
    LDA controllerNumber_hold
    BNE weAreCheckingCOntroller2
        ;; we are checking controller 1.
        
        LDA gamepad
        AND #%11110000
        BEQ changeToStop_NoDpadPressed
            RTS
        weAreCheckingCOntroller2:
        LDA gamepad2
        AND #%11110000
        BEQ changeToStop_NoDpadPressed
            RTS
    changeToStop_NoDpadPressed:
        ChangeActionStep temp, #$00 ;; assumes that "walk" is in action 1
        ;arg0 = what object?
        ;arg1 = what behavior?
    RTS

ChangeToAttack_BrawlerBase_deathAnim.asm
Code:
;; if you would like unlockable weapons,
    ;; that will be created with the b button
    ;; use this code.
 
 
    
 
    TXA
    STA temp ;; assumes the object that we want is in x.

    GetActionStep temp
    CMP #$06
    BCC +
        RTS   
    +
    CMP #$02 ;; is it already attacking?
    BNE +canAttack
        ;; wait until we're back to idle to attack again.
        RTS
    +canAttack
    ChangeActionStep temp, #$02 ;; assumes that "attack" is in action 2
    ;arg0 = what object?
    ;arg1 = what behavior?
    StopMoving temp, #$FF, #$00
    
    

        WEAPON_POSITION_RIGHT_X = #$10
        WEAPON_POSITION_RIGHT_Y = #$08
 
        WEAPON_POSITION_LEFT_X = #$F8
        WEAPON_POSITION_LEFT_Y = #$08
        WEAPON_OBJECT = #$01
        WEAPON_RIGHT_STATE = #$00
        WEAPON_LEFT_STATE = #$00

        
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        ;; Now, we have to create the object.
        ;; We will need to determine the direction
        ;; of the player.
        LDX player1_object
        TXA
        STA temp
        GetObjectDirection temp ;; temp still observed from above.
            ;;; this object's direction is now loaded into the
            ;;; accumulator for comparison after the macro.
            ;; 0 = down
            ;; 1 = downright
            ;; 2 = right
            ;; 3 = upright
            ;; 4 = up
            ;; 5 = upleft
            ;; 6 = left
            ;; 7 = downleft
          
                CMP #$02
                BNE +notRight
                ;;; CREATE RIGHT WEAPON
                LDX player1_object
                LDA Object_x_hi,x
                CLC
                ADC #WEAPON_POSITION_RIGHT_X
                STA tempA
                 LDA Object_screen,x
                ADC #$00
                STA tempD
                
                LDA Object_y_hi,x
                CLC
                ADC #WEAPON_POSITION_RIGHT_Y
                STA tempB
              
                LDA #WEAPON_OBJECT
                STA tempC
                
              
               ;; use this is you want to always create a single object, based on
                   ;; the constant above.
                   ; CreateObject tempA, tempB, #WEAPON_OBJECT, #WEAPON_RIGHT_STATE, currentNametable
                
                   ;;; use this if you want to create a variable object based on
                   ;;; the weaponChoice varaible.
                   CreateObjectOnScreen tempA, tempB, tempC, #WEAPON_RIGHT_STATE, tempD
                   LDA #%11000000
                   STA Object_direction,x
                   JMP +doneWithCreatingWeapon
            +notRight
            
                ;;; CREATE LEFT WEAPON
                LDX player1_object
                LDA Object_x_hi,x
                CLC
                ADC #WEAPON_POSITION_LEFT_X
                STA tempA
                LDA Object_screen,x
                SBC #$00
                STA tempD
                LDA Object_y_hi,x
                CLC
                ADC #WEAPON_POSITION_LEFT_Y
                STA tempB
                
                LDA #WEAPON_OBJECT
                STA tempC
               ;; use this is you want to always create a single object, based on
                   ;; the constant above.
                   ; CreateObject tempA, tempB, #WEAPON_OBJECT, #WEAPON_DOWN_STATE, currentNametable
                
                   ;;; use this if you want to create a variable object based on
                   ;;; the weaponChoice varaible.
                   CreateObjectOnScreen tempA, tempB, tempC, #WEAPON_LEFT_STATE, tempD
                 LDA #%10000000
                 STA Object_direction,x
                 JMP +doneWithCreatingWeapon
            +notLeft
            
        +doneWithCreatingWeapon 
        
    RTS

do_simpleScrollRight_deathAnim.asm
Code:
TXA
    STA temp
  GetActionStep temp
    CMP #$06
    BCC +notHurt
        RTS
    +notHurt
    RIGHT_SCROLL_PAD = #$90
    LDX player1_object
    LDA Object_x_hi,x
    SEC
    SBC camX
    CMP #RIGHT_SCROLL_PAD
    BEQ +doActivateScrollByte
    BCS +doActivateScrollByte
        LDA scrollByte
        AND #%00111111
        STA scrollByte
        RTS
+doActivateScrollByte
    LDA scrollByte
    AND #%01000000
    BNE +notChangingCamDirectionForUpdate
    LDA scrollByte
    ORA #%00000010
 +notChangingCamDirectionForUpdate
    AND #%00111111
    ORA #%11000000
    STA scrollByte
    ;;; just like movement byte of a player.
    ;;; bit 7 indicates horizontal movement of a player
    ;;; bit 6 indicates 0 for left, 1 for right.
    
    RTS

We don't need up update do_StopScrolling.asm, it can stay the same.

Now if all the input scripts are updated and assigned correctly, you should be unable to break out of the death animation while it's playing, and it should work as intended.
 
Last edited:

AllDarnDavey

Active member
Great tutorial, thanks Davey.
Think you that it will work with the MetroVania module ?
It wouldn't plug in directly (the input scripts you'd need to update would definitely be different), the 2 hurt scripts and Timer end scripts might be the same or very similar though.
 

offparkway

Active member
this is great! I've only tried it on the monsters and it works well. The only issue I'm running into is that "right edge for scroll" no longer is recognized. Meaning even if I defeat the monsters, I can't move forward.

I changed the CMP #$02 to CMP #$01 and that seems to solve it
 
Last edited:

AllDarnDavey

Active member
this is great! I've only tried it on the monsters and it works well. The only issue I'm running into is that "right edge for scroll" no longer is recognized. Meaning even if I defeat the monsters, I can't move forward.

I changed the CMP #$02 to CMP #$01 and that seems to solve it
Hmm... I assume you mean in the hurtMonster_BrawlerBase_deathAnim.asm script?

Normally, in the default version of the script, that checks if the number of monsters is not zero and keeps the scroll locked until you've removed all monsters. The problem with my changes is this code only runs when a monster is hurt, and because we don't destroy monsters right away anymore, we tell them to play a death animation and have them self destruct once done. The code would never run when monsters are zero. The
Code:
CMP #$02
BCC +notZeroCount
branches if less than 2. Unlocking when only 1 monster remains (which means there is only 1 monster left and it's going to self destruct after playing it's death animation).

There might be a bug my code (NES dev is tricky), but I'm worried your change might just make it never lock the scroll. Make sure you cannot just run past everything to the end ignoring enemies. If it's working as intended then maybe it's fine, and I'm being overly cautious.
 

offparkway

Active member
Setting it to 1 is working for what I want it to do. I have 2 enemies, and can't progress until i beat them both. Then scrolling continues.

When it was set to 2, i killed both enemies and couldn't progress any further. I'm still learning all this stuff! All I know is that it seems to work at the moment!
 

offparkway

Active member
Hmm... I assume you mean in the hurtMonster_BrawlerBase_deathAnim.asm script?

Normally, in the default version of the script, that checks if the number of monsters is not zero and keeps the scroll locked until you've removed all monsters. The problem with my changes is this code only runs when a monster is hurt, and because we don't destroy monsters right away anymore, we tell them to play a death animation and have them self destruct once done. The code would never run when monsters are zero. The
Code:
CMP #$02
BCC +notZeroCount
branches if less than 2. Unlocking when only 1 monster remains (which means there is only 1 monster left and it's going to self destruct after playing it's death animation).

There might be a bug my code (NES dev is tricky), but I'm worried your change might just make it never lock the scroll. Make sure you cannot just run past everything to the end ignoring enemies. If it's working as intended then maybe it's fine, and I'm being overly cautious.
Yo! Been playing around and building my game with this script.

Getting weird results sometimes. For example: once in a while, the enemy dies but doesn't unlock the scroll and I'm stuck. Or if the enemy takes 5 hits to kill, I'll sometimes get in 4 hits and then the enemy turns basically invincible, forcing me to restart. One issue I'm noticing now is a specific enemy will take a few hits, then kill me instantly (even if I have full health). It's the same enemy with the same settings. Screens are set the same.

I re-read your comment about using CMP #$02 and it makes sense, so I changed the script back to #$02 to see if things improved, but scrolling never unlocks and I can't progress. I had to put it back to #$01. I set up everything exactly as you described.

Just a heads up! I love having the death animations, but I'm running in to all sorts of issues that make it almost impossible to get through Level 1 of my game without some sort of problem.

So far the player death animation stuff seems to work great. Sometimes my player will freeze mid-hurt and I have to restart, and sometimes he dies instantly like I mentioned above. I don't know if that has to do with these script changes or not.
 

AllDarnDavey

Active member
Getting weird results sometimes. For example: once in a while, the enemy dies but doesn't unlock the scroll and I'm stuck. Or if the enemy takes 5 hits to kill, I'll sometimes get in 4 hits and then the enemy turns basically invincible, forcing me to restart. One issue I'm noticing now is a specific enemy will take a few hits, then kill me instantly (even if I have full health). It's the same enemy with the same settings. Screens are set the same.
I can't say for sure, but this sounds like a problem with the check if the enemy is already in the hurt or death state. The stuff about comparing the action state to 06 at the beginning of hurtMonster.
Code:
    TXA
    STA temp
    GetActionStep temp  ;;skip if already in either death or hurt state
    CMP #$06 ;; hurt state.
    BCC +notAlreadyInHurtState
        JMP +skipHurt
    +notAlreadyInHurtState

If this check isn't working as intended it could allow you to get in an extra hit while the enemy is playing the hurt animation (thus making it seem like their HP is less than normal). It could also cause the death animation to get interrupted if hit again while the death animation is playing, which would cause the enemy not to die properly (the self destruct trigger is set to play at the end of the animation, but if it switches states mid animation you'll get issues). That could explain the invincible enemy, as if you subtract one when its HP is already zero, it might wrap around to 255 HP. Not sure why it would then kill you instantly, maybe some other code changes or something wrong with the hurtPlayer script.

I re-read your comment about using CMP #$02 and it makes sense, so I changed the script back to #$02 to see if things improved, but scrolling never unlocks and I can't progress. I had to put it back to #$01. I set up everything exactly as you described.
I've never been completely happy with my solution, it's hacky, and has no fallback, so if the check fails for any reason you may get soft locked and unable to continue the game. It worked with my test project just fine, but it's never sat right with me. Maybe there's a way to add in a backup enemy check to unlock the scroll, attached to the enemy death animation itself perhaps. I'm not sure, I'll have to experiment with it.
 
Top Bottom