It is currently Mon Jul 23, 2018 5:11 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 14 posts ] 
Author Message
PostPosted: Wed Jul 18, 2018 2:20 pm 
Offline

Joined: Wed Jul 18, 2018 1:50 pm
Posts: 2
Hello all,

I started a homebrew NES game to learn NES assembly programming back in April or May, though I haven't worked on it much this summer but really want to get back into it. I had asked this question over on the NESDev subreddit, but was directed here by the users there.

My question is regarding a problem I can't seem to solve with separating the game logic from the NMI. I know it is best practice to keep PPU and APU update code in the NMI, and all of the game engine code in its own loop. The problem is, I haven't gotten this to work--it is just fine when the logic is in the NMI, but when I try to move the logic to the game loop, the loop never executes or updates. I followed the Nerdy Nights tutorials when writing this code, but he kept the logic inside NMI...and disch's tutorial didn't help me much with this problem. Essentially, my problem is that when I put code in the main game loop (which, in the working version, just loops forever while we wait for an NMI, and the NMI handles all of the game code like in Super Mario Bros), none of this code actually executes. Specifically, controllers aren't read, the game engine doesn't do anything, and test tones don't play. The screen still scrolls, but that happens during NMI. I set my "gamestate" variable to "playing" in the reset file (which, in theory, should let me run the logic), but despite my attempts, this does not seem to help me execute my game engine code in my game loop.

For reference, here is my game loop:

Code:
MainGameLoop:
  ;; put game logic here. Use a "sleep" flag to prevent us from doing too much per frame
  ;; "sleep" flag should only be set if we need to wait for an NMI
MainGameLoop_Wait:
  lda sleep_flag
  bne MainGameLoop_Wait
 
  jsr ReadController
  jsr GameEngine
 
  inc sleep_flag ; set sleep flag after running the game engine
 
  JMP MainGameLoop


Note the sleep flag is reset in the NMI -- I store #$00 in sleep_flag before popping the values off the stack that I pushed on at the beginning of the NMI.

Rather than write all the code out here, I'll give you the pastebin links--here is a link to the init/reset file, and here is a link to the main file.

Thank you so much for your help, I hope someone can help me figure this out/point me in the right direction!

-truffly


Top
 Profile  
 
PostPosted: Wed Jul 18, 2018 2:52 pm 
Offline
Formerly 43110
User avatar

Joined: Wed Feb 05, 2014 7:01 am
Posts: 344
Location: us-east
Going by the code in the pastebin links, it looks like the reset routine does not jump to MainGameLoop it instead returns to nothing at the last line. The main code mistakenly starts out with a subroutine call to reset but that never happens because the RESET vector correctly points to the reset routine.

P.S. Welcome to the nesdev forums, this is an excellent first post.


Top
 Profile  
 
PostPosted: Wed Jul 18, 2018 3:13 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10616
Location: Rio de Janeiro - Brazil
This would work if the reset vector pointed to $C000, where you have the jsr reset instruction, but since the reset vector pointed straight to RESET, that JSR is never executed, meaning that at the end of RESET, the RTS has nowhere to return.

You have 2 options:

1- have the reset vector point to $C000; this won't work, because the stack pointer is reset in the reset routine...

2- replace the RTS in reset with jmp MainGameLoop (and remove the jsr reset that's doing nothing);


Last edited by tokumaru on Wed Jul 18, 2018 3:30 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Wed Jul 18, 2018 3:16 pm 
Offline
Formerly 43110
User avatar

Joined: Wed Feb 05, 2014 7:01 am
Posts: 344
Location: us-east
jsr reset is also problematic in this case as the reset routine also resets the stack pointer therefore making that rts still return to nothing.


Top
 Profile  
 
PostPosted: Wed Jul 18, 2018 3:28 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10616
Location: Rio de Janeiro - Brazil
That's true. So yeah, changing the reset vector to $C000 won't do it, because the stack pointer is manipulated after that, making it impossible to return to the saved address.


Top
 Profile  
 
PostPosted: Wed Jul 18, 2018 4:19 pm 
Offline

Joined: Wed Jul 18, 2018 1:50 pm
Posts: 2
Seems, then, like the best solution would be to eliminate jsr reset and the reset: label, and swap the rts in the reset file to a jmp statement. Thanks friends -- I completely overlooked that. It's always helpful to get fresh eyes on something!


Top
 Profile  
 
PostPosted: Thu Jul 19, 2018 1:04 am 
Offline
User avatar

Joined: Thu Sep 15, 2016 6:29 am
Posts: 631
Location: Denmark (PAL)
Yeah, I haven't looked at a lot of homebrew code, but it's my impression that's how most people do it.
Instead of having one single "main loop", I have specific states that my game changes between (thus keeping something like the title screen completely separate from playing a level), and whenever I change state, I just JMP to the "main" loop of that particular state (or an initialization block leading up to it), and keep looping that section until something else happens.
In that perspective, the reset process is a state that ends with jumping into the title screen state (or whatever state I'm testing at the moment).

The take-away here is that the game logic completely ignores NMI, and whenever you don't need it (to do nametable updates, etc), I think it's best practice to pretend it doesn't exist. All you need to know is that you have a variable (bit) in memory that updates once a frame, which you can use to time your loops. I even made a simple macro for timed loops, so I don't even have to think about this variable.


You could use a lookup table to control individual logic of multiple states (I think a lot of commercial games did this), but I like keeping my code modular.


Top
 Profile  
 
PostPosted: Thu Jul 19, 2018 1:39 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10616
Location: Rio de Janeiro - Brazil
Sumez wrote:
Instead of having one single "main loop", I have specific states that my game changes between (thus keeping something like the title screen completely separate from playing a level), and whenever I change state, I just JMP to the "main" loop of that particular state (or an initialization block leading up to it), and keep looping that section until something else happens.
In that perspective, the reset process is a state that ends with jumping into the title screen state (or whatever state I'm testing at the moment).

That's how I do it too. Each state is a separate block of code, with initialization at the beginning, followed by the "main" loop. To change states, I just jump to the initialization of the next state.

Quote:
The take-away here is that the game logic completely ignores NMI, and whenever you don't need it (to do nametable updates, etc), I think it's best practice to pretend it doesn't exist. All you need to know is that you have a variable (bit) in memory that updates once a frame, which you can use to time your loops.

This is a good straightforward approach, but there are some drawbacks. First, sounds won't play during state transitions. Second, some things may break in case there are lag frames: the music will slow down, and any raster effects you have (status bar, parallax scrolling, etc.) won't be done. If you don't need music during transitions and/or the glitches don't bother you (or you're sure you absolutely don't have lag frames), then this is a good approach, otherwise, it's good to have the NMI go "hey, I see you're not done with the frame calculations, but there's some important stuff we have to do anyway!"...

My NMI doesn't do anything specific to particular game states, it just takes care of some basic stuff, like enabling/disabling rendering, setting the scroll (only if rendering is enabled, otherwise the NMI doesn't do anything that could screw up ongoing VRAM transfers the main loop might be doing) and calling the audio engine, but before that, it calls a sequence of custom VRAM update routines from a list that was built by the game logic. These update routines can be anything, from generic "copy N bytes to address $XXXX" to specialized routines that will do anything to update whatever is necessary as fast as possible. Whatever the active state needs.


Top
 Profile  
 
PostPosted: Thu Jul 19, 2018 2:46 am 
Offline
User avatar

Joined: Thu Sep 15, 2016 6:29 am
Posts: 631
Location: Denmark (PAL)
tokumaru wrote:
This is a good straightforward approach, but there are some drawbacks. First, sounds won't play during state transitions.

I'd keep the sound engine completely out of the core game logic too, though, and update it in NMI. When using something like Famitone 2, that's the most obvious approach anyway.

Quote:
Second, some things may break in case there are lag frames: the music will slow down, and any raster effects you have (status bar, parallax scrolling, etc.) won't be done.

This is what I meant with "whenever you don't need it". Just like timed raster effects are tied to the NMI, the same of course goes for stuff like setting scroll, incremential nametable updates when you are scrolling and so on.
Anything that is essential to do in vblank or just every 60 frames a second, I'd put in NMI. And I don't think any of that should be a part of your main overall game logic.


Top
 Profile  
 
PostPosted: Thu Jul 19, 2018 3:43 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10616
Location: Rio de Janeiro - Brazil
Sumez wrote:
I'd keep the sound engine completely out of the core game logic too, though, and update it in NMI.

But then the "wait for the flag to change" approach to VRAM updates won't work anymore, because the sound engine will run *before* the updates, stealing precious vblank time. I assumed that was what you meant by "variable in memory that updates once a frame, which you can use to time your loops".


Top
 Profile  
 
PostPosted: Thu Jul 19, 2018 7:54 am 
Offline
User avatar

Joined: Thu Sep 15, 2016 6:29 am
Posts: 631
Location: Denmark (PAL)
I was talking about timing your "main" loops.

I'd still run music updates in NMI, but after VRAM updates so it doesn't matter if it spills outside vblank. Isn't that how most people do it.


Top
 Profile  
 
PostPosted: Thu Jul 19, 2018 7:59 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10616
Location: Rio de Janeiro - Brazil
I see. From your post I had assumed you were talking about the minimalist inc FrameCount rti NMI handler.


Top
 Profile  
 
PostPosted: Thu Jul 19, 2018 12:48 pm 
Offline
User avatar

Joined: Tue Apr 04, 2017 1:22 pm
Posts: 33
Location: Ohio, USA
Sumez wrote:
Instead of having one single "main loop", I have specific states that my game changes between (thus keeping something like the title screen completely separate from playing a level), and whenever I change state, I just JMP to the "main" loop of that particular state (or an initialization block leading up to it), and keep looping that section until something else happens.
In that perspective, the reset process is a state that ends with jumping into the title screen state (or whatever state I'm testing at the moment).

I personally like to use a single main game loop and have INIT and CLEANUP sub-states for each game state. This takes the form of a jump table, where the proper routine is looked up in my main loop.
This way is consistent with how I do entities (e.g. an enemy's state: idle, attacking the player, etc.). I check the entity type and use a jump table to run the correct update routine, then use another jump table based on its state to actually perform the logic.

_________________
http://zutanogames.com/ <-- my dev blog


Top
 Profile  
 
PostPosted: Fri Jul 20, 2018 1:20 am 
Offline
User avatar

Joined: Thu Sep 15, 2016 6:29 am
Posts: 631
Location: Denmark (PAL)
tokumaru wrote:
I see. From your post I had assumed you were talking about the minimalist inc FrameCount rti NMI handler.


Oh jesus no. In fact, quite the opposite. Moving NMI code out from the NMI is just as bad IMO, and defeats the purpose of "ignoring that NMI exists" when creating your core game logic.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 4 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