GBAGuy's tutorials are notoriously bad... Some of the things he says are just plain wrong. My advice would be to stop reading them.
and Nerdy Nights and there's a lot of things that confuse me.
Nerdy Nights is much better. There are some oddities here and there, but nothing game-breaking IIRC.
I need someone experienced who'd look at my code and tell me what I should optimize, what I should and shouldn't do.
Don't worry about optimization right know. Just make sure your code works correctly.
First question: how to force sprite to move on an 8x8 grid? This is crucial to games like these, when you have to search on a certain tile to obtain an item or something. I can make a sprite move by 8 px, but it'll just jump by this number of pixels instead of moving smoothly.
Games are all about states and the small incremental updates that correspond to each state. Say, when the left button is pressed on the controller, instead of immediately changing the character's position, you change its state from "idle" to "moving left". Every frame you check what the character's status is: if it's "idle", don't do anything, if it's "moving left", subtract 1 from its X coordinate, and so on. after moving, you have to check if the character is aligned to the grid, in which case you change its status back to "idle". The pseudo-code goes something like this:
Code: Select all
;handle any movement associated with the current state
if status = "moving up"
playerY = playerY - 1
elseif status = "moving down"
playerY = playerY + 1
elseif status = "moving left"
playerX = playerX - 1
elseif status = "moving right"
playerX = playerX + 1
;stop moving if aligned to the 8x8 grid
if playerX AND %00000111 = 0
if playerY AND %00000111 = 0
status = "idle"
;start moving only when idle
if status = "idle"
if "up" is pressed
status = "moving up"
elseif "down" is pressed
status = "moving down"
elseif "left" is pressed
status = "moving left"
elseif "right" is pressed
status = "moving right"
Second: where should I store variables like main characters current position on map, level, hp, status etc.?
Wherever there's space for them. I like to store most variables in zero page, because accessing them there is slightly faster (although if you're using NESASM, you have to prefix the addresses or variable names with "<" so that the faster addressing is used, otherwise there's no speed improvement at all), and the rest of the memory (pages 3 and up, normally) for larger data structures, such as palettes, level maps, object states, and so on. I said pages 3 and up because page 1 always contains the stack (you can steal part of page 1 for other uses though) and page 2 is usually the OAM buffer.
I'm currently starting at location $29, saw this in Metroid's source.
Sounds pretty arbitrary. There's no reason for you to skip $00-$28... you can use whatever memory you want for your variables.
Before every SBC carry flag is set using SEC and before every ADC carry flag is cleared using CLC. Why?
The 6502 is an 8-bit CPU, meaning it can only do math 8-bits at a time. For it to be able to handle 16-bit (and larger) values, it needs the carry flag. The carry flag holds information about the result of the last mathematical operation you performed, which can be used to make decisions ("is this number bigger than that number" and things like that) or to propagate the operation to other bytes, allowing you to manipulate larger numbers.
Whenever you use ADC, the CPU will add whatever value you're adding PLUS the carry flag. It works exactly like when you learned to add in school:
First you add the units: 8 + 5 is 13, which is 2 digits, so you write the lower digit down and write the carry on top of the next space:
Now you add the tens, and add the carry as well: 7 + 1 + 1 = 9
There you go. The 6502 always adds 2 values + the carry flag, allowing you to detect and propagate overflows to the next byte, much like you propagate them to the next digit adding on paper. The reason you need to clear the carry when starting an addition is because there isn't anything to propagate at the start, but the 6502 doesn't have an ADD (add without carry) instruction (some CPUs do), so you're forced to make sure the carry won't get in the way.
Subtractions work in a similar way, but the carry flag works more like an "imaginary value" that may or may not be borrowed during a subtraction. It's like you're saying to the CPU: "Here 6502, I'm leaving this flag set for you, just in case you need to borrow it". Then, once the subtraction is done, the value in the carry flag has the following meanings:
0: there was an underflow, and the CPU need to borrow 1;
1: the carry is intact, so there was no underflow and the result of the subtraction is positive;
And the 6502 interprets the carry flag like this on the next subtraction. If the carry is clear, it will know it has to subtract one extra unit, because of he borrow. To make sure there's no borrow at the beginning of a subtraction operation, you have to set the carry.
Fourth: What does RTI exactly do? Should all of my code be placed before it or just the code I want to occur during vblank?
That depends on how your program is organized. Nerdy Nights goes for a program structure that places everything in the NMI handler. The vblank stuff comes first, and then the game logic, but it's all part of the NMI handler, so the RTI is at the very end of the game loop. The other approaches are:
Everything outside the NMI: The NMI doesn nothing more than set/increment a flag, and the main thread waits for this flag to change in order to detect the start of vblank. In this case, the only thing before the RTI is an instruction like INC vbcounter
, everything else is in the main thread.
Game logic outside the NMI, video and audio updates inside the NMI: With this you actually have 2 threads running in parallel, with the main thread processing everything about the game and buffering video updates, while the NMI thread dumps these updates to the PPU.