It is currently Wed May 24, 2017 4:25 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 22 posts ]  Go to page Previous  1, 2
Author Message
PostPosted: Wed Jan 18, 2017 5:21 pm 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9646
Location: Rio de Janeiro - Brazil
Pokun is right.


Top
 Profile  
 
PostPosted: Sun Jan 22, 2017 11:21 am 
Offline

Joined: Sat Jan 14, 2017 8:40 am
Posts: 23
tokumaru wrote:
Pokun is right.


Hi Tokumaru and Pokun! I completely re-wrote my PONG engine doing my best to apply your design ideas to my code. I think I've successfully seperated the game logic from the NMI and implemented it to run in the main loop. I also created a flag system for the NMI to make sure that only one MainLoop sequence is processed during NMI.

I wasn't quite sure how to do the transition state. In a sense, I think my transition state sort of happens automatically for me the way I built the mainloop and NMI. If I shut the screen off by writing #$00 to $2001 prior to loading a new background, the screen will not be turned on again until NMI fires due to my setup. Is this what you meant by Transition State?

Here is my code my main loop and my NMI. Would you be cool Toku or Pokun (or anyone else who might be interested) with checking this out to see if I'm on the right track concerning Transition States and Not processing logic in NMI? Also, I attached my code full code for this as well. The main file in the code is pong.asm and then everything else is an include. I read one of your posts Toku where you said the code reads easier this way so I went that direction. I cleaned it up a lot. Its much smaller and flows according to you guy's suggested layout. I really want to make sure I understand this before moving on, as I think the principles here are pretty important to make coding in the future much easier. Thanks again for all your help. I'm really enjoying learning this!

Main Loop
Code:
MainLoop: ;frameready is set to 0 (Cleared) to start with so that logic will be processed without NMI drawing to screen
 .include "read_controllers.asm"
 .include "game_engine.asm"

  LDA #$01        ;frameready is now set to 1. This will Allow NMI to Draw To Screen
  STA frameready

WaitForNMI:      ;Allow NMI to complete one pass before Starting Main Loop again. This ensure no game logic will happen during NMI while its writing.
  LDA frameready ;01
  CMP #READY     ;01
  BEQ WaitForNMI ;Since both frameready and READY are 01 this will loop back to WaitForNMI until NMI clears the frameready flag.

  JMP MainLoop     ;jump back to MainLoop, infinite loop


NMI
Code:
NMI:
  PHA         ; back up registers (important)
  TXA
  PHA
  TYA
  PHA

  LDA frameready ;first time through will be 0
  CMP #READY    ;01
  BNE NMIDone

;GameStatePlayingCheck: ;Check to make sure we are in gamestate "playing" to draw the score. Otherwise this will draw on the Title Screena and The Game Over Screen.
  LDA gamestate
  CMP #STATEPLAYING
  BNE SpriteDMA
 
;Draw Scores
  JSR DrawScore     ;Located in draw_scores.asm

SpriteDMA:
  LDA #$00
  STA $2003       ; set the low byte (00) of the RAM address
  LDA #$02
  STA $4014       ; set the high byte (02) of the RAM address, start the transfer

CleanUp:
  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1  -  ;;PPU clean up section, so rendering the next frame starts properly.
  STA $2000
  LDA #%00011110   ; enable sprites, enable background, no clipping on left side
  STA $2001
  LDA #$00        ;;tell the ppu there is no background scrolling
  STA $2005
  STA $2005       ;;;all graphics updates done by here, run game engine

ClearReadyFlag:   ;Clear flagready to be reinstated by MainLoop frame.
  LDA #$00
  STA frameready

NMIDone:

  PLA           ; restore regs and exit
  TAY
  PLA
  TAX
  PLA
  RTI


Attachments:
PongRewrite.zip [24.41 KiB]
Downloaded 10 times
Top
 Profile  
 
PostPosted: Sun Jan 22, 2017 11:41 am 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9646
Location: Rio de Janeiro - Brazil
hundonostudy wrote:
In a sense, I think my transition state sort of happens automatically for me the way I built the mainloop and NMI. If I shut the screen off by writing #$00 to $2001 prior to loading a new background, the screen will not be turned on again until NMI fires due to my setup. Is this what you meant by Transition State?

The purpose of a transition state was to prevent the NMI from touching the PPU when a transition was happening (and a new background was possibly being drawn). Think about it: you disable rendering and start to draw a new background, but NMIs are still enabled, so if one happens to fire now, it will change the scroll and corrupt the PPU address register, which will cause the rest of the background to be drawn to the wrong place.

The way you have it now, the "frameready" flag is enough to prevent the NMI from doing anything PPU related, but depending on the game there are cases when the frame isn't ready, but you still need to do PPU-related tasks. For example, if you have a status bar and a scrolling playfield, you absolutely have to set the scroll twice per frame even during lag frames, so the "frameready" flag will not be enough to make sure new backgrounds are rendered safely.

In my own projects I don't really have a "transition" state, what I have is a flag indicating whether rendering is on or off. If it's off, the NMI doesn't do anything PPU-related.


Top
 Profile  
 
PostPosted: Sun Jan 22, 2017 11:59 am 
Offline

Joined: Sat Jan 14, 2017 8:40 am
Posts: 23
tokumaru wrote:
hundonostudy wrote:
In a sense, I think my transition state sort of happens automatically for me the way I built the mainloop and NMI. If I shut the screen off by writing #$00 to $2001 prior to loading a new background, the screen will not be turned on again until NMI fires due to my setup. Is this what you meant by Transition State?

The purpose of a transition state was to prevent the NMI from touching the PPU when a transition was happening (and a new background was possibly being drawn). Think about it: you disable rendering and start to draw a new background, but NMIs are still enabled, so if one happens to fire now, it will change the scroll and corrupt the PPU address register, which will cause the rest of the background to be drawn to the wrong place.

The way you have it now, the "frameready" flag is enough to prevent the NMI from doing anything PPU related, but depending on the game there are cases when the frame isn't ready, but you still need to do PPU-related tasks. For example, if you have a status bar and a scrolling playfield, you absolutely have to set the scroll twice per frame even during lag frames, so the "frameready" flag will not be enough to make sure new backgrounds are rendered safely.

In my own projects I don't really have a "transition" state, what I have is a flag indicating whether rendering is on or off. If it's off, the NMI doesn't do anything PPU-related.


Aye, OK so I'm in the ballpark but not quite there? I tried building in a "transition state" into the game engine but didn't make it very far with that. I think I will try implementing this via a flag as you do. I take it what you do is turn rendering off (LDA #$00 STA $2001) at the begging of a piece of transitional code, then set a flag stating rendering is off and have the PPU check for that flag every NMI. Then at the end of the transitional code clear the rendering off flag? Does it need to be more complicated than that?


Top
 Profile  
 
PostPosted: Sun Jan 22, 2017 4:42 pm 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 579
Location: Sweden
That is how I also understood it.


It looks like you have some misunderstandings:
Quote:
I also created a flag system for the NMI to make sure that only one MainLoop sequence is processed during NMI.
...
Code:
WaitForNMI:      ;Allow NMI to complete one pass before Starting Main Loop again. This ensure no game logic will happen during NMI while its writing.

This wait isn't to prevent the main loop to happen during NMI, the main loop can't happen during an NMI anyway. The NMI interrupts the main loop, not the other way around (NMI - Non-Maskable Interrupt - an interrupt that can't be ignored and will always happen when it's setup to, if I understood it correctly), and that means the main loop is paused and then the NMI handler runs. When it reaches the RTI instruction the main loop resumes from wherever it was when the NMI happened.
This wait ensures that there's not more than one main loop on one NMI. However it doesn't prevent that there are two or more NMIs on one main loop if the main loop takes too long (NMI, when enabled, always happens every 60th (or 50th for PAL) of a second no matter what). But that's what the frameready flag is for. Your main loop is probably so short now that you won't have that problem in Pong though.

Quote:
SpriteDMA:
Code:
  LDA #$00
  STA $2003       ; set the low byte (00) of the RAM address
  LDA #$02
  STA $4014       ; set the high byte (02) of the RAM address, start the transfer

The code here is correct but the comments here are wrong. They should say something along:
"ensure OAM address $00 is selected as destination start address for sprite DMA" and
"set the RAM page to be used as source address and start sprite DMA" respectively.
We just discussed this missunderstanding in another thread. You are not to blame though, Nerdy Nights is teaching this incorrectly.


This won't really change anything in your game but, I'd put SpriteDMA before DrawScore. The general rules I follow for a good NMI handler is:
1) Sprite updates (SpriteDMA)
2) Nametable updates (DrawScore)
3) Palette updates (you don't have any)
4) PPU setting updates ($2000 and $2001)
5) Scroll updates (two writes to $2005)
6) Sound routine update
7) Any other update you want to happen every frame at 60 Hz (or 50 Hz for PAL) but doesn't need to be in vblank

Note: 1 to 5 are graphic updates and needs to finish before the vblank period is over so they can't take too much time. For 6 and 7 it doesn't matter if vblank is over.

Sprite DMA needs to happen early on PAL so it's standard practice to do it first. That's what comercial games generally do.
Nametable and palette updates can be done in any order but they should come right after sprite udpates because they still need to be done within the vblank period. You don't change palettes in your game, but it's good to remember in the future.
Scroll always needs to be done last among the graphic updates (due to the other PPU register writes messes up the scroll coordinates for some reason), but still within vblank. Since your game is single screen you write 0 to it every NMI so it doesn't move around.
Sound routine doesn't need to be in vblank, unlike the above graphic updates, so it's updated last in the NMI when there's a risk that vblank period is over (but the NMI isn't over yet). I guess you are not doing sound yet, but in the future...


Top
 Profile  
 
PostPosted: Sun Jan 22, 2017 5:38 pm 
Offline

Joined: Sat Jan 14, 2017 8:40 am
Posts: 23
Pokun wrote:
That is how I also understood it.


It looks like you have some misunderstandings:
Quote:
I also created a flag system for the NMI to make sure that only one MainLoop sequence is processed during NMI.
...
Code:
WaitForNMI:      ;Allow NMI to complete one pass before Starting Main Loop again. This ensure no game logic will happen during NMI while its writing.

This wait isn't to prevent the main loop to happen during NMI, the main loop can't happen during an NMI anyway. The NMI interrupts the main loop, not the other way around (NMI - Non-Maskable Interrupt - an interrupt that can't be ignored and will always happen when it's setup to, if I understood it correctly), and that means the main loop is paused and then the NMI handler runs. When it reaches the RTI instruction the main loop resumes from wherever it was when the NMI happened.
This wait ensures that there's not more than one main loop on one NMI. However it doesn't prevent that there are two or more NMIs on one main loop if the main loop takes too long (NMI, when enabled, always happens every 60th (or 50th for PAL) of a second no matter what). But that's what the frameready flag is for. Your main loop is probably so short now that you won't have that problem in Pong though.

Quote:
SpriteDMA:
Code:
  LDA #$00
  STA $2003       ; set the low byte (00) of the RAM address
  LDA #$02
  STA $4014       ; set the high byte (02) of the RAM address, start the transfer

The code here is correct but the comments here are wrong. They should say something along:
"ensure OAM address $00 is selected as destination start address for sprite DMA" and
"set the RAM page to be used as source address and start sprite DMA" respectively.
We just discussed this missunderstanding in another thread. You are not to blame though, Nerdy Nights is teaching this incorrectly.


This won't really change anything in your game but, I'd put SpriteDMA before DrawScore. The general rules I follow for a good NMI handler is:
1) Sprite updates (SpriteDMA)
2) Nametable updates (DrawScore)
3) Palette updates (you don't have any)
4) PPU setting updates ($2000 and $2001)
5) Scroll updates (two writes to $2005)
6) Sound routine update
7) Any other update you want to happen every frame at 60 Hz (or 50 Hz for PAL) but doesn't need to be in vblank

Note: 1 to 5 are graphic updates and needs to finish before the vblank period is over so they can't take too much time. For 6 and 7 it doesn't matter if vblank is over.

Sprite DMA needs to happen early on PAL so it's standard practice to do it first. That's what comercial games generally do.
Nametable and palette updates can be done in any order but they should come right after sprite udpates because they still need to be done within the vblank period. You don't change palettes in your game, but it's good to remember in the future.
Scroll always needs to be done last among the graphic updates (due to the other PPU register writes messes up the scroll coordinates for some reason), but still within vblank. Since your game is single screen you write 0 to it every NMI so it doesn't move around.
Sound routine doesn't need to be in vblank, unlike the above graphic updates, so it's updated last in the NMI when there's a risk that vblank period is over (but the NMI isn't over yet). I guess you are not doing sound yet, but in the future...


Thank You Pokun. I made some edits based on your comments! Fixed the comments and changed the drawing routine in DMA to move Scores after SpriteDMA. Also thank you for the explanation as to why to use the waitframe.

Thanks everyone for your help! I truly appreciate it! Having a blast learning to do this!


Top
 Profile  
 
PostPosted: Mon Jan 23, 2017 2:41 am 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 579
Location: Sweden
Good that it worked!

I actually also tried to add this render flag to my program, but it broke my screen drawing routines during screen transitions. If I'm skipping scroll when rendering flag is set, it breaks. But if I only skip sprite, nametable and palette updates it works fine. But Tokumaru said it's to prevent the scroll from messing with your drawing routine, which means I must skip all graphical updates including scroll.

I gotta check my screen drawing routine again.


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

All times are UTC - 7 hours


Who is online

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