[4.5.x] Animated Tiles with CHR-RAM Switching

JamesNES

Well-known member
Hi, I noticed there wasn't a step-by-step on this anywhere, or even how to do it in a NES Maker-specific way, so I'll share how I did it. The problem is that it's very project specific, what you actually need it to do, but getting it going at all is the hardest part. If you can follow along with this guide you'll be able to do basically whatever you want with it. In the zip I've included my DoScreen16.asm, but it's better to follow along.

Aims:
  • Load our own CHR graphics files rather than using up the pre-assigned tilesets NES Maker sets up
  • Animate the top row of the main tileset on a screen with an additional two frames. We will set it up for the Main + Screen x 3 tileset loadout for my example.
Things you need to have done first:
  • Follow my guide to move half of doLoadScreen to Bank 16 - most of this will take place in the new Bank 16 routine. Link
  • Download the attached zip and put the SwitchCHRBank macro in your BASE_4_5\System\Macros folder.
  • Setup user screenbytes. This method by chronicleoflegends is great.
  • Add a new temp variable in NES Maker's user variables, I'm using tempj in this example. This is so I know there isn't a macro messing with my temp variable.
  • Add variables animTimer and animFrame. Set animTimer to a default value of 16.
  • Set up the Handle Game Timer script. Basically just make a blank asm file and assign it to Handle Game Timer in script settings. It's included by default in NES Maker.
  • Make sure the tile layout for the screen you're trying it on is set to No Path (Main + Screen x 3)

Part 1 - Sideloading more graphics

This part isn't entirely necessary, you can use graphics NES Maker allows you to by default, but you'll quickly run out of room and it's kind of awkward to reference them.

Bank 17 is pretty much empty, so I've been including extra CHR files there. CHR files are the color indexed BMP files that can actually be used by the system.

I've included some graphics as an example in the attached zip, so I'll refer to those specifically. Move BckCHR_17.bmp to your project's graphics assets folder. This will be the base graphics set that we'll animate the top row of. Create a subfolder in your graphics folder called Animated Tiles. Here you can put the animated tile .bmps that are in the zip.

Now we convert them to .chr so that the NES can actually use them. This is easy:

Load each of the AnimTiles bmps up in the pixel editor. At the top of the window, click the Pixel Editor menu and hit Export CHR.

1 - Save as CHR.png

Save this in GameEngineData\Routines\BASE_4_5\Graphics as AnimTiles01.chr. You'll need to create the graphics folder, it's not there by default. Do the same with AnimTiles02.bmp.

Now that the graphics are in the right format, we can include them in our code. Open Bank 17 (easy to find from Script Settings) and at the top, add:

Code:
AnimTiles01:
.incbin ROOT\Graphics\AnimTiles01.chr

AnimTiles02:
.incbin ROOT\Graphics\AnimTiles02.chr

So now we have the two new graphics files included with our game, and two labels set up so we can reference them. Next is to make a reference table for them in Bank 16 where the CHR loading actually takes place.

Open up Bank 16 and find a spot near the top. We're going to add two tables for the addresses of the new tilesets:

Code:
AnimTiles_Lo:
.db #$00, #<AnimTiles01, #<AnimTiles02

AnimTiles_Hi:
.db #$00, #>AnimTiles01, #>AnimTiles02

This stores the addresses of the high and low bytes of the new tilesets, references that will be needed when loading them into RAM.

And that's it for part one. Extra graphics are loaded, and references are set up. Now the fun part, actually loading them into CHR-RAM during doLoadScreen.

Part 2 - Loading the graphics into CHR-RAM

Mapper30 has 4 CHR-RAM banks, by default NES Maker only loads the first one. Since we have two additional frames of animation, we're going to load up two more banks, and then do a timed switch between them to make the tiles animate.

This will all take place in DoLoadScreen16.asm.

First, at the top of the script under DoLoadScreen16:, add:

Code:
SwitchCHRBank #$00

To make sure we always start in CHR bank 0. Weird things will happen if your animation timer causes screenload to start in a different bank.

Scroll down to LOAD_MSSS:, which is around line 118. We're going to loop through this three times to fill three banks.

Set your temp variable to #$00, and add a label for the loop:

Code:
LOAD_MSSS:

----

    LDA #$00
    STA tempj                                ;;added code
    MSSS_CHR_LOAD_LOOP:

-----
    LDA backgroundTilesToLoad
    LSR
    LSR
    LSR
    LSR

We're going to use userScreenByte7 to tell each screen which animated tiles it should load. If you leave it at zero, it won't load anything. Since we're using AnimTiles01, we set userScreenByte7 to 1, on the screen info settings in NES Maker.

At the end of the LOAD_MSSS section, we're going to check for two things. First, is userScreenByte7 equal to zero? If it is, we won't bother looping and load more CHR. If it's not, we switch to the next CHR bank, loop back through the CHR loading routine, then do it again until we have banks 0, 1 and 2 loaded up. What bank we're in is tracked by tempj. This is what that looks like (around line 181):

Code:
    LDA userScreenByte7
    BEQ +noAnimatedTiles
        INC tempj
        LDA tempj
        CMP #$03
        BEQ +noAnimatedTiles
            SwitchCHRBank tempj
            JMP MSSS_CHR_LOAD_LOOP
    +noAnimatedTiles
  
    JMP doneLoadingChrs

As a progress check, you can compile it and check the PPU viewer in Mesen. On the second tab you can switch between CHR-RAM banks and make sure that they're loading (the fourth one should still be black).

Now we have three sets of identical background tiles loaded, we have to load the sprite graphics as well. These are loaded separately further down the script, so we'll do basically the same thing for them.

Find doneLoadingChrs: at the bottom of the script, and add these four lines under it:

Code:
doneLoadingChrs:
    SwitchCHRBank #$00
    LDA #$00
    STA tempj
      
    Load_Sprite_CHR_Loop:

and at the end of the script, right before the RTS, add (almost) the same check as the last one:

Code:
    LDA userScreenByte7
    BNE +hasAnimatedTiles
            JMP +noAnimatedTiles
        +hasAnimatedTiles
        INC tempj
        LDA tempj
        CMP #$03
        BEQ +loadAnimatedTiles
            SwitchCHRBank tempj
            JMP Load_Sprite_CHR_Loop
        +loadAnimatedTiles
      
         ;;animated tile loading code will go here
      
    +noAnimatedTiles  
     SwitchCHRBank #$00

Now the last part in this script, actually loading in the extra frames of animation to banks 1 and 2. Replace the comment in the last code with this:

Code:
        SwitchCHRBank #$01
        LoadChrData #$17, #$10, #$00, #$20, AnimTiles_Lo, AnimTiles_Hi, userScreenByte7
        jsr doWaitFrame
      
        INC userScreenByte7
      
        SwitchCHRBank #$02
        LoadChrData #$17, #$10, #$00, #$20, AnimTiles_Lo, AnimTiles_Hi, userScreenByte7
        jsr doWaitFrame

       SwitchCHRBank #$00  ;;switch back to the first bank

For reference, the first argument for LoadChrData is the bank where the graphics are stored (we included them in Bank 17 at the start of this), the second argument is what row to replace (#$10 is the first row), #$00 is what column to start the replacement at, and the last one to worry about is where we have userScreenByte7 - this is the position of the tileset we want to load in, relative to the start of our AnimTiles_Lo/Hi list in bank 16. That's why we set screenbyte7 to 1. The script then increases it to 2 so we load in AnimTiles02.

Set up the timer

In your handle game timer script, we want to decrement animTimer each frame, then when it hits zero, set it back to the starting interval, increase our animFrame variable, and switch CHR banks to the next frame. It's pretty simple and looks like this:

Code:
LDA userScreenByte7
BEQ +dontUpdateAnimatedTiles ;;making sure that this screen actually has animated tiles set up - thanks CGT
DEC animTimer
LDA animTimer
BNE +dontUpdateAnimatedTiles
    LDA #$10        ;;i'm using 10 as a starting point for animTimer
    STA animTimer
    INC animFrame
    LDA animFrame
    CMP #$03 ;;has it gone over the number of banks we filled?
    BEQ +resetAnimation
        SwitchCHRBank animFrame
        JMP +dontUpdateAnimatedTiles
    +resetAnimation
    LDA #$00
    STA animFrame
    SwitchCHRBank #$00
+dontUpdateAnimatedTiles

And done! The tiles should be animating now.

I wrote this as I went through on a fresh install of NES Maker so hopefully I haven't missed anything.



Extra stuff

Now that you know how to fundamentally do this, you should try customising it so that it fits your project better. Here are a couple of things I did:
  • Make a four frame animation.
  • Use a screenflag to make the loading routine load the animated tiles into one of the screen specific rows, rather than always the top row
  • Make a table in bank 16 that tells the game how many frames individual animations have, so they don't need to be the same number of frames on every screen. You'll need to change the last part of the CHR loading script to be an arbitrary loop for that.
 

Attachments

  • Animated tile stuff.zip
    4 KB · Views: 211
Last edited:

Bucket Mouse

Active member
Thank you so much for figuring this out. Maybe someday animated tiles will be built into NESMaker, but until then we finally have a backdoor way.

Just the first portion alone is useful...I never knew how to include additional graphics files before.
 

JamesNES

Well-known member
Great! Did you manage to get it working? Worried I missed something, and it's kind of a long process so I'm not sure if anyone's actually done it yet :p
 

crazygrouptrio

Active member
Awesome, got it working without much problem. Even works on scrolling screens which is super nice. The only issue I'm having is it takes a good 7 seconds before the tiles start animating after a screen loads. Hopefully I just messed something up, cause that's a little too long of a startup time.
animtiles.gif
 

JamesNES

Well-known member
That's great! I hadn't written a guide this long before so I'm glad it worked out for you.

With the timing problem did you give animTimer a default value in the user variables window? It should be the same as the interval you're using in your timer script, otherwise it'll start at zero and count down from 255 before starting to animate.

Edit: added this to the main guide, something I left out.
 
Last edited:

Moehr

New member
Thanks so much for this! Implemented it and it works excellent. I upped it to four frames immediately :D

Here's some generic tiles that I made for use with this routine when modified for the four frame animation version. They're up for grabs; anyone who wants them can use them.
 

Attachments

  • animatedTilesCHR.zip
    2.4 KB · Views: 62

crazygrouptrio

Active member
With the timing problem did you give animTimer a default value in the user variables window? It should be the same as the interval you're using in your timer script, otherwise it'll start at zero and count down from 255 before starting to animate.
Yep that was it. I knew it was my fault somehow. Thanks again!
 

Moehr

New member
Yep that was it. I knew it was my fault somehow. Thanks again!
Since you are using screenbyte 7 to check to run the animation anyhow, you could probably also use screenbyte7 to initialize the timer on a per-screen basis. Either via the highbyte (and masking the highbyte elsewhere) or just using any old nonzero in the lowbyte equal to how long you want it to last.
 

crazygrouptrio

Active member
Don't know if it's just me or if its just gone by unnoticed, but with this code as is, non-animated tile screens (using userscreenbyte7 set to 0) causes all graphic tiles on the screen to flash. So they appear for one frame and then black for 2 frames (including sprites which is kinda weird). I'm guessing its because the provided timer script is still trying to do stuff regardless of what screen it is? Not sure.
It's a simple fix if anyone runs into this. At the beginning of the timer script add this:
Code:
LDA userScreenByte7
    BEQ +dontUpdateAnimatedTiles
And you're good.
 

TakuikaNinja

Active member
I'm making a guess here.
The blank.asm script attached to Handle Game Timer is used as a default script for when a function isn't used. Make a new asm file instead of editing blank.asm.
 

Logana

Well-known member
Im confused, sorry to bother, but I got up all trough this post till her
"As a progress check, you can compile it and check the PPU viewer in Mesen. On the second tab you can switch between CHR-RAM banks and make sure that they're loading (the fourth one should still be black)."
and so I did that, and here are the results, they look completely normal, with none of the extra chrs loaded, I may just be dumb, but Im really confused on why this is happening, Also yes I did set a value for the userscreenbyte07

1622490666316.png
 

Jonny

Well-known member
I think I've gone wrong somewhere but can't figure it out. I've been over the tutorial several times with a fine tooth comb and am stuck.

For the most part it's working but for AnimTiles01.chr and AnimTiles02.chr the top row changes but also makes the tiles which are not animating blank.

chrswitch.gif

EDIT: Sorry spoke too soon! I'd left in a JMP doneLoadingChrs. I'll leave this here incase someone else makes the same mistake.

rain thing2.gif
 
Last edited:
Top Bottom