Tips on best coding practices?
Moderator: Moderators
- JWinslow23
- Posts: 30
- Joined: Mon Apr 24, 2017 5:52 am
- Location: West Allis, WI
Tips on best coding practices?
Hello NESDev community! I'm a newcomer that's interested in playing NES games, but lately I've been wondering how the heck people program for it. So I tried it myself.
I slightly modified one of the Nerdy Nights tutorials for a quick demo of a game I plan to make, called Wal-Rush!. For the curious, it'll involve a flying walrus, and it's actually a port of my Atari 2600 game of the same name, which is itself a port of a project I did on Scratch. The .asm and .chr files are attached if you want to see them, and I'm using NESASM3 for assembly.
My question is, what are some general coding practices I should follow to make programming easier? For instance, I didn't know from those tutorials how to create a full background, so I had to improvise , but I probably did so poorly. Also, I'm not sure putting the input handling routines in NMI is the best practice (from browsing the forums, I've seen that that's largely discouraged).
One last thing I'm confused about is that the walrus moves multiple pixels per frame in FCEUX, but on my EverDrive N8, it seems to correctly move 1 pixel per frame. Can I have a fix/explanation?
I slightly modified one of the Nerdy Nights tutorials for a quick demo of a game I plan to make, called Wal-Rush!. For the curious, it'll involve a flying walrus, and it's actually a port of my Atari 2600 game of the same name, which is itself a port of a project I did on Scratch. The .asm and .chr files are attached if you want to see them, and I'm using NESASM3 for assembly.
My question is, what are some general coding practices I should follow to make programming easier? For instance, I didn't know from those tutorials how to create a full background, so I had to improvise , but I probably did so poorly. Also, I'm not sure putting the input handling routines in NMI is the best practice (from browsing the forums, I've seen that that's largely discouraged).
One last thing I'm confused about is that the walrus moves multiple pixels per frame in FCEUX, but on my EverDrive N8, it seems to correctly move 1 pixel per frame. Can I have a fix/explanation?
- Attachments
-
- walrush.chr
- The .chr file for Wal-Rush! Has some in-progress graphics for items and obstacles.
- (8 KiB) Downloaded 298 times
-
- walrush.asm
- The source code (so far) for Wal-Rush!
- (9.51 KiB) Downloaded 302 times
Re: Tips on best coding practices?
If you haven't seen it already, I strongly recommend this excellent article from the wiki on how to handle NMI, what to put where, and best methods for VRAM writes during vblank. It's probably the single best resource for NES-specific "best practice".
That said, a ton of commercial games don't follow these guidelines.
https://wiki.nesdev.com/w/index.php/The_frame_and_NMIs
That said, a ton of commercial games don't follow these guidelines.
https://wiki.nesdev.com/w/index.php/The_frame_and_NMIs
Re: Tips on best coding practices?
There are three ways to structure a game loop on NES, all equally valid.
The other important part is that if you get slowdown, raster splits such as that for a status bar might mess up. This is hard to fix with "All in main", but an NMI handler can guarantee setting the scroll even if there are no updates to process.
- All in main (e.g. Thwaite)
NMI thread increments a variable. Main thread reads input, moves game objects, generates video memory updates and sprite display list, waits for the variable to change, performs the updates, and sets the scroll. - All in NMI (e.g. Super Mario Bros.)
Main thread clears the update buffer once and then spins in an endless JMP loop. NMI thread performs the previous frame's updates if complete, sets the scroll, reads input, moves game objects, and generates video memory updates and sprite display list, and marks the update list as complete. - Split (e.g. The Curse of Possum Hollow)
Main thread reads input, moves game objects, generates video memory updates and sprite display list, marks the update list as complete, and waits for the variable to change. NMI thread performs the updates if complete, changes the variable, and sets the scroll.
The other important part is that if you get slowdown, raster splits such as that for a status bar might mess up. This is hard to fix with "All in main", but an NMI handler can guarantee setting the scroll even if there are no updates to process.
Re: Tips on best coding practices?
I believe the "split" method is the best.
The NMI code should push VRAM updates (if they are ready), push the sprites to OAM, set the scroll, update the music. These things NEED to be done every frame, during v-blank (except music).
The main code can handle the rest.
I think Shiru's neslib has a good example of an NMI code.
The NMI code should push VRAM updates (if they are ready), push the sprites to OAM, set the scroll, update the music. These things NEED to be done every frame, during v-blank (except music).
The main code can handle the rest.
I think Shiru's neslib has a good example of an NMI code.
nesdoug.com -- blog/tutorial on programming for the NES
Re: Tips on best coding practices?
All methods are fine if your game never slows down, but the split method is the best to handle lag frames without glitching out. "All in main" is the worst, because even high priority tasks like music and raster effects get delayed in case of lag frames.
- JWinslow23
- Posts: 30
- Joined: Mon Apr 24, 2017 5:52 am
- Location: West Allis, WI
Re: Tips on best coding practices?
While I'm re-writing some of my code, how exactly do you define "constants"? For example, if I wanted to refer to $4017 as JOYPAD1, how would I make it so I can do this? I'm trying JOYPAD1 = $4017 at the beginning of my code (after the .ines things, but before the .bank 0), but it's not working.
Also, I'm trying to name certain areas of memory that I access, like the buttons being held at this frame being called ButtonsHeldNow. I'm using ButtonsHeldNow .rs 1 at the same place, but it's not working.
Also, I'm trying to name certain areas of memory that I access, like the buttons being held at this frame being called ButtonsHeldNow. I'm using ButtonsHeldNow .rs 1 at the same place, but it's not working.
Re: Tips on best coding practices?
How is it "not working"? What error message does it produce? Or is the assembled code incorrect?JWinslow23 wrote:While I'm re-writing some of my code, how exactly do you define "constants"? For example, if I wanted to refer to $4017 as JOYPAD1, how would I make it so I can do this? I'm trying JOYPAD1 = $4017 at the beginning of my code (after the .ines things, but before the .bank 0), but it's not working.
For the .rs, have you first .rsset to define the start of your RAM segment?
- JWinslow23
- Posts: 30
- Joined: Mon Apr 24, 2017 5:52 am
- Location: West Allis, WI
Re: Tips on best coding practices?
All of those defined "constants" I mentioned earlier give Unknown Instruction errors.
And no, I did not do .rsset. I did, however, just try .rsset $0000 before my declarations, and the same error still happens.
Keep in mind, I'm a complete beginner, and I didn't even know what an NMI was 5 minutes ago
And no, I did not do .rsset. I did, however, just try .rsset $0000 before my declarations, and the same error still happens.
Keep in mind, I'm a complete beginner, and I didn't even know what an NMI was 5 minutes ago
Re: Tips on best coding practices?
You don't need to do that, just use $4016 or $4017 directly, problem solved ^^JWinslow23 wrote:While I'm re-writing some of my code, how exactly do you define "constants"? For example, if I wanted to refer to $4017 as JOYPAD1, how would I make it so I can do this? I'm trying JOYPAD1 = $4017 at the beginning of my code (after the .ines things, but before the .bank 0), but it's not working.
Also, I'm trying to name certain areas of memory that I access, like the buttons being held at this frame being called ButtonsHeldNow. I'm using ButtonsHeldNow .rs 1 at the same place, but it's not working.
Personally I was against using labelled constants on the wiki but everyone was against me on this one so I had to resign.
Also, the joypad 1 is acessed by $4016, not $4017, which is joypad 2 reading and APU clock timer on writes (joypad 2 is strobed by $4016 just like joypad 1).
This pretty much sums everything up.All methods are fine if your game never slows down, but the split method is the best to handle lag frames without glitching out. "All in main" is the worst, because even high priority tasks like music and raster effects get delayed in case of lag frames.
- JWinslow23
- Posts: 30
- Joined: Mon Apr 24, 2017 5:52 am
- Location: West Allis, WI
Re: Tips on best coding practices?
I do know I can do that, but I want to have the benefit of a name. Especially for the ButtonsHeldNow thing.Bregalad wrote:You don't need to do that, just use $4016 or $4017 directly, problem solved ^^JWinslow23 wrote:While I'm re-writing some of my code, how exactly do you define "constants"? For example, if I wanted to refer to $4017 as JOYPAD1, how would I make it so I can do this? I'm trying JOYPAD1 = $4017 at the beginning of my code (after the .ines things, but before the .bank 0), but it's not working.
Also, I'm trying to name certain areas of memory that I access, like the buttons being held at this frame being called ButtonsHeldNow. I'm using ButtonsHeldNow .rs 1 at the same place, but it's not working.
I know that. I just goofed.Bregalad wrote:Also, the joypad 1 is acessed by $4016, not $4017, which is joypad 2 reading and APU clock timer on writes (joypad 2 is strobed by $4016 just like joypad 1).
Also, here is the code that's erroring upon compilation:
Code: Select all
.inesprg 1 ; 1x 16KB bank of PRG code
.ineschr 1 ; 1x 8KB bank of CHR data
.inesmap 0 ; mapper 0 = NROM, no bank swapping
.inesmir 1 ; background mirroring (ignore for now)
BUTTON_A = 1 << 7
BUTTON_B = 1 << 6
BUTTON_SELECT = 1 << 5
BUTTON_START = 1 << 4
BUTTON_UP = 1 << 3
BUTTON_DOWN = 1 << 2
BUTTON_LEFT = 1 << 1
BUTTON_RIGHT = 1 << 0
.rsset $0000
ButtonsHeldLastFrame .rs 1
ButtonsHeldNow .rs 1
Re: Tips on best coding practices?
If I recall correctly, NESASM has weird quirks about indentation. Like, some of the directives would break if you tabbed them in, or didn't tab it in. I can't remember.
Maybe some small oddity of syntax is throwing you off.
Quote from Kasumi...
"nesasm gives unknown instruction when you don't indent with whitespace characters."
From...
viewtopic.php?f=10&t=7922
Edit: personal opinion. Drop NESASM now and start using asm6 or ca65.
Maybe some small oddity of syntax is throwing you off.
Quote from Kasumi...
"nesasm gives unknown instruction when you don't indent with whitespace characters."
From...
viewtopic.php?f=10&t=7922
Edit: personal opinion. Drop NESASM now and start using asm6 or ca65.
nesdoug.com -- blog/tutorial on programming for the NES
Re: Tips on best coding practices?
Any line without a tab will be a label. After the label you can put tab and instruction; you can omit the label and just start with a tab if that line has no label.dougeff wrote:Quote from Kasumi...
"nesasm gives unknown instruction when you don't indent with whitespace characters."
NESASM can be confusing to someone who does not know it, although I think it is good. But, use which one you like.
(Free Hero Mesh - FOSS puzzle game engine)
- JWinslow23
- Posts: 30
- Joined: Mon Apr 24, 2017 5:52 am
- Location: West Allis, WI
Re: Tips on best coding practices?
I guess I'll stick with NESASM, because it'll work. I might consider a different compiler if I run into too many problems, though.
Also, I've updated the code! Now the walrus goes left and right, and falls forever. Also, he has blue eyes.
One thing, though: the button-reading routine is a subroutine, but all the code that reads the buttons and updates everything is in NMI, because I wanna make sure it's being done once per frame. Any ideas on how I can move the bulk of this code inside the main game loop (which is still empty)? I'm thinking I would store flags for getting things done inside the main loop, and use the NMI to actually update the relevant values, but I'm a bit iffy on what exactly needs to be done.
Also, I've updated the code! Now the walrus goes left and right, and falls forever. Also, he has blue eyes.
One thing, though: the button-reading routine is a subroutine, but all the code that reads the buttons and updates everything is in NMI, because I wanna make sure it's being done once per frame. Any ideas on how I can move the bulk of this code inside the main game loop (which is still empty)? I'm thinking I would store flags for getting things done inside the main loop, and use the NMI to actually update the relevant values, but I'm a bit iffy on what exactly needs to be done.
- Attachments
-
- walrush.nes
- The compiled .nes file.
- (24.02 KiB) Downloaded 303 times
-
- walrush.chr
- The .chr graphics.
- (8 KiB) Downloaded 292 times
-
- walrush.asm
- The .asm source.
- (9.13 KiB) Downloaded 295 times
Re: Tips on best coding practices?
Absolutely. You can easily store all 8 button states on a single NES controller in a single byte, with each bit indicating wether the button is pressed. I also do comparisons with the previous NMI to see if a button was just pressed to trigger events on button pulses (such as attacking or jumping), and store those in a separate byte.
Re: Tips on best coding practices?
IIRC, Nerdy Nights starts out with one of the worst possible program structures ever: everything in the NMI handler, with the game logic before PPU updates. This is terrible, because you basically have to do everything (logic + PPU updates) in 20 or so scanlines (while the remaining 242 lines of CPU time go unused) or else the PPU updates will spill into the visible picture and glitch everything. That basically means you only get to use less than 8% of the total CPU time if you use that structure.JWinslow23 wrote:all the code that reads the buttons and updates everything is in NMI, because I wanna make sure it's being done once per frame.
It's beyond me why anyone would base a tutorial around such a poor architecture. Everything in main would be a much better choice for beginners, because it's also very simple and allows you to use 100% of the CPU time without complications (the trouble only starts if you need more than 100%).
You can easily turn what you have into "everything in main". Move the game logic out of the NMI handler and put it into the main loop. Then put the following "wait for vblank" loop right after the game logic:Any ideas on how I can move the bulk of this code inside the main game loop (which is still empty)?
Code: Select all
lda FrameCounter
WaitForVblank:
cmp FrameCounter
beq WaitForVblank
Now you can remove the PPU update code out of the NMI handler and put it right after the wait for vblank. Then comes the JMP back to the start of the game logic. Your NMI handler should be empty now, and you can modify it to change that variable I mentioned earlier, signaling that vblank has started:
Code: Select all
NMI:
inc FrameCounter
rti
That would be split method, the most robust one of you do it right. It's slightly more complicated because you really need flags and such in order to have the separate threads communicate with each other, but you may try going that route if you think it's time.I'm thinking I would store flags for getting things done inside the main loop, and use the NMI to actually update the relevant values, but I'm a bit iffy on what exactly needs to be done.