It is currently Tue Apr 25, 2017 5:20 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Mon Jan 16, 2017 3:50 pm 
Offline

Joined: Sat Jan 14, 2017 8:40 am
Posts: 23
Hi All,

So in my pong game, I'm trying to learn how to transition from one background screen to another. I'm able to load a title screen, and switch from the title screen to my playfield screen without issues. The code I use to do that is here.

Code:
 LDA #%00000000 ;Turn Screen Off
  STA $2001

LoadPlayScreen:         ; load playfield screen
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA #$20
  STA $2006             ; write the high byte of $2000 address
  LDA #$00
  STA $2006             ; write the low byte of $2000 address

  LDA #$00
  STA pointerLo       ; put the low byte of the address of background into pointer
  LDA #HIGH(playfield)
  STA pointerHi       ; put the high byte of the address into pointer

  LDX #$00            ; start at pointer + 0
  LDY #$00
PlayFieldOutsideLoop:

PlayFieldInsideLoop:
  LDA [pointerLo], y  ; copy one background byte from address in pointer plus Y
  STA $2007           ; this runs 256 * 4 times

  INY                 ; inside loop counter
  CPY #$00
  BNE PlayFieldInsideLoop      ; run the inside loop 256 times before continuing down

  INC pointerHi       ; low byte went 0 to 256, so high byte needs to be changed now

  INX
  CPX #$04
  BNE PlayFieldOutsideLoop     ; run the outside loop 256 times before continuing down




But when I use basically the same code to switch from playfield screen to the gameover screen (after a winning score has been achieved) the code doesn't function properly. The screen goes blank and stays blank. Now I expect it to go blank because I turn the screen off to allow time to draw the background,
But the screen doesn't come back on. My NMI handler should turn it back on because it has a "clean up" piece of code that re-enables everything.

Here is the code I use to draw the game over screen. Again basically the same code I use to draw the playfield after I switch from title. Allthough the screen stays blank, if I check FCEUX's Nametable viewer, it shows that screen has indeed drawn

Code:
LDA #%00000000 ;Turn Screen Off
  STA $2001

LoadGameOverScreen:     ;Load Game Over Screen
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA #$20
  STA $2006             ; write the high byte of $2000 address
  LDA #$00
  STA $2006             ; write the low byte of $2000 address

  LDA #$00
  STA pointerLo       ; put the low byte of the address of background into pointer
  LDA #HIGH(gameover)
  STA pointerHi       ; put the high byte of the address into pointer

  LDX #$00            ; start at pointer + 0
  LDY #$00
GameOverOutsideLoop:
 
GameOverInsideLoop:
  LDA [pointerLo], y  ; copy one background byte from address in pointer plus Y
  STA $2007           ; this runs 256 * 4 times
 
  INY                 ; inside loop counter
  CPY #$00
  BNE GameOverInsideLoop      ; run the inside loop 256 times before continuing down

  INC pointerHi       ; low byte went 0 to 256, so high byte needs to be changed now
 
  INX
  CPX #$04
  BNE GameOverOutsideLoop     ; run the outside loop 256 times before continuing down


And here is my NMI code

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

  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

Score:
  LDA gamestate
  CMP #STATETITLE
  BEQ CleanUp
  JSR DrawScore

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

  JSR ReadController1  ;;get the current button data for player 1
  JSR ReadController2  ;;get the current button data for player 2

GameEngine:

StateTitle:
  LDA gamestate
  CMP #STATETITLE
  BNE StateGameOver    ;;game is displaying title screen
  JMP EngineTitle

StateGameOver:
  LDA gamestate
  CMP #STATEGAMEOVER
  BNE StatePlaying  ;;game is displaying ending screen
  JMP EngineGameOver

StatePlaying:
  LDA gamestate
  CMP #STATEPLAYING
  BNE GameEngineDone
  JMP EnginePlaying   ;;game is playing
GameEngineDone:

  JSR UpdateSprites  ;;set ball/paddle sprites from positions

  PLA           ; restore regs and exit
  TAY
  PLA
  TAX
  PLA
  RTI


Any thoughts on this would be greatly appreciated here. I'm really banging my head on this. Seeing as how switching screens is pretty important I really want to understand the concept well. Also, I have attached the full code of my PONG game to this post as well if anyone would like to see it to help me determine my problem. There is also a screen shot in there of the nametable viewer in FCEUX showing the GameOver screen.

Thank You So Much

Hundo


Attachments:
PongCodeRev111617Hundo.zip [225.43 KiB]
Downloaded 10 times
Top
 Profile  
 
PostPosted: Mon Jan 16, 2017 4:00 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9529
Location: Rio de Janeiro - Brazil
Have you used FCEUX's debugger to trace the code? If something isn't behaving as expected, the best thing to do is set a breakpoint and step through the code to see what's actually happening.

I normally set breakpoints on writes to $ff (a memory position that's hardly ever used in my programs), so I can just put an sta $ff right before the thing I want to debug.


Top
 Profile  
 
PostPosted: Mon Jan 16, 2017 4:01 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 3192
Location: Mountain View, CA, USA
Gut feeling says this is being caused by $2000/2002/2005/2006 manipulation in such an order that certain internal VRAM addresses are pointing to a location different than you expect, especially if you're seeing the screen in FCEUX's nametable viewer but it isn't visibly rendering on-screen. This is a very common problem for homebrewers and complex to understand, which is why it's one of the largest pages on the wiki (do not let the name of the wiki page make you think it specifically has to do with "screen panning" -- it's more complicated than that). I'm sorry to say you'll need to read it (not skim it), although someone on the forum may be a able to write a more terse version for you: https://wiki.nesdev.com/w/index.php/PPU_scrolling

Edit: add $2000 too.


Last edited by koitsu on Mon Jan 16, 2017 4:20 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Mon Jan 16, 2017 4:14 pm 
Offline

Joined: Sat Jan 14, 2017 8:40 am
Posts: 23
tokumaru wrote:
Have you used FCEUX's debugger to trace the code? If something isn't behaving as expected, the best thing to do is set a breakpoint and step through the code to see what's actually happening.

I normally set breakpoints on writes to $ff (a memory position that's hardly ever used in my programs), so I can just put an sta $ff right before the thing I want to debug.


I do use the debuger, however I've never thought to use $ff to set a marker to make the debugger easier to use. Thank You! That is a super helpful tip.
I stepped through the code with the debugger and it looks to be behaving as it should be. So I'm still confused.


Top
 Profile  
 
PostPosted: Mon Jan 16, 2017 4:23 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9529
Location: Rio de Janeiro - Brazil
I think I found the problem. You have 3 states, but they don't behave the same way. The title screen state works because it waits for the time to change the background, and when it does change the background it switches to the gameplay state, meaning the background is drawn only once. But the game over state is different, it immediately draws a new background, and doesn't change states. Since your NMI handler calls the current state every frame, the game over state is redrawing the background over and over again. The NMI tries to enable rendering, but when this state is called it turns rendering off again and draws the background again... that's why the screen is blank.


Top
 Profile  
 
PostPosted: Mon Jan 16, 2017 4:39 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9529
Location: Rio de Janeiro - Brazil
You should try to format all game states in a similar way, because currently they're kinda all over the place and don't follow a set pattern. The title screen is drawn during reset, and its handler draws the gameplay background. The gameplay handler has the game logic but doesn't draw any backgrounds, and the game over handler only draws its background over and over.

I suggest you have a reset routine for each state, that draws its background sets the necessary variables, and then sets the variable that indicates the active state. Another important thing to do is disable NMIs (or have them don't do anything, which's better) during state transitions, otherwise the NMI handler might interrupt the transitions and screw everything up.

the different states could follow a standard like this:
Code:
ResetTitle:
   ;draw background
   ;set state to 'TITLE'
UpdateTitle:
   ;wait for start to jump to ResetGameplay
   jsr GameEngineDone

ResetGameplay:
   ;draw background
   ;set state to 'GAMEPLAY'
UpdateGameplay:
   ;run game logic
   ;jump to ResetGameOver on death
   jsr GameEngineDone

ResetGameOver:
   ;draw background
   ;set state to 'GAMEOVER'
UpdateGameOver:
   ;wait for timer to expire before jumping to ResetTitle
   jsr GameEngineDone

To prevent the NMI from doing anything that could ruin the loading process of the states, you could maybe have a 'TRANSITIONING' state, that when active cause the NMI handler to not do a sprite DMA or touch any PPU registers. Playing audio is OK.


Top
 Profile  
 
PostPosted: Mon Jan 16, 2017 5:56 pm 
Offline

Joined: Sat Jan 14, 2017 8:40 am
Posts: 23
tokumaru wrote:
I think I found the problem. You have 3 states, but they don't behave the same way. The title screen state works because it waits for the time to change the background, and when it does change the background it switches to the gameplay state, meaning the background is drawn only once. But the game over state is different, it immediately draws a new background, and doesn't change states. Since your NMI handler calls the current state every frame, the game over state is redrawing the background over and over again. The NMI tries to enable rendering, but when this state is called it turns rendering off again and draws the background again... that's why the screen is blank.


Thank you Tokumaru! That was the problem. Not sure why I missed it. I think my brain fried out a bit after staring at the code for 2 hours. With your suggestion I fixed the problem very quickly!


Top
 Profile  
 
PostPosted: Mon Jan 16, 2017 6:00 pm 
Offline

Joined: Sat Jan 14, 2017 8:40 am
Posts: 23
tokumaru wrote:
You should try to format all game states in a similar way, because currently they're kinda all over the place and don't follow a set pattern. The title screen is drawn during reset, and its handler draws the gameplay background. The gameplay handler has the game logic but doesn't draw any backgrounds, and the game over handler only draws its background over and over.

I suggest you have a reset routine for each state, that draws its background sets the necessary variables, and then sets the variable that indicates the active state. Another important thing to do is disable NMIs (or have them don't do anything, which's better) during state transitions, otherwise the NMI handler might interrupt the transitions and screw everything up.

the different states could follow a standard like this:
Code:
ResetTitle:
   ;draw background
   ;set state to 'TITLE'
UpdateTitle:
   ;wait for start to jump to ResetGameplay
   jsr GameEngineDone

ResetGameplay:
   ;draw background
   ;set state to 'GAMEPLAY'
UpdateGameplay:
   ;run game logic
   ;jump to ResetGameOver on death
   jsr GameEngineDone

ResetGameOver:
   ;draw background
   ;set state to 'GAMEOVER'
UpdateGameOver:
   ;wait for timer to expire before jumping to ResetTitle
   jsr GameEngineDone

To prevent the NMI from doing anything that could ruin the loading process of the states, you could maybe have a 'TRANSITIONING' state, that when active cause the NMI handler to not do a sprite DMA or touch any PPU registers. Playing audio is OK.


I am totally going to rewrite my code for this little pong game. As the general pieces of it started to come together I realized it was something of a hacked together mess. I was also right on the verge of having it work, so I figured I may as well press on despite it being a shit show. Now that its done and all the major pieces are working I'm going to re-write it following the guidelines you have suggested. I'm sure it will be a good learning exercise. Also after browsing through the literature here, I've come to the conclusion that handling the logic in the NMI as the original tutuorial suggests is a bad thing, so I'm going to try to change that as well. I'll do my best, but I'm sure you guys will be hearing from me : ) Thanks again for the help!

Hundo


Top
 Profile  
 
PostPosted: Mon Jan 16, 2017 6:03 pm 
Offline

Joined: Sat Jan 14, 2017 8:40 am
Posts: 23
koitsu wrote:
Gut feeling says this is being caused by $2000/2002/2005/2006 manipulation in such an order that certain internal VRAM addresses are pointing to a location different than you expect, especially if you're seeing the screen in FCEUX's nametable viewer but it isn't visibly rendering on-screen. This is a very common problem for homebrewers and complex to understand, which is why it's one of the largest pages on the wiki (do not let the name of the wiki page make you think it specifically has to do with "screen panning" -- it's more complicated than that). I'm sorry to say you'll need to read it (not skim it), although someone on the forum may be a able to write a more terse version for you: https://wiki.nesdev.com/w/index.php/PPU_scrolling

Edit: add $2000 too.


Thank you Koitsu for the suggestion. I will be sure to read it. Currently in school trying to become a Doctor, so I'll fit this paper in between Neuroanatomy and Biochemistry. Lol this classic NES stuff is WAY to interesting and addicting ; )


Top
 Profile  
 
PostPosted: Tue Jan 17, 2017 8:13 am 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 554
Location: Sweden
hundonostudy wrote:
after browsing through the literature here, I've come to the conclusion that handling the logic in the NMI as the original tutuorial suggests is a bad thing, so I'm going to try to change that as well.

Hundo

The All in NMI way is a valid approach that some comercial games use (like Super Mario Bros). But I think that keeping logic in the Reset handler and graphic updates in the NMI is a clean way to separate the two.


Top
 Profile  
 
PostPosted: Tue Jan 17, 2017 8:40 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9529
Location: Rio de Janeiro - Brazil
Pokun wrote:
The All in NMI way is a valid approach that some comercial games use (like Super Mario Bros).

Not without some undesirable side effects, such as music slowdown and raster effect glitches on lag frames. I heard that in some cases the game can even crash (due to a missed sprite 0 hit, I'm assuming).

Quote:
But I think that keeping logic in the Reset handler and graphic updates in the NMI is a clean way to separate the two.

It's the most robust setup when it comes to handling lag frames, because you get to prioritize some tasks that must run at 60Hz even in case of slowdown, while with the other methods (all in NMI or all in Reset) everything slows down ("slowing down" being an euphemism when it comes to raster effects, since when slowed down they can cause severe visual glitches).

I'd only recommend the "all in X" methods for games that are guaranteed to never slow down, which is normally only the case of single screen or screen by screen games with a controlled number of active objects. Anything more dynamic than that is bound to run into lag frames every once in a while.


Top
 Profile  
 
PostPosted: Tue Jan 17, 2017 6:25 pm 
Offline

Joined: Sat Jan 14, 2017 8:40 am
Posts: 23
tokumaru wrote:
You should try to format all game states in a similar way, because currently they're kinda all over the place and don't follow a set pattern. The title screen is drawn during reset, and its handler draws the gameplay background. The gameplay handler has the game logic but doesn't draw any backgrounds, and the game over handler only draws its background over and over.

I suggest you have a reset routine for each state, that draws its background sets the necessary variables, and then sets the variable that indicates the active state. Another important thing to do is disable NMIs (or have them don't do anything, which's better) during state transitions, otherwise the NMI handler might interrupt the transitions and screw everything up.

the different states could follow a standard like this:
Code:
ResetTitle:
   ;draw background
   ;set state to 'TITLE'
UpdateTitle:
   ;wait for start to jump to ResetGameplay
   jsr GameEngineDone

ResetGameplay:
   ;draw background
   ;set state to 'GAMEPLAY'
UpdateGameplay:
   ;run game logic
   ;jump to ResetGameOver on death
   jsr GameEngineDone

ResetGameOver:
   ;draw background
   ;set state to 'GAMEOVER'
UpdateGameOver:
   ;wait for timer to expire before jumping to ResetTitle
   jsr GameEngineDone

To prevent the NMI from doing anything that could ruin the loading process of the states, you could maybe have a 'TRANSITIONING' state, that when active cause the NMI handler to not do a sprite DMA or touch any PPU registers. Playing audio is OK.


Hi Tokumaru! I'm about to start re-writing this monstrosity! Just a quick question before I start. Would you recommend putting these "Reset Routines" Before the Infinite Game Loop so that they're not infinitely repeating? Just looking for some best practices to work with before I get started.

Thanks!

Hundo


Top
 Profile  
 
PostPosted: Tue Jan 17, 2017 7:41 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9529
Location: Rio de Janeiro - Brazil
hundonostudy wrote:
Just a quick question before I start. Would you recommend putting these "Reset Routines" Before the Infinite Game Loop so that they're not infinitely repeating?

How they're laid out in the ROM doesn't really matter, as long as they're out of the flow. You could have something like this:
Code:
Reset:

   ;- clear RAM, initialize the PPU, APU, sub-systems, etc.

   jsr InitializeTitle

   ;- clear 'frame ready' flag
   ;- enable NMIs

MainLoop:

   ;- do tasks common to all modes
   ;- call the 'UpdateXXXXX' routine for the current mode (or none if in 'TRANSITION')
   ;- set 'frame ready' flag and wait for it to be cleared by the NMI handler

   jmp MainLoop

;----------------------------------------------------------------

NMI:

   ;- if the game mode is 'TRANSITION', do absolutely nothing to the PPU
   ;- if the game mode is any other, and the 'frame ready' flag is set,
   ;- process pending VRAM updates, enable rendering and clear the 'frame ready' flag
   ;- update audio

   rti

;----------------------------------------------------------------

InitializeTitle:

   ;- set game mode to 'TRANSITION'
   ;- disable rendering and draw background
   ;- initialize title screen variables
   ;- set game mode to 'TITLE'

   rts

UpdateTitle:

   ;- if 'start' is pressed, call 'InitializeGameplay'
   
   rts

;----------------------------------------------------------------

InitializeGameplay:

   ;- set game mode to 'TRANSITION'
   ;- disable rendering and draw background
   ;- initialize gameplay variables
   ;- set game mode to 'GAMEPLAY'

   rts

UpdateGameplay:

   ;- process input
   ;- move game objects
   ;- buffer VRAM updates
   ;- in case of death, call 'InitializeGameOver'

   rts


Quote:
Just looking for some best practices to work with before I get started.

The structure above is just a suggestion (I myself don't do it quite like that), you have to create a structure you feel comfortable with, as this can be done a million different ways and each programmer prefers to do things differently. The important thing is that you're consistent in your choices. ASM gives us a lot of freedom, there are no rules on how to implement subroutines, pass parameters, declare variables, initialize stuff, and so on... For this reason, it's important that you consistently follow a set of rules of your liking to do these basic things, so you can easily follow the structure of your program even when it grows to tens of thousands of lines.


Top
 Profile  
 
PostPosted: Wed Jan 18, 2017 1:39 pm 
Offline

Joined: Sat Jan 14, 2017 8:40 am
Posts: 23
tokumaru wrote:
hundonostudy wrote:
Just a quick question before I start. Would you recommend putting these "Reset Routines" Before the Infinite Game Loop so that they're not infinitely repeating?

How they're laid out in the ROM doesn't really matter, as long as they're out of the flow. You could have something like this:
Code:
Reset:

   ;- clear RAM, initialize the PPU, APU, sub-systems, etc.

   jsr InitializeTitle

   ;- clear 'frame ready' flag
   ;- enable NMIs

MainLoop:

   ;- do tasks common to all modes
   ;- call the 'UpdateXXXXX' routine for the current mode (or none if in 'TRANSITION')
   ;- set 'frame ready' flag and wait for it to be cleared by the NMI handler

   jmp MainLoop

;----------------------------------------------------------------

NMI:

   ;- if the game mode is 'TRANSITION', do absolutely nothing to the PPU
   ;- if the game mode is any other, and the 'frame ready' flag is set,
   ;- process pending VRAM updates, enable rendering and clear the 'frame ready' flag
   ;- update audio

   rti

;----------------------------------------------------------------

InitializeTitle:

   ;- set game mode to 'TRANSITION'
   ;- disable rendering and draw background
   ;- initialize title screen variables
   ;- set game mode to 'TITLE'

   rts

UpdateTitle:

   ;- if 'start' is pressed, call 'InitializeGameplay'
   
   rts

;----------------------------------------------------------------

InitializeGameplay:

   ;- set game mode to 'TRANSITION'
   ;- disable rendering and draw background
   ;- initialize gameplay variables
   ;- set game mode to 'GAMEPLAY'

   rts

UpdateGameplay:

   ;- process input
   ;- move game objects
   ;- buffer VRAM updates
   ;- in case of death, call 'InitializeGameOver'

   rts


Quote:
Just looking for some best practices to work with before I get started.

The structure above is just a suggestion (I myself don't do it quite like that), you have to create a structure you feel comfortable with, as this can be done a million different ways and each programmer prefers to do things differently. The important thing is that you're consistent in your choices. ASM gives us a lot of freedom, there are no rules on how to implement subroutines, pass parameters, declare variables, initialize stuff, and so on... For this reason, it's important that you consistently follow a set of rules of your liking to do these basic things, so you can easily follow the structure of your program even when it grows to tens of thousands of lines.


Thank you for the suggestion Toku. Can you help me out a little with the frame ready flag? Not sure what you want me to write there. Should I write a piece of code that is Incrementing a variable to use as a wait counter for example, or should I simply create a variable that is set to #$00 when not ready and have the NMI update that flag to #$01 at the beggining of NMI and #$00 at the end of NMI to use as an indicator for when NMI starts and stops?


Top
 Profile  
 
PostPosted: Wed Jan 18, 2017 4:56 pm 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 554
Location: Sweden
You could go with either a frame counter (counts NMIs) or a flag. In Tokumaru's example he is using a flag. Setting and then clearing the flag within the NMI is pointless though. Once NMI has happened it won't go back to the main loop until it reaches the rti instruction.
You set the flag in the main loop and clear it in the NMI. You also need to wait (by looping) in main until the flag is cleared again (which indicates an NMI has happened). In the NMI handler you clear it at the end of NMI to indicate that an NMI has happened.
Waiting like this ensures that there are no more than one iteration of the main loop on one NMI.

In the example it looks like the flag also indicates that there are graphic updates to be done in vblank, so you can make graphic updates in the NMI conditional based on the state of the flag (if it's set, you update, else you skip updates that NMI). This ensures that if the NMI happens before the main loop has finished (i.e. has not set the flag) the NMI won't do any graphical updates. So it won't draw partly done screens that are still being processed in the main loop logic.

Edit: Fixed mixups.


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

All times are UTC - 7 hours


Who is online

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