Yep, Sprite DMA. You remember how we wrote to SPR-RAM (OAM) with $2003 and $2004? Well, this is actually unrealiable on the real system (or so I've heard). What we actually need to do is utalize (utallise?) memory as OAM, and then put a number into a register and everything is copied for us automatically into the actual OAM. If you didn't get that it'll (hopefully) become clearer as we do it.
As I said above, we need to use our "variable" memory to make a sort of duplicate of OAM. In this tutorial we will use $0300 for our OAM Copy if you will, and you will, 'cause I will/am... nevermind. ok... Note that there are only something like 64 sprites on the NES so this only really takes (64*4(4bytes for each entry)=256 bytes aways from what we can use. So basically, leave all your "variables" at $0000-$0200 and only go over $0500 with your "variables" if you have to.
Now when I say that we'll be using $0300 for an OAM Copy, I mean that it will (actually HAS to) be the same layout as the regular OAM. Also, try to remember that $0300 is a memory address, which is somewhat different from a memory register as in we have to increment the address every write/store ourselfs rather than just keep giving values to a (memory) register.
Also (alot of alsos, I know... I just want to make this as clear as possible), there is a reason we use an even number in the hundreds for our OAM Copy. I'll tell you in the next section.
Alright, hopefully you get the theory. Now let's see some assembler! Here goes:
;;--- START OF CODE ---;; .inesmap 0 ; .inesprg 1 ; I do these in different orders sometimes, it doesn't matter .ineschr 1 ; as long as you keep the 1s with the right ones. I hope that .inesmir 1 ; didn't confuse you as much as I just confused myself. ;) .bank 1 .org $FFFA .dw 0 .dw Start .dw 0 .bank 0 ; code bank .org $0000 ; variable ORG location $0000 ; normally variables defined here .org $0300 ; OAM Copy location $0300 Sprite1_Y: .db 0 ; sprite #1's Y value Sprite1_T: .db 0 ; sprite #1's Tile Number Sprite1_S: .db 0 ; sprite #1's special byte Sprite1_X: .db 0 ; sprite #1's X value Sprite2_Y: .db 0 ; same thing, same order for sprite #2 Sprite2_T: .db 0 ; note that I numbered 1 2 ... Sprite2_S: .db 0 ; some people may actually prefer starting Sprite2_X: .db 0 ; the count at 0, but it doesn't really matter. ; this would just go on and on for however many sprites you have .org $8000 ; code ORG location $8000 Start: ; code goes here, the code that goes here or atleast the code to get the ; above sprite definitions into the actual OAM, will be given in the next ; section. infin: jmp infin ; infinite loop ;;--- END OF CODE FILE ---;;
If you don't get it, email me telling exactly what you don't get.
The DMA Register is $4014. We want to write a 3 to it. Why 3? Because our OAM Copy is at $0300. Writing to the register causes the stuff at $0n00 (where 'n' is the number we wrote) to be copied into the actual OAM. So if we had our stuff at $0400 we would write a 4, if it was at $0500 then write a 5. Get it? I sure hope so. If not tell me please, I will not hesitate to help you.
So, now to get our values in the actual OAM assuming we put the sprite's info in the Sprite#-whatever "variables", we just go like this:
lda #$3 ; could be #3, 3 is the same hex and decimal (obviously). sta $4014 ; when we write to it, the copy from $0300+ to actual OAM, is carried out.
That's it! It's not only more reliable than our old method, it's easier!
Well, we need to do several things. First, copy the .org $0300 and stuff over our old variables section. We don't need the variables any more (for sprite info that is) because we're using the OAM Copy itself for the sprite's X and Y.
Second, use a find and replace function to replace all the 'X_Pos' and 'Y_Pos' with 'Sprite1_X' and 'Sprite1_Y' respectively.
Third find the block of code that loads $2003 with a value and then proceeds to write to $2004 several times. Find that block of code and replace it with:
lda #3 sta $4014
That should be it! We also save several bytes of code with this method.
Hope you enjoy Sprite DMA, I tried to make it as painless as possible. Now that we have a better way to put data in OAM, we can look at a better way to capture VBlank tomorrow. On to interrupts...
May Your Programs Run Without Bugs,
-Mike H a.k.a GbaGuy