GameDev math recipes: Velocity

22. November 2012

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

http://www.khanacademy.org/math/trigonometry

Programming







blog comments powered by Disqus