[4.5.9] Manual Animation

crazygrouptrio

Active member
I wanted to wait until I got this system working exactly how I wanted before I shared it, and I finally reached that point! Apologies if this is a little confusing, and is a more intermediate tutorial, and requires altering and adding code in various places. Back up your project before diving in!

What is Manual Animation?
If you remember Joe's tutorial for the shooter module, he removed the draw sprites script with his own custom script that specifically drew the sprites to lessen the load on the system. This is an extension of that which allows objects to animate, with a few other neat options we'll get to further down. This is good for projectiles, objects that have looping animations, and will speed up our system slightly since we are specifically saying what sprites we want to draw, and in turn free up some space in Bank #$1C where NESmaker's standard animations are stored.

Step 1:
Create this User Variable
Code:
manualAnimation

Step 2:
Open dohandleGameTimer, and add this bit of code anywhere in there.
Code:
INC manualAnimation
LDA manualAnimation
CMP #33
BNE +
LDA #$00
STA manualAnimation
+
This is our clock for animations. It'll make more sense later.

Step 3:
Go to your project labels, and under Monster Action Step Flags change Flag 5 to "Manual Animation" (any flag will work if you're already using this, but the code in this tutorial is for Flag 5). This is how we know if an action step uses Manual Animation or NESmaker's animation.

Step 4:
Go ahead and set up an object you want to manually animate. We'll just do the standard Projectile object (#$01) just below the player. REMEMBER, you do not need to make ANY animation for this object in NESmaker's UI. You can leave it's animation frames at 1 and sprites blank, it does not matter. Just set everything else as normal - that it's a projectile, speed, bounding box, etc. And make sure to check "Manual Animation" for the first action step. Again, animation speed does not matter and will be ignored.
tutorial1.png
Note: This method applies to specific object states, so you can use NESmaker's animation system for other steps if you wish.

Step 5:
Now open up doHandleObjects_withinCamera, and just after "JustDrawObject" add this bit of code:
Code:
;; We'll try manual animation here, so we can handle it in another bank
    LDA Object_vulnerability,x
        AND #%00010000
        BEQ +skip
        ;;; this object manually animates
        SwitchBank #$YOURBANKHERE
        JSR manuallyAnimate
        JMP ObjectDoesNotDraw
    +skip
Replace #$YOURBANKHERE with whatever bank you decide to put the code. It can exist in any bank as far as I know, I use #$18 in mine. What this does is bypass the standard NESmaker draw/animation script with our own if the flag if checked.

Step 6:
Finally the manual animation code. Open up a new script and paste this code into it (we're going to add more code into the middle of it in the next steps).
Code:
manuallyAnimate:
; here we handle manually drawn objects
    LDA gameHandler
    AND #%01000000
    BEQ +doDrawThisSprite
    JMP +doneDrawingThisSprite
    +doDrawThisSprite
; set up static sprites or basic animations by the frame counter here
    LDA Object_x_hi,x
    SEC
    SBC camX
    STA tempA
    
    LDA Object_y_hi,x
    STA tempB
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; check which object is being manually drawn ;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    LDA Object_type,x
    ;; objects manually drawn go here
    
    
    
    drawManualSprite:
    ; store actual sprite # last
    STA tempC
    ; still need to check if we should draw it
    JSR +evaluateTileAgainstCameraPosition
    BEQ +thisTileIsInCamRange
        DestroyObject ; this makes sure that it stops existing, whether it's drawn or not

    JMP +doneDrawingThisSprite
    +thisTileIsInCamRange

    ;;;;;;;;;;    x   ,  y   , object, palette
    DrawSprite tempA, tempB, tempC, tempD
    +doneDrawingThisSprite
    RTS
    
    ;; evaluation camera
    +evaluateTileAgainstCameraPosition:

    LDA Object_x_hi,x
    STA pointer
    LDA Object_screen,x
    AND #%00001111
    STA pointer+1
    
    LDA pointer
    
                
    Compare16 pointer+1, pointer, camX_hi, camX
    ; arg0 = high byte of first value
    ; arg1 = low byte of first value
    ; arg2 = high byte of second value
    ; arg3 = low byte of second value

    +
    JMP +checkRightForDrawingOffCamera
    
    ++       
        LDA #$01       
        RTS
+checkRightForDrawingOffCamera
    LDA Object_x_hi,x
    CLC
    ADC self_right
    STA pointer
    LDA Object_screen,x
    ADC #$00
    AND #%00001111
    STA pointer+1

    LDA camX
            
    STA pointer5
    LDA camX+1
    clc
    ADC #$01
    STA temp
    Compare16 pointer+1, pointer, temp, pointer5; camX
    +
    LDA #$01
    rts
    ++
    LDA #$00
    rts
This borrows some code from the draw sprite script, to decide if a sprite should be drawn, and destroys the sprite if it's off camera. This should be fine with scrolling games, but I've only really used it for single screen games. Save the script.
Now go to your Script Settings, add the new code like so:
tutorial2.png
Then assign it to the script we just saved. Then go to whatever bank you decided to store this script, and add this line somewhere in the bank (isolated from other code is usually a safe spot)
Code:
.include MANUAL_ANIMATION

Step 7:
Okay, now we begin the customization and you can see what all this code is for. Make sure you have your different frames drawn and know where they are on your sprite sheet, and know which palette you want them to use. In this tutorial we will be making a 1 tile object in GameObject #1 spot. To do so, start add this code at "LDA Object_type,x" line in the manual animation script:
Code:
LDA Object_type,x
    ;; objects manually drawn go here
    CMP #1 ; projectile
    BNE +
    JMP +makeProjectile
    +
           ; CMP with more objects here
    JMP +doneDrawingThisSprite
    
    +makeProjectile:
    LDA #$00
    STA tempD ; use subpalette 0 for all frames
    LDA manualAnimation
    CMP #4
    BCC +frame1
    CMP #8
    BCC +frame2
    CMP #12
    BCC +frame3
    CMP #16
    BCC +frame4
    CMP #20
    BCC +frame1
    CMP #24
    BCC +frame2
    CMP #28
    BCC +frame3
    CMP #33
    BCC +frame4
    JMP +doneDrawingThisSprite
    
    +frame1
    LDA #TILE1 ; first frame
    JMP drawManualSprite
    
    +frame2
    LDA #TILE2 ; second frame
    JMP drawManualSprite
    
    +frame3
    LDA #TILE3; third frame
    JMP drawManualSprite
    
    +frame4
    LDA #TILE4 ; fourth frame
    JMP drawManualSprite

What this does is check for Object 1 (aka your Projectile) and it checks with manualAnimation to decide which frame of animation is being drawn, making a 4 frame animation that loops depending on manualAnimation's number. Replace the "#TILE" in each frame with the number of the tile on your sprite sheet you wish to draw there. This would work well for making a ball roll for example. But that's it! If you've set up everything correctly the object will animate in 4 looping frames at a decent speed.

Customizing!:
To add more objects, simply follow the pattern I used for the projectile, and add more inside the script (remember, objects are numbered starting at 0 with the first game object, so the first monster would be 15, then 16, and so on. ManualAnimation runs through numbers until 32, so keep that in mind when animating. Numbers far away from each other will animate more slowly, quicker will be faster, etc. Just make sure the amount of frames you want are divisible by 32.

More fun examples!
You can do a lot more than the bareboned example I provided for projectiles. Here is an example of how I used this to make the player only show a climbing animation while moving:
Code:
LDA #$00 ; player uses this palette
    STA tempD
    ; this will make the climbing player
    ; if the gamepad is being pressed we want to animate it, else we don't
    LDA gamepad
    AND #%00110000
    BNE +isPress
    ; we're not animating
    climbFrame1:
        ; first climbing animation goes here
        JMP drawManualSprite
    +isPress
    ; we are animating
        LDA manualAnimation
        AND #%00001000
        BNE climbFrame1
        ; else it's frame 2
        JMP drawManualSprite
How this works is we have one action step for climbing checked for manual animation. The animation subroutine then checks to see if the player is pressing up or down. If no button is pressed it doesn't animate, but if it is, it alternates between drawing frame 1 and frame 2 to give a quick 2 frame animation!

Another example is the arrow in my game Tryptic:
Early Hallway Look.gif
See how the arrow is changing? This is done with manualAnimation, simply drawing a different sprite depending on the arrow's location on the screen, and all using 1 single action step for the player object. This way we don't use as many assets or CPU while the arrow icons still look nice and feel very alive.


Additional Notes:
- Using this method as is, an object can only be animated a single way using manualAnimation. You could possibly have it draw something different depending on screentypes or userscreenbytes, but it would still apply to all action steps that are checked with manualAnimation on that object.
- This shows how to draw a single sprite tile, but you can also do several tiles if you wish. Just switch the "JMP drawManualSprite" to "JSR +thisTileIsInCamRange" then set the coordinates for the next sprite, and repeat until done. I've done this for up to 4x4 tile sized objects without any issue.
 

dale_coop

Moderator
Staff member
Thank you for that tutorial, @crazygrouptrio.
It is important to remember that the default sprite drawing routine uses a lot of CPU and as Joe explained in the Advenced shooter tutorial, it is sometimes necessary to modify it to hard code the drawing of some objects to optimize the game.
Your approach is interesting, I see a few things that can be optimized for some projects (like the evaluateTileAgainstCameraPosition... that could be done once per object instead of each sprites).

I use a similar kind of code in my games (for all the simple animation objects) and I recommand people to try that themselves.
But yes... it's a advanced tutorial !
 
this is just for no lag correct? need to save a CARP-TON of space for my project in Bank 1F still and the way I see this it won't save that much space
(and at worse fills up more space)
 

crazygrouptrio

Active member
this is just for no lag correct? need to save a CARP-TON of space for my project in Bank 1F still and the way I see this it won't save that much space
(and at worse fills up more space)
Correct this is mostly for preventing slowdowns. This all can be done outside of Bank 1F tho, and there's tons of ways to free up 1F (did you know you can put all of handle game timer in Bank 18 for example?) Probably common knowledge tho.
 
Correct this is mostly for preventing slowdowns. This all can be done outside of Bank 1F tho, and there's tons of ways to free up 1F (did you know you can put all of handle game timer in Bank 18 for example?) Probably common knowledge tho.
Sweet! That's awesome! So moving the Animation stuff out of Bank 1F? Also putting the game timer in Bank 18? That's awesome! Gonna look into that asap!
 

Jonny

Well-known member
Thanks for this @crazygrouptrio . Curious, what makes the normal doDrawSprites end up causing lag? Is it the doDrawSpritesLoop or all the setup before that? I'm wondering if this might be useful for an object that doesn't animate AT ALL when in a certain state. I'm using an object as a door, it has an open and close animation but in its closed state it has no animation. Do you think using this would have any benefit in that situation?
 

crazygrouptrio

Active member
Thanks for this @crazygrouptrio . Curious, what makes the normal doDrawSprites end up causing lag? Is it the doDrawSpritesLoop or all the setup before that? I'm wondering if this might be useful for an object that doesn't animate AT ALL when in a certain state. I'm using an object as a door, it has an open and close animation but in its closed state it has no animation. Do you think using this would have any benefit in that situation?
It just eats up the CPU having to check every sprite tile in an object using NESmaker's default sprite drawing. Doing animation this way skips all of it, and instead we specify what sprite to draw. This would work easily for static objects, just skip the animation part and have it draw a specific sprite.
 

crazygrouptrio

Active member
Sweet! That's awesome! So moving the Animation stuff out of Bank 1F? Also putting the game timer in Bank 18? That's awesome! Gonna look into that asap!
Animation stuff is actually handled in Bank 1C. This method can keep stuff out of Bank 1C and doesn't affect 1F much is what I meant, my bad.
As for clearing out 1F there's many tutorials here in the forums such as moving inputs, moving load screen that can help with clearing 1F. Also many monster actions can be handled in another bank. If you get creative you can figure out how to handle lots of things in other banks!
 
Animation stuff is actually handled in Bank 1C. This method can keep stuff out of Bank 1C and doesn't affect 1F much is what I meant, my bad.
As for clearing out 1F there's many tutorials here in the forums such as moving inputs, moving load screen that can help with clearing 1F. Also many monster actions can be handled in another bank. If you get creative you can figure out how to handle lots of things in other banks!
Already followed the tutorials on the loadsscreen and inputs! But now i understand how to move more stuff... Thank You!
 

Jonny

Well-known member
Hey @crazygrouptrio, I'm currently working on optimizing for my own needs. I was just wondering, for animations with more than one sprite to animate, did you use lookup tables for that or just the same as tutorial? I'm thinking about using tabes to get the correct sprites by referecing the Object_type in X. If there are multiple manually animating objects it will probably be better to do it that way than multiple checks and branches.
Something else that's great about your tutorial - we can use ANY sprite for Game Objects i.e those in the loaded monster sheet too.
 
Last edited:

TalkingCat

Member
I tried to repeat your lesson, but only the first frame of the animation is drawn. Any idea why the next animation frame isn't being drawn?

I found the answer to my question. I inserted a code snippet into the wrong dohandleGameTimer script. Now the animation is working. But there was another problem this lesson is not compatible with Sprite Cycling (flicker) https://www.nesmakers.com/index.php?threads/4-5-6-sprite-cycling-flicker.5960/
 
Last edited:

TalkingCat

Member
- This shows how to draw a single sprite tile, but you can also do several tiles if you wish. Just switch the "JMP drawManualSprite" to "JSR +thisTileIsInCamRange" then set the coordinates for the next sprite, and repeat until done. I've done this for up to 4x4 tile sized objects without any issue.
Can you give an example of how to make a 4*4 tile animation? I didn't quite understand how to do it.
 

crazygrouptrio

Active member
Can you give an example of how to make a 4*4 tile animation? I didn't quite understand how to do it.
Sure. You just have to draw each sprite that you need in each frame you have. So you set it up like above, it draws the first sprite, but swap out the JMP to JSR, then do the next one. Here's an example of adding another sprite to the right of the first one:

Code:
LDA #TILE1 ; first frame
    JSR +thisTileIsInCamRange
    LDA tempA ; the x coordinate of our first tile
    CLC
    ADC #$08 ; adding 8 so it will be to the right of it
    STA tempA
    LDA #TILE2 ; location of the sprite we need here
    JMP drawManualSprite

This will draw 2 sprites, the first one and one to the right of it. You can keep adding more sprites by using the JSR and continuing. tempA stores the x coordinate, and tempB stores the y coordinate, so you'll alter those as you go in reference to the first sprite placement (the top left most sprite).
Hope that helps!
 

crazygrouptrio

Active member
Hey @crazygrouptrio, I'm currently working on optimizing for my own needs. I was just wondering, for animations with more than one sprite to animate, did you use lookup tables for that or just the same as tutorial? I'm thinking about using tabes to get the correct sprites by referecing the Object_type in X. If there are multiple manually animating objects it will probably be better to do it that way than multiple checks and branches.
Something else that's great about your tutorial - we can use ANY sprite for Game Objects i.e those in the loaded monster sheet too.
I haven't tried lookup tables but I assume it would work? I'd be interested to know if it works out better!
 

Jonny

Well-known member
I haven't tried lookup tables but I assume it would work? I'd be interested to know if it works out better!
It would work out well if you had, say 5 different projectiles graphics. Most of the code would be the same just with a variable to get different CHR to use from the LUT. Might be the exact thing I use your tutorial for actually! The thought of trying to hook up manual anims for bigger objects/different sized objects is too overwhelming for me lol. It's going to be interesting to hear what people use manual anims for in their projects.
 

TalkingCat

Member
Code:
LDA #TILE1 ; first frame
    JSR +thisTileIsInCamRange
    LDA tempA ; the x coordinate of our first tile
    CLC
    ADC #$08 ; adding 8 so it will be to the right of it
    STA tempA
    LDA #TILE2 ; location of the sprite we need here
    JMP drawManualSprite
Thanks for the help. I tried to insert the code that you specified, but it looks like I'm doing something wrong. Sprite #1 and #2 loads tile graphics with index #$00 instead of tiles with indexes #$88 and #$89. The sprite under #3 is displayed correctly.

Code:
+makeShieldPredboss
    LDA #$03  ; use subpalette 4 for all frames
    STA tempD ;
    LDA manualAnimation
    CMP #0
    BCC +frame1
    CMP #16
    BCC +frame2
  +frame1
    LDA #$88 ; first sprite
    JSR +thisTileIsInCamRange
    LDA tempB ; the Y coordinate of our first tile
    CLC
    ADC #$08 ; adding 8 so it will be to the down of it
    STA tempB
 
    LDA #$98 ; second sprite
    JSR +thisTileIsInCamRange
    LDA tempB ; the y coordinate of our second tile
    CLC
    ADC #$08 ; adding 8 so it will be to the down of it
    STA tempB
    LDA #$A8 ; location of the sprite we need here
    JMP drawManualSprite
 +frame2
    LDA #$89 ; first sprite
    JSR +thisTileIsInCamRange
    LDA tempB ; the Y coordinate of our first tile
    CLC
    ADC #$08 ; adding 8 so it will be to the down of it
    STA tempB
 
    LDA #$99 ; location of the sprite we need here
    JSR +thisTileIsInCamRange
    LDA tempB ; the Y coordinate of our second tile
    CLC
    ADC #$08 ; adding 8 so it will be to the down of it
    STA tempB
    LDA #$A9 ; location of the sprite we need here
    JMP drawManualSprite


Снимок экрана 2023-09-01 230336.JPGVideo_230901232137.gif
 
Last edited:

TalkingCat

Member
I also tried to insert in to your example the code that Joe showed in the lesson on SHMUP. In this case, the graphics is displayed correctly.
Code:
+makeShieldPredboss
            LDA #$02
            STA tempD
            
            LDA vBlankTimer
            AND #%00010000
            BNE +isnoEvenFrame
            JMP +isEvenFrame
    +isnoEvenFrame       
        DrawSprite tempA, tempB, #$88, tempD
            LDA tempB
            CLC
            ADC #$08
            STA temp1
        DrawSprite tempA, temp1, #$98, tempD
            LDA temp1
            CLC
            ADC #$08
            STA temp2   
        DrawSprite tempA, temp2, #$A8, tempD
            JMP +doneDrawingThisSprite
            
    +isEvenFrame
        DrawSprite tempA, tempB, #$89, tempD
            LDA tempB
            CLC
            ADC #$08
            STA temp1
        DrawSprite tempA, temp1, #$99, tempD
            LDA temp1
            CLC
            ADC #$08
            STA temp2   
        DrawSprite tempA, temp2, #$A9, tempD
            JMP +doneDrawingThisSprite

Video_230901232456.gif
If you point out my mistakes, it will be just wonderful)
 

crazygrouptrio

Active member
I also tried to insert in to your example the code that Joe showed in the lesson on SHMUP. In this case, the graphics is displayed correctly.
Code:
+makeShieldPredboss
            LDA #$02
            STA tempD
          
            LDA vBlankTimer
            AND #%00010000
            BNE +isnoEvenFrame
            JMP +isEvenFrame
    +isnoEvenFrame     
        DrawSprite tempA, tempB, #$88, tempD
            LDA tempB
            CLC
            ADC #$08
            STA temp1
        DrawSprite tempA, temp1, #$98, tempD
            LDA temp1
            CLC
            ADC #$08
            STA temp2 
        DrawSprite tempA, temp2, #$A8, tempD
            JMP +doneDrawingThisSprite
          
    +isEvenFrame
        DrawSprite tempA, tempB, #$89, tempD
            LDA tempB
            CLC
            ADC #$08
            STA temp1
        DrawSprite tempA, temp1, #$99, tempD
            LDA temp1
            CLC
            ADC #$08
            STA temp2 
        DrawSprite tempA, temp2, #$A9, tempD
            JMP +doneDrawingThisSprite

View attachment 7524
If you point out my mistakes, it will be just wonderful)
Oh my bad. Add a "STA tempC" between your lada and
Thanks for the help. I tried to insert the code that you specified, but it looks like I'm doing something wrong. Sprite #1 and #2 loads tile graphics with index #$00 instead of tiles with indexes #$88 and #$89. The sprite under #3 is displayed correctly.

Code:
+makeShieldPredboss
    LDA #$03  ; use subpalette 4 for all frames
    STA tempD ;
    LDA manualAnimation
    CMP #0
    BCC +frame1
    CMP #16
    BCC +frame2
  +frame1
    LDA #$88 ; first sprite
    JSR +thisTileIsInCamRange
    LDA tempB ; the Y coordinate of our first tile
    CLC
    ADC #$08 ; adding 8 so it will be to the down of it
    STA tempB
 
    LDA #$98 ; second sprite
    JSR +thisTileIsInCamRange
    LDA tempB ; the y coordinate of our second tile
    CLC
    ADC #$08 ; adding 8 so it will be to the down of it
    STA tempB
    LDA #$A8 ; location of the sprite we need here
    JMP drawManualSprite
 +frame2
    LDA #$89 ; first sprite
    JSR +thisTileIsInCamRange
    LDA tempB ; the Y coordinate of our first tile
    CLC
    ADC #$08 ; adding 8 so it will be to the down of it
    STA tempB
 
    LDA #$99 ; location of the sprite we need here
    JSR +thisTileIsInCamRange
    LDA tempB ; the Y coordinate of our second tile
    CLC
    ADC #$08 ; adding 8 so it will be to the down of it
    STA tempB
    LDA #$A9 ; location of the sprite we need here
    JMP drawManualSprite


View attachment 7519View attachment 7520
Oh that's my bad. Just add a "STA tempC" between your LDA and JSR in each instance and that should fix it.

Code:
LDA #$88 ; first sprite
STA tempC
    JSR +thisTileIsInCamRange
 
Top Bottom