This recipe takes a look at the concept of velocity. At first glance you might think velocity… oh, that’s speed. But that isn’t quite all of it.
Velocity is simply put, something with speed and a direction. You can use velocity for hundreds of game related tasks, firing a bullet, flying a spaceship, etc. As part of the recipe, we will also look at normalizing update speed using elapsed time, so the code runs the same on various machines.
Let’s start with a very simple form, velocity along a single axis.
Just the math
var delta = elapsedTime /1000;
jetSprite.y = jetSprite.y – (speed * delta);
if(jetSprite.y < –(jetSprite.image.height/2))
jetSprite.y = stage.canvas.height;
Description
One of the problems with dealing with speed is that different computers and devices run at different speeds. You can’t simply update a sprite’s location in a loop, because that loop will run at a different speed on every machine. Therefore we need a value to normalize the movement by. In the case of EaselJS, our event loop is passed a value elapsedTime, containing the number of milliseconds that have elapsed since it was last called. What we want to do is convert this value from milliseconds to fractions of a second, which we do by dividing it by 1000.
Next we update our sprites movement in the y direction by subtracting ( since Y decreases as it goes towards the top of the canvas ) speed * delta from the current Y value. Our speed is a per second value, so a speed of 50 means we want to move 50 pixels over the course of a second. By multiplying the speed against the time the previous frame took to complete as a fraction of a second, we essentially increment by little pieces that over the course of a second should add up to our total speed.
Let’s take a look with real world numbers. Let’s say that last frame took 100ms to complete, or 1/10th of a second. 100/1000 = 0.1. Now we multiply our speed of 50 by 0.1 and see that we should update by 5 pixels this frame. Assuming the game continues to run about 100ms per frame, that means after 10 frames, a second will have elapsed and we will have moved the desired amount. The nice part is, the movement is now tied to actual time elapsed instead of processing speed.
The rest is simply a matter of checking to see if our game sprite went off the top of the screen and if it has, we send it back to the starting position.
Velocity along a single axis
Controls:
Click top half the canvas to increase speed
Click lower half to decrease the speed
Arrow up/ Arrow down to increase/decrease speed
The Complete Code
<!DOCTYPE html>
<html>
<head>
<script src="http://code.createjs.com/easeljs-0.5.0.min.js"></script>
<script>
var jetSprite;
var stage;
// Speed is total pixels moved in a second
var speed = 50;
document.addEventListener(‘DOMContentLoaded’, demo,false);
document.onkeydown = function(e){
switch(e.keyCode)
{
case 38: // up arrow
speed += 10;
break;
case 40: // down arrow
speed -= 10;
break;
}
}
function demo(){
stage = new createjs.Stage("theCanvas");
// When the user clicks mouse, if the on the top half, increase speed
// If clicked on the bottom half, reduce speed. Yes, it will go in reverse eventually
stage.onMouseDown = function(e){
if(e.stageY < 200)
speed += 50;
else
speed -= 50;
}
// Create and configure our jet sprite. regX/Y set the pivot point, we center it
jetSprite = new createjs.Bitmap("jetsprite.png");
jetSprite.regX =91; //Hardcode image widths because HTML sucks and fires onLoad
jetSprite.regY =120;//well before onLoad is well, loaded.
jetSprite.x = stage.canvas.width/2;
jetSprite.y = stage.canvas.height;
stage.addChild(jetSprite);
//And go…
stage.update();
// onFrame will be called each "tick". Default is 50ms, or 20FPS
createjs.Ticker.addListener(onFrame);
}
function onFrame(elapsedTime) {
// Convert from milliseconds to fraction of a second
var delta = elapsedTime /1000;
jetSprite.y = jetSprite.y – (speed * delta);
if(jetSprite.y < –(jetSprite.image.height/2))
jetSprite.y = stage.canvas.height;
stage.update();
}
</script>
</head>
<body>
<canvas width=400 height=400 id="theCanvas"style="background-color:black"/>
</body>
</html>
So velocity along an axis is remarkably easy, but not entirely useful if you want to travel in different directions. You can of course move along both axis independently, but this quickly becomes annoying, especially when you want to deal with multiple sprites positions relative to each other. A much better way to express the direction component of velocity than using an axis is to use an angle.
Just the Math
var delta = elapsedTime /1000;
var velocityX = Math.cos((angleAdjustment + angle) * Math.PI / 180) * (speed * delta);
var velocityY = Math.sin((angleAdjustment + angle) * Math.PI / 180) * (speed * delta);
jetSprite.rotation = angle;
jetSprite.x = jetSprite.x + velocityX;
jetSprite.y = jetSprite.y + velocityY;
if(jetSprite.y < –(jetSprite.image.height/2))
jetSprite.y = stage.canvas.height;
if(jetSprite.x > stage.canvas.width+(jetSprite.image.width/2) ||
jetSprite.x < 0–(jetSprite.image.width/2)){
jetSprite.y = stage.canvas.height;
jetSprite.x = stage.canvas.width/2;
}
Description
Logic in this example is very similar to the linear velocity example. Of course, first we need to determine what how many pixels to move by in the X and Y address. To figure this out, we solve X and Y separately.
The X component is found by taking the Cos of the angle in radians. We convert a value to radians by multiplying it by pi/180. The angle adjustment is to account for the fact our sprite was drawn as though 0 is straight up, while the math treats 0 as being to the right. At this point we have the direction of the x coordinate to travel in our given angle, we now need to figure out the amount to travel in that direction. This is determined by multiplying the speed by the delta. Remember the delta is the amount of time as a fraction of a second that our frame takes to run, so if our game is running at 100ms a frame, it’s value is .10. So for the jet to travel at a total of 50 pixels per second, its going to travel 5 pixels this frame.
To solve the Y component, its virtually an identical process, but this time we figure out the y direction by taking the Sin of the angle instead. We then update our X and Y values by the newly calculated velocity values.
Now in addition to being able to leave the top of the screen, it is also possible for our sprite to leave the right or left side of the screen, so we check to make sure the sprite hasn’t exited in those directions either. If it has, we send it back to where it started.
Angled Velocity
In addition to the above controls you can now:
Press left and right arrows to turn
** Be sure to click to focus the canvas for input may not be received.
The Complete Code
<!DOCTYPE html>
<html>
<head>
<script src="http://code.createjs.com/easeljs-0.5.0.min.js"></script>
<script>
var jetSprite;
var stage;
// Speed is total pixels moved in a second
var speed = 50;
// angle to travel, with 0 being straight up the Y-axis
var angle = 45;
// Angle adjustment to make 0 up to match how our sprite was drawn.
var angleAdjustment = –90;
document.addEventListener(‘DOMContentLoaded’, demo,false);
document.onkeydown = function(e){
switch(e.keyCode)
{
case 37: // left arrow
angle-=5;
break;
case 38: // up arrow
speed += 10;
break;
case 39: // right arrow
angle+=5;
break;
case 40: // down arrow
speed -= 10;
break;
}
}
function demo(){
stage = new createjs.Stage("theCanvas");
// When the user clicks mouse, if the on the top half, increase speed
// If clicked on the bottom half, reduce speed. Yes, it will go in reverse eventually
stage.onMouseDown = function(e){
if(e.stageY < 200)
speed += 50;
else
speed -= 50;
}
// Create and configure our jet sprite. regX/Y set the pivot point, we center it
jetSprite = new createjs.Bitmap("jetsprite.png");
jetSprite.regX =91; //Hardcode image widths because HTML sucks and fires onLoad
jetSprite.regY =120;//well before onLoad is well, loaded.
jetSprite.x = stage.canvas.width/2;
jetSprite.y = stage.canvas.height;
stage.addChild(jetSprite);
//And go…
stage.update();
// onFrame will be called each "tick". Default is 50ms, or 20FPS
createjs.Ticker.addListener(onFrame);
}
function onFrame(elapsedTime) {
// Convert from milliseconds to fraction of a second
var delta = elapsedTime /1000;
var velocityX = Math.cos((angleAdjustment + angle) * Math.PI / 180) * (speed * delta);
var velocityY = Math.sin((angleAdjustment + angle) * Math.PI / 180) * (speed * delta);
jetSprite.rotation = angle;
jetSprite.x = jetSprite.x + velocityX;
jetSprite.y = jetSprite.y + velocityY;
if(jetSprite.y < –(jetSprite.image.height/2))
jetSprite.y = stage.canvas.height;
if(jetSprite.x > stage.canvas.width+(jetSprite.image.width/2) ||
jetSprite.x < 0–(jetSprite.image.width/2)){
jetSprite.y = stage.canvas.height;
jetSprite.x = stage.canvas.width/2;
}
stage.update();
}
</script>
</head>
<body>
<canvas width=400 height=400 id="theCanvas"style="background-color:black"/>
</body>
</html>
See Also
To actually understand WHY you use the cos to solve X and sin to solve Y, check out the following.
SOHCAHTOA. It’s a simple mnemonic device, one I learned almost 20 years ago and I still remember to this day, it is how you solve each angle using the different sides of a triangle:
Sin = Opposite over the Hypotenuse Cos = Adjacent over the Hypotenuse Tan = Opposite over Adjacent == SOHCAHTOA
http://www.mathwords.com/s/sohcahtoa.htm
Fundamentally this is all trigonometry in action, you can learn a hell of a lot at the Kahn Academy