Game Doesn't Run

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

User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

After you fix the palette problem, there are a couple of other things you have to do: before enabling rendering, reset the scroll using $2005, or else the tiles you wrote to the name table will not be displayed at the location you want. The other thing is to wait for Vblank before enabling rendering so that this doesn't happen by the middle of the screen (even some commercial games failed to do this sometimes).
WJYkK
Posts: 60
Joined: Thu Dec 24, 2009 12:23 am
Location: Igloo and Bear Land (Canada)
Contact:

Post by WJYkK »

Alright, another question: say I want to place a timer that's constantly ticking. I can't simply put it in an infinite loop that will write the tiles to the same spot, right? E.g:

Code: Select all

tick:
	lda $2002
	lda #$20
	sta $2006
	lda #$5f
	sta $2006
	lda #$40
	sta $2007
	rts
I can't do this to place tile $40 onto spot $5f.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

A NES game loop usually has the following structure:

1. read input;
2. update the game world;
3. wait for VBlank;
4. update video and audio;
5. go back to 1;

The timer has to be maintained as part of the game world. If your timer has seconds and minutes, and the NES runs at 60 frames per second, you have to modify your timer every 60 frames. You need a variable to count to 60, and when it reaches that number you modify the timer and start counting to 60 again. Later, during the video update part you write to the nametables whatever the timer holds.

Here's an example of a timer that can go up to 59 minutes and 59 seconds (then it wraps back to 00:00). The first part is the initialization, where the timer is cleared:

Code: Select all

	lda #0
	sta Frames
	sta SecondsOnes
	sta SecondsTens
	sta MinutesOnes
	sta MinutesTens
It goes before the game loop. Then, somewhere inside the game loop you have to update the timer:

Code: Select all

	inc Frames
	lda Frames
	cmp #60
	bne Done

	ldx #0

	stx Frames
	inc SecondsOnes
	lda SecondsOnes
	cmp #10
	bne Done

	stx SecondOnes
	inc SecondTens
	lda SecondTens
	cmp #6
	bne Done

	stx SecondTens
	inc MinutesOnes
	lda MinutesOnes
	cmp #10
	bne Done

	stx MinutesOnes
	inc MinutesTens
	lda MinutesTens
	cmp #6
	bne Done

	stx MinutesTens
Done:
The code is somewhat long when the timer is split in individual digits, but this makes it easier to draw the numbers later (the other option would be to convert from binary to decimal, and that's kinda hard/slow on the NES).

The last thing to do is draw the numbers to the nametables during VBlank:

Code: Select all

	lda #$20
	sta $2006
	lda #$5f
	sta $2006
	clc
	lda MinutesTens
	adc #NUMBERS_BASE
	sta $2007
	lda MinutesOnes
	adc #NUMBERS_BASE
	sta $2007
	lda #TIME_SEPARATOR
	sta $2007
	lda SecondsTens
	adc #NUMBERS_BASE
	sta $2007
	lda SecondsOnes
	adc #NUMBERS_BASE
	sta $2007
"NUMBERS_BASE" is the index of the tile that holds the number "0", we need to add the values to it so that the numbers are displayed using the correct tiles (of course you must have "1" through "9" following the "0"). If the "0" is at tile number 0 you can skip those ADCs. "TIME_SEPARATOR" is the index of the tile that will stay between the minutes and the seconds.

Anyway, I don't usually write code to answer questions, but in this case I though it could help you understand a bit of how games work. Almost everything else in the game (such as the main character, the enemies, etc) will work somewhat like this, they'll have to be initialized, updated every frame, and drawn during VBlank.
WJYkK
Posts: 60
Joined: Thu Dec 24, 2009 12:23 am
Location: Igloo and Bear Land (Canada)
Contact:

Post by WJYkK »

Okay, so I did the following and it doesn't work:

I placed this code after the second wait for VBLANK and before all of the jsr instructions:

Code: Select all

   Frames=0
   SecondsOnes=0
   SecondsTens=0
   MinutesOnes=0
   MinutesTens=0
   NUMBERS_BASE=48
   TIME_SEPARATOR=58
NUMBERS_BASE and TIME_SEPARATOR relate to 0 and : because I arranged my symbols by ASCII codes.

Next, this is my main loop:

Code: Select all

loop:
   inc Frames
   lda Frames
   cmp #60
   bne Done

   ldx #0

   stx Frames
   inc SecondsOnes
   lda SecondsOnes
   cmp #10
   bne Done

   stx SecondsOnes
   inc SecondsTens
   lda SecondsTens
   cmp #6
   bne Done

   stx SecondsTens
   inc MinutesOnes
   lda MinutesOnes
   cmp #10
   bne Done

   stx MinutesOnes
   inc MinutesTens
   lda MinutesTens
   cmp #6
   bne Done

   stx MinutesTens
Done:	jmp loop
And finally, I place this after my HUD and before the attribute table formation:

Code: Select all

temp_bg:
   lda #$20
   sta $2006
   lda #$60
   sta $2006
   clc
   lda MinutesTens
   adc #NUMBERS_BASE
   sta $2007
   lda MinutesOnes
   adc #NUMBERS_BASE
   sta $2007
   lda #TIME_SEPARATOR
   sta $2007
   lda SecondsTens
   adc #NUMBERS_BASE
   sta $2007
   lda SecondsOnes
   adc #NUMBERS_BASE
   sta $2007
After compilation, the HUD is still displayed but the timer doesn't work; it's not being displayed at all. Maybe I'm placing the code in the wrong place or missing something obvious?
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

WJYkK wrote:

Code: Select all

   Frames=0
   SecondsOnes=0
   SecondsTens=0
   MinutesOnes=0
   MinutesTens=0
This is wrong, because what the assembler understands from this is that all of these variables will use memory location $00. You can imagine that the logic will fail completely in this case. Each of them must use a different memory location:

Code: Select all

	.enum $0000

Frames .dsb 1
SecondsOnes .dsb 1
SecondsTens .dsb 1
MinutesOnes .dsb 1
MinutesTens .dsb 1

	.ende
I believe you can place this anywhere, but it makes more sense before the actual program, at the top of the source file. This is the best way to declare variables, because it's easy to move them around, remove them or add others as necessary. I suggest you declare all your variables inside ".enum" blocks. Then, before the main loop you clear these variables like I told you to before:

Code: Select all

   lda #0
   sta Frames
   sta SecondsOnes
   sta SecondsTens
   sta MinutesOnes
   sta MinutesTens

Code: Select all

   NUMBERS_BASE=48
   TIME_SEPARATOR=58
NUMBERS_BASE and TIME_SEPARATOR relate to 0 and : because I arranged my symbols by ASCII codes.
This is correct, because these are constants. But it also makes more sense to declare them at the top of the file, so that all your declarations are easy to find and not scattered across the whole program.
Next, this is my main loop:

Code: Select all

loop:
   inc Frames
   lda Frames
   cmp #60
   bne Done

   ldx #0

   stx Frames
   inc SecondsOnes
   lda SecondsOnes
   cmp #10
   bne Done

   stx SecondsOnes
   inc SecondsTens
   lda SecondsTens
   cmp #6
   bne Done

   stx SecondsTens
   inc MinutesOnes
   lda MinutesOnes
   cmp #10
   bne Done

   stx MinutesOnes
   inc MinutesTens
   lda MinutesTens
   cmp #6
   bne Done

   stx MinutesTens
Done:	jmp loop
The problem is that you are jumping back right after updating the timer, which means that the timer will update several times in a single frame (it will run MUCH faster than an actual clock), while you have to update it only once. The program will also be stuck in this loop forever, as there is no way of getting out of it. And since you're stuck in this loop you are never going to write the numbers to the screen.
And finally, I place this after my HUD and before the attribute table formation:

Code: Select all

temp_bg:
   lda #$20
   sta $2006
   lda #$60
   sta $2006
   clc
   lda MinutesTens
   adc #NUMBERS_BASE
   sta $2007
   lda MinutesOnes
   adc #NUMBERS_BASE
   sta $2007
   lda #TIME_SEPARATOR
   sta $2007
   lda SecondsTens
   adc #NUMBERS_BASE
   sta $2007
   lda SecondsOnes
   adc #NUMBERS_BASE
   sta $2007
This part of the code has nothing to do with the inial drawing of the screen. The initial screen will be drawn normally, before the game loop, but the piece of code above must run EVERY VBlank, not only once, so it must be part of your main loop.

See, this is what you should have as your main loop:

Code: Select all

loop:

	;CODE TO UPDATE THE TIMER GOES HERE

	jsr wait_for_vblank

	;CODE THAT DRAWS THE TIMER TO THE SCREEN GOES HERE

	;CODE THAT RESETS THE SCROLL GOES HERE

	jmp loop
I omitted the huge blocks of code because they all look correct, there's no reason for me to copy/paste them, so I just marked where they should go.

Remember what I told you about the structure of a game loop. First you update the state of the whole game world (this includes your timer), then you wait for VBlank, and then you do your graphical updates.

You have to program the "wait_for_vblank" subroutine. A very good example was posted here by tepples. Note that you have to declare the "vblank" variable (you don't have to call it "vblank", you can call it whatever you want) and modify your NMI handler:

Code: Select all

nmi:
	inc vblank
	rti
And of course, since you will be using NMIs you must make sure to enable them before the main loop starts (it seems you already are, so don't worry).

Resetting the scroll is just selecting the visible name table through $2000 and writing the pixel offsets to $2005. I didn't post any code because it's you who decides what part of the name tables you want to show (make sure to show the part where the numbers are written to!).

I guess this is it. Try to think a little about the order in which you are doing things, think about it logically and you should come to a conclusion about what makes sense. Don't just copy and paste code around hoping for the best, that never works.
WJYkK
Posts: 60
Joined: Thu Dec 24, 2009 12:23 am
Location: Igloo and Bear Land (Canada)
Contact:

Post by WJYkK »

Ha, I never thought actually putting all these jsrs in the loop. I guess that makes sense, but I thought NES logic was different. Thanks for clarifying that.

And actually, I didn't directly copy-pasted the code as I previously thought up of other methods that didn't work, but then again, the jsrs were not in the main loop. :wink:
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Uh... the JSRs are not supposed to be in the main loop. So far, only the routine that waits for VBlank k is called inside the main loop. The JSRs you had before were used for drawing the screen, and that should be done before the main loop, like it is now.

Vblank time is very limited, so you only have time to draw what has changed since last frame, which is why the code I wrote only rewrote the timer (the only thing constantly changing). You can't redraw the whole screen every time because there is no time, and if you try you'll get corrupt video.
Post Reply