Alright. I have never wrote any blogposts before especially on programming... Especially not on my native speaking language. But wth lets give it a try. Most of things I know about coding I got from different articles on the web. It's time to give something back. I hope this suff will be usefull. Besides it's good to get all those infinite thoughts out to somewhere.

Here is what we're gonna build (space to jump):

First of all we're gonna need a canvas:

  <canvas></canvas>

That's all html we need btw. Now let's add some CSS just to be aware of where our canvas is and make it be alligned by center:

  body {
  margin: 0;
  background: #3D3D3E;
}

canvas {
  display: block;
  margin: 0 auto;
}

Finally, initial setting for JS is like this:

  function mainLoop () {
  requestAnimationFrame(mainLoop);
};

function init () {
  requestAnimationFrame(mainLoop);
};

window.onload = function () {
  init();
};

Everything is pretty much self explanatory right now. window.onload function starts the party by being invoked automatically right when the window is loaded i.e. when the DOM is ready and images/scripts/links/otherstuff are loaded. In init() we're gonna put things related to initialization later. Finally mainLoop() is the lifecycle function. It will be invoked each time a browser is ready to redraw the screen. Basically we ask browser to schedule some drawing by calling requestAnimationFrame() - a native function that allows us to get access to browser's vsync. After you launch this, animation will be going already. It won't draw anything though so lets initiate our canvas and start drawing something:

  var world = {
  cnv: null,    //canvas
  ctx: null,    //drawing context
  gravity: 1000,
  width: 730,
  height: 310,

  init: function () {
    this.cnv = document.querySelector('canvas');
    this.cnv.width = this.width;
    this.cnv.height = this.height;
    this.ctx = this.cnv.getContext('2d');
  },

  clear: function () {
    this.ctx.fillStyle = 'white';
    this.ctx.fillRect(0, 0, this.cnv.width, this.cnv.height);
  }
};

function update () {};

function render () {
  world.clear();
};

function mainLoop () {
  requestAnimationFrame(mainLoop);
  update();
  render();
};

function init () {
  world.init();
  requestAnimationFrame(mainLoop);
};

We've created a world object. It holds a gravity value, a reference to our canvas and its drawing context. As for now it has init() method which looks up for canvas element in DOM, sets sizes for it and requests its drawing context. clear() method simply draws big white rectangle sizes of canvas at the coordinate's system origin (0,0 position). Note that we also added two global functions update() and render(). They are both called in mainLoop() and will be used to update and draw our objects respectively.

Now lets create our player:

  var player = {
  x: 100,
  y: 100,
  vx: 0,
  vy: 0,
  width: 50,
  height: 50,

  update: function (dt) {},

  render: function (ctx) {}
};

To track the object in 2d space we need 2 coordinates x and y. We also need variables to take care of it's motion speed, that is vx and vy are for. The update(dt) method is required to refresh player's position and render(ctx) to draw it. The dt parameter is the difference in time since the previous frame, we'll get to it in a moment, and the ctx is the canvas's drawing context. So to make the actual gravity work we need to change our update a little bit:

  var player = {
...
  update: function (dt) {
    this.vy += world.gravity * dt;
    this.y += this.vy * dt;

    //for demo only
    if(this.y >= world.height) {
      this.y = 100;
      this.vy = 0;
    }
    //
  },
...
}

Now let's think for a moment. Before drawing each frame we're going inside of update function. Parameter dt as I said before is the time since previous frame. In case we have solid 60 frames per second performance dt will be ~16 milliseconds. You might be wandering - why the heck we need that dt at all? Well the point is that with dt you can sync positions of the objects in space ragardles of computer's performance. In other words a minute later the position of the object will be the same whether it's 60, 30 or 10 FPS. Besides with dt at hands you can use any physics equations you want!

Lets draw our player:

  var player = {
...
  render: function (ctx) {
    ctx.fillStyle = '#00DCFF';
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
...
}

All we need to do now is to invoke player's methods in global update and render functions:

  function update (dt) {
    player.update(dt);
};

function render () {
  world.clear();
  player.render(world.ctx);
};

function mainLoop () {
  requestAnimationFrame(mainLoop);
  update(0.016);
  render();
};

Notice 0.016 passed to the update. It's those 16ms I told you before (0.016 seconds = 16ms). If you followed this article so far you should be able to see falling square by now. If so, you can say YAY GRAVITY!

Take a closer look and see how smooth it falls. It starts with zero speed and slowly accelerates as the time goes. You can also play around with world.gravity to get a different acceleration rate.

Bumping into walls

It would be boring to left this thing in it's current state. Lets add a bit more. First of all we need to properly take care of 0.016:

  var world = {
...
 prevTime: 0,

 init: function () {
   ...
   this.prevTime = new Date().getTime() / 1000;
   ...
 }
...
};

function mainLoop () {
  requestAnimationFrame(mainLoop);
  var now = new Date().getTime() / 1000;
  var dt = Math.min(now - world.prevTime, .1);
  update(dt);
  render();
  world.prevTime = now;
};


As I said before dt is the time since the previous frame. Note that in now we place Date().getTime() devided by 1000 which is the unix timestamp or time in seconds since January 1st 1970 till present moment. In this case dt becomes "how much seconds since the last frame". With seconds you can build up your physics using pixels per second thinking to describe velocities. Also note that dt is limited by 0.1. You see when you switch to other browser tab, screen refresh stops. So once you switch back, time since last frame will be so large it'll cause undesired behavior.

Let's also add some basic collision checks for screen boundaries like this:

  function handleCollisions () {
  if(player.y + player.height >= world.height) {
    player.grounded = true;
    player.vy = 0;
  }

  player.y = Math.min(Math.max(0, player.y), world.height - player.height);
  player.x = Math.min(Math.max(0, player.x), world.width - player.width);
};

function mainLoop () {
  ...
  update(dt);
  handleCollisions();
  render();
  ...
};


Note that handleCollisions() is being called after update(dt). It is necessary to move our player first and then check if it penetrates some boundaries so we can fix it. Now when everything is okay we render() it:

Controls

So it's kind of safe there now and if we had some control over player we would be able to move around. Let's do this:

  var keyboard = {
  KEYS: {
    LEFT: 37,
    RIGHT: 39,
    SPACE: 32
  },

  pressed: [],

  init: function () {
    document.addEventListener('keydown', this.onkeydown.bind(this));
    document.addEventListener('keyup', this.onkeyup.bind(this));
  },

  onkeydown: function (e) {
    this.pressed[e.which] = true;
  },

  onkeyup: function (e) {
    this.pressed[e.which] = false;
  },
};

function init () {
  keyboard.init();
  world.init();
  requestAnimationFrame(mainLoop);
};


Our keyboard module has everything we need to track keyboard interactions. There is a KEYS dictionarry that allows us to reference keycodes in a convenient, human readable way. There is also a pressed array that tracks which key is pressed. Because our system is simply an infinite loop that reacts to input we need both keyup and keydown events. In that case system is aware whether the key is pressed or released. To make it work we add event listeners in keyboard.init() function. Note that handler function itself is passed after .bind() method is applied. It is done only to have this binded to keyboard inside of handler. Otherwise this will reference the document. Also note how we use pressed array. In JavaScript when you set values in an empty array in a way arr[10] = 'something'; memory is being allocated only for that particular element. So we can efficiently store keycodes even if they're far enough from each other.

Now let's teach our system to react to keyboard interactions. Player's update function should look like this:

    update: function (dt) {
    if(keyboard.pressed[keyboard.KEYS.LEFT]) {
      this.vx = -200;
    }

    if(keyboard.pressed[keyboard.KEYS.RIGHT]) {
      this.vx = 200;
    }

    if(this.grounded && keyboard.pressed[keyboard.KEYS.SPACE]) {
      this.vy = -600;
    }

    this.vy += world.gravity * dt;
    this.y += this.vy * dt;
    this.x += this.vx * dt;
    this.vx = 0;
    this.grounded = false;
  },

That's it. We simply change player's velocities depending on which key is pressed right now then equations take care of the rest. Note that it's necessary to reset vx and grounded values after they were used in order our logic to work properly once we get to next cycle.

There you have it. Jumping blue square with basic gravity.

I hope this article was interesting or at least usefull for you. Try to play around with this code. It is pretty much an extendable system already so you can add more objects or simply tweak physics.

Have fun!