Simple html5+javascript game flickering

1.5k views Asked by At

I just started learning javascript, and i already faced some problems. I have a very simple "game" where you move red box with arrow keys and there is black background. I saw some topics about the same question and some of the guys says that make a buffer and other half says that don't do buffer because your browser does it for you. But anyway, I just thought that there is something wrong in my code because it's flickering but it's just so simple that i think it should not.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

canvas.width = 800;
canvas.height = 600;

var mySprite = {
    x: 200,
    y: 200,
    width: 50,
    height: 50,
    color: '#c00'
};
var yvel = 0;
var xvel = 0 ;
var moved = false;
var mspeed = 15;

var keysDown = {};
window.addEventListener('keydown', function(e) {
    keysDown[e.keyCode] = true;
});
window.addEventListener('keyup', function(e) {
    delete keysDown[e.keyCode];
});

function update() {
    if (37 in keysDown) {
    moved = true;
    if(xvel > -mspeed){
        xvel -= 0.5;
        }
    }
    if (38 in keysDown) {
    moved = true;
        if(yvel > -mspeed){
        yvel -= 0.5
        }
    }
    if (39 in keysDown) {
    moved = true;
        if(xvel < mspeed){
        xvel += 0.5
        }
    }
    if (40 in keysDown) {
    moved = true;
        if(yvel < mspeed){
        yvel += 0.5
        }
    }
    mySprite.x += xvel;
    mySprite.y += yvel;
    if(moved == false){
        xvel = xvel * 0.95;
        yvel = yvel * 0.95;
        }
    moved = false;

}

function render() {
    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = mySprite.color;
    ctx.fillRect(mySprite.x, mySprite.y, mySprite.width, mySprite.height); 
}

function run() {
    update();   
    render();
}

setInterval(run, 1000/60);

You can try it here:w3school Just copy my code and put it inside tags and change the "MyCanvas" name to "canvas"

1

There are 1 answers

9
GameAlchemist On BEST ANSWER

The double buffering is in fact allready activated on major browsers. What you need to do is to get in sync with the screen, that's what's provide requestAnimationFrame :
https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame

here's the polyfill for requestAnimationFrame :

requestAnimationFrame (rAF) is not standard yet : every 'vendor' i.e. browser (Chrome, FF, Safari, ...) has its own naming.
A polyfill is a piece of code that will 'install' a function in your environement whatever the vendor. Here you will be able to access requestAnimationFrame using window.requestAnimationFrame and you will not care any more about the vendor.
The || operator acts as a 'scan' : it will stop on the first 'truthy' ( == true) expression and return it without evaluating the remaining ones. So the last function definition, for instance, won't get evaluated if msRequestAnimationFrame is defined.
I have to add that the fallback in case requestAnimationFrame is not found is awfull : setTimeout has a very poor accuracy, but thankfully rAF will be found : it is allready there on Chrome(without prefix), Safari (webkit), IE >9 (ms), Firefox (moz) and Opera (o). )

// requestAnimationFrame polyfill
var  w=window, foundRequestAnimationFrame  =    w.requestAnimationFrame ||
                w.webkitRequestAnimationFrame || w.msRequestAnimationFrame ||
                w.mozRequestAnimationFrame    || w.oRequestAnimationFrame  ||
                      function(cb) { setTimeout(cb,1000/60); } ;
window.requestAnimationFrame  = foundRequestAnimationFrame ;

and after exchanging update and render (way better), your run loop will be :

function run() {
   render();
   update();   
   window.requestAnimationFrame(run);
}

run();

in any case you are interested, i discussed the javascript game loop here : http://gamealchemist.wordpress.com/2013/03/16/thoughts-on-the-javascript-game-loop/

Why would you need a timer ? the code above will call run again and again, since run ends by a defered call to run : run will be called next time display is available (typically 60 times per seconds). And you can know about the current time with Date.now().

For more insights about the game loop, rAF, and some time handling issues, see my link above.

Here is your code after some refactoring :

(fiddle is here : http://jsbin.com/uvACEVA/2/edit )

var canvasWidth = 800, canvasHeight = 600;

var canvas = document.getElementById('canvas');
canvas.width = canvasWidth;   canvas.height = canvasHeight;
var ctx = canvas.getContext('2d');

// player definition

var mySprite = {
    x: 200,
    y: 200,
    xvel : 0,
    yvel : 0,
    velIncr : 0.5,
    velAttenuation : 0.95,
    maxVel : 15,
    width: 50,
   height: 50,
   color: '#c00',
  moved : false
};

mySprite.update = function (dt) {
  if (keysDown[37]) {
     this.moved = true;
     if(this.xvel > -this.maxVel){
        this.xvel -= this.velIncr;
      }
  }
  if (keysDown[38]) {
      this.moved = true;
      if(this.yvel > -this.maxVel){
         this.yvel -= this.velIncr;
      }
  }
  if (keysDown[39]) {
      this.moved = true;
      if(this.xvel < this.maxVel){
       this.xvel += this.velIncr;
      }
  }
  if (keysDown[40]) {
      this.moved = true;
      if(this.yvel < this.maxVel){
         this.yvel += this.velIncr;
      }
   }
                     // to have a frame-rate independant game,
                     // compute the real dt in run() and ...
  this.x += this.xvel; // ... use this.x += this.xvel * dt;
  this.y += this.yvel; // ...     this.y += this.yvel * dt;

  if(this.moved == false){
    this.xvel *= this.velAttenuation;
    this.yvel *= this.velAttenuation;
  }
  this.moved = false;
};

mySprite.draw = function(ctx) {
   ctx.fillStyle = this.color;
   ctx.fillRect(this.x, this.y, this.width, this.height); 
};

// Keyboard handling

var keysDown = [];

window.addEventListener('keydown', function(e) {
     keysDown[e.keyCode] = true;
     e.preventDefault();
     e.stopPropagation();
});

window.addEventListener('keyup', function(e) {
     keysDown[e.keyCode] = false;
     e.preventDefault();
     e.stopPropagation();
 });

// requestAnimationFrame polyfill

var  w=window, foundRequestAnimationFrame  =    w.requestAnimationFrame ||
                w.webkitRequestAnimationFrame || w.msRequestAnimationFrame ||
                w.mozRequestAnimationFrame    || w.oRequestAnimationFrame  ||
                      function(cb) { setTimeout(cb,1000/60); } ;
window.requestAnimationFrame  = foundRequestAnimationFrame ;


// main animation loop

function update(dt) {
    mySprite.update(dt); // only one for the moment.
}

function render(ctx) {
    ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    mySprite.draw(ctx);
}

var dt = 16;  // milliseconds elapsed since last call.
              // ideally you should compute it in run(), i didn't for simplicity
function run() {
   render(ctx);
   update(dt);   
   window.requestAnimationFrame(run);
}

run();