Migrating code from NMI & handling gameloop code timing.
Moderator: Moderators
Migrating code from NMI & handling gameloop code timing.
Hi there fellow NES Development Newbies!
I'm Rychan and I've started to get into NES development recently after having spent a year or two coding up Gameboy games in GBDK and C. There's a link to a few of them here if you're interested BTW: http://gamejolt.com/@refreshgames/games
I've never played with assembly until about a month ago and have finally plucked up the cpourage to take on the beast that is NESASM.
So far I've been able to get the basics of a game working well inside the FCEUX NES Emulator. Wrote some collision detection, faux randomness, played with sprites and moving from the title screen to the main screen and you can probably tell where this is going
Before we get there though, here's a link to an animation of the main game screen in it's current state via emulation:
https://twitter.com/refreshgamesuk/stat ... 6661805057
So, I purchased a flash cart and with the help of a friend got my old NES up and running with the thing. Which is when I noticed a few fundamental flaws with my code haha! Yes joypad button detection and also running out of vblank time by putting EVERYTHING into NMI, like a noob which is where I am with ASM I guess
I've updated my joypad handling code which now works on device and understand that I now need to separate my game logic out into the main game loop and minimise the amount of stuff in NMI but I'm a little unsure how to both perform large background loads safely and how to inform my code that it needs to wait until vBlank is done for safe looping.
I mean, is it possible to OAM a whole background over in a single vblank amount of time? I don't know the cycle count for this but I imagine it's a lot less flexible then the gameboy
Any advice would be really handy, I'm still sorta dreading moving my code from NMI into the main game loop but understand that it needs to be done in order to persue my dreams of owning a NES cart which I made, 8 year old me would be so happy also
I'm Rychan and I've started to get into NES development recently after having spent a year or two coding up Gameboy games in GBDK and C. There's a link to a few of them here if you're interested BTW: http://gamejolt.com/@refreshgames/games
I've never played with assembly until about a month ago and have finally plucked up the cpourage to take on the beast that is NESASM.
So far I've been able to get the basics of a game working well inside the FCEUX NES Emulator. Wrote some collision detection, faux randomness, played with sprites and moving from the title screen to the main screen and you can probably tell where this is going
Before we get there though, here's a link to an animation of the main game screen in it's current state via emulation:
https://twitter.com/refreshgamesuk/stat ... 6661805057
So, I purchased a flash cart and with the help of a friend got my old NES up and running with the thing. Which is when I noticed a few fundamental flaws with my code haha! Yes joypad button detection and also running out of vblank time by putting EVERYTHING into NMI, like a noob which is where I am with ASM I guess
I've updated my joypad handling code which now works on device and understand that I now need to separate my game logic out into the main game loop and minimise the amount of stuff in NMI but I'm a little unsure how to both perform large background loads safely and how to inform my code that it needs to wait until vBlank is done for safe looping.
I mean, is it possible to OAM a whole background over in a single vblank amount of time? I don't know the cycle count for this but I imagine it's a lot less flexible then the gameboy
Any advice would be really handy, I'm still sorta dreading moving my code from NMI into the main game loop but understand that it needs to be done in order to persue my dreams of owning a NES cart which I made, 8 year old me would be so happy also
Probably The "Most Filthy Casual" NES ASM Developer https://refreshgames.co.uk/
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: Migrating code from NMI & handling gameloop code timing.
OAM is Object Attribute Memory, I think, and I also believe that deals with sprites. OAM a whole background? For me, writing nametables 00 and 01 requires disabling rendering and it takes multiple frames, so I doubt it's possible to draw an entire background in one vblank (NMI).rychan wrote:I mean, is it possible to OAM a whole background over in a single vblank amount of time? I don't know the cycle count for this but I imagine it's a lot less flexible then the gameboy
Any advice would be really handy, I'm still sorta dreading moving my code from NMI into the main game loop but understand that it needs to be done in order to persue my dreams of owning a NES cart which I made, 8 year old me would be so happy also
You are 8 years old and interested in the NES? That's pretty fantastic!
Re: Migrating code from NMI & handling gameloop code timing.
Eh heh, nope, I'm way over 8 years old unregistered (more like 35 currently, although my son, who happens to be 8 years old loves the NES now!) But I remember my 8 year old self wishing to make a game that runs on the NES
I take it that's why my game crashes to black on actual hardware then? when I'm disabling rendering then attempting to restart it later on, I'm still inside NMI, perhaps it just isn't making it to the point (due to NMI being re-run)w here it's requesting it to be turned back on then! That would make sense now I think about it.
I take it that's why my game crashes to black on actual hardware then? when I'm disabling rendering then attempting to restart it later on, I'm still inside NMI, perhaps it just isn't making it to the point (due to NMI being re-run)w here it's requesting it to be turned back on then! That would make sense now I think about it.
Probably The "Most Filthy Casual" NES ASM Developer https://refreshgames.co.uk/
Re: Migrating code from NMI & handling gameloop code timing.
Short reply because I can't be here long: No, you can't update a full background in a single vblank. The fastest you can copy a byte (from ROM or normal RAM) is 8 cycles (LDA + STA), and a full background is 1024 bytes, meaning you'd need at least 8192 cycles (not considering the overhead of loops or the NMI handler) but vblank only lasts ~2273 cycles. In the best case, you're looking at 4 frames for a full background.
Re: Migrating code from NMI & handling gameloop code timing.
Is the structure of your program copied from the Nerdy Nights tutorial? The problem there isn't exactly that everything is in the NMI handler (which although not optimal, is a valid program structure - SMB for example uses it), but the fact that the game logic is placed before the PPU updates in said NMI handler. Simply inverting this order should allow you to make full use of a frame's time, with PPU updates using the first 20 scanlines (vblank) and the game logic using the remaining 240 scanlines.
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: Migrating code from NMI & handling gameloop code timing.
Ah ok, I'm about the same age as you. Sorry for misunderstanding.rychan wrote:Eh heh, nope, I'm way over 8 years old unregistered (more like 35 currently, although my son, who happens to be 8 years old loves the NES now!) But I remember my 8 year old self wishing to make a game that runs on the NES
I take it that's why my game crashes to black on actual hardware then? when I'm disabling rendering then attempting to restart it later on, I'm still inside NMI, perhaps it just isn't making it to the point (due to NMI being re-run)w here it's requesting it to be turned back on then! That would make sense now I think about it.
Yes, that sounds about right since tokumaru said it takes ~ 2273 cycles for a vblank; it's not at all.
Re: Migrating code from NMI & handling gameloop code timing.
Thanks for the tips everyone. Going to look into re-writing my code into the main loop tomorrow night now. I'm assuming that so long as my code per frame doesn't go over the start of vblank if can BIT test $2002 at the end of the main loop to prevent execution from looping too many times.
I'm thinking I need to pre-prepare the screen to be off then use frames to load in the new data over 4-5 frames worth of time before re-enabling it again, should be fun!
Will go through the rewrites tomorrow anyhoo, thanks once again, I'll post my results when I have them!
I'm thinking I need to pre-prepare the screen to be off then use frames to load in the new data over 4-5 frames worth of time before re-enabling it again, should be fun!
Will go through the rewrites tomorrow anyhoo, thanks once again, I'll post my results when I have them!
Probably The "Most Filthy Casual" NES ASM Developer https://refreshgames.co.uk/
Re: Migrating code from NMI & handling gameloop code timing.
Not really. Reading $2002 at the exact time the vblank flag gets set cancels the NMI (and the flag returns clear). But even if this quirk didn't exist, the vblank flag is cleared when vblank ends, so if your NMI handler used all of the vblank time you'd miss the flag anyway. You have to implement a flag in software to properly detect vblanks.rychan wrote:I'm assuming that so long as my code per frame doesn't go over the start of vblank if can BIT test $2002 at the end of the main loop to prevent execution from looping too many times.
Most games do indeed turn rendering off between levels or any screens that require large video updates, to gain unrestricted access to VRAM. If a blank screen isn't a problem, that's way easier than spreading large updates across several vblanks.I'm thinking I need to pre-prepare the screen to be off then use frames to load in the new data over 4-5 frames worth of time before re-enabling it again, should be fun!
- rainwarrior
- Posts: 8731
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: Migrating code from NMI & handling gameloop code timing.
As tokumaru said, polling $2002 is unreliable for a vblank wait; even in the best conditions it misses a frame occasionally and you will wait a whole extra frame.
If you want all your code outside the NMI, the simplest way to poll for the next vblank is something like this:
In this case you have an NMI handler that does nothing except change a value (nmi), and when you want to wait for it you can just spin testing that value until it changes (wait_for_nmi). Immediately following the wait test you'll be close to the start of vblank and can freely use the PPU for a short time.
Edit: fixed a mistake spotted by tepples.
If you want all your code outside the NMI, the simplest way to poll for the next vblank is something like this:
Code: Select all
nmi:
inc nmi_counter
rti
wait_for_nmi:
lda nmi_counter
@loop:
cmp nmi_counter
beq @loop
rts
; ...
main_loop:
; ...
jsr wait_for_nmi
; ... do all PPU updates right here, immediately after the wait
; ...
jmp main_loop
Edit: fixed a mistake spotted by tepples.
Last edited by rainwarrior on Wed Jun 21, 2017 5:50 pm, edited 1 time in total.
Re: Migrating code from NMI & handling gameloop code timing.
Shouldn't that be beq @loop? You want to keep waiting while the frame count stays the same.rainwarrior wrote:Code: Select all
nmi: inc nmi_counter rti wait_for_nmi: lda nmi_counter @loop: cmp nmi_counter bne @loop rts
Re: Migrating code from NMI & handling gameloop code timing.
The simplicity of this approach is great, but I want to take this opportunity to say that in practice, this offers no advantage over the all-in-NMI method, since it doesn't provide any means to deal with lag frames. If the game logic takes too long, the music will slow down, and raster effects (e.g. status bars, parallax effects) on the next frame might glitch. If lag frames aren't an issue, this is probably the simplest structure you can use, and also versatile in the sense that you can easily have different vblank handlers for different parts of the program, or you can simply JSR to a common one if that works best for you.rainwarrior wrote:Code: Select all
nmi: inc nmi_counter rti wait_for_nmi: lda nmi_counter @loop: cmp nmi_counter beq @loop rts ; ... main_loop: ; ... jsr wait_for_nmi ; ... do all PPU updates right here, immediately after the wait ; ... jmp main_loop
The all-in-NMI method, when done the correct way (i.e. PPU updates first, game logic second - not how Nerdy Nights presents it), at least offers the possibility of doing something about lag frames, since the NMI will interrupt the game logic, at which point you can choose to handle some high priority tasks such as music and raster effects before the new frame begins, even if the logic for the previous frame hasn't finished yet.
Re: Migrating code from NMI & handling gameloop code timing.
Just in case you're not aware, you can program for the nes in C just fine. For simpler games you don't need to touch asm at all, for more complex ones you only need to write performance-sensitive parts in it.
Re: Migrating code from NMI & handling gameloop code timing.
Oh, there's a C compiler out there? I honestly wasn't aware of that. I might fall back to that should I need to, but I feel that, having only ever used high level languages, it's one of those things which after many years of coding I should at least have a go at making something in. Even though, yup, simplistic game using mapper 0, it's one of those challenges I want to beat
Does the @ prefix change anything about a label? Or is it one of those good practises to help organise code better.
Got a couple of hours now to play around with things in code again, yay!
Does the @ prefix change anything about a label? Or is it one of those good practises to help organise code better.
Got a couple of hours now to play around with things in code again, yay!
Probably The "Most Filthy Casual" NES ASM Developer https://refreshgames.co.uk/
Re: Migrating code from NMI & handling gameloop code timing.
It depends on the assembler, but that normally means the label is local, so you can reuse names if there's a global label between them:rychan wrote:Does the @ prefix change anything about a label?
Code: Select all
@LocalLabel:
jmp @LocalLabel
GlobalLabel:
@LocalLabel:
jmp @LocalLabel
Re: Migrating code from NMI & handling gameloop code timing.
Cool, will investigate into using those later on, I'll stick to global labels for the moment to keep things simpler for me
Also, this happened! Yay!
https://youtu.be/gc1q4Ph1-x8
and there's even an hour left for me this morning to re-enable those missing features, so psyched!
Also, this happened! Yay!
https://youtu.be/gc1q4Ph1-x8
and there's even an hour left for me this morning to re-enable those missing features, so psyched!
Probably The "Most Filthy Casual" NES ASM Developer https://refreshgames.co.uk/