Writing Game Boy Game : Flappy Bird

Caution

What you must absolutely now before beginning

  • Never access video ram if the screen is ON, or do it in vblank or hblank methods.

  • Game Boy CPU have a bug with asm instruction LDI (HL) in OAM address range ($FE00 to $FE9F).

Download the final version on this link flappy-bird.gb

It introduces the new concept of OAM registers and their use.
The introduction of different states makes it possible to manage an initialization,
menu and game modes.

State diagram :

In this game we use a small state diagram, we have 3 modes:
MODE_START_SCREEN = 0 - displays the "Flappy Bird" start-up screen + website
MODE_MENU = 10 - the menu is displayed : game title + tap A
MODE_GAME = 20 - it’s the game itself
MODE_GAMEOVER = 30 - game over + small animation with the falling bird

In the setup function, the registers are initialized. The background image is loaded for the startup screen.

/**
 * Initialisation
 */
def setup() {

  // Mute the sound
  set(0,$FF26);

  // Load background image
  initStartScreen();

  // Disable interrupt
  asm{
    DI
  }

  // Switch off the screen
  screenOff();

  // Clear OAM
  clearOAM();

  // Background
  //Scroll Y
  //Scroll X
  set(0,$FF42);
  set(0,$FF43);


  // Palette BG
  set(%11100100,$FF47);
  // Palette sprite 0
  set(%11100100,$FF48);
  // Palette sprite 1 (sert pas)
  set(%11000110,$FF49);
  // Allume l'ecran
  set(%11010011,$FF40);

  // Switch on the screen
  screenOn();

  // Enable VBlank interrupts
  set(%00000001,$FFFF);

  // Enable interrupt
  asm{
    EI
  }
}

In the loop loop, one of the four states of the game is selected with the mode variable.

/**
 * Game loop
 */
def loop() {

  // Start screen
  if(mode == MODE_START_SCREEN) {
    startScreen();
  } else if(mode == MODE_MENU) {
    // Menu
    menu();
  } else if(mode == MODE_GAME) {
    // Game
    game();
  } else if(mode == MODE_GAMEOVER) {
    // Game over
    game();
  }
}

game loop : mode MODE_START_SCREEN

In the start screen mode, we check if the player press A or START button.
If he presses one of these buttons, we switches to the menu mode (MODE_MENU = 10), and the background image is loaded.

def startScreen(){

  // Pour eviter de traverser le mode trop vite avec start
  if (flag > 30) {
    counter = 1;
  }

  // Menu Start
  if (counter == 1) {
    // Get value of gamepad's buttons
    joystick = gamepadButtons();
    joystickValueStart = joystick & GAMEPAD_START;
    joystickValueA = joystick & GAMEPAD_A;
    if (joystickValueStart == KEY_DOWN || joystickValueA == KEY_DOWN) {
      mode = MODE_MENU;
      initMenu();
    }
  }

}

game loop : mode MODE_MENU

In this mode, a short message explains to the player that he must press A button.
As soon as he presses A, we switches to game mode (MODE_GAME = 20).

def menu(){
// Pour eviter de traverser le mode trop vite avec start
  if (flag > 30) {
    counter = 1;
  }

  // Press A ?
  if (counter == 1) {
    // Get value of gamepad's buttons
    joystick = gamepadButtons();
    joystickValueA = joystick & GAMEPAD_A;
    if (joystickValueA == KEY_DOWN) {
      mode = MODE_GAME;
      initGame();
    }
  }
}

game loop : mode MODE_GAME and MODE_GAMEOVER

The game is initialized, the background image is loaded. The pipe is initialized in the OAM array.
The bird is initialized in OAM too. We also initialize the OAM array with score characters.
The background image is loaded as usual.
Then the pipe and the bird are initialized.

def game(){

  if (flag == 5) {
    // Counter
    flag = 0;
    if(mode == MODE_GAME){
    	scrollX+=5;
    	pipeX-=5;
    	flapWings++;

    	// Pipe
    	if(pipeX ==0){
      		pipeX = PIPE_X_START;
      		// Entre 16 et 80
      	    pipeY = randomPipeY();
    	}
    	movePipe(pipeX,pipeY);

    	// Check Bird collision
    	checkBirdCollision();
    }else{
    	birdY+=4;
    	if(birdY > COLLISION_BOTTOM){
    		birdY = COLLISION_BOTTOM;
    		counter++;
    	}
    	if(counter==5){
    		 mode = MODE_MENU;
      		 initMenu();
    	}
    }

    // Move Bird
    moveBird(birdY);

    // Bird;
    flapWingsOAM(flapWings);

    // Compute score;
    computeScore();
  }
}

In this method, if the current mode is MODE_GAMEOVER, we do a small animation with the falling bird.
When the bird position Y is below COLLISION_BOTTOM, we set the mode to MODE_MENU.