10. Game loop

Let’s talk about a game loop. Here’s some sample code for a simplified breakout clone.

Games need an infinite loop. while (1) works.

The first item is ppu_wait_nmi(). This sits and waits till the start of a new frame. 60 per second (50 in Europe), the nmi will trigger. But, if the game logic takes too long, and we’re already past the start of nmi before we get to the ppu_wait_nmi(), then it waits an extra frame, and the game will seem to slow down. That hasn’t happened yet, because our code is so short, but it will later, in the platformer game, so keep the loop short and concise.

Then I read the controllers. Both regular pad_poll(0) and the new presses get_pad_new(0).

Then I clear_vram_buffer(), this is unique to my code. Since the nmi code surely pushed our last update, and we need to reset it so we can add more. Which we do next… score_lives_draw() reprints the scoreboard at the top of the screen, using one_vram_buffer() statements. These are like the vram_put(), but it is storing them in a buffer, and the nmi code will automatically pushing them to the ppu during v-blank.

Then the game logic, move the paddle. Move the ball. Check if collision with blocks.

Move paddle…

if(pad1 & PAD_LEFT){
    Paddle.X -= 2;
    if(Paddle.X < PADDLE_MIN) Paddle.X = PADDLE_MIN;
}
if(pad1 & PAD_RIGHT){
    Paddle.X += 2;
    if(Paddle.X > PADDLE_MAX) Paddle.X = PADDLE_MAX;
}

Move ball, if active…

if(ball_direction == GOING_UP){
    Ball.Y -= 3;
    if(Ball.Y < MAX_UP){
        ball_direction = GOING_DOWN;
    }
}
else { // going down
    Ball.Y += 3;
    if(Ball.Y > MAX_DOWN){
        --lives01;
        ball_state = BALL_OFF;
    }
}

Then draw the sprites, first by clearing the old, then redrawing all the active sprite objects.

If a block is hit with the ball, hit_block(), it deletes it from the collision map, and then writes some blank tiles to the background at that same position. We just need to push 2 tiles pushed to delete it from the BG. Again, I use the vram buffer to store that temporarily, and automatically send it to the PPU during v-blank.

Normally, I would have different “game_states”, like for title, game, pause, end. I am using different “ball_states” to handle the ball off screen wait “BALL_OFF”, the ball ready to go “BALL_STUCK” (stuck on the paddle), and ball moving “BALL_ACTIVE”.

I made the standard background in NES Screen Tool. The breakable tiles are defined an array, c1.csv. I did not end up using Tiled for it, because it was easy to type. If this was modified, it would change the layout of the breakable tiles.

const unsigned char c1[]={
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,

0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

The gray bricks were drawn in NES Screen Tool, and exported as a compressed RLE file.

I changed the attribute tables beforehand, in NES Screen Tool, and save the background. That’s what gives the tiles their color, they are different palettes. But they all use the same tiles. Here is the attribute checker view (press “A”).

And it updates the scoreboard every frame. Notice, I’m keeping each digit of the score as a separate variable (value assumed to be 0-9). score10 is tens digit and score01 is ones digit. Division and modulus are very slow operations on the NES, due to the lack of built in math in the 6502 processor, so keeping each digit separate speeds up the code.

https://github.com/nesdoug/12_Break

Feel free to turn this into a full game. Sideways movement would complicate the logic a bit more. I wanted to keep the example code as simple as possible.

Last updated