Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

17. December 2012

This math recipe is a bit different.  It is in direct response to a question I received about how to give your sprite the ability to shoot.  That is exactly what we will be doing in this recipe.  There is no actual new math, it actually is just applying our prior recipes on Velocity and our recipe on Rotating to face a point.  As those prior recipes both explained how they work, this particular entry is going to be light on description.

 

 

Just the Math

// calculate the speed relative to the mouse click distance

bullet.speed *= (bullet.y - bullet.targetY)/100;

 

// now calculate the angle between the bullet and mouse click

bullet.angle = Math.atan2(bullet.targetY - bullet.y, bullet.targetX - bullet.x );

bullet.angle = bullet.angle * (180/Math.PI);

 

bullet.onTick = function(delta){

    var velocityX = Math.cos((bullet.angle) * Math.PI / 180* (bullet.speed * delta);

    var velocityY = Math.sin((bullet.angle) * Math.PI / 180* (bullet.speed * delta);

 

    bullet.x += velocityX;

    bullet.y += velocityY;

}


 

 

Shooting

Controls:

Click anywhere on screen to shoot. The location determines the angle of the shot, while distance from the jet determines the speed

Description

The actual description behind the mathematics are available at the two linked tutorials at the beginning and end of this recipe.  This is just a quick explanation of exactly what this application does.  We are creating an Array of bullet objects, a class we define ourselves inline.  The bullet simply contains its location (x,y), the location it is targeting (the user click position, targetX and targetY), the angle, speed ( pixels per second ) and finally a graphic that we will create in a moment.  We set the users speed using a multiple relative to the click distance away from the jet sprite.  For example, if the user clicks at 40 on the Y axis, the bullet be moving at  50 * (340-40)/100 == 50 * 3 == 150 pixels / second.  For the record, don't click at 340 in the Y axis or your bullet won't go anywhere! :)

Now that we have the speed, we calculate the initial angle between the jet and the location the user clicked.  Next we register an on tick() method that will be called each frame to update the bullet.  We simply check to see if the bullet is completely off screen in any direction, and if it is, we remove it from our array.  Otherwise we apply our velocity along our given angle, and update the X and Y values accordingly.  The remains of the tick function simply draw the newly updated bullet.

 

Complete Code

<!DOCTYPE html>

<html>

<head>

    <script src="http://code.createjs.com/easeljs-0.5.0.min.js"></script>

    <script>

        var jetSprite;

        var stage;

        var bullets;

 

 

        document.addEventListener('DOMContentLoaded', demo,false);

 

        function demo(){

            bullets = new Array();

            stage = new createjs.Stage("theCanvas");

 

            stage.canvas.onmouseover = function(e){

                document.body.style.cursor = "crosshair";

            }

 

            stage.canvas.onclick = function(e){

 

                userClicked = true;

 

                var bullet = {

                    x: 200,

                    y:340,

                    targetX:e.x,

                    targetY:e.y,

                    angle:0,

                    speed:50,

                    bulletGraphic:null

                };

 

                // calculate the speed relative to the mouse click distance

                bullet.speed *= (bullet.y - bullet.targetY)/100;

 

                // now calculate the angle between the bullet and mouse click

                bullet.angle = Math.atan2(bullet.targetY - bullet.y, bullet.targetX - bullet.x );

                bullet.angle = bullet.angle * (180/Math.PI);

 

 

                bullet.onTick = function(delta){

 

                    if(this.x < -10 || this.x > stage.canvas.width+10 || this.y < -10 || this.y > stage.canvas.height+10){

                        stage.removeChild(this.bulletGraphic);

                        // Remove this bullet from the bullet list

                        var idx = bullets.indexOf(this);

                        bullets.splice(idx,1);

                        console.log(idx + " " + bullets.length);

                        //console.log("Bullet destroyed");

                    }

 

                    var velocityX = Math.cos((bullet.angle) * Math.PI / 180* (bullet.speed * delta);

                    var velocityY = Math.sin((bullet.angle) * Math.PI / 180* (bullet.speed * delta);

 

                    bullet.x += velocityX;

                    bullet.y += velocityY;

                    if(this.bulletGraphic !== null)

                        stage.removeChild(this.bulletGraphic);

                    var g = new createjs.Graphics();

                    g.setStrokeStyle(5);

                    g.beginStroke(createjs.Graphics.getRGB(255,0,0));

                    g.drawCircle(this.x,this.y,10);

 

                    this.bulletGraphic = new createjs.Shape(g);

                    stage.addChild(this.bulletGraphic);

                }

                bullets.push(bullet);

            }

 

            jetSprite = new createjs.Bitmap("jetsprite.small.png");

            jetSprite.regX = 30// Half image width

            jetSprite.regY = 40// Half image height

            jetSprite.y = 360;

            jetSprite.x = 200;

 

            stage.addChild(jetSprite);

 

            jetSprite.image.onload = function(e){

            }

 

            //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;

 

            for(var i = 0; i < bullets.length; i++){

                bullets[i].onTick(delta);

            }

            stage.update();

        }

    </script>

 

</head>

<body>

<canvas width=400 height=400 id="theCanvas" style="background-color:black" />

</body>

</html>

See Also

See Velocity and Angular Velocity for information on how to move the bullet along a given angle at a given speed.

See Rotating to face another object for details on how to calculate the angle between the jet and the mouse click location.

 

Programming

14. December 2012

Whoops.  I generally keep on top of new Blender releases, but this one slipped past my radar.  So, this new is a bit dated.

Blender 2.65 splash

 

Anyways, Blender 2.65 was released a couple days ago.  This post takes a look at what's in this release of interest for game developers.  At first glance, not too much.  At second glance, quite a bit actually.  At third glance, you are glancing too much and it's time to simply look!

 

 

First off, stability.  Over 200 items were knocked off the bug list.  More stability is always nice.

 

Stuff not really all that gamedev related

  • Fire simulation and smoke flow force field added
  • Open Shading language support added to Cycles renderer
  • anisotrophic shading node added
  • anti-aliased viewport drawing

 

Game dev related additions

  • decimator modifier rewritten and now preserves UV 
  • new smooth modifier that can preserve edges and volumes
  • triangulate modifier which can be used for creating baked normal maps
  • bevel now includes round and no longer sucks
  • a symmetrize tool was added
  • a tool for transferring vertex weights between objects

 

Bevel

There is not a ton to the bevel controls:

Blender Bevel

 Basically you have offset and segment.

 

 Offset is the amount to bevel by

 

 Segments is the number of iterations or edges to use when composing the bevel

 

 

More impressive are the results, before bevelling multiple edges was… ugly.  Now:

Bevel Results

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Symmetrize 

So how exactly does Symmetrize work in Blender?  Remarkably well actually…  Check this out.

 

Before:

Blender Symmetrize Before

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

After:

Blender Symmetize After

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Too damned cool.  So, basically it's like a mirror modifier… that you can apply after the fact.  I like.  Options are pretty simple over all. 

 

Mirror Direction

 

 

 

 

 

 

 

 

Basically you just pick the axis and direction you want the symmetry applied along.  Again, very cool.

 

Great job on the release Blender team.  Head on over and download it here.

Art, News

13. December 2012

 

Some months back we announced that Torque3D had gone open source, now it’s 2D cousin has gone open source as well. 

 

Torque2D is a 2D version of the Torque game engine that also includes WYSIWYG level editing tools and it’s own scripting language, TorqueScript.

 

As you can see from the picture to the right, iTorque2D, the iOS version of Torque2D, is being folded in to this release.  Torque2DMIT will be released early next year.  However there is one gotcha with that release date:

 

In order to work in an open source environment as soon as possible, we made a decision to publish our initial version of Torque 2D MIT without the editors; in other words, the initial version will be an API only engine with tool development to follow thereafter.

 

So basically that WYSIWIG level editor I just mentioned?  Well, it wont be ready day one.

 

As to the license, the MIT license is one of the least restrictive open source licenses available.  Basically you can do what you want with it.

 

You can read more details about Torque2D here.

 

Pretty cool news over all.  When Torque3D was open sourced, they moved pretty quickly to release the source code, so hopefully the same happens here.

News

12. December 2012

In our prior tutorial we looked at using an axis aligned bounding box to perform collision testing.  One big downside was the handling of sprite rotation.  Today, we will look at using a circle instead of a rectangle for our collision detection. The circle has the obvious advantage of being the same size no matter how much you rotate it.  It is also extremely fast to calculate and test for collisions.  Although as you can see from the application to the right, it isn't extremely accurate.

 

Just the Math

Calculating the bounding circle radius:

function getBoundingCircleRadius(sprite){

    return Math.sqrt(((sprite.image.width/2 * sprite.image.width/2)

            + (sprite.image.height/2 * sprite.image.height/2)));

 

}

 

Checking for an intersection between circles


function circlesIntersect(c1X,c1Y,c1Radius, c2X, c2Y, c2Radius){

    var distanceX = c2X - c1X;

    var distanceY = c2Y - c1Y;

 

    var magnitude = Math.sqrt(distanceX * distanceX + distanceY * distanceY);

    return magnitude < c1Radius + c2Radius;

}


 

 

Description

As mentioned earlier, a bounding circle removes the complexity of dealing with rotation.  This is a bit of a double edge sword though.  An axis aligned bounding box can be tighter and more accurate than a bounding circle, but as it rotates it quickly becomes less so.  The following application illustrates the same shape bounded by both a bounding box and a bounding circle.  As you can see, at some points the bounding box is a great deal more accurate, but as it rotates, it becomes a great deal less accurate:

 The calculations for the bounding circle are however a great deal easier to perform.  Let's take a look at them now.

Math.sqrt(((sprite.image.width/2 * sprite.image.width/2)

            + (sprite.image.height/2 * sprite.image.height/2)));

First we start off by calculating the radius of our sprite's image.  This is a matter of calculating the length of furthest point from the centre, giving us the smallest possible radius that encompasses our sprite.  The process is rather straight forward and is calculated using pythagorean theorem again.  We are essentially calculating the magnitude (or distance) from the centre of our sprite to the corner, we do this by forming a right angle triangle.

For a bit of a refresher on pythagorean theorem (which is used A LOT), consider this diagram I stole:

pythagorean theorem

 

a is the X coordinate of our corner, b is the Y coordinate of our corner, therefore the distance or magnitude ( the second is the correct term mathematically ) between those two points is c, which you can calculate by taking the square root of the square of a plus the square of b.  Or using our actual variable names, distance = square root( x * x + y * y).  The resulting value of this equation is the distance between x and y.  So, how did we come up with the values for x and y?  That part was simple, since our pivot is at the centre of our sprite, x is simply half the width of the image, while y is half the height.

If that just confused the hell out of you, the following diagram might help a bit.  It illustrates how pythagorean theorem is being applied to our actual jet sprite to calculate the distance to the corner.

DistanceToCorner

 

So, now we have the distance to the corner, which we can now use it as our circle's radius.  Now we need to figure out how to determine if an intersection occurs.

var distanceX = c2X - c1X;

var distanceY = c2Y - c1Y;

 

var magnitude = Math.sqrt(distanceX * distanceX + distanceY * distanceY);

return magnitude < c1Radius + c2Radius;

This again is a simple and quick equation.  Actually, its the exact same formula again, this time though, we calculate x and y by measuring the magnitude ( distance ) between each of our circles centre points.  Once we have calculate the magnitude between the two circles, we simply check to see if that distance value is less than the total radius of both circles.  If the magnitude is less than the radius of both circles combined, they intersect, otherwise they don't.

One thing you should be aware of ( but not too concerned with initially ) is the square root operation is an expensive one and generally something you want to avoid.  A square root is many times slower to perform than a multiplication or division.  In this situation, it is a very easy to eliminate, you simply square both sides, like so:

function circlesIntersect(c1X,c1Y,c1Radius, c2X, c2Y, c2Radius){

    var distanceX = c2X - c1X;

    var distanceY = c2Y - c1Y;     

    var magnitudeSquared = distanceX * distanceX + distanceY * distanceY;

    return magnitudeSquared < (c1Radius + c2Radius) * (c1Radius + c2Radius);

}

 

If you are still struggling with the math though, these kinds of optimizations can happen later if they are needed at all.  It's often easier to optimize after the fact anyways, so don't worry too much about being fast quite yet.  It's far too easy to get caught up optimizing prematurely.

 

Complete Code

<!DOCTYPE html>

<html>

<head>

    <script src="http://code.createjs.com/easeljs-0.5.0.min.js"></script>

    <script>

        var jetSprite,jetSprite2;

        var boundingCircle1, boundingCircle2;

        var actualBounds;

 

        var stage;

 

        document.addEventListener('DOMContentLoaded', demo,false);

 

 

        function getBoundingCircleRadius(sprite){

            return Math.sqrt(((sprite.image.width/2 * sprite.image.width/2)

                    + (sprite.image.height/2 * sprite.image.height/2)));

 

        }

 

        function createBoundingCircle(sprite,useMaxExtents){

            var g = new createjs.Graphics();

            g.setStrokeStyle(2);

            g.beginStroke(createjs.Graphics.getRGB(0,0,255));

            var radius = getBoundingCircleRadius(sprite);

            g.drawCircle(sprite.x,sprite.y,radius);

            return new createjs.Shape(g);

        }

 

        function circlesIntersect(c1X,c1Y,c1Radius, c2X, c2Y, c2Radius){

            var distanceX = c2X - c1X;

            var distanceY = c2Y - c1Y;

 

            var magnitude = Math.sqrt(distanceX * distanceX + distanceY * distanceY);

            return magnitude < c1Radius + c2Radius;

            // Note, sqrt is a slow operation, square both sides for better performance

            // var magnitudeSquared = distanceX * distanceX + distanceY * distanceY;

            // return magnitudeSquared < (c1Radius + c2Radius) * (c1Radius + c2Radius);

        }

 

        function demo(){

            stage = new createjs.Stage("theCanvas");

 

            jetSprite = new createjs.Bitmap("jetsprite.small.png");

            jetSprite.regX = 30; // Half image width

            jetSprite.regY = 40; // Half image height

            jetSprite.y = 200;

            jetSprite.x = 100;

 

            jetSprite.image.onload = function(){

                boundingCircle1 = createBoundingCircle(jetSprite);

                stage.addChild(boundingCircle1);

            }

 

            stage.addChild(jetSprite);

 

 

 

            jetSprite2 = new createjs.Bitmap("jetsprite.small.png");

            jetSprite2.regX = 30; // Half image width

            jetSprite2.regY = 40; // Half image height

            jetSprite2.y = 200;

            jetSprite2.x = 300;

 

            jetSprite2.image.onload = function(){

                boundingCircle2 = createBoundingCircle(jetSprite2);

                stage.addChild(boundingCircle2);

            }

 

 

            stage.addChild(jetSprite2);

 

            //And go...

            stage.update();

 

            // onFrame will be called each "tick". Default is 50ms, or 20FPS

            createjs.Ticker.addListener(onFrame);

        }

 

        function onFrame(elapsedTime) {

            jetSprite.x++;

            jetSprite.rotation = jetSprite.rotation+1;

            stage.removeChild(boundingCircle1);

            boundingCircle1 = createBoundingCircle(jetSprite);

 

            stage.addChild(boundingCircle1);

            stage.addChild(boundingCircle2);

 

            if(circlesIntersect(jetSprite.x, jetSprite.y, getBoundingCircleRadius(jetSprite),

                    jetSprite2.x, jetSprite2.y, getBoundingCircleRadius(jetSprite2)))

            {

                jetSprite.x = 100;

            }

 

            stage.update();

        }

    </script>

 

</head>

<body>

<canvaswidth=400height=400id="theCanvas"style="background-color:black"/>

</body>

</html>

 

 

See Also

See Collision Detection using an axis-aligned bounding box for details on bounding boxes.

 

If you are still struggling with the use of triangles, you really need to wrap your head around this concept.  This Youtube video ( with horrible audio ), gives a good example of pythagorean theorem in action:

 

 

Programming

10. December 2012

 

I don’t throw around words like ‘Nanny State’ often or lightly, but when it comes to epically stupid “think of the children” laws effecting video games I take notice.  This one is a whopper though.  When I think Nanny State… there are a few countries and companies I think of, Germany and Nintendo nearly topping both of those lists ( well.. and Australia…  they’ve mastered the art of stupid law making in the name of protecting children ).  When Germany and Nintendo combine, the results are some epic stupidity.

 

As of right now you can’t buy games online in Europe that have a PEGI 18 rating, unless it’s after 11PM and before 3AM.  So, if you want to buy Assassin's Creed or ZombieU online and aren’t a night owl, you can’t.

 

Here is a quote from Nintendo in response to Eurogamer:

“At Nintendo we always aim to provide a safe gaming experience for fans of all ages and ensure that we comply with applicable legal age restriction requirements across Europe,” a Nintendo spokesperson told Eurogamer.

“Legal age restriction requirements vary across a number of European countries. Since Nintendo of Europe is based in Germany, Nintendo eShop is complying with German youth protection regulation which therefore applies to all our European markets. Under German law, content rated 18+ must be made available only at night.

“Therefore the accessibility of 18+ content in Nintendo eShop is limited to [USK: 22:00 UTC until 4:00 UTC] [PEGI: 23:00 UTC until 3:00 UTC].”

 

So, Germany has a downright stupid child protection law on the books, and Nintendo Europe’s offices are based out of Germany, so they are applying the law TO ALL OF EUROPE.

 

So, Germany get’s the stupid prize for enacting a law that makes not a lick of sense.  It’s modeled in the mode of television restrictions where adult content can’t be played until a certain time window, which itself is completely ignorant of 10 years of progress in digital distribution rendering the entire concept archaic and mostly pointless.  Not to mention the fact… what are the demograp