Tips on best coding practices?

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
JWinslow23
Posts: 30
Joined: Mon Apr 24, 2017 5:52 am
Location: West Allis, WI

Tips on best coding practices?

Post by JWinslow23 »

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 :P , 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
User avatar
Sumez
Posts: 919
Joined: Thu Sep 15, 2016 6:29 am
Location: Denmark (PAL)

Re: Tips on best coding practices?

Post by Sumez »

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. :P

https://wiki.nesdev.com/w/index.php/The_frame_and_NMIs
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Tips on best coding practices?

Post by tepples »

There are three ways to structure a game loop on NES, all equally valid.
  • 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 important part is that the input is read only once per game tick. Otherwise, your logic to detect presses and releases might get confused. This can be done either by always reading input right before moving objects or by reading only after having performed updates.

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.
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Tips on best coding practices?

Post by dougeff »

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.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Tips on best coding practices?

Post by tokumaru »

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.
User avatar
JWinslow23
Posts: 30
Joined: Mon Apr 24, 2017 5:52 am
Location: West Allis, WI

Re: Tips on best coding practices?

Post by JWinslow23 »

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.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Tips on best coding practices?

Post by tepples »

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.
How is it "not working"? What error message does it produce? Or is the assembled code incorrect?

For the .rs, have you first .rsset to define the start of your RAM segment?
User avatar
JWinslow23
Posts: 30
Joined: Mon Apr 24, 2017 5:52 am
Location: West Allis, WI

Re: Tips on best coding practices?

Post by JWinslow23 »

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 :P
User avatar
Bregalad
Posts: 8056
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Re: Tips on best coding practices?

Post by Bregalad »

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.
You don't need to do that, just use $4016 or $4017 directly, problem solved ^^

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).
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.
This pretty much sums everything up.
User avatar
JWinslow23
Posts: 30
Joined: Mon Apr 24, 2017 5:52 am
Location: West Allis, WI

Re: Tips on best coding practices?

Post by JWinslow23 »

Bregalad wrote:
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.
You don't need to do that, just use $4016 or $4017 directly, problem solved ^^
I do know I can do that, but I want to have the benefit of a name. Especially for the ButtonsHeldNow thing.
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).
I know that. I just goofed. :P

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
EDIT: Problem solved. I simply had to put colons after all of the names. A bit annoying, but functional.
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Tips on best coding practices?

Post by dougeff »

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.
nesdoug.com -- blog/tutorial on programming for the NES
zzo38
Posts: 1096
Joined: Mon Feb 07, 2011 12:46 pm

Re: Tips on best coding practices?

Post by zzo38 »

dougeff wrote:Quote from Kasumi...
"nesasm gives unknown instruction when you don't indent with whitespace characters."
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.

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)
User avatar
JWinslow23
Posts: 30
Joined: Mon Apr 24, 2017 5:52 am
Location: West Allis, WI

Re: Tips on best coding practices?

Post by JWinslow23 »

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.

Image

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
User avatar
Sumez
Posts: 919
Joined: Thu Sep 15, 2016 6:29 am
Location: Denmark (PAL)

Re: Tips on best coding practices?

Post by Sumez »

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.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Tips on best coding practices?

Post by tokumaru »

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.
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.

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%).
Any ideas on how I can move the bulk of this code inside the main game loop (which is still empty)?
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:

Code: Select all

  lda FrameCounter
WaitForVblank:
  cmp FrameCounter
  beq WaitForVblank 
This will keep the CPU waiting until the "FrameCounter" variable changes, something that will happen in the NMI handler.

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
And that's it. Let me know if something about how this works isn't clear.
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.
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.
Post Reply