Welcome to Creitive Global

Let's start

Partner with us

biz@creitive.com

Work with us

office@creitive.com

JS Flappy Bird - Part 2

If you haven't already, you should probably read the part 1 of the blog. In the part 1, we set up a basis for our Flappy Bird game, purely in vanilla JS. In this one, we'll make improvements all round including, score counting, improved gravity, text, death screen etc.

This is where we left off:

Sure it works kind of, but it's not the most beautiful of games. Let's start prettifying!

First, add a splash of color:

var randomColorFirst = "rgb(" + Math.round(Math.random() * 256) + ", " + Math.round(Math.random() * 256) + ", " + Math.round(Math.random() * 256) + ")";
var randomColorSecond = "rgb(" + Math.round(Math.random() * 256) + ", " + Math.round(Math.random() * 256) + ", " + Math.round(Math.random() * 256) + ")";
var randomColorThird = "rgb(" + Math.round(Math.random() * 256) + ", " + Math.round(Math.random() * 256) + ", " + Math.round(Math.random() * 256) + ")";

var render = function() {
  context.fillStyle = randomColorFirst;  
  // ...
};

Bird.prototype.render = function () {
  // ...
  context.fillStyle = randomColorSecond;
  // ...
};

Pipe.prototype.render = function () {
  // ...
  context.fillStyle = randomColorThird;
  // ...
};

Then, make the gravity more realistic:

var fallSpeed = 0;
var GRAVITY = 0.75;
var FALL_ACCELERATION = 0.9;

Bird.prototype.update = function () {
  fallSpeed += GRAVITY;
  fallSpeed *= FALL_ACCELERATION;
  this.y += fallSpeed;
};
Bird.prototype.jump = function (jumpAmmount) {
  fallSpeed += jumpAmmount;
  // ...
};

// On keydown event.
birdie.jump(-15);

Ahh much better. The falling and jump animation is now much more smooth. I could watch it for hours. mesmerized

Now, let's see how good at the game we actually are. To not complicate the canvas too much, we'll create a bit of HTML and style it with scss.

<body>
	<span class="score">0</span>
</body>
* {
  font-family: Helvetica;
}

canvas {
  position: absolute;
  top: 50%;
  left: 50%;

  transform: translate(-50%, -50%);
}

.score {
  position: absolute;
  top: calc(50% - 100px);
  left: 50%;
  z-index: 1;

  font-size: 80px;

  transform: translate(-50%, -50%);
}
var score = 0;
var realScore;

var update = function() {
  for (var i = 0; i < pipes.length; i++) {
    // ...
    if (birdie.score(pipes[i])) {
      realScore = Math.floor(score / 2);

      document.getElementsByClassName('score')[0].innerHTML = realScore;
    }
  }
  // ...
};

After this, we're able to see how far we've gotten with our flying and jumping. Now, let's set up a game over screen so we actually have a feel that the game is over. We'll use markup again for this.

<div class="ripScreen is-hidden">
  <h1>Game Over</h1>
  <p class="ripScreenScore">Your score was </p>
  <button class="tryAgainButton">Try again</button>
  <span class="tryAgainText">or just press "R" :)</span>
</div>
.ripScreen {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;

  width: 660px;
  height: 330px;

  color: #fff;

  background-color: #000;

  transform: translate(-50%, -50%);

  h1 {
    position: absolute;
    top: 5%;

    width: 100%;

    margin: 0;

    font-size: 100px;
    text-align: center;
  }

  p {
    position: absolute;
    top: 40%;

    width: 100%;

    margin: 0;

    font-size: 30px;
    text-align: center;
  }

  button {
    position: absolute;
    bottom: 30px;
    left: 50%;

    transform: translateX(-50%);
  }
  
  span {
    position: absolute;
    bottom: 10px;
    left: 50%;

    font-size: 12px;

    transform: translateX(-50%);
  }

  &.is-hidden {
    display: none;
  }
}
stop = true;

var update = function() {
  for (var i = 0; i < pipes.length; i++) {
    if (birdie.crash(pipes[i])) {
      // ...
      document.getElementsByClassName('ripScreen')[0].classList.remove('is-hidden');
      
      document.getElementsByClassName('ripScreenScore')[0].innerText = 'Your score was ' + scoreText;

      stop = true;
	  // ...
    }
    // ...
  }
  // ...
};

With this done, we should also add a reset() function so that the user we can play this indefinitely.

var reset = function () {
  pipes = [];
  progress = 0;

  score = 0;
  document.getElementsByClassName('score')[0].innerHTML = 0;

  stop = false;

  birdie.x = 60;
  birdie.y = worldHeight / 2 - birdie.height;

  document.getElementsByClassName('ripScreen')[0].classList.add('is-hidden');

  animate(step);
};

window.addEventListener('keydown', function (event) {
  var key = event.which || event.keyCode || 0;

  if (key === 82 && hasCrashed) {
    reset();
  }
});

document.getElementsByClassName('tryAgainButton')[0].addEventListener('click', function () {
  reset();
});

We have and actual game now. Everything you do after this are basically QoL changes. There should definitely be some kind of a help message so the player knows how to jump for example:

<span class="help">Use spacebar to jump</span>
.help {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;

  font-size: 54px;

  transition: opacity .6s;

  transform: translate(-50%, -50%);

  &.is-hidden {
    opacity: 0;
  }
}
window.addEventListener('keydown', function (event) {
  if (key === 32 && !hasCrashed) {
  	// ...
    document.getElementsByClassName('help')[0].classList.add('is-hidden');
	// ...
  }
});

Or some extra collision breathing room so that the game is a bit more forgiving when hitting the pipes.

var BREATHING_ROOM = 5;

Bird.prototype.crash = function (pipe) {
  // ...
  if ((birdBottom - BREATHING_ROOM < pipeTop) || (birdTop + BREATHING_ROOM > pipeBottom) || (birdRight - BREATHING_ROOM < pipeLeft) || (birdLeft + BREATHING_ROOM > pipeRight)) {
    hasCrashed = false;
  } else {
    hasCrashed = true;
  }
  // ...
};

Now the player has extra 5px before the game detects collision between the bird and a pipe.

And that's it for now. We have a completely playable game that's actually pretty fun. There is still plenty room for improvement, graphics for starters, sound, adjusting the constants, saving the score to a server etc. But we won't cover everything here, if we did we'd have an Anna Karenina on our hands. Create your own version with the help of these articles, share it around the office and see who's the best.

Cheers!

Insights

Explore all