Getting started again!! Romhacker to NES Game Programmer

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

tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by tepples »

Controller reading in the NMI and game logic in the main thread could cause your game logic to miss presses, especially if the NMI handler also calculates the "pressed or released since last frame" flag.
User avatar
Memblers
Site Admin
Posts: 4044
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by Memblers »

tokumaru wrote:Don't forget about raster effects. If you have a sprite 0 status bar, effects based on IRQs, and stuff like that, you also need to reset them for the new frame, otherwise you'll get glitched frames every time there's slowdown.
Ah yeah. On that topic too, I think it's crazy that so many games have the sprite 0 status bar on the bottom of the screen (looking at you, Rare!), that trick is a lot fancier than most (non-dev) people would think. You can't even have a frame-time overflow in the case. I mean, you can without a complete disaster, but it'll look like Konami's first TMNT game - maybe the glitchiest status bar ever?
tepples wrote:
Memblers wrote:I've been doing a soundtrack for a game I'm making and I haven't even started considering sound effects yet, but I've had my old Nerdtracker 2 habits in mind.
As far as I know, of the current game-oriented homebrew NES sound drivers, only Pently supports NT2-style decaying envelopes.
Oh man, kill it with fire.. :lol: I'll always love NT2 but I don't make new stuff in it. I spent so much effort working around that to make it sound better without knowing why exactly, didn't realize at the time that because the NES volume control is linear, a linear fade sounds terrible. That's why the NES's hardware decay sounds so distinctive and weird, it's just.. wrong. A built-in logarithmic decay could be useful perhaps, I suppose everyone will just build their own envelopes to do it themselves, though. But, being able to scale that (like the FT2's volume control channel) is something I really like, I'm glad you're considering that for the future. I suppose it can be quickly done with a 4-bit x 4-bit look-up table?

I had it in my head that Pently only accepted MML-type input, but looking into it again I see that NovaSquirrel had made a Famitracker converter for it. With that, I am immediately intrigued. The little testing I did previously seemed to have Pently appearing to be CPU efficient to an impressive degree, that's definitely something I will be exploring!
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by thefox »

Memblers wrote:Ah yeah. On that topic too, I think it's crazy that so many games have the sprite 0 status bar on the bottom of the screen (looking at you, Rare!), that trick is a lot fancier than most (non-dev) people would think. You can't even have a frame-time overflow in the case. I mean, you can without a complete disaster, but it'll look like Konami's first TMNT game - maybe the glitchiest status bar ever?
In some earlier discussions we figured that this is probably because it's much more difficult to set the scroll arbitrarily mid-screen than in VBlank. Few developers seemed to manage it back in the day (like Rare in Battletoads). But it is indeed a PITA to have the status bar at the bottom without mapper IRQs.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by DRW »

thefox wrote:In some earlier discussions we figured that this is probably because it's much more difficult to set the scroll arbitrarily mid-screen than in VBlank.
What's so difficult about it? My game is mapper 0 without any fancy stuff and yet I easily managed clean parallax scrolling.*
Just make sure that there's one pixel line that consists of all the same color. Put the sprite 0 there. And then set the scrolling. The scrolling change will be done before rendering the line is over and since the line consists of pixels of the same color, the player won't notice any artifacts.

* In fact, I do three scrolling changes per frame:
First one in NMI that goes to scrolling position 0.
Then the status bar is drawn. While this is done, I do some game logic whose duration is always pretty constant.
Then I wait for the nine sprites per scanline flag because I positioned nine empty sprites below the status bar. There, I set the scrolling for the background.
Then the background is drawn. In the meantime, I do the majority of the game logic.
Then sprite 0 split.
Then the foreground is drawn and I use the remaining time for some other constant-time logic.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by thefox »

DRW wrote:
thefox wrote:In some earlier discussions we figured that this is probably because it's much more difficult to set the scroll arbitrarily mid-screen than in VBlank.
What's so difficult about it? My game is mapper 0 without any fancy stuff and yet I easily managed clean parallax scrolling.
Setting the X scroll is trivial (they managed that even in SMB1), but setting the Y scroll with pixel precision is rather involved. See http://wiki.nesdev.com/w/index.php/PPU_ ... g#Examples
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by DRW »

Oh, you're talking about vertical scrolling. Sorry, I thought it was just about status bar on top vs. status bar at the botton.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
Gil-Galad
Posts: 321
Joined: Sat Nov 13, 2004 9:43 pm
Location: Ohio, USA
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by Gil-Galad »

tepples wrote:Controller reading in the NMI and game logic in the main thread could cause your game logic to miss presses, especially if the NMI handler also calculates the "pressed or released since last frame" flag.
It sounds like I need to move the controller code out of the NMI then if it's going to be buggy.

Now I'm back to the beginning. Should I make a loop in the reset to run the controller and logic code?

For example.

Code: Select all


LDA #%10010000    ; enable NMI, background $1000 VRAM, sprites $0000 VRAM
STA $2000         ; sprite size 8x8, PPU increment 1, name table $2000 VRAM
LDA #%0011000     ; background visible, sprites visible, background & sprites clipped
STA $2001         ; color display

startloop:

JMP startloop

Where I have the JMP startloop is where I should put logic code instead? One time I tried putting code there and it was running way faster than it should be. I also ran the sound driver init here once and the NTSC music driver I was using sped up big time, sounded like PAL. So then I tried keeping code out of there because at the time I didn't have the skill to work out some type of timing or I just had the wrong idea. This has always been a problem for me.
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by dougeff »

You don't need to move the controller code, unless you're making a zapper game. :)

Wait, no, tepples is right..IF your game logic goes longer than 1 frame, the logic will miss a change in button presses event. (Events like, Title Screen, exit if Start wasn't pressed last frame && Start is pressed this frame)

Move the jsr button_check to the infinite loop.

EDIT...
Have a flag for when the game logic is done, and only check buttons if it is set, or something like that.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by DRW »

Gil-Galad wrote:Now I'm back to the beginning. Should I make a loop in the reset to run the controller and logic code?
I'm writing you a shortened version of my own code. This works without problems and the NMI only does stuff that needs to be done during vblank.

Code: Select all

Reset:
	; All the initialization stuff.
	; ...

@gameLogic:
	LDA WaitForNmi
	BNE @end
	JSR ControllerInput
	JSR GameLogic
	LDA #true
	STA WaitForNmi
@end:
	JMP @gameLogic

Nmi:
	PHA
	TXA
	PHA
	TYA
	PHA
	LDA WaitForNmi
	BEQ @end
@nmiStart:
	LDA #false
	STA WaitForNmi
	LDA PpuMaskValue
	STA PpuMask
	BEQ @end
	LDA #<Sprites
	STA OamAddr
	LDA #>Sprites
	STA OamDma
	JSR UpdatePpu
	JSR SetScrolling
@end:
	JSR SoundUpdate
	PLA
	TAY
	PLA
	TAX
	PLA
	RTI
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by DRW »

dougeff wrote:IF your game logic goes longer than 1 frame, the logic will miss a change in button presses event.
Why is this a bad thing? Of course, if your game lags, the controller won't be updated until the logic is done. But that's what it's supposed to do.

If I read the controller in every NMI interrupt call, even if my game logic isn't finished yet, then this means that half of the logic will do status updates based on another input than the other half:

- Read Controller: Player presses right.
- Start move function: Update vertical movement based on player pressing right.
- Continue move function: Update weapon attack based on player pressing right.
- Oops! Lag. NMI interrupts the program and reads the controller again: Player presses left.
- Continue move function: Update horizontal movement based on player pressing left.
--> Inconsistency! The value changed in the middle of a function that was never meant to handle a change of the value.

You want that?

This one is better in my opinion:

- Read Controller: Player presses right.
- Start move function: Update vertical movement based on player pressing right.
- Continue move function: Update weapon attack based on player pressing right.
- Oops! Lag. NMI interrupts the program, but doesn't read the controller again: Player presses left, but the value is still set to right.
- Continue move function: Update horizontal movement based on player pressing right.
--> No inconsistency at all. The game logic will recognize the new button press at the start of the next frame when the game logic starts again.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by Kasumi »

Why is this a bad thing? Of course, if your game lags, the controller won't be updated until the logic is done. But that's what it's supposed to do.
You're misunderstanding. It's not about the a change in button state during lag not being read. That is indeed expected and normal. It's about a change in button state during lag being read, and then having an undesired effect.

NMI happens at ~60 FPS while it's enabled regardless of anything. I am reading my controller in the NMI, and so by extension it's happening at ~60FPS regardless of anything.

My game loop takes too long.

Code: Select all

GAMELOOP STARTS
NMI OCCURS: I have pressed A. 
GAMELOOP CONTINUES
NMI OCCURS: I am still holding A. My "press" of A has become a hold.
GAMELOOP CHECKS FOR A PRESS OF A TO JUMP. But I am now holding A because the NMI hit twice during the game loop.
The jump fails.
This is the case people are saying he should look out for when reading from the NMI.

Yes, you can read the joypad but not update whatever controls presses vs holds in your game in the NMI, and yes, you can conditionally not read/update the joypad in the NMI if the game loop has taken too long. But you wouldn't need those extra conditions at all if you just read the joypad not in your NMI.
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by DRW »

Kasumi wrote:

Code: Select all

GAMELOOP STARTS
NMI OCCURS: I have pressed A. 
GAMELOOP CONTINUES
NMI OCCURS: I am still holding A. My "press" of A has become a hold.
GAMELOOP CHECKS FOR A PRESS OF A TO JUMP. But I am now holding A because the NMI hit twice during the game loop.
The jump fails.
This is the case people are saying he should look out for when reading from the NMI.
That's why I think you should never read the controller during NMI, but at the start of the game logic:

Code: Select all

CONTROLLER READING: I have pressed A.
GAMELOOP STARTS
NMI OCCURS: I am still holding A. But it isn't read. So, it doesn't become a hold.
GAMELOOP CONTINUES
NMI OCCURS: I am still holding A. But it isn't read. So, it doesn't become a hold.
GAMELOOP CHECKS FOR A PRESS OF A TO JUMP. Everything is fine. It's not a hold.
Kasumi wrote:But you wouldn't need those extra conditions at all if you just read the joypad not in your NMI.
Exactly. So, why is reading the controller in the NMI an issue at all?

Even if I absolutely have to read the controller in NMI for some reason, it would still be trivial to avoid this problem: You don't need to implement any special conditions.
Since you need to have a condition to leave the NMI early anyway:

Code: Select all

Nmi:
   PHA
   TXA
   PHA
   TYA
   PHA
   LDA WaitForNmi
   BEQ @end
@nmiStart:
   LDA #false
   STA WaitForNmi
   LDA PpuMaskValue
   STA PpuMask
   BEQ @end
   LDA #<Sprites
   STA OamAddr
   LDA #>Sprites
   STA OamDma
   JSR UpdatePpu
   JSR SetScrolling
@end:
   JSR SoundUpdate
   PLA
   TAY
   PLA
   TAX
   PLA
   RTI
you would just add this:

Code: Select all

Nmi:
   PHA
   TXA
   PHA
   TYA
   PHA
   LDA WaitForNmi
   BEQ @end
@nmiStart:
   LDA #false
   STA WaitForNmi
   LDA PpuMaskValue
   STA PpuMask
   BEQ @end
   JSR ReadController ; --> New code.
   LDA #<Sprites
   STA OamAddr
   LDA #>Sprites
   STA OamDma
   JSR UpdatePpu
   JSR SetScrolling
@end:
   JSR SoundUpdate
   PLA
   TAY
   PLA
   TAX
   PLA
   RTI
That's it. Controller reading in NMI without any issues and without any additional dedicated code to check for lag in the controller routine.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by Kasumi »

That's why I think you should never read the controller during NMI, but at the start of the game logic:
I'm not sure we're reading the same things. That's what everyone is saying!
Exactly. So, why is reading the controller in the NMI an issue at all?
For that reason I said? Also note that I said this too:
Yes, you can read the joypad but not update whatever controls presses vs holds in your game in the NMI, and yes, you can conditionally not read/update the joypad in the NMI if the game loop has taken too long. But you wouldn't need those extra conditions at all if you just read the joypad not in your NMI.
It's like... so we're warning him about a thing that can happen. And you're posting ways to fix that thing that can happen to say it's not a problem. And yeah, it's not a problem if you know about it. But... we warned him because he may not know about it, but also because there are better ways to do it. See the first thing I quoted from you in this post. Does that make sense?

Maybe you don't need an extra condition, but it depends on your setup. And again: It's more about, "Hey, know about this thing." It's not, "Never do this thing, because there's no way around it." It's, "Here's a better way so you don't have to even think about it."
Last edited by Kasumi on Sat Aug 27, 2016 9:21 am, edited 1 time in total.
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by dougeff »

DRW's example would work fine. That's basically what I do....

The first example that starts off like
I'm writing you a shortened...
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by thefox »

Has anybody done measurements on what is a typical minimum "pulse width" on button presses (or releases) that one can expect? That is, if a player quickly taps the button. Or, holds it down, quickly releases it and then holds it down again.

I guess I might throw together a small test ROM for it.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Post Reply