Sprite wackyness
Moderator: Moderators
- battagline
- Posts: 152
- Joined: Wed Sep 05, 2018 11:13 am
- Location: Colorado
- Contact:
Sprite wackyness
So I've been messing around working on an Asteroids game, and I'm getting some sprite wackyness when I fire more than a single bullet at a time. I'm stepping through my NMI code and looking at the OAM data and everything looks like it's ok to me. I'm guessing I'm doing something stupid/simple that I'm just not aware of.
If anyone is willing to take a quick look I'd appreciate it. I've attached the .nes and the Messen debug .dbg file (which I had to change to a .txt to get it to upload).
Thanks
If anyone is willing to take a quick look I'd appreciate it. I've attached the .nes and the Messen debug .dbg file (which I had to change to a .txt to get it to upload).
Thanks
- Attachments
-
- nesteroids.nes.dbg.txt
- Nesteroids Label File (change to .dbg)
- (118.05 KiB) Downloaded 153 times
-
- nesteroids.nes
- Failing Nesteroids
- (40.02 KiB) Downloaded 167 times
A few of my web games
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
Re: Sprite wackyness
When I ran it in my emulator, I got a totally blank screen, likely because your Sprite DMA is being done too close to the end of VBlank (it starts at the beginning of scanline 259 and then ends in the middle of scanline 1) - when I switched to PAL mode, everything worked great (likely because I'm not emulating the extra OAM refreshing that the PAL PPU does to prevent the memory from decaying).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
P.S. If you don't get this note, let me know and I'll write you another.
- battagline
- Posts: 152
- Joined: Wed Sep 05, 2018 11:13 am
- Location: Colorado
- Contact:
Re: Sprite wackyness
Part of my confusion in this new world of NES Dev, is I don't fully understand what I should be doing in the NMI and what I should be doing in the main game loop.Quietust wrote:When I ran it in my emulator, I got a totally blank screen, likely because your Sprite DMA is being done too close to the end of VBlank (it starts at the beginning of scanline 259 and then ends in the middle of scanline 1) - when I switched to PAL mode, everything worked great (likely because I'm not emulating the extra OAM refreshing that the PAL PPU does to prevent the memory from decaying).
I'm also not clear on how much I can do in the NMI vs. the main game loop
Any suggestions?
Thanks
A few of my web games
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
Re: Sprite wackyness
In vblank, you have 2270 cycles. To make the best use of them, you'll need to buffer the changes to sprites and backgrounds and push them to the PPU before you start calculating the changes for the next frame. There are a few video memory buffer libraries floating around; I wrote one called Popslide.
As for how NMI relates to vblank, there are 3 different ways to divide the tasks associated with every frame: everything in NMI, everything in main, or calculate updates in main and apply them in NMI. Each approach has its pros and cons.
Everything in main
NMI:
1. Increment a variable
Main:
1. Read the controller
2. Move the game objects
3. Calculate new sprite display list and background and palette updates
4. Wait for NMI handler to increment the variable
5. If a new sprite display list is ready, push it to OAM
6. If background and palette updates are ready, push them to video memory
7. Run music as many times as the variable was incremented
Everything in NMI
NMI:
1. If a new sprite display list is ready, push it to OAM
2. If background and palette updates are ready, push them to video memory
3. Run music
4. If in a lag frame, return
5. Read the controller
6. Move the game objects
7. Calculate new sprite display list and background and palette updates
Main:
1. Goto 1
Split responsibility
NMI:
1. If a new sprite display list is ready, push it to OAM
2. If background and palette updates are ready, push them to video memory
3. Run music (bank switching for this can prove tricky)
4. Increment a variable
Main:
1. Read the controller
2. Move the game objects
3. Calculate new sprite display list and background and palette updates
4. Wait for NMI handler to increment the variable
See also tokumaru's explanation
As for how NMI relates to vblank, there are 3 different ways to divide the tasks associated with every frame: everything in NMI, everything in main, or calculate updates in main and apply them in NMI. Each approach has its pros and cons.
Everything in main
NMI:
1. Increment a variable
Main:
1. Read the controller
2. Move the game objects
3. Calculate new sprite display list and background and palette updates
4. Wait for NMI handler to increment the variable
5. If a new sprite display list is ready, push it to OAM
6. If background and palette updates are ready, push them to video memory
7. Run music as many times as the variable was incremented
Everything in NMI
NMI:
1. If a new sprite display list is ready, push it to OAM
2. If background and palette updates are ready, push them to video memory
3. Run music
4. If in a lag frame, return
5. Read the controller
6. Move the game objects
7. Calculate new sprite display list and background and palette updates
Main:
1. Goto 1
Split responsibility
NMI:
1. If a new sprite display list is ready, push it to OAM
2. If background and palette updates are ready, push them to video memory
3. Run music (bank switching for this can prove tricky)
4. Increment a variable
Main:
1. Read the controller
2. Move the game objects
3. Calculate new sprite display list and background and palette updates
4. Wait for NMI handler to increment the variable
See also tokumaru's explanation
Re: Sprite wackyness
Even thought I've been ninja'd by tepples (who even linked to a previous explanation I did on this subject, which I totally forgot BTW), I'm still gonna post my reply because I took the time to write it:
The main thing to keep in mind is that PPU updates must take place during vblank, and never spill into the visible frame, otherwise Bad Things (tm) will happen. As for *how* exactly you can make it so this rule is respected is up to you. There are 3 main possible structures for an NES program:
1- Everything in the main loop: Do your game logic first (buffering all PPU updates along the way), and wait for vblank by polling a flag modified by the NMI handler (all the NMI does is change this flag). When the flag changes, you know you're in vblank and you have about 2273 CPU cycles to carry out the buffered PPU updates. Then you can do sound. Repeat. This is a very simple structure, but has poor handling of lag frames (music will slow down, raster effects will break, etc.).
2- Everything in the NMI handler: This is more versatile, as long as you don't disable NMIs while doing game logic. As soon as the NMI fires, you're in vblank, so you have to carry out any buffered PPU updates. Normally there'll be one or more flags indicating what updates must be performed. Then comes the audio, and finally the game logic, during which PPU updates for the next frame will be buffered. The advantage this method has over the last one is that if an NMI fires before the previous frame's game logic is done, you can still handle the things that must keep working at 60Hz even when the gameplay lags, such as music and raster effects (status bar, parallax layers, etc.).
3- Game logic in the main loop, PPU updates in the NMI handler: this works much like the last method, only with better separation between the game logic and the PPU update code. This structure makes it easier to have multiple game logic loops scattered around your ROM, but they're all interrupted by the same NMI handler when vblank starts. This works very well if your PPU update system is generic enough to work with all game modules, but if you really need it, you can have different sets of PPU update logic. The same system of flags is used here, so the PPU code knows which operations can and can't be done.
The main thing to keep in mind is that PPU updates must take place during vblank, and never spill into the visible frame, otherwise Bad Things (tm) will happen. As for *how* exactly you can make it so this rule is respected is up to you. There are 3 main possible structures for an NES program:
1- Everything in the main loop: Do your game logic first (buffering all PPU updates along the way), and wait for vblank by polling a flag modified by the NMI handler (all the NMI does is change this flag). When the flag changes, you know you're in vblank and you have about 2273 CPU cycles to carry out the buffered PPU updates. Then you can do sound. Repeat. This is a very simple structure, but has poor handling of lag frames (music will slow down, raster effects will break, etc.).
2- Everything in the NMI handler: This is more versatile, as long as you don't disable NMIs while doing game logic. As soon as the NMI fires, you're in vblank, so you have to carry out any buffered PPU updates. Normally there'll be one or more flags indicating what updates must be performed. Then comes the audio, and finally the game logic, during which PPU updates for the next frame will be buffered. The advantage this method has over the last one is that if an NMI fires before the previous frame's game logic is done, you can still handle the things that must keep working at 60Hz even when the gameplay lags, such as music and raster effects (status bar, parallax layers, etc.).
3- Game logic in the main loop, PPU updates in the NMI handler: this works much like the last method, only with better separation between the game logic and the PPU update code. This structure makes it easier to have multiple game logic loops scattered around your ROM, but they're all interrupted by the same NMI handler when vblank starts. This works very well if your PPU update system is generic enough to work with all game modules, but if you really need it, you can have different sets of PPU update logic. The same system of flags is used here, so the PPU code knows which operations can and can't be done.
Re: Sprite wackyness
And in case you feel overwhelmed by the 3 options and pros/cons, keep in mind that if you're doing a small simple project for the sake of learning the NES, most likely any of these 3 methods will be JUST FINE.
This is the important part. As long as all PPU updates happen during vblank, you'll generally be ok for a simple project.The main thing to keep in mind is that PPU updates must take place during vblank, and never spill into the visible frame, otherwise Bad Things (tm) will happen. As for *how* exactly you can make it so this rule is respected is up to you.
My games: http://www.bitethechili.com
- battagline
- Posts: 152
- Joined: Wed Sep 05, 2018 11:13 am
- Location: Colorado
- Contact:
Re: Sprite wackyness
Thanks for the explanation. Does it make sense to double buffer the oam data? Or is there a more efficient way to do all my sprite calculations in the game loop so I can just move the data over in the nmi code?
Thanks
Thanks
A few of my web games
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
Re: Sprite wackyness
Just NEVER do what Nerdy Nights does in the first few lessons (I think), which's everything in the NMI with the game logic first. That just wastes your precious vblank time with things that don't need it.gauauu wrote:keep in mind that if you're doing a small simple project for the sake of learning the NES, most likely any of these 3 methods will be JUST FINE.
- battagline
- Posts: 152
- Joined: Wed Sep 05, 2018 11:13 am
- Location: Colorado
- Contact:
Re: Sprite wackyness
@clearvus set me straight. I didn't realize that the OAM data was being copied during a DMA and not somehow shared with the PPU. So double buffering makes no sense.
Thanks everyone for setting me straight on this stuff... hopefully I get this part now.
Thanks everyone for setting me straight on this stuff... hopefully I get this part now.
A few of my web games
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
https://www.embed.com
Or if you're bored at work
https://www.classicsolitaire.com
Re: Sprite wackyness
Yeah, the memory page where you write OAM data to is already a buffer, the actual OAM inside the PPU remains unchanged until you do a sprite DMA. Actually, it will decay if PPU rendering is off for too long, but under normal circumstances, just skipping a DMA if the buffer isn't ready will cause the previous frame's sprites to be displayed again.