SlideShare une entreprise Scribd logo
1  sur  65
Télécharger pour lire hors ligne
Asteroid(s)*


                         with HTML5 Canvas




(c) 2012 Steve Purkis




                                        * Asteroids™ is a trademark of Atari Inc.
About Steve

Software Dev + Manager
  not really a front-end dev
         nor a game dev
           (but I like to play!)
Uhh...
Asteroids?


                      What’s that, then?




             http://www.flickr.com/photos/onkel_wart/201905222/
Asteroids™




      http://en.wikipedia.org/wiki/File:Asteroi1.png
Asteroids™
“ Asteroids is a video arcade game released in November 1979 by Atari
Inc. It was one of the most popular and influential games of the Golden
Age of Arcade Games, selling 70,000 arcade cabinets.[1] Asteroids uses
a vector display and a two-dimensional view that wraps around in both
screen axes. The player controls a spaceship in an asteroid field which
is periodically traversed by flying saucers. The object of the game is to
shoot and destroy asteroids and saucers while not colliding with either,
or being hit by the saucers' counter-fire.”
                     http://en.wikipedia.org/wiki/Asteroids_(video_game)




                                                             http://en.wikipedia.org/wiki/File:Asteroi1.png
Asteroids™
Note that the term “Asteroids” is © Atari when used with a game.
              I didn’t know that when I wrote this...
                              Oops.


Atari:
• I promise I’ll change the name of the game.
• In the meantime, consider this free marketing! :-)
Why DIY Asteroid(s)?

• Yes, it’s been done before.
• Yes, I could have used a Game Engine.
• Yes, I could have used open|web GL.
• No, I’m not a “Not Invented Here” guy.
Why not?

• It’s a fun way to learn new tech.
• I learn better by doing.

   (ok, so I’ve secretly wanted to write my own version of asteroids since I was a kid.)




                                                                       I was learning about HTML5 (yes I know it came out several years ago. I’ve been busy).
                                                                       A few years ago, the only way you’d be able to do this is with Flash.
                                                                                                                                                          demo
It’s not Done...




     (but it’s playable)
It’s a Hack!




       http://www.flickr.com/photos/cheesygarlicboy/269419718/
It’s a Hack!


• No Tests (!)
• somewhat broken in IE
• mobile? what’s that?
• no sound, fancy gfx, ...
                         http://www.flickr.com/photos/cheesygarlicboy/269419718/
If you see this...




     you know what to expect.
What I’ll cover...

• Basics of canvas 2D (as I go)
• Overview of how it’s put together
• Some game mechanics
• Performance
What’s Canvas?
• “New” to HTML5!
• lets you draw 2D graphics:
 •   images, text

 •   vector graphics (lines, curves, etc)

 •   trades performance & control for convenience


• ... and 3D graphics (WebGL)
 •   still a draft standard
Drawing a Ship
•     Get canvas element

•     draw lines that make up the ship
    <body onload="drawShip()">
        <h1>Canvas:</h1>
        <canvas id="demo" width="300" height="200" style="border: 1px solid black" />
    </body>


           function drawShip() {
             var canvas = document.getElementById("demo");
             var ctx = canvas.getContext('2d');

               var center = {x: canvas.width/2, y: canvas.height/2};
               ctx.translate( center.x, center.y );

               ctx.strokeStyle = 'black';                                     Canvas is an element.
               ctx.beginPath();                                               You use one of its ‘context’ objects to draw to it.

               ctx.moveTo(0,0);                                               2D Context is pretty simple
               ctx.lineTo(14,7);
                                                                              Walk through ctx calls:
               ctx.lineTo(0,14);                                              •     translate: move “origin” to center of canvas
               ctx.quadraticCurveTo(7,7, 0,0);                                •     moveTo: move without drawing
               ctx.closePath();                                               •     lineTo: draw a line
                                                                              •     curve: draw a curve
               ctx.stroke();
                                                                                                                        demo v
           }
                                     syntax highlighting: http://tohtml.com                                                     ship.html
Moving it around
var canvas, ctx, center, ship;                                               function drawShip() {
                                                                               ctx.save();
function drawShipLoop() {                                                      ctx.clearRect( 0,0, canvas.width,canvas.height );
  canvas = document.getElementById("demo");                                    ctx.translate( ship.x, ship.y );
  ctx = canvas.getContext('2d');                                               ctx.rotate( ship.facing );
  center = {x: canvas.width/2, y: canvas.height/2};
  ship = {x: center.x, y: center.y, facing: 0};                                  ctx.strokeStyle = 'black';
                                                                                 ctx.beginPath();
    setTimeout( updateAndDrawShip, 20 );                                         ctx.moveTo(0,0);
}                                                                                ctx.lineTo(14,7);
                                                                                 ctx.lineTo(0,14);
function updateAndDrawShip() {                                                   ctx.quadraticCurveTo(7,7, 0,0);
  // set a fixed velocity:                                                       ctx.closePath();
  ship.y += 1;                                                                   ctx.stroke();
  ship.x += 1;
  ship.facing += Math.PI/360 * 5;                                                ctx.restore();
                                                                             }
    drawShip();

    if (ship.y < canvas.height-10) {
      setTimeout( updateAndDrawShip, 20 );
    } else {
      drawGameOver();
    }                                                                        function drawGameOver() {
}                                                                              ctx.save();
                       Introducing:                                            ctx.globalComposition = "lighter";
                       •     animation loop: updateAndDraw...                  ctx.font = "20px Verdana";
                       •     keeping track of an object’s co-ords              ctx.fillStyle = "rgba(50,50,50,0.9)";
                       •     velocity & rotation                               ctx.fillText("Game Over", this.canvas.width/2 - 50,
                       •     clearing the canvas                                            this.canvas.height/2);
                                                                               ctx.restore();
                       Don’t setInterval - we’ll get to that later.
                                                                             }
                       globalComposition - drawing mode, lighter so we
                       can see text in some scenarios.
                                                                  demo -->
                                                                                                                             ship-move.html
Controls
          Wow!
<div id="controlBox">
    <input id="controls" type="text"
     placeholder="click to control" autofocus="autofocus"/>
    <p>Controls:
                                                            super-high-tech solution:
         <ul class="controlsInfo">
                                                                • use arrow keys to control your ship
             <li>up/i: accelerate forward</li>                  • space to fire
             <li>down/k: accelerate backward</li>               • thinking of patenting it :)
             <li>left/j: spin ccw</li>
             <li>right/l: spin cw</li>
             <li>space: shoot</li>       $("#controls").keydown(function(event) {self.handleKeyEvent(event)});
             <li>w: switch weapon</li>   $("#controls").keyup(function(event) {self.handleKeyEvent(event)});
         </ul>
    </p>                                 AsteroidsGame.prototype.handleKeyEvent = function(event) {
</div>                                       // TODO: send events, get rid of ifs.
                                             switch (event.which) {
                                               case 73: // i = up
                                               case 38: // up = accel
                                                  if (event.type == 'keydown') {
                                                      this.ship.startAccelerate();
                                                  } else { // assume keyup
                                                      this.ship.stopAccelerate();
                                                  }
                                                  event.preventDefault();
                                                  break;
                                             ...


                                                                                                       AsteroidsGame.js
Controls: Feedback
• Lines: thrust forward, backward, or spin
          (think exhaust from a jet...)


• Thrust: ‘force’ in status bar.
 // renderThrustForward
 // offset from center of ship
 // we translate here before drawing
 render.x = -13;
 render.y = -3;

 ctx.strokeStyle = 'black';
 ctx.beginPath();
 ctx.moveTo(8,0);
 ctx.lineTo(0,0);
 ctx.moveTo(8,3);                            Thrust
 ctx.lineTo(3,3);
 ctx.moveTo(8,6);
 ctx.lineTo(0,6);
 ctx.closePath();
 ctx.stroke();
                                                      Ships.js
Status bars...
                    ... are tightly-coupled to Ships atm:
Ship.prototype.initialize = function(game, spatial) {
    // Status Bars
    // for displaying ship info: health, shield, thrust, ammo
    // TODO: move these into their own objects
    ...
    this.thrustWidth = 100;
    this.thrustHeight = 10;
    this.thrustX = this.healthX;
    this.thrustY = this.healthY + this.healthHeight + 5;
    this.thrustStartX = Math.floor( this.thrustWidth / 2 );
    ...

Ship.prototype.renderThrustBar = function() {
    var render = this.getClearThrustBarCanvas();
    var ctx = render.ctx;

    var   thrustPercent = Math.floor(this.thrust/this.maxThrust * 100);
    var   fillWidth = Math.floor(thrustPercent * this.thrustWidth / 100 / 2);
    var   r = 100;
    var   b = 200 + Math.floor(thrustPercent/2);
    var   g = 100;
    var   fillStyle = 'rgba('+ r +','+ g +','+ b +',0.5)';

    ctx.fillStyle = fillStyle;
    ctx.fillRect(this.thrustStartX, 0, fillWidth, this.thrustHeight);

    ctx.strokeStyle = 'rgba(5,5,5,0.75)';
    ctx.strokeRect(0, 0, this.thrustWidth, this.thrustHeight);

    this.render.thrustBar = render;
}

                                                                                http://www.flickr.com/photos/imelda/497456854/
Drawing an Asteroid
                                               (or planet)
 ctx.beginPath();
 ctx.arc(this.radius, this.radius, this.radius, 0, deg_to_rad[360], false);
 ctx.closePath()
 if (this.fillStyle) {
   ctx.fillStyle = this.fillStyle;
   ctx.fill();
 } else {
   ctx.strokeStyle = this.strokeStyle;
   ctx.stroke();
 }




Show: cookie cutter level                                                        Planets.js
                                                                              Compositing
Drawing an Asteroid
                                              (or planet)
 ctx.beginPath();
 ctx.arc(this.radius, this.radius, this.radius, 0, deg_to_rad[360], false);
 ctx.closePath()
 if (this.fillStyle) {
   ctx.fillStyle = this.fillStyle;
   ctx.fill();
 } else {
   ctx.strokeStyle = this.strokeStyle;
   ctx.stroke();
 }                                  // Using composition as a cookie cutter:
                                  if (this.image != null) {
                                    this.render = this.createPreRenderCanvas(this.radius*2, this.radius*2);
                                    var ctx = this.render.ctx;

                                      // Draw a circle to define what we want to keep:
                                      ctx.globalCompositeOperation = 'destination-over';
                                      ctx.beginPath();
                                      ctx.arc(this.radius, this.radius, this.radius, 0, deg_to_rad[360], false);
                                      ctx.closePath();
                                      ctx.fillStyle = 'white';
                                      ctx.fill();

                                      // Overlay the image:
                                      ctx.globalCompositeOperation = 'source-in';
                                      ctx.drawImage(this.image, 0, 0, this.radius*2, this.radius*2);
                                      return;
                                  }


Show: cookie cutter level                                                                                          Planets.js
                                                                                                               Compositing
Space Objects

•   DRY                                               Planet           Asteroid


•   Base class for all things spacey.

•   Everything is a circle.                Ship                Planetoid




                                        SpaceObject
Err, why is everything a
         circle?
•   An asteroid is pretty much a circle, right?

•   And so are planets...

•   And so is a Ship with a shield around it! ;-)


•   ok, really:
    •   Game physics can get complicated.

    •   Keep it simple!
Space Objects
have...                             can...

 •        radius                     •       draw themselves

 •        coords: x, y               •       update their positions

 •        facing angle               •       accelerate, spin

 •        velocity, spin, thrust     •       collide with other objects

 •        health, mass, damage       •       apply damage, die

 •        and a whole lot more...    •       etc...




                                                                          SpaceObject.js
Game Mechanics



  what makes the game feel right.
Game Mechanics



        what makes the game feel right.




This is where it gets hairy.
                                          http://www.flickr.com/photos/maggz/2860543788/
The god class...

• AsteroidsGame does it all!
 •   user controls, game loop, 95% of the game mechanics ...

 •   Ok, so it’s not 5000 lines long (yet), but...

 •   it should really be split up!




                                                               AsteroidsGame.js
Game Mechanics
•   velocity & spin

•   acceleration & drag

•   gravity

•   collision detection, impact, bounce

•   health, damage, life & death

•   object attachment & push

•   out-of-bounds, viewports & scrolling
Velocity & Spin
SpaceObject.prototype.initialize = function(game,       SpaceObject.prototype.updateX = function(dX) {
spatial) {                                                if (this.stationary) return false;
   ...                                                    if (dX == 0) return false;
                                                          this.x += dX;
    this.x = 0;      // starting position on x axis       return true;
    this.y = 0;      // starting position on y axis     }
    this.facing = 0; // currently facing angle (rad)
                                                        SpaceObject.prototype.updateY = function(dY) {
    this.stationary = false; // should move?              if (this.stationary) return false;
                                                          if (dY == 0) return false;
    this.vX = spatial.vX || 0; // speed along X axis      this.y += dY;
    this.vY = spatial.vY || 0; // speed along Y axis      return true;
    this.maxV = spatial.maxV || 2; // max velocity      }
    this.maxVSquared = this.maxV*this.maxV; // cache
                                                        SpaceObject.prototype.updateFacing = function(delta)
    // thrust along facing                              {
    this.thrust = spatial.initialThrust || 0;             if (delta == 0) return false;
    this.maxThrust = spatial.maxThrust || 0.5;            this.facing += delta;
    this.thrustChanged = false;
                                                            // limit facing   angle to 0 <= facing <= 360
    this.spin = spatial.spin || 0; // spin in Rad/sec       if (this.facing   >= deg_to_rad[360] ||
    this.maxSpin = deg_to_rad[10];                              this.facing   <= deg_to_rad[360]) {
}                                                             this.facing =   this.facing % deg_to_rad[360];
                                                            }
SpaceObject.prototype.updatePositions = function
(objects) {                                                 if (this.facing < 0) {
    ...                                                       this.facing = deg_to_rad[360] + this.facing;
    if (this.updateFacing(this.spin)) changed = true;       }
    if (this.updateX(this.vX)) changed = true;
    if (this.updateY(this.vY)) changed = true;              return true;
}                                                       }




velocity = ∆ distance / time
spin = angular velocity = ∆ angle / time
Velocity & Spin
SpaceObject.prototype.initialize = function(game,       SpaceObject.prototype.updateX = function(dX) {
spatial) {                                                if (this.stationary) return false;
   ...                                                    if (dX == 0) return false;
                                                          this.x += dX;
    this.x = 0;      // starting position on x axis       return true;
    this.y = 0;      // starting position on y axis     }
    this.facing = 0; // currently facing angle (rad)
                                                        SpaceObject.prototype.updateY = function(dY) {
    this.stationary = false; // should move?              if (this.stationary) return false;
                                                          if (dY == 0) return false;
    this.vX = spatial.vX || 0; // speed along X axis      this.y += dY;
    this.vY = spatial.vY || 0; // speed along Y axis      return true;
    this.maxV = spatial.maxV || 2; // max velocity      }
    this.maxVSquared = this.maxV*this.maxV; // cache
                                                        SpaceObject.prototype.updateFacing = function(delta)
    // thrust along facing                              {
    this.thrust = spatial.initialThrust || 0;             if (delta == 0) return false;
    this.maxThrust = spatial.maxThrust || 0.5;            this.facing += delta;
    this.thrustChanged = false;
                                                            // limit facing   angle to 0 <= facing <= 360
    this.spin = spatial.spin || 0; // spin in Rad/sec       if (this.facing   >= deg_to_rad[360] ||
    this.maxSpin = deg_to_rad[10];                              this.facing   <= deg_to_rad[360]) {
}                                                             this.facing =   this.facing % deg_to_rad[360];
                                                            }
SpaceObject.prototype.updatePositions = function
(objects) {                                                 if (this.facing < 0) {
    ...                                                       this.facing = deg_to_rad[360] + this.facing;
    if (this.updateFacing(this.spin)) changed = true;       }
    if (this.updateX(this.vX)) changed = true;
    if (this.updateY(this.vY)) changed = true;              return true;
}                                                       }




velocity = ∆ distance / time
spin = angular velocity = ∆ angle / time                where: time = current frame rate
Acceleration
SpaceObject.prototype.initialize = function(game, spatial) {
   ...
    // thrust along facing
    this.thrust = spatial.initialThrust || 0;
    this.maxThrust = spatial.maxThrust || 0.5;
    this.thrustChanged = false;
}

SpaceObject.prototype.accelerateAlong = function(angle, thrust) {
    var accel = thrust/this.mass;
    var dX = Math.cos(angle) * accel;
    var dY = Math.sin(angle) * accel;
    this.updateVelocity(dX, dY);
}




 acceleration = ∆ velocity / time
 acceleration = mass / force
Acceleration
SpaceObject.prototype.initialize = function(game, spatial) {
   ...
    // thrust along facing
    this.thrust = spatial.initialThrust || 0;                       Ship.prototype.startAccelerate = function() {
    this.maxThrust = spatial.maxThrust || 0.5;                          if (this.accelerate) return;
    this.thrustChanged = false;                                         this.accelerate = true;
}                                                                       //console.log("thrust++");

SpaceObject.prototype.accelerateAlong = function(angle, thrust) {        this.clearSlowDownInterval();
    var accel = thrust/this.mass;
    var dX = Math.cos(angle) * accel;                                    var self = this;
    var dY = Math.sin(angle) * accel;                                    this.incThrustIntervalId = setInterval(function(){
    this.updateVelocity(dX, dY);                                    !     self.increaseThrust();
}                                                                        }, 20); // real time
                                                                    };

                                                                    Ship.prototype.increaseThrust = function() {
                                                                        this.incThrust(this.thrustIncrement);
                                                                        this.accelerateAlong(this.facing, this.thrust);
   Ship.prototype.initialize = function(game, spatial) {            }
       ...

       spatial.mass = 10;                                           Ship.prototype.stopAccelerate = function() {
                                                                        //console.log("stop thrust++");
       // current state of user action:                                 if (this.clearIncThrustInterval())
       this.increaseSpin = false;                                   this.resetThrust();
       this.decreaseSpin = false;                                       this.startSlowingDown();
       this.accelerate = false;                                         this.accelerate = false;
       this.decelerate = false;                                     };
       this.firing = false;
                                                                    Ship.prototype.clearIncThrustInterval = function() {
       // for moving about:                                             if (! this.incThrustIntervalId) return false;
       this.thrustIncrement = 0.01;                                     clearInterval(this.incThrustIntervalId);
       this.spinIncrement = deg_to_rad[0.5];                            this.incThrustIntervalId = null;
       ...                                                              return true;
   }                                                                }

 acceleration = ∆ velocity / time
 acceleration = mass / force
Acceleration
SpaceObject.prototype.initialize = function(game, spatial) {
   ...
    // thrust along facing
    this.thrust = spatial.initialThrust || 0;                       Ship.prototype.startAccelerate = function() {
    this.maxThrust = spatial.maxThrust || 0.5;                          if (this.accelerate) return;
    this.thrustChanged = false;                                         this.accelerate = true;
}                                                                       //console.log("thrust++");

SpaceObject.prototype.accelerateAlong = function(angle, thrust) {        this.clearSlowDownInterval();
    var accel = thrust/this.mass;
    var dX = Math.cos(angle) * accel;                                    var self = this;
    var dY = Math.sin(angle) * accel;                                    this.incThrustIntervalId = setInterval(function(){
    this.updateVelocity(dX, dY);                                    !     self.increaseThrust();
}                                                                        }, 20); // real time
                                                                    };

                                                                    Ship.prototype.increaseThrust = function() {
                                                                        this.incThrust(this.thrustIncrement);
                                                                        this.accelerateAlong(this.facing, this.thrust);
   Ship.prototype.initialize = function(game, spatial) {            }
       ...

       spatial.mass = 10;                                           Ship.prototype.stopAccelerate = function() {
                                                                        //console.log("stop thrust++");
       // current state of user action:                                 if (this.clearIncThrustInterval())
       this.increaseSpin = false;                                   this.resetThrust();
       this.decreaseSpin = false;                                       this.startSlowingDown();
       this.accelerate = false;                                         this.accelerate = false;
       this.decelerate = false;                                     };
       this.firing = false;
                                                                    Ship.prototype.clearIncThrustInterval = function() {
       // for moving about:                                             if (! this.incThrustIntervalId) return false;
       this.thrustIncrement = 0.01;                                     clearInterval(this.incThrustIntervalId);
       this.spinIncrement = deg_to_rad[0.5];                            this.incThrustIntervalId = null;
       ...                                                              return true;
   }                                                                }

 acceleration = ∆ velocity / time                                   where: time = real time
 acceleration = mass / force                                        (just to confuse things)
Drag


         Yes, yes, there is no drag in outer space. Very clever.




I disagree.
Drag


         Yes, yes, there is no drag in outer space. Very clever.




                                                          http://nerdadjacent.deviantart.com/art/Ruby-Rhod-Supergreen-265156565
I disagree.
Drag
Ship.prototype.startSlowingDown = function() {          Ship.prototype.slowDown = function() {
    // console.log("slowing down...");                      var vDrag = 0.01;
    if (this.slowDownIntervalId) return;                    if (this.vX > 0) {
                                                        !     this.vX -= vDrag;
    var self = this;                                        } else if (this.vX < 0) {
    this.slowDownIntervalId = setInterval(function(){   !     this.vX += vDrag;
!    self.slowDown()                                        }
    }, 100); // eek! another hard-coded timeout!            if (this.vY > 0) {
}                                                       !     this.vY -= vDrag;
                                                            } else if (this.vY < 0) {
Ship.prototype.clearSlowDownInterval = function() {     !     this.vY += vDrag;
    if (! this.slowDownIntervalId) return false;            }
    clearInterval(this.slowDownIntervalId);
    this.slowDownIntervalId = null;                         if (Math.abs(this.vX) <= vDrag) this.vX = 0;
    return true;                                            if (Math.abs(this.vY) <= vDrag) this.vY = 0;
}
                                                            if (this.vX == 0 && this.vY == 0) {
                                                        !     // console.log('done slowing down');
                                                        !     this.clearSlowDownInterval();
                                                            }
                                                        }




Demo: accel + drag in blank level
Gravity
              var dvX_1 = 0, dvY_1 = 0;
              if (! object1.stationary) {
                var accel_1 = object2.cache.G_x_mass / physics.dist_squared;
                if (accel_1 > 1e-5) { // skip if it's too small to notice
                  if (accel_1 > this.maxAccel) accel_1 = this.maxAccel;
                  var angle_1 = Math.atan2(physics.dX, physics.dY);
                  dvX_1 = -Math.sin(angle_1) * accel_1;
                  dvY_1 = -Math.cos(angle_1) * accel_1;
                  object1.delayUpdateVelocity(dvX_1, dvY_1);
                }
              }

              var dvX_2 = 0, dvY_2 = 0;
              if (! object2.stationary) {
                var accel_2 = object1.cache.G_x_mass / physics.dist_squared;
                if (accel_2 > 1e-5) { // skip if it's too small to notice
                  if (accel_2 > this.maxAccel) accel_2 = this.maxAccel;
                  // TODO: angle_2 = angle_1 - PI?
                  var angle_2 = Math.atan2(-physics.dX, -physics.dY); // note the - signs
                  dvX_2 = -Math.sin(angle_2) * accel_2;
                  dvY_2 = -Math.cos(angle_2) * accel_2;
                  object2.delayUpdateVelocity(dvX_2, dvY_2);
                }
              }




force = G*mass1*mass2 / dist^2
acceleration1 = force / mass1
Collision Detection
AsteroidsGame.prototype.applyGamePhysicsTo = function(object1, object2) {
    ...
    var dX = object1.x - object2.x;
    var dY = object1.y - object2.y;

    // find dist between center of mass:
    // avoid sqrt, we don't need dist yet...
    var dist_squared = dX*dX + dY*dY;

    var total_radius = object1.radius + object2.radius;
    var total_radius_squared = Math.pow(total_radius, 2);

    // now check if they're touching:
    if (dist_squared > total_radius_squared) {
       // nope
    } else {
       // yep
       this.collision( object1, object2, physics );
    }

    ...                                                                     http://www.flickr.com/photos/wsmonty/4299389080/




          Aren’t you glad we stuck with circles?
Bounce


Formula:

 •   Don’t ask.
*bounce*
*bounce*
        !    // Thanks Emanuelle! bounce algorithm adapted from:
        !    // http://www.emanueleferonato.com/2007/08/19/managing-ball-vs-ball-collision-with-flash/
        !    collision.angle = Math.atan2(collision.dY, collision.dX);
        !    var magnitude_1 = Math.sqrt(object1.vX*object1.vX + object1.vY*object1.vY);
        !    var magnitude_2 = Math.sqrt(object2.vX*object2.vX + object2.vY*object2.vY);

        !    var direction_1 = Math.atan2(object1.vY, object1.vX);
        !    var direction_2 = Math.atan2(object2.vY, object2.vX);

        !    var   new_vX_1   =   magnitude_1*Math.cos(direction_1-collision.angle);
        !    var   new_vY_1   =   magnitude_1*Math.sin(direction_1-collision.angle);
        !    var   new_vX_2   =   magnitude_2*Math.cos(direction_2-collision.angle);
        !    var   new_vY_2   =   magnitude_2*Math.sin(direction_2-collision.angle);

        !    [snip]

        !    // bounce the objects:
        !    var final_vX_1 = ( (cache1.delta_mass * new_vX_1 + object2.cache.mass_x_2 * new_vX_2)
        !    !      !         / cache1.total_mass * this.elasticity );
        !    var final_vX_2 = ( (object1.cache.mass_x_2 * new_vX_1 + cache2.delta_mass * new_vX_2)
        !    !      !         / cache2.total_mass * this.elasticity );
        !    var final_vY_1 = new_vY_1 * this.elasticity;
        !    var final_vY_2 = new_vY_2 * this.elasticity;


        !    var   cos_collision_angle = Math.cos(collision.angle);
        !    var   sin_collision_angle = Math.sin(collision.angle);
        !    var   cos_collision_angle_halfPI = Math.cos(collision.angle + halfPI);
        !    var   sin_collision_angle_halfPI = Math.sin(collision.angle + halfPI);

        !    var vX1 = cos_collision_angle*final_vX_1 + cos_collision_angle_halfPI*final_vY_1;
        !    var vY1 = sin_collision_angle*final_vX_1 + sin_collision_angle_halfPI*final_vY_1;
        !    object1.delaySetVelocity(vX1, vY1);

        !    var vX2 = cos_collision_angle*final_vX_2 + cos_collision_angle_halfPI*final_vY_2;
        !    var vY2 = sin_collision_angle*final_vX_2 + sin_collision_angle_halfPI*final_vY_2;
        !    object2.delaySetVelocity(vX2, vY2);




Aren’t you *really* glad we stuck with circles?
Making it *hurt*
AsteroidsGame.prototype.collision = function(object1, object2, collision) {              Shield
  ...
  // “collision” already contains a bunch of calcs                                                                                  Health
  collision[object1.id] = {
    cplane: {vX: new_vX_1, vY: new_vY_1}, // relative to collision plane
    dX: collision.dX,
    dY: collision.dY,
    magnitude: magnitude_1
  }
  // do the same for object2
                                                  SpaceObject.prototype.collided = function(object, collision) {
  // let the objects fight it out                     this.colliding[object.id] = object;
  object1.collided(object2, collision);
  object2.collided(object1, collision);               if (this.damage) {
}                                                 !     var damageDone = this.damage;
                                                  !     if (collision.impactSpeed != null) {
                                                  !         damageDone = Math.ceil(damageDone * collision.impactSpeed);
                                                  !     }
                                                  !     object.decHealth( damageDone );
                                                      }
                                                  }

                                                SpaceObject.prototype.decHealth = function(delta) {
                                                    this.healthChanged = true;
                                                    this.health -= delta;
                                                    if (this.health <= 0) {
                                                !     this.health = -1;
                                                !     this.die();
                                                    }
Ship.prototype.decHealth = function(delta) {    }
    if (this.shieldActive) {                                             When a collision occurs the Game Engine fires off 2 events to the objects in
!     delta = this.decShield(delta);                                     question
    }                                                                    •      For damage, I opted for a property rather than using mass * impact
    if (delta) Ship.prototype.parent.decHealth.call(this, delta);               speed in the general case.
}                                                                        Applying damage is fairly straightforward:
                                                                         •      Objects are responsible for damaging each other
                                                                         •      When damage is done dec Health (for a Ship, shield first)
                                                                         •      If health < 0, an object dies.
                                                                                                                                           SpaceObject.js
Object Lifecycle
       SpaceObject.prototype.die = function() {
           this.died = true;
           this.update = false;
           this.game.objectDied( this );
       }


AsteroidsGame.prototype.objectDied = function(object) {
    // if (object.is_weapon) {
    //} else if (object.is_asteroid) {                        Asteroid.prototype.die = function() {
                                                                this.parent.die.call( this );
    if (object.is_planet) {                                     if (this.spawn <= 0) return;
!     throw "planet died!?"; // not allowed                     for (var i=0; i < this.spawn; i++) {
    } else if (object.is_ship) {                                  var mass = Math.floor(this.mass / this.spawn * 1000)/1000;
!     // TODO: check how many lives they've got                   var radius = getRandomInt(2, this.radius);
!     if (object == this.ship) {                                  var asteroid = new Asteroid(this.game, {
!         this.stopGame();                                            mass: mass,
!     }                                                               x: this.x + i/10, // don't overlap
                                                                      y: this.y + i/10,
    }                                                                 vX: this.vX * Math.random(),
                                                                      vX: this.vY * Math.random(),
    this.removeObject(object);                                        radius: radius,
}                                                                     health: getRandomInt(0, this.maxSpawnHealth),
                                                                      spawn: getRandomInt(0, this.spawn-1),
AsteroidsGame.prototype.removeObject = function(object) {             image: getRandomInt(0, 5) > 0 ? this.image : null,
    var objects = this.objects;                                       // let physics engine handle movement
                                                                  });
    var i = objects.indexOf(object);                              this.game.addObject( asteroid );
    if (i >= 0) {                                               }
!     objects.splice(i,1);                                    }
!     this.objectUpdated( object );
    }

    // avoid memory bloat: remove references to this object   AsteroidsGame.prototype.addObject = function(object) {
    // from other objects' caches:                                //console.log('adding ' + object);
    var oid = object.id;                                          this.objects.push( object );
    for (var i=0; i < objects.length; i++) {                      this.objectUpdated( object );
!     delete objects[i].cache[oid];                               object.preRender();
    }                                                             this.cachePhysicsFor(object);
}                                                             }
Attachment
• Attach objects that are ‘gently’ touching
  •   then apply special physics

• Why?



                                              AsteroidsGame.js
Attachment
• Attach objects that are ‘gently’ touching
  •   then apply special physics

• Why?
  Prevent the same collision from recurring.
                     +
            Allows ships to land.
                     +
              Poor man’s Orbit.
                                               AsteroidsGame.js
Push!

         • When objects get too close
          • push them apart!
          • otherwise they overlap...
                            (and the game physics gets weird)




demo: what happens when you disable applyPushAway()
Out-of-bounds
                       When you have a map that is not wrapped...
                                                                                             Simple strategy:
                                                                                               •   kill most objects that stray
                                                                                               •   push back important things like ships

AsteroidsGame.prototype.applyOutOfBounds = function(object) {
    if (object.stationary) return;                                  ...

    var level = this.level;                                         if (object.y < 0) {
    var die_if_out_of_bounds =                                  !     if (level.wrapY) {
        !(object.is_ship || object.is_planet);                  !         object.setY(level.maxY + object.y);
                                                                !     } else {
    if (object.x < 0) {                                         !         if (die_if_out_of_bounds && object.vY < 0) {
!     if (level.wrapX) {                                        !     !     return object.die();
!         object.setX(level.maxX + object.x);                   !         }
!     } else {                                                  !         // push back into bounds
!         if (die_if_out_of_bounds && object.vX < 0) {          !         object.updateVelocity(0, 0.1);
!     !     return object.die();                                !     }
!         }                                                         } else if (object.y > level.maxY) {
!         object.updateVelocity(0.1, 0);                        !     if (level.wrapY) {
!     }                                                         !         object.setY(object.y - level.maxY);
    } else if (object.x > level.maxX) {                         !     } else {
!     if (level.wrapX) {                                        !         if (die_if_out_of_bounds && object.vY > 0) {
!         object.setX(object.x - level.maxX);                   !     !     return object.die();
!     } else {                                                  !         }
!         if (die_if_out_of_bounds && object.vX > 0) {          !         // push back into bounds
!     !     return object.die();                                !         object.updateVelocity(0, -0.1);
!         }                                                     !     }
!         object.updateVelocity(-0.1, 0);                           }
!     }                                                         }
    }
    ...
Viewport + Scrolling
                      When the dimensions of your map exceed
                              those of your canvas...


AsteroidsGame.prototype.updateViewOffset = function() {
    var canvas = this.ctx.canvas;
    var offset = this.viewOffset;
    var dX = Math.round(this.ship.x - offset.x - canvas.width/2);
    var dY = Math.round(this.ship.y - offset.y - canvas.height/2);

    // keep the ship centered in the current view, but don't let the view
    // go out of bounds
    offset.x += dX;
    if (offset.x < 0) offset.x = 0;
    if (offset.x > this.level.maxX-canvas.width) offset.x = this.level.maxX-canvas.width;

    offset.y += dY;
    if (offset.y < 0) offset.y = 0;
    if (offset.y > this.level.maxY-canvas.height) offset.y = this.level.maxY-canvas.height;
}


                                                                       AsteroidsGame.prototype.redrawCanvas = function() {
                                                                       ...
                                                                           // shift view to compensate for current offset
                                                                           var offset = this.viewOffset;
                                                                           ctx.save();
           Let browser manage complexity: if you draw to canvas            ctx.translate(-offset.x, -offset.y);
           outside of current width/height, browser doesn’t draw it.
Putting it all together




Demo: hairballs & chainsaws level.
Weapons
Gun + Bullet
              •       Gun                                                                     Ammo

                    •       Fires Bullets

                    •       Has ammo
                                                                                                  Planet             Astero
                    •       Belongs to a Ship

                                                                              Gun
                                                                     Bullet            Ship                Planetoid




                                                                                    SpaceObject
When you shoot, bullets inherit the Ship’s velocity.
Each weapon has a different recharge rate (measured in real time).
                                                                                                                 Weapons.js
Other Weapons
• Gun
• SprayGun
• Cannon
• GrenadeCannon
• GravBenda™
                      (back in my day, we
                      used to read books!)




    Current weapon
Enemies.




(because shooting circles gets boring)
A basic enemy...

                       ComputerShip.prototype.findAndDestroyClosestEnemy = function() {
                           var enemy = this.findClosestEnemy();
                           if (enemy == null) return;

                           // Note: this is a basic algorith, it doesn't take a lot of things
                           // into account (enemy trajectory & facing, other objects, etc)

                           // navigate towards enemy
                           // shoot at the enemy
                       }




Demo: Level Lone enemy.
Show: ComputerShip class...                                                                     Ships.js
A basic enemy...

                       ComputerShip.prototype.findAndDestroyClosestEnemy = function() {
                           var enemy = this.findClosestEnemy();
                           if (enemy == null) return;

                           // Note: this is a basic algorith, it doesn't take a lot of things
                           // into account (enemy trajectory & facing, other objects, etc)

                           // navigate towards enemy
                           // shoot at the enemy
                       }




                 of course, it’s a bit more involved...


Demo: Level Lone enemy.
Show: ComputerShip class...                                                                     Ships.js
Levels
• Define:
 • map dimensions
 • space objects
 • spawning
 • general properties of the canvas - color,
    etc


                                               Levels.js
/******************************************************************************
 * TrainingLevel: big planet out of field of view with falling asteroids.
 */

function TrainingLevel(game) {
    if (game) return this.initialize(game);
    return this;
}

TrainingLevel.inheritsFrom( Level );
TrainingLevel.description = "Training Level - learn how to fly!";
TrainingLevel.images = [ "planet.png", "planet-80px-green.png" ];

gameLevels.push(TrainingLevel);

TrainingLevel.prototype.initialize = function(game) {
    TrainingLevel.prototype.parent.initialize.call(this, game);
    this.wrapX = false;
    this.wrapY = false;

    var maxX = this.maxX;
    var maxY = this.maxY;

    var canvas = this.game.ctx.canvas;
    this.planets.push(
!   {x: 1/2*maxX, y: 1/2*maxY, mass: 100, radius: 50, damage: 5, stationary: true, image_src: "planet.png" }
!   , {x: 40, y: 40, mass: 5, radius: 20, vX: 2, vY: 0, image_src:"planet-80px-green.png"}
!   , {x: maxX-40, y: maxY-40, mass: 5, radius: 20, vX: -2, vY: 0, image_src:"planet-80px-green.png"}
    );

    this.ships.push(
!   {x: 4/5*canvas.width, y: 1/3*canvas.height}
    );

    this.asteroids.push(
!   {x: 1/10*maxX, y: 6/10*maxY, mass: 0.5, radius: 14, vX: 0, vY: 0, spawn: 1, health: 1},
        {x: 1/10*maxX, y: 2/10*maxY, mass: 1, radius: 5, vX: 0, vY: -0.1, spawn: 3 },
        {x: 5/10*maxX, y: 1/10*maxY, mass: 2, radius: 6, vX: -0.2, vY: 0.25, spawn: 4 },
        {x: 5/10*maxX, y: 2/10*maxY, mass: 3, radius: 8, vX: -0.22, vY: 0.2, spawn: 7 }
    );
}

                        As usual, I had grandiose plans of an interactive level editor... This was all I had time for.
                                                                                                                         Levels.js
Performance


“Premature optimisation is the root of all evil.”
     http://c2.com/cgi/wiki?PrematureOptimization
Use requestAnimationFrame


Paul Irish knows why:
• don’t animate if your canvas is not visible
• adjust your frame rate based on actual performance
• lets the browser manage your app better
Profile your code
•   profile in different browsers
•   identify the slow stuff
•   ask yourself: “do we really need to do this?”
•   optimise it?
    • cache slow operations
    • change algorithm?
    • simplify?
Examples...
// see if we can use cached values first:                                              // put any calculations we can avoid repeating here
var g_cache1 = physics.cache1.last_G;                                                  AsteroidsGame.prototype.cachePhysicsFor = function(object1) {
var g_cache2 = physics.cache2.last_G;                                                      for (var i=0; i < this.objects.length; i++) {
                                                                                       !      var object2 = this.objects[i];
if (g_cache1) {                                                                        !      if (object1 == object2) continue;
  var delta_dist_sq = Math.abs( physics.dist_squared - g_cache1.last_dist_squared);
  var percent_diff = delta_dist_sq / physics.dist_squared;                             !       // shared calcs
  // set threshold @ 5%                                                                !       var total_radius = object1.radius + object2.radius;
  if (percent_diff < 0.05) {                                                           !       var total_radius_squared = Math.pow(total_radius, 2);
    // we haven't moved much, use last G values                                        !       var total_mass = object1.mass + object2.mass;
    //console.log("using G cache");
    object1.delayUpdateVelocity(g_cache1.dvX, g_cache1.dvY);                           !       // create separate caches from perspective of objects:
    object2.delayUpdateVelocity(g_cache2.dvX, g_cache2.dvY);                           !       object1.cache[object2.id] = {
    return;                                                                            !           total_radius: total_radius,
  }                                                                                    !           total_radius_squared: total_radius_squared,
}                                                                                      !           total_mass: total_mass,
                                                                                       !           delta_mass: object1.mass - object2.mass
                                                                                       !       }
   // avoid overhead of update calculations & associated checks: batch together        !       object2.cache[object1.id] = {
   SpaceObject.prototype.delayUpdateVelocity = function(dvX, dvY) {                    !           total_radius: total_radius,
       if (this._updates == null) this.init_updates();                                 !           total_radius_squared: total_radius_squared,
       this._updates.dvX += dvX;                                                       !           total_mass: total_mass,
       this._updates.dvY += dvY;                                                       !           delta_mass: object2.mass - object1.mass
   }                                                                                   !       }
                                                                                           }
                                                                                       }

      var dist_squared = dX*dX + dY*dY; // avoid sqrt, we don't need dist yet




                                            this.maxVSquared = this.maxV*this.maxV;   // cache for speed


                                             if (accel_1 > 1e-5) { // skip if it's too small to notice



                                                                        ...
Performance
Great Ideas from Boris Smus:
•   Only redraw changes
•   Pre-render to another canvas
•   Draw background in another canvas / element
•   Don't use floating point co-ords
    ... and more ...
Can I Play?


http://www.spurkis.org/asteroids/
See Also...
Learning / examples:
 •       https://developer.mozilla.org/en-US/docs/Canvas_tutorial
 •       http://en.wikipedia.org/wiki/Canvas_element
 •       http://www.html5rocks.com/en/tutorials/canvas/performance/
 •       http://www.canvasdemos.com
 •       http://billmill.org/static/canvastutorial/
 •       http://paulirish.com/2011/requestanimationframe-for-smart-animating/
Specs:
 •       http://dev.w3.org/html5/spec/
 •       http://www.khronos.org/registry/webgl/specs/latest/
Questions?

Contenu connexe

Tendances

HTML5 Canvas - The Future of Graphics on the Web
HTML5 Canvas - The Future of Graphics on the WebHTML5 Canvas - The Future of Graphics on the Web
HTML5 Canvas - The Future of Graphics on the WebRobin Hawkes
 
MongoDB Live Hacking
MongoDB Live HackingMongoDB Live Hacking
MongoDB Live HackingTobias Trelle
 
Introduction to HTML5 Canvas
Introduction to HTML5 CanvasIntroduction to HTML5 Canvas
Introduction to HTML5 CanvasMindy McAdams
 
Gpu programming with java
Gpu programming with javaGpu programming with java
Gpu programming with javaGary Sieling
 
Introduction to CUDA C: NVIDIA : Notes
Introduction to CUDA C: NVIDIA : NotesIntroduction to CUDA C: NVIDIA : Notes
Introduction to CUDA C: NVIDIA : NotesSubhajit Sahu
 
Intro to Clojure's core.async
Intro to Clojure's core.asyncIntro to Clojure's core.async
Intro to Clojure's core.asyncLeonardo Borges
 
Html5 canvas
Html5 canvasHtml5 canvas
Html5 canvasGary Yeh
 
CUDA Raytracing을 이용한 Voxel오브젝트 가시성 테스트
CUDA Raytracing을 이용한 Voxel오브젝트 가시성 테스트CUDA Raytracing을 이용한 Voxel오브젝트 가시성 테스트
CUDA Raytracing을 이용한 Voxel오브젝트 가시성 테스트YEONG-CHEON YOU
 
Box2D with SIMD in JavaScript
Box2D with SIMD in JavaScriptBox2D with SIMD in JavaScript
Box2D with SIMD in JavaScriptIntel® Software
 
Trident International Graphics Workshop 2014 1/5
Trident International Graphics Workshop 2014 1/5Trident International Graphics Workshop 2014 1/5
Trident International Graphics Workshop 2014 1/5Takao Wada
 
Introduction to cuda geek camp singapore 2011
Introduction to cuda   geek camp singapore 2011Introduction to cuda   geek camp singapore 2011
Introduction to cuda geek camp singapore 2011Raymond Tay
 
WebGL and three.js - Web 3D Graphics
WebGL and three.js - Web 3D Graphics WebGL and three.js - Web 3D Graphics
WebGL and three.js - Web 3D Graphics PSTechSerbia
 
WebGL - 3D in your Browser
WebGL - 3D in your BrowserWebGL - 3D in your Browser
WebGL - 3D in your BrowserPhil Reither
 
Monolith to Reactive Microservices
Monolith to Reactive MicroservicesMonolith to Reactive Microservices
Monolith to Reactive MicroservicesReactivesummit
 
kissy-past-now-future
kissy-past-now-futurekissy-past-now-future
kissy-past-now-futureyiming he
 
Exploiting Concurrency with Dynamic Languages
Exploiting Concurrency with Dynamic LanguagesExploiting Concurrency with Dynamic Languages
Exploiting Concurrency with Dynamic LanguagesTobias Lindaaker
 
KISSY 的昨天、今天与明天
KISSY 的昨天、今天与明天KISSY 的昨天、今天与明天
KISSY 的昨天、今天与明天tblanlan
 

Tendances (20)

HTML5 Canvas - The Future of Graphics on the Web
HTML5 Canvas - The Future of Graphics on the WebHTML5 Canvas - The Future of Graphics on the Web
HTML5 Canvas - The Future of Graphics on the Web
 
MongoDB Live Hacking
MongoDB Live HackingMongoDB Live Hacking
MongoDB Live Hacking
 
Introduction to HTML5 Canvas
Introduction to HTML5 CanvasIntroduction to HTML5 Canvas
Introduction to HTML5 Canvas
 
Gpu programming with java
Gpu programming with javaGpu programming with java
Gpu programming with java
 
Introduction to CUDA C: NVIDIA : Notes
Introduction to CUDA C: NVIDIA : NotesIntroduction to CUDA C: NVIDIA : Notes
Introduction to CUDA C: NVIDIA : Notes
 
tutorial5
tutorial5tutorial5
tutorial5
 
Intro to Clojure's core.async
Intro to Clojure's core.asyncIntro to Clojure's core.async
Intro to Clojure's core.async
 
Html5 canvas
Html5 canvasHtml5 canvas
Html5 canvas
 
CUDA Raytracing을 이용한 Voxel오브젝트 가시성 테스트
CUDA Raytracing을 이용한 Voxel오브젝트 가시성 테스트CUDA Raytracing을 이용한 Voxel오브젝트 가시성 테스트
CUDA Raytracing을 이용한 Voxel오브젝트 가시성 테스트
 
Box2D with SIMD in JavaScript
Box2D with SIMD in JavaScriptBox2D with SIMD in JavaScript
Box2D with SIMD in JavaScript
 
Trident International Graphics Workshop 2014 1/5
Trident International Graphics Workshop 2014 1/5Trident International Graphics Workshop 2014 1/5
Trident International Graphics Workshop 2014 1/5
 
Prototype UI Intro
Prototype UI IntroPrototype UI Intro
Prototype UI Intro
 
Introduction to cuda geek camp singapore 2011
Introduction to cuda   geek camp singapore 2011Introduction to cuda   geek camp singapore 2011
Introduction to cuda geek camp singapore 2011
 
WebGL and three.js - Web 3D Graphics
WebGL and three.js - Web 3D Graphics WebGL and three.js - Web 3D Graphics
WebGL and three.js - Web 3D Graphics
 
WebGL - 3D in your Browser
WebGL - 3D in your BrowserWebGL - 3D in your Browser
WebGL - 3D in your Browser
 
Monolith to Reactive Microservices
Monolith to Reactive MicroservicesMonolith to Reactive Microservices
Monolith to Reactive Microservices
 
Qt Widget In-Depth
Qt Widget In-DepthQt Widget In-Depth
Qt Widget In-Depth
 
kissy-past-now-future
kissy-past-now-futurekissy-past-now-future
kissy-past-now-future
 
Exploiting Concurrency with Dynamic Languages
Exploiting Concurrency with Dynamic LanguagesExploiting Concurrency with Dynamic Languages
Exploiting Concurrency with Dynamic Languages
 
KISSY 的昨天、今天与明天
KISSY 的昨天、今天与明天KISSY 的昨天、今天与明天
KISSY 的昨天、今天与明天
 

En vedette

A Brief Introduction to JQuery Mobile
A Brief Introduction to JQuery MobileA Brief Introduction to JQuery Mobile
A Brief Introduction to JQuery MobileDan Pickett
 
Developing Developers Through Apprenticeship
Developing Developers Through ApprenticeshipDeveloping Developers Through Apprenticeship
Developing Developers Through ApprenticeshipDan Pickett
 
Entertaining pixie
Entertaining pixieEntertaining pixie
Entertaining pixieSteve Purkis
 
jQuery Mobile
jQuery MobilejQuery Mobile
jQuery Mobilemowd8574
 
High Availability Perl DBI + MySQL
High Availability Perl DBI + MySQLHigh Availability Perl DBI + MySQL
High Availability Perl DBI + MySQLSteve Purkis
 

En vedette (6)

A Brief Introduction to JQuery Mobile
A Brief Introduction to JQuery MobileA Brief Introduction to JQuery Mobile
A Brief Introduction to JQuery Mobile
 
Developing Developers Through Apprenticeship
Developing Developers Through ApprenticeshipDeveloping Developers Through Apprenticeship
Developing Developers Through Apprenticeship
 
Entertaining pixie
Entertaining pixieEntertaining pixie
Entertaining pixie
 
jQuery Mobile
jQuery MobilejQuery Mobile
jQuery Mobile
 
High Availability Perl DBI + MySQL
High Availability Perl DBI + MySQLHigh Availability Perl DBI + MySQL
High Availability Perl DBI + MySQL
 
Intro to jquery
Intro to jqueryIntro to jquery
Intro to jquery
 

Similaire à Build Your Own Asteroids Game with HTML5 Canvas

How to make a video game
How to make a video gameHow to make a video game
How to make a video gamedandylion13
 
How to build a html5 websites.v1
How to build a html5 websites.v1How to build a html5 websites.v1
How to build a html5 websites.v1Bitla Software
 
Rotoscope inthebrowserppt billy
Rotoscope inthebrowserppt billyRotoscope inthebrowserppt billy
Rotoscope inthebrowserppt billynimbleltd
 
HTML5 - Daha Flash bir web?
HTML5 - Daha Flash bir web?HTML5 - Daha Flash bir web?
HTML5 - Daha Flash bir web?Ankara JUG
 
Exploring Canvas
Exploring CanvasExploring Canvas
Exploring CanvasKevin Hoyt
 
Google's HTML5 Work: what's next?
Google's HTML5 Work: what's next?Google's HTML5 Work: what's next?
Google's HTML5 Work: what's next?Patrick Chanezon
 
Exploring Canvas
Exploring CanvasExploring Canvas
Exploring CanvasKevin Hoyt
 
Intro to Python (High School) Unit #3
Intro to Python (High School) Unit #3Intro to Python (High School) Unit #3
Intro to Python (High School) Unit #3Jay Coskey
 
HTML5って必要?
HTML5って必要?HTML5って必要?
HTML5って必要?GCS2013
 
Canvas
CanvasCanvas
CanvasRajon
 
Jetpack Compose - Hands-on February 2020
Jetpack Compose - Hands-on February 2020Jetpack Compose - Hands-on February 2020
Jetpack Compose - Hands-on February 2020Pedro Veloso
 
HTML5 Canvas (Wall Clock).pptx
HTML5 Canvas (Wall Clock).pptxHTML5 Canvas (Wall Clock).pptx
HTML5 Canvas (Wall Clock).pptxAhmadAbba6
 
SVGo: a Go Library for SVG generation
SVGo: a Go Library for SVG generationSVGo: a Go Library for SVG generation
SVGo: a Go Library for SVG generationAnthony Starks
 

Similaire à Build Your Own Asteroids Game with HTML5 Canvas (20)

How to make a video game
How to make a video gameHow to make a video game
How to make a video game
 
How to build a html5 websites.v1
How to build a html5 websites.v1How to build a html5 websites.v1
How to build a html5 websites.v1
 
Rotoscope inthebrowserppt billy
Rotoscope inthebrowserppt billyRotoscope inthebrowserppt billy
Rotoscope inthebrowserppt billy
 
Intro to HTML5 Canvas
Intro to HTML5 CanvasIntro to HTML5 Canvas
Intro to HTML5 Canvas
 
Yavorsky
YavorskyYavorsky
Yavorsky
 
A More Flash Like Web?
A More Flash Like Web?A More Flash Like Web?
A More Flash Like Web?
 
HTML5 - Daha Flash bir web?
HTML5 - Daha Flash bir web?HTML5 - Daha Flash bir web?
HTML5 - Daha Flash bir web?
 
Exploring Canvas
Exploring CanvasExploring Canvas
Exploring Canvas
 
Google's HTML5 Work: what's next?
Google's HTML5 Work: what's next?Google's HTML5 Work: what's next?
Google's HTML5 Work: what's next?
 
Exploring Canvas
Exploring CanvasExploring Canvas
Exploring Canvas
 
Intro to HTML5
Intro to HTML5Intro to HTML5
Intro to HTML5
 
Css5 canvas
Css5 canvasCss5 canvas
Css5 canvas
 
Intro to Python (High School) Unit #3
Intro to Python (High School) Unit #3Intro to Python (High School) Unit #3
Intro to Python (High School) Unit #3
 
HTML5って必要?
HTML5って必要?HTML5って必要?
HTML5って必要?
 
Canvas
CanvasCanvas
Canvas
 
Jetpack Compose - Hands-on February 2020
Jetpack Compose - Hands-on February 2020Jetpack Compose - Hands-on February 2020
Jetpack Compose - Hands-on February 2020
 
Canvas al ajillo
Canvas al ajilloCanvas al ajillo
Canvas al ajillo
 
Node.js - As a networking tool
Node.js - As a networking toolNode.js - As a networking tool
Node.js - As a networking tool
 
HTML5 Canvas (Wall Clock).pptx
HTML5 Canvas (Wall Clock).pptxHTML5 Canvas (Wall Clock).pptx
HTML5 Canvas (Wall Clock).pptx
 
SVGo: a Go Library for SVG generation
SVGo: a Go Library for SVG generationSVGo: a Go Library for SVG generation
SVGo: a Go Library for SVG generation
 

Plus de Steve Purkis

Start the Wardley Mapping Foundation
Start the Wardley Mapping FoundationStart the Wardley Mapping Foundation
Start the Wardley Mapping FoundationSteve Purkis
 
Maps: a better way to organise
Maps: a better way to organiseMaps: a better way to organise
Maps: a better way to organiseSteve Purkis
 
Making sense of complex systems
Making sense of complex systemsMaking sense of complex systems
Making sense of complex systemsSteve Purkis
 
Glasswall Wardley Maps & Services
Glasswall Wardley Maps & ServicesGlasswall Wardley Maps & Services
Glasswall Wardley Maps & ServicesSteve Purkis
 
What do Wardley Maps mean to me? (Map Camp 2020)
What do Wardley Maps mean to me?  (Map Camp 2020)What do Wardley Maps mean to me?  (Map Camp 2020)
What do Wardley Maps mean to me? (Map Camp 2020)Steve Purkis
 
Introduction to Wardley Maps
Introduction to Wardley MapsIntroduction to Wardley Maps
Introduction to Wardley MapsSteve Purkis
 
COVID-19 - Systems & Complexity Thinking in Action
COVID-19 - Systems & Complexity Thinking in ActionCOVID-19 - Systems & Complexity Thinking in Action
COVID-19 - Systems & Complexity Thinking in ActionSteve Purkis
 
Predicting & Influencing with Kanban Metrics
Predicting & Influencing with Kanban MetricsPredicting & Influencing with Kanban Metrics
Predicting & Influencing with Kanban MetricsSteve Purkis
 
Map Your Values: Connect & Collaborate
Map Your Values: Connect & CollaborateMap Your Values: Connect & Collaborate
Map Your Values: Connect & CollaborateSteve Purkis
 
Modern agile overview
Modern agile overviewModern agile overview
Modern agile overviewSteve Purkis
 
Kanban in the Kitchen
Kanban in the KitchenKanban in the Kitchen
Kanban in the KitchenSteve Purkis
 
TAP-Harness + friends
TAP-Harness + friendsTAP-Harness + friends
TAP-Harness + friendsSteve Purkis
 

Plus de Steve Purkis (13)

Start the Wardley Mapping Foundation
Start the Wardley Mapping FoundationStart the Wardley Mapping Foundation
Start the Wardley Mapping Foundation
 
Maps: a better way to organise
Maps: a better way to organiseMaps: a better way to organise
Maps: a better way to organise
 
Making sense of complex systems
Making sense of complex systemsMaking sense of complex systems
Making sense of complex systems
 
Glasswall Wardley Maps & Services
Glasswall Wardley Maps & ServicesGlasswall Wardley Maps & Services
Glasswall Wardley Maps & Services
 
What do Wardley Maps mean to me? (Map Camp 2020)
What do Wardley Maps mean to me?  (Map Camp 2020)What do Wardley Maps mean to me?  (Map Camp 2020)
What do Wardley Maps mean to me? (Map Camp 2020)
 
Introduction to Wardley Maps
Introduction to Wardley MapsIntroduction to Wardley Maps
Introduction to Wardley Maps
 
COVID-19 - Systems & Complexity Thinking in Action
COVID-19 - Systems & Complexity Thinking in ActionCOVID-19 - Systems & Complexity Thinking in Action
COVID-19 - Systems & Complexity Thinking in Action
 
Predicting & Influencing with Kanban Metrics
Predicting & Influencing with Kanban MetricsPredicting & Influencing with Kanban Metrics
Predicting & Influencing with Kanban Metrics
 
Map Your Values: Connect & Collaborate
Map Your Values: Connect & CollaborateMap Your Values: Connect & Collaborate
Map Your Values: Connect & Collaborate
 
Modern agile overview
Modern agile overviewModern agile overview
Modern agile overview
 
Kanban in the Kitchen
Kanban in the KitchenKanban in the Kitchen
Kanban in the Kitchen
 
Scalar::Footnote
Scalar::FootnoteScalar::Footnote
Scalar::Footnote
 
TAP-Harness + friends
TAP-Harness + friendsTAP-Harness + friends
TAP-Harness + friends
 

Dernier

Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
Vector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesVector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesZilliz
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024The Digital Insurer
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr LapshynFwdays
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 

Dernier (20)

Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
Vector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesVector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector Databases
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 

Build Your Own Asteroids Game with HTML5 Canvas

  • 1. Asteroid(s)* with HTML5 Canvas (c) 2012 Steve Purkis * Asteroids™ is a trademark of Atari Inc.
  • 2. About Steve Software Dev + Manager not really a front-end dev nor a game dev (but I like to play!)
  • 3. Uhh... Asteroids? What’s that, then? http://www.flickr.com/photos/onkel_wart/201905222/
  • 4. Asteroids™ http://en.wikipedia.org/wiki/File:Asteroi1.png
  • 5. Asteroids™ “ Asteroids is a video arcade game released in November 1979 by Atari Inc. It was one of the most popular and influential games of the Golden Age of Arcade Games, selling 70,000 arcade cabinets.[1] Asteroids uses a vector display and a two-dimensional view that wraps around in both screen axes. The player controls a spaceship in an asteroid field which is periodically traversed by flying saucers. The object of the game is to shoot and destroy asteroids and saucers while not colliding with either, or being hit by the saucers' counter-fire.” http://en.wikipedia.org/wiki/Asteroids_(video_game) http://en.wikipedia.org/wiki/File:Asteroi1.png
  • 6. Asteroids™ Note that the term “Asteroids” is © Atari when used with a game. I didn’t know that when I wrote this... Oops. Atari: • I promise I’ll change the name of the game. • In the meantime, consider this free marketing! :-)
  • 7. Why DIY Asteroid(s)? • Yes, it’s been done before. • Yes, I could have used a Game Engine. • Yes, I could have used open|web GL. • No, I’m not a “Not Invented Here” guy.
  • 8. Why not? • It’s a fun way to learn new tech. • I learn better by doing. (ok, so I’ve secretly wanted to write my own version of asteroids since I was a kid.) I was learning about HTML5 (yes I know it came out several years ago. I’ve been busy). A few years ago, the only way you’d be able to do this is with Flash. demo
  • 9. It’s not Done... (but it’s playable)
  • 10. It’s a Hack! http://www.flickr.com/photos/cheesygarlicboy/269419718/
  • 11. It’s a Hack! • No Tests (!) • somewhat broken in IE • mobile? what’s that? • no sound, fancy gfx, ... http://www.flickr.com/photos/cheesygarlicboy/269419718/
  • 12. If you see this... you know what to expect.
  • 13. What I’ll cover... • Basics of canvas 2D (as I go) • Overview of how it’s put together • Some game mechanics • Performance
  • 14. What’s Canvas? • “New” to HTML5! • lets you draw 2D graphics: • images, text • vector graphics (lines, curves, etc) • trades performance & control for convenience • ... and 3D graphics (WebGL) • still a draft standard
  • 15. Drawing a Ship • Get canvas element • draw lines that make up the ship <body onload="drawShip()"> <h1>Canvas:</h1> <canvas id="demo" width="300" height="200" style="border: 1px solid black" /> </body> function drawShip() { var canvas = document.getElementById("demo"); var ctx = canvas.getContext('2d'); var center = {x: canvas.width/2, y: canvas.height/2}; ctx.translate( center.x, center.y ); ctx.strokeStyle = 'black'; Canvas is an element. ctx.beginPath(); You use one of its ‘context’ objects to draw to it. ctx.moveTo(0,0); 2D Context is pretty simple ctx.lineTo(14,7); Walk through ctx calls: ctx.lineTo(0,14); • translate: move “origin” to center of canvas ctx.quadraticCurveTo(7,7, 0,0); • moveTo: move without drawing ctx.closePath(); • lineTo: draw a line • curve: draw a curve ctx.stroke(); demo v } syntax highlighting: http://tohtml.com ship.html
  • 16. Moving it around var canvas, ctx, center, ship; function drawShip() { ctx.save(); function drawShipLoop() { ctx.clearRect( 0,0, canvas.width,canvas.height ); canvas = document.getElementById("demo"); ctx.translate( ship.x, ship.y ); ctx = canvas.getContext('2d'); ctx.rotate( ship.facing ); center = {x: canvas.width/2, y: canvas.height/2}; ship = {x: center.x, y: center.y, facing: 0}; ctx.strokeStyle = 'black'; ctx.beginPath(); setTimeout( updateAndDrawShip, 20 ); ctx.moveTo(0,0); } ctx.lineTo(14,7); ctx.lineTo(0,14); function updateAndDrawShip() { ctx.quadraticCurveTo(7,7, 0,0); // set a fixed velocity: ctx.closePath(); ship.y += 1; ctx.stroke(); ship.x += 1; ship.facing += Math.PI/360 * 5; ctx.restore(); } drawShip(); if (ship.y < canvas.height-10) { setTimeout( updateAndDrawShip, 20 ); } else { drawGameOver(); } function drawGameOver() { } ctx.save(); Introducing: ctx.globalComposition = "lighter"; • animation loop: updateAndDraw... ctx.font = "20px Verdana"; • keeping track of an object’s co-ords ctx.fillStyle = "rgba(50,50,50,0.9)"; • velocity & rotation ctx.fillText("Game Over", this.canvas.width/2 - 50, • clearing the canvas this.canvas.height/2); ctx.restore(); Don’t setInterval - we’ll get to that later. } globalComposition - drawing mode, lighter so we can see text in some scenarios. demo --> ship-move.html
  • 17. Controls Wow! <div id="controlBox"> <input id="controls" type="text" placeholder="click to control" autofocus="autofocus"/> <p>Controls: super-high-tech solution: <ul class="controlsInfo"> • use arrow keys to control your ship <li>up/i: accelerate forward</li> • space to fire <li>down/k: accelerate backward</li> • thinking of patenting it :) <li>left/j: spin ccw</li> <li>right/l: spin cw</li> <li>space: shoot</li> $("#controls").keydown(function(event) {self.handleKeyEvent(event)}); <li>w: switch weapon</li> $("#controls").keyup(function(event) {self.handleKeyEvent(event)}); </ul> </p> AsteroidsGame.prototype.handleKeyEvent = function(event) { </div> // TODO: send events, get rid of ifs. switch (event.which) { case 73: // i = up case 38: // up = accel if (event.type == 'keydown') { this.ship.startAccelerate(); } else { // assume keyup this.ship.stopAccelerate(); } event.preventDefault(); break; ... AsteroidsGame.js
  • 18. Controls: Feedback • Lines: thrust forward, backward, or spin (think exhaust from a jet...) • Thrust: ‘force’ in status bar. // renderThrustForward // offset from center of ship // we translate here before drawing render.x = -13; render.y = -3; ctx.strokeStyle = 'black'; ctx.beginPath(); ctx.moveTo(8,0); ctx.lineTo(0,0); ctx.moveTo(8,3); Thrust ctx.lineTo(3,3); ctx.moveTo(8,6); ctx.lineTo(0,6); ctx.closePath(); ctx.stroke(); Ships.js
  • 19. Status bars... ... are tightly-coupled to Ships atm: Ship.prototype.initialize = function(game, spatial) { // Status Bars // for displaying ship info: health, shield, thrust, ammo // TODO: move these into their own objects ... this.thrustWidth = 100; this.thrustHeight = 10; this.thrustX = this.healthX; this.thrustY = this.healthY + this.healthHeight + 5; this.thrustStartX = Math.floor( this.thrustWidth / 2 ); ... Ship.prototype.renderThrustBar = function() { var render = this.getClearThrustBarCanvas(); var ctx = render.ctx; var thrustPercent = Math.floor(this.thrust/this.maxThrust * 100); var fillWidth = Math.floor(thrustPercent * this.thrustWidth / 100 / 2); var r = 100; var b = 200 + Math.floor(thrustPercent/2); var g = 100; var fillStyle = 'rgba('+ r +','+ g +','+ b +',0.5)'; ctx.fillStyle = fillStyle; ctx.fillRect(this.thrustStartX, 0, fillWidth, this.thrustHeight); ctx.strokeStyle = 'rgba(5,5,5,0.75)'; ctx.strokeRect(0, 0, this.thrustWidth, this.thrustHeight); this.render.thrustBar = render; } http://www.flickr.com/photos/imelda/497456854/
  • 20. Drawing an Asteroid (or planet) ctx.beginPath(); ctx.arc(this.radius, this.radius, this.radius, 0, deg_to_rad[360], false); ctx.closePath() if (this.fillStyle) { ctx.fillStyle = this.fillStyle; ctx.fill(); } else { ctx.strokeStyle = this.strokeStyle; ctx.stroke(); } Show: cookie cutter level Planets.js Compositing
  • 21. Drawing an Asteroid (or planet) ctx.beginPath(); ctx.arc(this.radius, this.radius, this.radius, 0, deg_to_rad[360], false); ctx.closePath() if (this.fillStyle) { ctx.fillStyle = this.fillStyle; ctx.fill(); } else { ctx.strokeStyle = this.strokeStyle; ctx.stroke(); } // Using composition as a cookie cutter: if (this.image != null) { this.render = this.createPreRenderCanvas(this.radius*2, this.radius*2); var ctx = this.render.ctx; // Draw a circle to define what we want to keep: ctx.globalCompositeOperation = 'destination-over'; ctx.beginPath(); ctx.arc(this.radius, this.radius, this.radius, 0, deg_to_rad[360], false); ctx.closePath(); ctx.fillStyle = 'white'; ctx.fill(); // Overlay the image: ctx.globalCompositeOperation = 'source-in'; ctx.drawImage(this.image, 0, 0, this.radius*2, this.radius*2); return; } Show: cookie cutter level Planets.js Compositing
  • 22. Space Objects • DRY Planet Asteroid • Base class for all things spacey. • Everything is a circle. Ship Planetoid SpaceObject
  • 23. Err, why is everything a circle? • An asteroid is pretty much a circle, right? • And so are planets... • And so is a Ship with a shield around it! ;-) • ok, really: • Game physics can get complicated. • Keep it simple!
  • 24. Space Objects have... can... • radius • draw themselves • coords: x, y • update their positions • facing angle • accelerate, spin • velocity, spin, thrust • collide with other objects • health, mass, damage • apply damage, die • and a whole lot more... • etc... SpaceObject.js
  • 25. Game Mechanics what makes the game feel right.
  • 26. Game Mechanics what makes the game feel right. This is where it gets hairy. http://www.flickr.com/photos/maggz/2860543788/
  • 27. The god class... • AsteroidsGame does it all! • user controls, game loop, 95% of the game mechanics ... • Ok, so it’s not 5000 lines long (yet), but... • it should really be split up! AsteroidsGame.js
  • 28. Game Mechanics • velocity & spin • acceleration & drag • gravity • collision detection, impact, bounce • health, damage, life & death • object attachment & push • out-of-bounds, viewports & scrolling
  • 29. Velocity & Spin SpaceObject.prototype.initialize = function(game, SpaceObject.prototype.updateX = function(dX) { spatial) { if (this.stationary) return false; ... if (dX == 0) return false; this.x += dX; this.x = 0; // starting position on x axis return true; this.y = 0; // starting position on y axis } this.facing = 0; // currently facing angle (rad) SpaceObject.prototype.updateY = function(dY) { this.stationary = false; // should move? if (this.stationary) return false; if (dY == 0) return false; this.vX = spatial.vX || 0; // speed along X axis this.y += dY; this.vY = spatial.vY || 0; // speed along Y axis return true; this.maxV = spatial.maxV || 2; // max velocity } this.maxVSquared = this.maxV*this.maxV; // cache SpaceObject.prototype.updateFacing = function(delta) // thrust along facing { this.thrust = spatial.initialThrust || 0; if (delta == 0) return false; this.maxThrust = spatial.maxThrust || 0.5; this.facing += delta; this.thrustChanged = false; // limit facing angle to 0 <= facing <= 360 this.spin = spatial.spin || 0; // spin in Rad/sec if (this.facing >= deg_to_rad[360] || this.maxSpin = deg_to_rad[10]; this.facing <= deg_to_rad[360]) { } this.facing = this.facing % deg_to_rad[360]; } SpaceObject.prototype.updatePositions = function (objects) { if (this.facing < 0) { ... this.facing = deg_to_rad[360] + this.facing; if (this.updateFacing(this.spin)) changed = true; } if (this.updateX(this.vX)) changed = true; if (this.updateY(this.vY)) changed = true; return true; } } velocity = ∆ distance / time spin = angular velocity = ∆ angle / time
  • 30. Velocity & Spin SpaceObject.prototype.initialize = function(game, SpaceObject.prototype.updateX = function(dX) { spatial) { if (this.stationary) return false; ... if (dX == 0) return false; this.x += dX; this.x = 0; // starting position on x axis return true; this.y = 0; // starting position on y axis } this.facing = 0; // currently facing angle (rad) SpaceObject.prototype.updateY = function(dY) { this.stationary = false; // should move? if (this.stationary) return false; if (dY == 0) return false; this.vX = spatial.vX || 0; // speed along X axis this.y += dY; this.vY = spatial.vY || 0; // speed along Y axis return true; this.maxV = spatial.maxV || 2; // max velocity } this.maxVSquared = this.maxV*this.maxV; // cache SpaceObject.prototype.updateFacing = function(delta) // thrust along facing { this.thrust = spatial.initialThrust || 0; if (delta == 0) return false; this.maxThrust = spatial.maxThrust || 0.5; this.facing += delta; this.thrustChanged = false; // limit facing angle to 0 <= facing <= 360 this.spin = spatial.spin || 0; // spin in Rad/sec if (this.facing >= deg_to_rad[360] || this.maxSpin = deg_to_rad[10]; this.facing <= deg_to_rad[360]) { } this.facing = this.facing % deg_to_rad[360]; } SpaceObject.prototype.updatePositions = function (objects) { if (this.facing < 0) { ... this.facing = deg_to_rad[360] + this.facing; if (this.updateFacing(this.spin)) changed = true; } if (this.updateX(this.vX)) changed = true; if (this.updateY(this.vY)) changed = true; return true; } } velocity = ∆ distance / time spin = angular velocity = ∆ angle / time where: time = current frame rate
  • 31. Acceleration SpaceObject.prototype.initialize = function(game, spatial) { ... // thrust along facing this.thrust = spatial.initialThrust || 0; this.maxThrust = spatial.maxThrust || 0.5; this.thrustChanged = false; } SpaceObject.prototype.accelerateAlong = function(angle, thrust) { var accel = thrust/this.mass; var dX = Math.cos(angle) * accel; var dY = Math.sin(angle) * accel; this.updateVelocity(dX, dY); } acceleration = ∆ velocity / time acceleration = mass / force
  • 32. Acceleration SpaceObject.prototype.initialize = function(game, spatial) { ... // thrust along facing this.thrust = spatial.initialThrust || 0; Ship.prototype.startAccelerate = function() { this.maxThrust = spatial.maxThrust || 0.5; if (this.accelerate) return; this.thrustChanged = false; this.accelerate = true; } //console.log("thrust++"); SpaceObject.prototype.accelerateAlong = function(angle, thrust) { this.clearSlowDownInterval(); var accel = thrust/this.mass; var dX = Math.cos(angle) * accel; var self = this; var dY = Math.sin(angle) * accel; this.incThrustIntervalId = setInterval(function(){ this.updateVelocity(dX, dY); ! self.increaseThrust(); } }, 20); // real time }; Ship.prototype.increaseThrust = function() { this.incThrust(this.thrustIncrement); this.accelerateAlong(this.facing, this.thrust); Ship.prototype.initialize = function(game, spatial) { } ... spatial.mass = 10; Ship.prototype.stopAccelerate = function() { //console.log("stop thrust++"); // current state of user action: if (this.clearIncThrustInterval()) this.increaseSpin = false; this.resetThrust(); this.decreaseSpin = false; this.startSlowingDown(); this.accelerate = false; this.accelerate = false; this.decelerate = false; }; this.firing = false; Ship.prototype.clearIncThrustInterval = function() { // for moving about: if (! this.incThrustIntervalId) return false; this.thrustIncrement = 0.01; clearInterval(this.incThrustIntervalId); this.spinIncrement = deg_to_rad[0.5]; this.incThrustIntervalId = null; ... return true; } } acceleration = ∆ velocity / time acceleration = mass / force
  • 33. Acceleration SpaceObject.prototype.initialize = function(game, spatial) { ... // thrust along facing this.thrust = spatial.initialThrust || 0; Ship.prototype.startAccelerate = function() { this.maxThrust = spatial.maxThrust || 0.5; if (this.accelerate) return; this.thrustChanged = false; this.accelerate = true; } //console.log("thrust++"); SpaceObject.prototype.accelerateAlong = function(angle, thrust) { this.clearSlowDownInterval(); var accel = thrust/this.mass; var dX = Math.cos(angle) * accel; var self = this; var dY = Math.sin(angle) * accel; this.incThrustIntervalId = setInterval(function(){ this.updateVelocity(dX, dY); ! self.increaseThrust(); } }, 20); // real time }; Ship.prototype.increaseThrust = function() { this.incThrust(this.thrustIncrement); this.accelerateAlong(this.facing, this.thrust); Ship.prototype.initialize = function(game, spatial) { } ... spatial.mass = 10; Ship.prototype.stopAccelerate = function() { //console.log("stop thrust++"); // current state of user action: if (this.clearIncThrustInterval()) this.increaseSpin = false; this.resetThrust(); this.decreaseSpin = false; this.startSlowingDown(); this.accelerate = false; this.accelerate = false; this.decelerate = false; }; this.firing = false; Ship.prototype.clearIncThrustInterval = function() { // for moving about: if (! this.incThrustIntervalId) return false; this.thrustIncrement = 0.01; clearInterval(this.incThrustIntervalId); this.spinIncrement = deg_to_rad[0.5]; this.incThrustIntervalId = null; ... return true; } } acceleration = ∆ velocity / time where: time = real time acceleration = mass / force (just to confuse things)
  • 34. Drag Yes, yes, there is no drag in outer space. Very clever. I disagree.
  • 35. Drag Yes, yes, there is no drag in outer space. Very clever. http://nerdadjacent.deviantart.com/art/Ruby-Rhod-Supergreen-265156565 I disagree.
  • 36. Drag Ship.prototype.startSlowingDown = function() { Ship.prototype.slowDown = function() { // console.log("slowing down..."); var vDrag = 0.01; if (this.slowDownIntervalId) return; if (this.vX > 0) { ! this.vX -= vDrag; var self = this; } else if (this.vX < 0) { this.slowDownIntervalId = setInterval(function(){ ! this.vX += vDrag; ! self.slowDown() } }, 100); // eek! another hard-coded timeout! if (this.vY > 0) { } ! this.vY -= vDrag; } else if (this.vY < 0) { Ship.prototype.clearSlowDownInterval = function() { ! this.vY += vDrag; if (! this.slowDownIntervalId) return false; } clearInterval(this.slowDownIntervalId); this.slowDownIntervalId = null; if (Math.abs(this.vX) <= vDrag) this.vX = 0; return true; if (Math.abs(this.vY) <= vDrag) this.vY = 0; } if (this.vX == 0 && this.vY == 0) { ! // console.log('done slowing down'); ! this.clearSlowDownInterval(); } } Demo: accel + drag in blank level
  • 37. Gravity var dvX_1 = 0, dvY_1 = 0; if (! object1.stationary) { var accel_1 = object2.cache.G_x_mass / physics.dist_squared; if (accel_1 > 1e-5) { // skip if it's too small to notice if (accel_1 > this.maxAccel) accel_1 = this.maxAccel; var angle_1 = Math.atan2(physics.dX, physics.dY); dvX_1 = -Math.sin(angle_1) * accel_1; dvY_1 = -Math.cos(angle_1) * accel_1; object1.delayUpdateVelocity(dvX_1, dvY_1); } } var dvX_2 = 0, dvY_2 = 0; if (! object2.stationary) { var accel_2 = object1.cache.G_x_mass / physics.dist_squared; if (accel_2 > 1e-5) { // skip if it's too small to notice if (accel_2 > this.maxAccel) accel_2 = this.maxAccel; // TODO: angle_2 = angle_1 - PI? var angle_2 = Math.atan2(-physics.dX, -physics.dY); // note the - signs dvX_2 = -Math.sin(angle_2) * accel_2; dvY_2 = -Math.cos(angle_2) * accel_2; object2.delayUpdateVelocity(dvX_2, dvY_2); } } force = G*mass1*mass2 / dist^2 acceleration1 = force / mass1
  • 38. Collision Detection AsteroidsGame.prototype.applyGamePhysicsTo = function(object1, object2) { ... var dX = object1.x - object2.x; var dY = object1.y - object2.y; // find dist between center of mass: // avoid sqrt, we don't need dist yet... var dist_squared = dX*dX + dY*dY; var total_radius = object1.radius + object2.radius; var total_radius_squared = Math.pow(total_radius, 2); // now check if they're touching: if (dist_squared > total_radius_squared) { // nope } else { // yep this.collision( object1, object2, physics ); } ... http://www.flickr.com/photos/wsmonty/4299389080/ Aren’t you glad we stuck with circles?
  • 39. Bounce Formula: • Don’t ask.
  • 41. *bounce* ! // Thanks Emanuelle! bounce algorithm adapted from: ! // http://www.emanueleferonato.com/2007/08/19/managing-ball-vs-ball-collision-with-flash/ ! collision.angle = Math.atan2(collision.dY, collision.dX); ! var magnitude_1 = Math.sqrt(object1.vX*object1.vX + object1.vY*object1.vY); ! var magnitude_2 = Math.sqrt(object2.vX*object2.vX + object2.vY*object2.vY); ! var direction_1 = Math.atan2(object1.vY, object1.vX); ! var direction_2 = Math.atan2(object2.vY, object2.vX); ! var new_vX_1 = magnitude_1*Math.cos(direction_1-collision.angle); ! var new_vY_1 = magnitude_1*Math.sin(direction_1-collision.angle); ! var new_vX_2 = magnitude_2*Math.cos(direction_2-collision.angle); ! var new_vY_2 = magnitude_2*Math.sin(direction_2-collision.angle); ! [snip] ! // bounce the objects: ! var final_vX_1 = ( (cache1.delta_mass * new_vX_1 + object2.cache.mass_x_2 * new_vX_2) ! ! ! / cache1.total_mass * this.elasticity ); ! var final_vX_2 = ( (object1.cache.mass_x_2 * new_vX_1 + cache2.delta_mass * new_vX_2) ! ! ! / cache2.total_mass * this.elasticity ); ! var final_vY_1 = new_vY_1 * this.elasticity; ! var final_vY_2 = new_vY_2 * this.elasticity; ! var cos_collision_angle = Math.cos(collision.angle); ! var sin_collision_angle = Math.sin(collision.angle); ! var cos_collision_angle_halfPI = Math.cos(collision.angle + halfPI); ! var sin_collision_angle_halfPI = Math.sin(collision.angle + halfPI); ! var vX1 = cos_collision_angle*final_vX_1 + cos_collision_angle_halfPI*final_vY_1; ! var vY1 = sin_collision_angle*final_vX_1 + sin_collision_angle_halfPI*final_vY_1; ! object1.delaySetVelocity(vX1, vY1); ! var vX2 = cos_collision_angle*final_vX_2 + cos_collision_angle_halfPI*final_vY_2; ! var vY2 = sin_collision_angle*final_vX_2 + sin_collision_angle_halfPI*final_vY_2; ! object2.delaySetVelocity(vX2, vY2); Aren’t you *really* glad we stuck with circles?
  • 42. Making it *hurt* AsteroidsGame.prototype.collision = function(object1, object2, collision) { Shield ... // “collision” already contains a bunch of calcs Health collision[object1.id] = { cplane: {vX: new_vX_1, vY: new_vY_1}, // relative to collision plane dX: collision.dX, dY: collision.dY, magnitude: magnitude_1 } // do the same for object2 SpaceObject.prototype.collided = function(object, collision) { // let the objects fight it out this.colliding[object.id] = object; object1.collided(object2, collision); object2.collided(object1, collision); if (this.damage) { } ! var damageDone = this.damage; ! if (collision.impactSpeed != null) { ! damageDone = Math.ceil(damageDone * collision.impactSpeed); ! } ! object.decHealth( damageDone ); } } SpaceObject.prototype.decHealth = function(delta) { this.healthChanged = true; this.health -= delta; if (this.health <= 0) { ! this.health = -1; ! this.die(); } Ship.prototype.decHealth = function(delta) { } if (this.shieldActive) { When a collision occurs the Game Engine fires off 2 events to the objects in ! delta = this.decShield(delta); question } • For damage, I opted for a property rather than using mass * impact if (delta) Ship.prototype.parent.decHealth.call(this, delta); speed in the general case. } Applying damage is fairly straightforward: • Objects are responsible for damaging each other • When damage is done dec Health (for a Ship, shield first) • If health < 0, an object dies. SpaceObject.js
  • 43. Object Lifecycle SpaceObject.prototype.die = function() { this.died = true; this.update = false; this.game.objectDied( this ); } AsteroidsGame.prototype.objectDied = function(object) { // if (object.is_weapon) { //} else if (object.is_asteroid) { Asteroid.prototype.die = function() { this.parent.die.call( this ); if (object.is_planet) { if (this.spawn <= 0) return; ! throw "planet died!?"; // not allowed for (var i=0; i < this.spawn; i++) { } else if (object.is_ship) { var mass = Math.floor(this.mass / this.spawn * 1000)/1000; ! // TODO: check how many lives they've got var radius = getRandomInt(2, this.radius); ! if (object == this.ship) { var asteroid = new Asteroid(this.game, { ! this.stopGame(); mass: mass, ! } x: this.x + i/10, // don't overlap y: this.y + i/10, } vX: this.vX * Math.random(), vX: this.vY * Math.random(), this.removeObject(object); radius: radius, } health: getRandomInt(0, this.maxSpawnHealth), spawn: getRandomInt(0, this.spawn-1), AsteroidsGame.prototype.removeObject = function(object) { image: getRandomInt(0, 5) > 0 ? this.image : null, var objects = this.objects; // let physics engine handle movement }); var i = objects.indexOf(object); this.game.addObject( asteroid ); if (i >= 0) { } ! objects.splice(i,1); } ! this.objectUpdated( object ); } // avoid memory bloat: remove references to this object AsteroidsGame.prototype.addObject = function(object) { // from other objects' caches: //console.log('adding ' + object); var oid = object.id; this.objects.push( object ); for (var i=0; i < objects.length; i++) { this.objectUpdated( object ); ! delete objects[i].cache[oid]; object.preRender(); } this.cachePhysicsFor(object); } }
  • 44. Attachment • Attach objects that are ‘gently’ touching • then apply special physics • Why? AsteroidsGame.js
  • 45. Attachment • Attach objects that are ‘gently’ touching • then apply special physics • Why? Prevent the same collision from recurring. + Allows ships to land. + Poor man’s Orbit. AsteroidsGame.js
  • 46. Push! • When objects get too close • push them apart! • otherwise they overlap... (and the game physics gets weird) demo: what happens when you disable applyPushAway()
  • 47. Out-of-bounds When you have a map that is not wrapped... Simple strategy: • kill most objects that stray • push back important things like ships AsteroidsGame.prototype.applyOutOfBounds = function(object) { if (object.stationary) return; ... var level = this.level; if (object.y < 0) { var die_if_out_of_bounds = ! if (level.wrapY) { !(object.is_ship || object.is_planet); ! object.setY(level.maxY + object.y); ! } else { if (object.x < 0) { ! if (die_if_out_of_bounds && object.vY < 0) { ! if (level.wrapX) { ! ! return object.die(); ! object.setX(level.maxX + object.x); ! } ! } else { ! // push back into bounds ! if (die_if_out_of_bounds && object.vX < 0) { ! object.updateVelocity(0, 0.1); ! ! return object.die(); ! } ! } } else if (object.y > level.maxY) { ! object.updateVelocity(0.1, 0); ! if (level.wrapY) { ! } ! object.setY(object.y - level.maxY); } else if (object.x > level.maxX) { ! } else { ! if (level.wrapX) { ! if (die_if_out_of_bounds && object.vY > 0) { ! object.setX(object.x - level.maxX); ! ! return object.die(); ! } else { ! } ! if (die_if_out_of_bounds && object.vX > 0) { ! // push back into bounds ! ! return object.die(); ! object.updateVelocity(0, -0.1); ! } ! } ! object.updateVelocity(-0.1, 0); } ! } } } ...
  • 48. Viewport + Scrolling When the dimensions of your map exceed those of your canvas... AsteroidsGame.prototype.updateViewOffset = function() { var canvas = this.ctx.canvas; var offset = this.viewOffset; var dX = Math.round(this.ship.x - offset.x - canvas.width/2); var dY = Math.round(this.ship.y - offset.y - canvas.height/2); // keep the ship centered in the current view, but don't let the view // go out of bounds offset.x += dX; if (offset.x < 0) offset.x = 0; if (offset.x > this.level.maxX-canvas.width) offset.x = this.level.maxX-canvas.width; offset.y += dY; if (offset.y < 0) offset.y = 0; if (offset.y > this.level.maxY-canvas.height) offset.y = this.level.maxY-canvas.height; } AsteroidsGame.prototype.redrawCanvas = function() { ... // shift view to compensate for current offset var offset = this.viewOffset; ctx.save(); Let browser manage complexity: if you draw to canvas ctx.translate(-offset.x, -offset.y); outside of current width/height, browser doesn’t draw it.
  • 49. Putting it all together Demo: hairballs & chainsaws level.
  • 51. Gun + Bullet • Gun Ammo • Fires Bullets • Has ammo Planet Astero • Belongs to a Ship Gun Bullet Ship Planetoid SpaceObject When you shoot, bullets inherit the Ship’s velocity. Each weapon has a different recharge rate (measured in real time). Weapons.js
  • 52. Other Weapons • Gun • SprayGun • Cannon • GrenadeCannon • GravBenda™ (back in my day, we used to read books!) Current weapon
  • 54. A basic enemy... ComputerShip.prototype.findAndDestroyClosestEnemy = function() { var enemy = this.findClosestEnemy(); if (enemy == null) return; // Note: this is a basic algorith, it doesn't take a lot of things // into account (enemy trajectory & facing, other objects, etc) // navigate towards enemy // shoot at the enemy } Demo: Level Lone enemy. Show: ComputerShip class... Ships.js
  • 55. A basic enemy... ComputerShip.prototype.findAndDestroyClosestEnemy = function() { var enemy = this.findClosestEnemy(); if (enemy == null) return; // Note: this is a basic algorith, it doesn't take a lot of things // into account (enemy trajectory & facing, other objects, etc) // navigate towards enemy // shoot at the enemy } of course, it’s a bit more involved... Demo: Level Lone enemy. Show: ComputerShip class... Ships.js
  • 56. Levels • Define: • map dimensions • space objects • spawning • general properties of the canvas - color, etc Levels.js
  • 57. /****************************************************************************** * TrainingLevel: big planet out of field of view with falling asteroids. */ function TrainingLevel(game) { if (game) return this.initialize(game); return this; } TrainingLevel.inheritsFrom( Level ); TrainingLevel.description = "Training Level - learn how to fly!"; TrainingLevel.images = [ "planet.png", "planet-80px-green.png" ]; gameLevels.push(TrainingLevel); TrainingLevel.prototype.initialize = function(game) { TrainingLevel.prototype.parent.initialize.call(this, game); this.wrapX = false; this.wrapY = false; var maxX = this.maxX; var maxY = this.maxY; var canvas = this.game.ctx.canvas; this.planets.push( ! {x: 1/2*maxX, y: 1/2*maxY, mass: 100, radius: 50, damage: 5, stationary: true, image_src: "planet.png" } ! , {x: 40, y: 40, mass: 5, radius: 20, vX: 2, vY: 0, image_src:"planet-80px-green.png"} ! , {x: maxX-40, y: maxY-40, mass: 5, radius: 20, vX: -2, vY: 0, image_src:"planet-80px-green.png"} ); this.ships.push( ! {x: 4/5*canvas.width, y: 1/3*canvas.height} ); this.asteroids.push( ! {x: 1/10*maxX, y: 6/10*maxY, mass: 0.5, radius: 14, vX: 0, vY: 0, spawn: 1, health: 1}, {x: 1/10*maxX, y: 2/10*maxY, mass: 1, radius: 5, vX: 0, vY: -0.1, spawn: 3 }, {x: 5/10*maxX, y: 1/10*maxY, mass: 2, radius: 6, vX: -0.2, vY: 0.25, spawn: 4 }, {x: 5/10*maxX, y: 2/10*maxY, mass: 3, radius: 8, vX: -0.22, vY: 0.2, spawn: 7 } ); } As usual, I had grandiose plans of an interactive level editor... This was all I had time for. Levels.js
  • 58. Performance “Premature optimisation is the root of all evil.” http://c2.com/cgi/wiki?PrematureOptimization
  • 59. Use requestAnimationFrame Paul Irish knows why: • don’t animate if your canvas is not visible • adjust your frame rate based on actual performance • lets the browser manage your app better
  • 60. Profile your code • profile in different browsers • identify the slow stuff • ask yourself: “do we really need to do this?” • optimise it? • cache slow operations • change algorithm? • simplify?
  • 61. Examples... // see if we can use cached values first: // put any calculations we can avoid repeating here var g_cache1 = physics.cache1.last_G; AsteroidsGame.prototype.cachePhysicsFor = function(object1) { var g_cache2 = physics.cache2.last_G; for (var i=0; i < this.objects.length; i++) { ! var object2 = this.objects[i]; if (g_cache1) { ! if (object1 == object2) continue; var delta_dist_sq = Math.abs( physics.dist_squared - g_cache1.last_dist_squared); var percent_diff = delta_dist_sq / physics.dist_squared; ! // shared calcs // set threshold @ 5% ! var total_radius = object1.radius + object2.radius; if (percent_diff < 0.05) { ! var total_radius_squared = Math.pow(total_radius, 2); // we haven't moved much, use last G values ! var total_mass = object1.mass + object2.mass; //console.log("using G cache"); object1.delayUpdateVelocity(g_cache1.dvX, g_cache1.dvY); ! // create separate caches from perspective of objects: object2.delayUpdateVelocity(g_cache2.dvX, g_cache2.dvY); ! object1.cache[object2.id] = { return; ! total_radius: total_radius, } ! total_radius_squared: total_radius_squared, } ! total_mass: total_mass, ! delta_mass: object1.mass - object2.mass ! } // avoid overhead of update calculations & associated checks: batch together ! object2.cache[object1.id] = { SpaceObject.prototype.delayUpdateVelocity = function(dvX, dvY) { ! total_radius: total_radius, if (this._updates == null) this.init_updates(); ! total_radius_squared: total_radius_squared, this._updates.dvX += dvX; ! total_mass: total_mass, this._updates.dvY += dvY; ! delta_mass: object2.mass - object1.mass } ! } } } var dist_squared = dX*dX + dY*dY; // avoid sqrt, we don't need dist yet this.maxVSquared = this.maxV*this.maxV; // cache for speed if (accel_1 > 1e-5) { // skip if it's too small to notice ...
  • 62. Performance Great Ideas from Boris Smus: • Only redraw changes • Pre-render to another canvas • Draw background in another canvas / element • Don't use floating point co-ords ... and more ...
  • 64. See Also... Learning / examples: • https://developer.mozilla.org/en-US/docs/Canvas_tutorial • http://en.wikipedia.org/wiki/Canvas_element • http://www.html5rocks.com/en/tutorials/canvas/performance/ • http://www.canvasdemos.com • http://billmill.org/static/canvastutorial/ • http://paulirish.com/2011/requestanimationframe-for-smart-animating/ Specs: • http://dev.w3.org/html5/spec/ • http://www.khronos.org/registry/webgl/specs/latest/