It is currently Fri Dec 15, 2017 2:57 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 44 posts ]  Go to page 1, 2, 3  Next
Author Message
PostPosted: Mon Apr 24, 2017 7:01 am 
Offline
User avatar

Joined: Mon Apr 24, 2017 5:52 am
Posts: 30
Location: West Allis, WI
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:
File comment: The .chr file for Wal-Rush! Has some in-progress graphics for items and obstacles.
walrush.chr [8 KiB]
Downloaded 32 times
File comment: The source code (so far) for Wal-Rush!
walrush.asm [9.51 KiB]
Downloaded 36 times
Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 8:06 am 
Offline
User avatar

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


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 8:11 am 
Offline

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


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 9:11 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1868
Location: DIGDUG
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


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 9:32 am 
Offline
User avatar

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


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 9:47 am 
Offline
User avatar

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


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 9:57 am 
Offline

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


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 10:27 am 
Offline
User avatar

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


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 10:57 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7317
Location: Chexbres, VD, Switzerland
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).
Quote:
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.


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 11:21 am 
Offline
User avatar

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


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 1:52 pm 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1868
Location: DIGDUG
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


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 3:04 pm 
Offline
User avatar

Joined: Mon Feb 07, 2011 12:46 pm
Posts: 941
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.

_________________
.


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 3:10 pm 
Offline
User avatar

Joined: Mon Apr 24, 2017 5:52 am
Posts: 30
Location: West Allis, WI
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:
File comment: The compiled .nes file.
walrush.nes [24.02 KiB]
Downloaded 34 times
File comment: The .chr graphics.
walrush.chr [8 KiB]
Downloaded 35 times
File comment: The .asm source.
walrush.asm [9.13 KiB]
Downloaded 36 times
Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 3:46 pm 
Offline
User avatar

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


Top
 Profile  
 
PostPosted: Mon Apr 24, 2017 3:53 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
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%).

Quote:
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:
  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:
NMI:
  inc FrameCounter
  rti

And that's it. Let me know if something about how this works isn't clear.

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


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 44 posts ]  Go to page 1, 2, 3  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group