BioMechanical Dude wrote:Is this something, that differs from assembler to assembler (I'm using ASM6, if it does)? How do you assign a name to a certain address?
All assemblers I'm aware of support the VariableName = $03ab
syntax, but having to manually select addresses still isn't the most reliable thing to do. You could accidentally define overlapping variables, and you'd need to retype a bunch of addresses if you decided to rearrange them. For ASM6, dougeff's first suggestion is the best: create an .enum
block where you specify the starting address and then declare all variables and their lengths inside it, so the assembler will automatically assign the correct address to each one.
Second, where exactly does the stack store its information? Is defining a stack pointer necessary?
The stack is always at $0100-$01ff, and if you're going to use that space exclusively for the stack, you don't really need to initialize the stack pointer. It wraps around, so the stack will still be 256 bytes long no matter where it starts. However, it's good practice to always initialize the stack pointer... This will allow you to use other parts of that memory page for other things you might need, such as a VRAM update buffer. Also, knowing exactly where the stack starts makes debugging easier.
Why do people start Shadow OAM at $0200?
Since page 0 and page 1 have are used by the 6502 for special purposes, NES programmers find it natural to use the page after that for a special purpose too. In practice, any other page, from $00 to $ff, can be copied to OAM. Yes, even ROM pages can be used as shadow OAM, but there's hardly any use for that besides title screens and the like (and even then it's usually more compact to decompress the sprite data to RAM instead of DMA'ing it directly from ROM).
Where does Zero page end and what exactly is in there?
It's the very first page of memory, from $0000 to $00ff. It's special because several instructions have optimized versions to access data there using less ROM (the high byte of the address is omitted from the instructions, since it's known to be 0) and 1 cycle faster (since the CPU doesn't have to read the high byte of the address, which is known to be 0). There are also some operation that can only be done through ZP, such as accessing data through pointers (e.g. lda (Pointer), y
I don't know if there's a better way to do this, at least when it comes to setting an individual bit in a byte, without changing the other ones.
Honestly, I can't make any sense out of the controller reading loop since it's using addresses instead of labeled variables. From your description it sounds like it could work, but it looks a little convoluted with all the loading and storing that are going on. It can definitely be reworked to be much smaller and easier to read. The loop itself could probably be just this (this will ignore Famicom controllers that plug into the expansion port though... the code rainwarrior posted supports those):
Code: Select all
With time you'll learn that a lot of things can be done by simply moving bits and flags (such as the carry) around, without the need for decisions and branching. Another thing you'll learn is that counting down is usually faster than counting up, because the CPU automatically changes its status flags for results like 0 ($00) and -1 ($ff), so you can decide whether to break out of the loop with an explicit CMP/CPX/CPY instruction. Whenever you can count down instead of up, do it.
In this case in particular you can actually not count at all, and put a flag in the button buffer to indicate when all buttons have been read:
Code: Select all
Since we initialized the "Button" buffer with all zeroes, except for the least significant bit, we can be sure that only zeroes will be shifted out after each rotation. That "1" bit we put in the least significant position will only be shifted out after 8 bits have been shifted in, so when the carry is set we'll know it's time to end the loop. Combine that with rainwarrior's suggestion of taking the Famicom expansion controller into consideration and you get this:
Code: Select all
Which is fairly compact and very reliable.
dougeff wrote:Also, I don't think it's a bad idea to update the palette every frame, since that way the game can make sudden updates without too much additional code.
Yes, some people like to do it that way, there's nothing terribly wrong about that. It's still a little bit of CPU time wasted nearly every frame, but if your game doesn't need to make a lot of VRAM updates and the gameplay itself isn't too hungry for CPU cycles, there shouldn't be any problems with updating the palette every frame.
dougeff wrote:So, does that mean the controller read code should be done during NMI?
I wouldn't do that. If your game doesn't have lag frames, it shouldn't make any difference, but if by any chance the game logic is not done by the time the NMI is called, it will overwrite the controller state with a new one, and when the game logic resumes, whatever of it that still has to run will use the new state. This means that the same logic frame will end up using two different sets of controller states, which could cause inconsistencies.