Game loops/structures

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Game loops/structures

Post by zkip »

I'm thinking this question could probably be applied to anywhere but I'm posting it here because this is what I'm doing with it.

I'm having trouble understating the basic layout of entire game logic. Ultimately, I'm trying to incorporate a jump table (as many games do) for the "game mode" of a demo I made to test metasprites but I can't figure out the placing of it. Such as, 00 being the title screen, 01 being phase 1, 02 being regular gameplay etc. The problem I'm having with it is understanding the whole structure of it. The way I have my code set up it initializes the NES and then kick starts to showing the sprite on screen. After searching around and reading a little it seems as if a lot of people suggest "don't do logic in NMI" albeit I'm learning from SMB1's source and if I traced it right it runs game mode routine smack dab in the middle of NMI time.

Also, question number 2 is a lot of places teach the "infinite jump at the end of the main routine" technique. However, I found the need to do this within my main:

Code: Select all

GameLoop:

	LDX #$04	;wait 4 vblanks
.c
	BIT $2002
	BPL .c
	DEX
	BNE .c

	INC GameTimer
	;do other stuff
        JMP GameLoop
Is this good practice or not?

tl;dr: Can someone make me a basic outline of how a simple game with a title screen and game over screen is structured?
User avatar
thefox
Posts: 3139
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Game loops/structures

Post by thefox »

zkip wrote:After searching around and reading a little it seems as if a lot of people suggest "don't do logic in NMI" albeit I'm learning from SMB1's source and if I traced it right it runs game mode routine smack dab in the middle of NMI time.
There's a difference between NMI and VBlank. NMI is the IRQ triggered by the start of VBlank. It's fine to do whatever in your NMI handler, but you don't want to do game logic during VBlank because VBlank time is very limited (you want to devote the VBlank to pushing updates to the PPU). After you're done with PPU updates, you can run the game logic even in NMI, like SMB does.

If you do run your game logic in the NMI, you have to make sure that an another NMI triggering in the middle of your current NMI handler doesn't mess things up. There are various ways to do this (flags to indicate whether NMI is still running, disabling NMIs entirely, etc.)
Also, question number 2 is a lot of places teach the "infinite jump at the end of the main routine" technique. However, I found the need to do this within my main:

Code: Select all

GameLoop:

	LDX #$04	;wait 4 vblanks
.c
	BIT $2002
	BPL .c
	DEX
	BNE .c

	INC GameTimer
	;do other stuff
        JMP GameLoop
Is this good practice or not?
Can you explain more about why you think you "need" to do this? What this effectively does, is that it makes your GameTimer tick at 15 Hz. That by itself is fine. However, it's NOT good practice to use $2002 polling to check for VBlank, because the $2002 reads will sometimes fail to report that a VBlank happened.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: Game loops/structures

Post by zkip »

Within that gameloop is also updates to the sprite logic code. That's why I feel it was needed to establish correct timing. Which brings us back to my original question.... I have no idea how to structure this because so many guides, etc. follow different rules. Basically, I know how to code things I'm just struggling with the placement of the whole deal.
User avatar
thefox
Posts: 3139
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Game loops/structures

Post by thefox »

zkip wrote:Within that gameloop is also updates to the sprite logic code. That's why I feel it was needed to establish correct timing.
But why 4 vblanks? By doing this you're forcing your game to run at maximum of 15 FPS.

Have you read the article at http://wiki.nesdev.com/w/index.php/NMI_thread? It doesn't have a whole lot of detail on anything except the "NMI and main" method of organizing your game, but you may find some useful information in there. I hope somebody else will fill in the more of the details, I'm too tired to write anything about any of it right now.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game loops/structures

Post by tokumaru »

The simplest game structure I can think of is this:

Code: Select all

TitleScreenStart:
	;INITIALIZE THE TITLE SCREEN STATE
TitleScreenLoop:
	;DO TITLE SCREEN LOGIC
	;WAIT FOR VBLANK
	;DO TITLE SCREEN PPU UPDATES
	jmp TitleScreenLoop

MainGameStart:
	;INITIALIZE THE MAIN GAME STATE
MainGameLoop:
	;DO MAIN GAME LOGIC
	;WAIT FOR VBLANK
	;DO MAIN GAME PPU UPDATES
	jmp MainGameLoop

GameOverStart:
	;INITIALIZE THE GAME OVER STATE
GameOverLoop:
	;DO GAME OVER LOGIC
	;WAIT FOR VBLANK
	;DO GAME OVER PPU UPDATES
	jmp GameOverLoop
For each program mode you have an initialization step, where you should set everything up. For the title screen and game over modes, this would be a good time to draw the screens. The initialization of the main game is more complex... in addition to drawing the first screen you'll have to initialize objects, set up pointers to the level data, and so on. After the initialization comes the loop, which runs over and over and alternates between logic and PPU updates, with a Vblank wait in-between. This VBlank wait should NOT be polling $2002, bacause as has been pointed out, 2002 is not reliable for in-game use. The correct solution here would be to poll a flag that's modified by a barebones NMI routine (i.e. INC VBlankFlag; RTI;).

With this structure, you can switch to another game mode at any time by jumping to its Initialization label. For example, if you're in the MainGameLoop and detect that the player lost all his lives, you can simply JMP to GameOverStart to show the game over screen. It will of course look better if you fade out before doing that, to make the transition smooth (assuming that the game over state fades in)! the only serious disadvantage with this approach is that when your game logic takes longer than a frame to complete, you'll miss a VBlank. This means the music will slow down and raster effects (like status bars or parallax scrolling) might break (and possibly crash the program if you're not prepared to handle these slowdowns). If you're 100% sure your game logic will never go over a frame's time, you don't have to worry.

If slowdowns are a possibility, you can't just "wait for VBlank", you must let the NMI do what it's supposed to do and interrupt your game logic (after all, the "I" in NMI means interrupt!). The problem is that since you have many different game modes, your NMI code will either have to be very generic (so that it can be used for all game modes) or you'll have to set up a way to run different NMIs for the different game modes.

Generic NMI handlers are great: they use buffers (address, legth and data) to update the name tables, they update sprites with a sprite DMA, they set the scroll and finally call the music engine. If you can get away with using that for all you game modes, good for you, but that's not always possible.

In my case, I have chosen to use 2 NMI routines: one that just increments a flag (so it can be used to wait for VBlank) and another one for the main game, which is the only program mode that can actually slow down. So in this particular NMI handler I'll only perform the PPU updates if the game logic has finished, but raster effects and the music are processed regardless.
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: Game loops/structures

Post by zkip »

Wow, thank you guys so much for the detailed responses and tying to help. I thought I knew a great deal of NES workings but apparently not! :|

Take a look at this simple example I cooked up quickly to try to mimic the 'barebones NMI' thing. At first I thought "Oh, okay I get it!" but now I'm not so sure. The code assembled above works, albeit, the movement is a little gawky.. I'm not sure how to explain it, but going downward seems to move the sprite a little faster vs. the other directions. I can't figure out where this bug lies. Is it inconsistent vblank wait time, a bug in the controller reads?
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game loops/structures

Post by tokumaru »

I don't see anything that would explain the increased speed when moving down. Actually, I don't see anything wrong with your code, looks like a perfectly valid structure to me. There are a few things you could improve, like enabling rendering only after you've put the sprite in place (currently there will be a quick, possibly glitched frame since you're enabling rendering before processing the first frame), and doing it during VBlank to avoid a "jumpy" screen. That's not an error though, just a harmless visual glitch.

Now, about UpdateSpriteBuffer: you're using this routine to perform tasks that shouldn't be together. I can see that first you prepare the OAM data, then you transfer that data to the PPU though a sprite DMA. It may not make any difference now because you're changing so little OAM data (just the coordinates of 1 sprite), but once the game gets more complex that task is gonna take much more time, and you'll be wasting precious VBlank time doing something you could have done beforehand. To fix this you should prepare all the OAM data BEFORE waiting for VBlank, and leave only the actual DMA for the Vertical Blank, because it can't be done at any other time.
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: Game loops/structures

Post by zkip »

Hmmm.. thanks for that. I understand. As for the movement problem if anyone has any idea your input would be greatly appreciated. I've tried moving around the logic routines and sometimes I get different results but the underlying problem of one direction being faster than the others remains.
User avatar
thefox
Posts: 3139
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Game loops/structures

Post by thefox »

zkip wrote:Hmmm.. thanks for that. I understand. As for the movement problem if anyone has any idea your input would be greatly appreciated. I've tried moving around the logic routines and sometimes I get different results but the underlying problem of one direction being faster than the others remains.
If you'd share the ROM and/or complete source, it'd be much easier for others to help you.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: Game loops/structures

Post by zkip »

What I posted earlier is essential the whole source, save the GFX and init rotuine. I'm really racking my brain on this because I see no reason for this effect to be happening. :|

If it will help I've attached the complete source and ROM.
Attachments
files.zip
test
(2.28 KiB) Downloaded 97 times
User avatar
thefox
Posts: 3139
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Game loops/structures

Post by thefox »

It's a very simple but deceptive bug:

Code: Select all

waitLoop:
	LDA NMITimer			;vblank wait
	CMP NMITimer
	BEQ waitLoop
Should be:

Code: Select all

	LDA NMITimer			;vblank wait
waitLoop:
	CMP NMITimer
	BEQ waitLoop
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Post Reply