A flicker routine that's compatible with adventure module?

darkhog

New member
Thing is, I'm running into the sprite limit quite much so I wonder if some of the clever people here could write me a flicker routine that's compatible with the adventure module. I'd certainly prefer a flicker to sprites just disappearing at random.
 

FrankenGraphics

New member
[edit: removed some typos that were confusing and added some clarifications.]

Here's a tutorial. It includes the easiest method possible (to my knowledge) you can make, with just a few instructions, then somewhat easy but more dynamic method, and lastly a description of a more complex method. If you're unsure, start with the easiest method.

But first, some facts that will help inform the rest and why it works:
-To display its 64 sprites on (or off) screen, the PPU uses a dedicated RAM storage called the OAM memory. This memory is separate from normal RAM and only accessible via the PPU.
-OAM memory is a page (256 bytes) long.
-Each hardware sprite entry is 4 bytes long. 4 x 64 sprites = 256 bytes - just the same size as the memory.
-there is an operation called OAMDMA which can tranfer any page from the CPU address space to the PPU OAM memory. CPU is suspended while doing so. Since this is the quickest, most effective method, most games use it on a per frame basis.
-Most games keep a buffer of OAM in a RAM page in order to copy upload it quickly into the real PPU OAM memory in good time when an NMI happens.
-That buffer is commonly located at addr $200, but check to be sure as it is up to the programmer of each program. Actually, for the super simple method, you don't even need to check.

-you can take advantage of the fact that 8bit counting wraps around at #255 to #0.
-Those are all the facts we need.

If you just want to cycle the hardware sprites and be done with it, this is all you need:
1) declare a variable. Let's call it OAMIndexOffset or something like that.*
2) each frame, right before OAMDMA commences, set OAMADDR to OAMIndexOffset. That's:
Code:
lda OAMIndexOffset
sta OAMADDR ;(if NESmaker uses some obscure name for OAMADDR, try typing this instead: sta $2003 - since that's the address of the actual register).
3) then after OAMDMA has happened, immediately restore OAMADDR to 0**. that's
Code:
lda #0
sta OAMADDR
4) lastly, increase OAMIndexOffset four times. in asm6 that's
Code:
.rept 4 
inc OAMIndexOffset 
.endr

and done.

This method is as simple as sprite cycling can get, but on some systems (using a certain PPU revision) there may be some glitches... Consult appendix ** for a technical cliffsnotes. But all you really need to know is this:
-For learning the ropes or for a project intended as freeware, this is perfectly fine.
-For a commercial release, this is also fine as long as you make sure that a full 256-byte long OAMDMA happens (it does in our case) after the addr shift trick, and as long as the game doesn't do anything funky with OAMADDR just right before before rendering (it really shouldn't. You shouldn't update OAM manually anyway if the game is using a sprite buffer in RAM).

"Grammatici certant", but as far i'm concerned the only practical/real drawback is that you can't easily set certain sprites to always be prioritized or deprioritized with this method.

Method 2:
If you do want to spend just a little more time to remove the risk of hardware glitches (even though they can only appear by flawed program design), or for some other reason want to cycle the OAM buffer rather than the OAM itself, this is what you need to do:

1) almost same as above: declare a variable. Let's call it OAMbufferIndexOffset or something like that.
2) each frame, before any writes to the OAM buffer are done, increase that variable by 4. also same as above.
3) in the routine(s) that NESm may contain that write to the OAM buffer, make sure that the index doing all the placement either is OAMbufferIndexOffset, or that OAMbufferIndexOffset is added to whatever other index is used before it starts doing its business.
4)sit back and simply wait for the existing NMI routine to pick up the buffer and transfer it to OAM memory.

That should be all. Where it can get haywire is if you miss a routine that modifies the OAM buffer, else it is about as straightforward as the direct hardware write method.

Third method:
Now if you need to create different priority zones in OAM, that's also something you can do (most games don't bother, though). Mostly, it's the same technique but you need some additional logic and can't rely on the index wrapping around on itself. You need to check for min and max values for the wrapping, and use branches to decide in what cases the now routine-controlled wraparound should apply. That could provide 2 or 3 priority tiers with one stable "top", one cycling stratum, and possibly one stable "bottom". Or some other variant.


A note on caveats with sprite cycling:
-Slanted topdown games, sometimes, and isometric games in particular, need to sort OAM priority based on Y position in order to look good whenever actors overlap. They need a different cycling scheme. Whether you cycle sprites on the same y position is up to you, but generally of second priority.
-Some games may benefit from certain actors having priority over other actors. In a game like Solar Jetman, you ideally don't want your shuttle to flicker when passing through a narrow crevasse, as an example.


Footnotes:
*this step is unfortunately unnecessarily unfriendly in asm6 if you're not used to manual memory management, since you need to manually assign each variable an absolute address and make sure you don't overwrite something some other variable, like for example so: .enum $56 OAMIndexOffset .ende - check the list of .enums in NM:s code for a vacant spot with ctrl+f and search for either .enum or ENUM. Fortunately it doesn't take very long to find out. A good idea is to keep all zp and ram enums in one place to keep track of them easily, at the top of some document.

** It is recommended that the OAMADDR is set back to 0 before vblank ends though to avoid some potential glitches (this is done in my example). Also, this method is still not considered entirely glitch free, but for your first game, it works.

Quoting the nesdev wiki:
"On the 2C02G, writes to OAMADDR reliably corrupt OAM.[5] This can then be worked around by writing all 256 bytes of OAM. [my notes: we've done this, so this is not a problem.]

It is also the case that if OAMADDR is not less than eight when rendering starts, the eight bytes starting at OAMADDR & 0xF8 are copied to the first eight bytes of OAM; it seems likely that this is related. On the Dendy, the latter bug is required for 2C02 compatibility. [we've restored OAMADDR so this shouldn't be a problem unless something in NESmaker will leave OAMADDR at a weird address when rendering starts. This can be remedied too by simply restoring OAMADDR to #0 after each such call. So not much of a problem either]

It is known that in the 2C03, 2C04, 2C05[6], and 2C07, OAMADDR works as intended. It is not known whether this bug is present in all revisions of the 2C02."

Here's a list of all known PPU revisions and their changes. https://wiki.nesdev.com/w/index.php/User:Lidnariq/Known_PPU_revisions

Glossary:
OAM - object attribute memory. A 256 byte long memory range used by the PPU to tell where on the screen (or off the screen) a sprite should be, what palette it is using, and what attributes it should have. An "object", in nintendos' own official reference parlance, is actually what we call a hardware sprite, while programmers typically mean something entirely else when they say object.
DMA - direct memory access. A feature that some computer architectures have to be able to load large chunks of memory from one place to another while the CPU suspends its normal activities.
 

FrankenGraphics

New member
Update: i finally downloaded NESmaker and had a look at its asm code. The assumption that it stores a buffer at $200 was correct, so methods #2 and #3 in the tutorial should work as seamless as method #1.
 
Top Bottom