How do you make the player animate simular to the smurfs?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
How do you make the player animate simular to the smurfs?
by on (#28749)
I recently found ''The Smurfs (E).nes'' and it uses CHR-RAM. One thing i wondered about the game's CHR-RAM player animation though, I need to implent a ''Animate Character using only 1 Character in 32x32 (or 16x32) Slots in the CHR-RAM'' in my game.

I need some help in getting 16x32(or 32x32) characters in CHR-RAM to update only in one Character place!

by on (#28750)
There is no trick, really. You just have to redraw the patterns during VBlank. That's a lot of data to update during VBlank though, so you'll have to code very efficient code to do this (unrolled loop), or keep the PPU disabled for a while after VBlank ends to get a little extra time. You'll probably have to do both actually!

I do exactly this in my current project. All the game graphics are stored in the ROM as blocks of 256 bytes (16 tiles). Every frame there is time to copy one of these blocks to a place in the pattern table. Those 256 bytes are scrambled a bit, to make reading them faster.

All blocks are inside 16KB ROM banks, meaning there are 64 of them in each a bank. The pattern-updating routine will switch in the bank with the desired block of tiles, load the X register with the index of the block inside that bank (0 to 63), set the destination address through $2006 and execute the following code:
Code:
   lda Byte00, x
   sta $2007
   lda Byte01, x
   sta $2007
   lda Byte02, x
   sta $2007
   (...)
   lda ByteFE, x
   sta $2007
   lda ByteFF, x
   sta $2007

This code takes quite a bit of ROM space (1536 bytes), but makes it possible to update 256 bytes worth of patterns in 2048 CPU cycles (about 18 scanlines). That's almost the entire VBlank time, so I sure have to delay the start of the frame a bit in order to do other things while rendering is still off (update the palette, metatiles, sprites, etc). Those other routines should be pretty efficient too, since so much time was dedicated to copying patterns already.

Also, as I said before, the tiles are a bit scrambled. The first byte of each of the 64 blocks in the bank are stored together, and this list is pointed by the "Byte00" label. Then follow all the second bytes (pointed by "Byte01"), and so on.

by on (#28751)
At 16 bytes per tile, 32x32 pixels is gonna be 256 bytes to update. That's pushing right up to the limit of how much CHR you can update during a vblank period. Assuming you're doing a sprite-DMA, and no other CHR updates (like nametable, palette) you'll have about 7 cycles on average to transfer each of those 256 bytes.

Load the animated tiles into a buffer before vblank, if indirect reads are too slow.

Quote:
Also, as I said before, the tiles are a bit scrambled. The first byte of each of the 64 blocks in the bank are stored together, and this list is pointed by the "Byte00" label. Then follow all the second bytes (pointed by "Byte01"), and so on.


Cool trick! :)

by on (#28752)
If you're using MMC3, you can get extra copy time by having an IRQ go off 8 scanlines before vblank. Turn off the screen then, and think of it as an early vblank.

by on (#28753)
Memblers wrote:
Cool trick! :)

Heh, I needed a way to easily index those blocks without using indirect indexed addressing, in which case each read would use 5 cycles (plus the 2 cycles to increment Y after every byte). This is the best I could come up with so far...

Dwedit wrote:
If you're using MMC3, you can get extra copy time by having an IRQ go off 8 scanlines before vblank.

I kinda assumed UNROM, since that's what the Smurfs game is. But yeah, you can use the scanline IRQ if you are using the MMC3, as long as you respect the rules for it to work right.

I always use 8x16 sprites and access both sides of the pattern tables, so that IRQ is worthless for me. I don't know what the designers of the MMC3 were on when they decided to take away such an important feature of the NES so that you could use their IRQ... I'm sure many of you guys will say that is not such an important feature, but for me it is.

Anyway, you should debug this game's NMI routine and see what it does during VBlank exactly. Since it has a few black scanlines at the top, I bet it keeps rendering disabled for those few extra scanlines.

by on (#28758)
In 8x16-pixel sprite mode, you have 128 distinct spaces in $1000-$1FFF. This is enough to double-buffer all 64 sprites.

But updating CHR RAM might be easier in a (E) game because unlike (JU) games, which have about 2300 cycles to do vblank updates, (E) games have about 7500 due to the extra blank scanlines of 50 Hz TV.

by on (#28759)
How would double-buffering help? Either way you can still update VRAM only during vblank.

by on (#28764)
blargg wrote:
How would double-buffering help? Either way you can still update VRAM only during vblank.

For atomicity. If it takes longer to update all the tiles that you want to update than a single vblank, you won't show any half-updated tiles.

by on (#28781)
Quote:
For atomicity. If it takes longer to update all the tiles that you want to update than a single vblank, you won't show any half-updated tiles.

You could still avoid updating only part of a particular object's sprite tiles without using double buffering, though it might result in less throughput due to an unoptimal granularity (dedicated double buffering would always do 16 tiles per frame, while this might do less on some frames if there were no combination of whole objects to update that totaled exactly 16 tiles). You'd only need double buffering if more than 16 tiles had to change on the same frame, for example if they were all part of a single object.