Game Doesn't Run
Moderator: Moderators
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).
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:
I can't do this to place tile $40 onto spot $5f.
Code: Select all
tick:
lda $2002
lda #$20
sta $2006
lda #$5f
sta $2006
lda #$40
sta $2007
rtsA 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:
It goes before the game loop. Then, somewhere inside the game loop you have to update the timer:
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:
"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.
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 MinutesTensCode: 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 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 $2007Anyway, 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.
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:NUMBERS_BASE and TIME_SEPARATOR relate to 0 and : because I arranged my symbols by ASCII codes.
Next, this is my main loop:
And finally, I place this after my HUD and before the attribute table formation: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?
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=58Next, 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 loopCode: 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 $2007This 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:WJYkK wrote:Code: Select all
Frames=0 SecondsOnes=0 SecondsTens=0 MinutesOnes=0 MinutesTens=0
Code: Select all
.enum $0000
Frames .dsb 1
SecondsOnes .dsb 1
SecondsTens .dsb 1
MinutesOnes .dsb 1
MinutesTens .dsb 1
.endeCode: Select all
lda #0
sta Frames
sta SecondsOnes
sta SecondsTens
sta MinutesOnes
sta MinutesTensThis 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.NUMBERS_BASE and TIME_SEPARATOR relate to 0 and : because I arranged my symbols by ASCII codes.Code: Select all
NUMBERS_BASE=48 TIME_SEPARATOR=58
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.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
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.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
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 loopRemember 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
rtiResetting 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.
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.
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.
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.
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.