Learn CreateJS by Building an HTML5 Pong Game
The web moves fast – so fast that our original EaselJS tutorial is already out of date! In this tutorial, you’ll learn how to use the newest CreateJS suite by creating a simple Pong clone.
Final Result Preview
Let’s take a look at the final result we will be working towards:
This tutorial is based on Carlos Yanez’s Create a Pong Game in HTML5 With EaselJS, which in turn built on his Getting Started With EaselJS guide. The graphics and sound effects are all taken from the former tutorial.
Step 1: Create index.html
This will be our main index.html file:
<!DOCTYPE html>
<html>
<head>
<title>Pong</title>
<style>/* Removes Mobile Highlight */ *{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style>
<script src="http://code.createjs.com/easeljs-0.4.2.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.2.0.min.js"></script>
<script src="http://code.createjs.com/soundjs-0.2.0.min.js"></script>
<script src="http://code.createjs.com/preloadjs-0.1.0.min.js"></script>
<script src="http://code.createjs.com/movieclip-0.4.1.min.js"></script>
<script src="assets/soundjs.flashplugin-0.2.0.min.js"></script>
<script src="Main.js"></script>
</head>
<body onload="Main();">
<canvas id="PongStage" width="480" height="320"></canvas>
</body>
</html>
As you can see, it’s pretty short and consists mainly of loading the CreateJS libraries.
Since the release of CreateJS (which basically bundles all the separate EaselJS libraries) we no longer have to download the JS files and host them on our website; the files are now placed in a CDN (Content Delivery Network) which allows us to load these files remotely as quickly as possible.
Let’s review the code:
<style>/* Removes Mobile Highlight */ *{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style>
This line removes the mobile highlight which may appear when you trying to play the game on mobile. (The mobile highlight causes the canvas object to get highlighted and thus ignore your finger movements.)
Next up, we have the loading of the CreateJS libraries:>
<script src="http://code.createjs.com/easeljs-0.4.2.min.js"></script> <script src="http://code.createjs.com/tweenjs-0.2.0.min.js"></script> <script src="http://code.createjs.com/soundjs-0.2.0.min.js"></script> <script src="http://code.createjs.com/preloadjs-0.1.0.min.js"></script> <script src="http://code.createjs.com/movieclip-0.4.1.min.js"></script>
This code loads the JS files from the CreateJS CDN and it basically allows us to use any of the CreateJS functions in our code
Next, we will load the SoundJS Flash plugin, which provides sound support for browsers that don’t support HTML5 Audio. This is done by using a SWF (a Flash object) to load the sounds.
<script src="assets/soundjs.flashplugin-0.2.0.min.js"></script>
In this case we will not use the CDN; instead, we’ll download the SoundJS library from http://createjs.com/#!/SoundJS/download and place the soundjs.flashplugin-0.2.0.min.js and FlashAudioPlugin.swf files in a local folder named assets.
Last among the JS files, we’ll load the Main.js file which will contain all the code to our game:
<script src="Main.js"></script>
Finally, let’s place a Canvas object on our stage.
<body onload="Main();"> <canvas id="PongStage" width="480" height="320"></canvas> </body>
Now we can start working on the game code.
Step 2: The Variables
Our game code will be inside a file named Main.js, so create and save this now.
First of all, let’s define variables for all the graphic objects in the game:
var canvas; //Will be linked to the canvas in our index.html page var stage; //Is the equivalent of stage in AS3; we'll add "children" to it // Graphics //[Background] var bg; //The background graphic //[Title View] var main; //The Main Background var startB; //The Start button in the main menu var creditsB; //The credits button in the main menu //[Credits] var credits; //The Credits screen //[Game View] var player; //The player paddle graphic var ball; //The ball graphic var cpu; //The CPU paddle var win; //The winning popup var lose; //The losing popup
I’ve added a comment for each variable so that you’ll know what we’ll be loading in that variable
Next up, the scores:
//[Score] var playerScore; //The main player score var cpuScore; //The CPU score var cpuSpeed=6; //The speed of the CPU paddle; the faster it is the harder the game is
We’ll, need variables for the speed of the ball:
// Variables var xSpeed = 5; var ySpeed = 5;
You can change these values to whatever you want, if you’d like to make the game easier or harder.
If you’re a Flash developer, you know that Flash’s onEnterFrame is very useful when creating games, as there are things that need to happen in every given frame. (If you’re not familiar with this idea, check out this article on the Game Loop.)
We have an equivalent for onEnterFrame in CreateJS, and that is the ticker object, which can run code every fraction of a second. Let’s create the variable that will link to it:
var tkr = new Object;
Next we have the preloader, which will use the new PreloadJS methods.
//preloader var preloader; var manifest; var totalLoaded = 0;
preloader– will contain the PreloadJS object.manifest– will hold the list of files we need to load.totalLoaded– this variable will hold the number of files already loaded.
Last but not least in our list of variables, we have TitleView, which will hold several graphics within in order to display them together (like a Flash DisplayObjectContainer).
var TitleView = new Container();
Let’s move on to the Main function…
Step 3: The Main() Function
This function is the first function that runs after all the JS files from the index.html are loaded. But what’s calling this function?
Well, remember this line from the index.html file?
<body onload="Main();">
This code snippet states that once the HTML (and JS libraries) are loaded, the Main function should run.
Let’s review it:
function Main()
{
/* Link Canvas */
canvas = document.getElementById('PongStage');
stage = new Stage(canvas);
stage.mouseEventsEnabled = true;
/* Set The Flash Plugin for browsers that don't support SoundJS */
SoundJS.FlashPlugin.BASE_PATH = "assets/";
if (!SoundJS.checkPlugin(true)) {
alert("Error!");
return;
}
manifest = [
{src:"bg.png", id:"bg"},
{src:"main.png", id:"main"},
{src:"startB.png", id:"startB"},
{src:"creditsB.png", id:"creditsB"},
{src:"credits.png", id:"credits"},
{src:"paddle.png", id:"cpu"},
{src:"paddle.png", id:"player"},
{src:"ball.png", id:"ball"},
{src:"win.png", id:"win"},
{src:"lose.png", id:"lose"},
{src:"playerScore.ogg", id:"playerScore"},
{src:"enemyScore.ogg", id:"enemyScore"},
{src:"hit.ogg", id:"hit"},
{src:"wall.ogg", id:"wall"}
];
preloader = new PreloadJS();
preloader.installPlugin(SoundJS);
preloader.onProgress = handleProgress;
preloader.onComplete = handleComplete;
preloader.onFileLoad = handleFileLoad;
preloader.loadManifest(manifest);
/* Ticker */
Ticker.setFPS(30);
Ticker.addListener(stage);
}
Let’s break down each part:
canvas = document.getElementById('PongStage');
stage = new Stage(canvas);
stage.mouseEventsEnabled = true;
Here we link the PongStage Canvas object from the index.html file to the canvas variable, and then create a Stage object from that canvas. (The stage will allow us to place objects on it.)
mouseEventsEnabled enables us to use mouse events, so we can detect mouse movements and clicks.
/* Set The Flash Plugin for browsers that don't support SoundJS */
SoundJS.FlashPlugin.BASE_PATH = "assets/";
if (!SoundJS.checkPlugin(true)) {
alert("Error!");
return;
}
Here we configure where the Flash sound plugin resides for those browsers in which HTML5 Audio is not supported
manifest = [
{src:"bg.png", id:"bg"},
{src:"main.png", id:"main"},
{src:"startB.png", id:"startB"},
{src:"creditsB.png", id:"creditsB"},
{src:"credits.png", id:"credits"},
{src:"paddle.png", id:"cpu"},
{src:"paddle.png", id:"player"},
{src:"ball.png", id:"ball"},
{src:"win.png", id:"win"},
{src:"lose.png", id:"lose"},
{src:"playerScore.ogg", id:"playerScore"},
{src:"enemyScore.ogg", id:"enemyScore"},
{src:"hit.ogg", id:"hit"},
{src:"wall.ogg", id:"wall"}
];
In the manifest variable we place an array of files we want to load (and provide a unique ID for each one
preloader = new PreloadJS();
preloader.installPlugin(SoundJS);
preloader.onProgress = handleProgress;
preloader.onComplete = handleComplete;
preloader.onFileLoad = handleFileLoad;
preloader.loadManifest(manifest);
Here we configure the preloader object using PreloadJS. PreloadJS is a new addition to the CreateJS libraries and quite a useful one.
We create a new PreloadJS object and place it in the preloader variable, then assign a method to each event (onProgress, onComplete, onFileLoad). Finally we use the preloader to load the manifest we created earlier.
Ticker.setFPS(30); Ticker.addListener(stage);
Here we add the Ticker object to the stage and set the frame rate to 30 FPS; we’ll use it later in the game for the enterFrame functionality.
Step 4: Creating the Preloader Functions
function handleProgress(event)
{
//use event.loaded to get the percentage of the loading
}
function handleComplete(event) {
//triggered when all loading is complete
}
function handleFileLoad(event) {
//triggered when an individual file completes loading
switch(event.type)
{
case PreloadJS.IMAGE:
//image loaded
var img = new Image();
img.src = event.src;
img.onload = handleLoadComplete;
window[event.id] = new Bitmap(img);
break;
case PreloadJS.SOUND:
//sound loaded
handleLoadComplete();
break;
}
}
Let’s review the functions:
handleProgress– In this function you’ll be able to follow the percentage of the loading progress using this parameter:event.loaded. You could use this to create for example a progress bar.handleComplete– This function is called once all the files have been loaded (in case you want to place something there).handleFileLoad– Since we load two types of files – images and sounds – we have this function that will handle each one separately. If it’s an image, we create a bitmap image and place it in a variable (whose name is the same as the ID of the loaded image) and then call thehandleLoadCompletefunction (which we’ll write next); if it’s a sound then we just call thehandleLoadCompleteimmediately.
Now let’s discuss the handleLoadComplete function I just mentioned:
function handleLoadComplete(event)
{
totalLoaded++;
if(manifest.length==totalLoaded)
{
addTitleView();
}
}
It’s a pretty straightforward function; we increase the totalLoaded variable (that holds the number of assets loaded so far) and then we check if the number of items in our manifest is the same as the number of loaded assets, and if so, go to the Main Menu screen.
Step 5: Creating the Main Menu

function addTitleView()
{
//console.log("Add Title View");
startB.x = 240 - 31.5;
startB.y = 160;
startB.name = 'startB';
creditsB.x = 241 - 42;
creditsB.y = 200;
TitleView.addChild(main, startB, creditsB);
stage.addChild(bg, TitleView);
stage.update();
// Button Listeners
startB.onPress = tweenTitleView;
creditsB.onPress = showCredits;
Nothing special here. We place the images of the Background, Start Button and Credits Button on the stage and link onPress event handlers to the Start and Credits buttons.
Here are the functions that display and remove the credits screen and the tweenTitleView which starts the game:
function showCredits()
{
// Show Credits
credits.x = 480;
stage.addChild(credits);
stage.update();
Tween.get(credits).to({x:0}, 300);
credits.onPress = hideCredits;
}
// Hide Credits
function hideCredits(e)
{
Tween.get(credits).to({x:480}, 300).call(rmvCredits);
}
// Remove Credits
function rmvCredits()
{
stage.removeChild(credits);
}
// Tween Title View
function tweenTitleView()
{
// Start Game
Tween.get(TitleView).to({y:-320}, 300).call(addGameView);
}
Step 6: The Game Code

We’ve reached the main part of this tutorial which is the code of the game itself.
First of all, we need to add all the required assets to the stage, so we do that in the addGameView function:
function addGameView()
{
// Destroy Menu & Credits screen
stage.removeChild(TitleView);
TitleView = null;
credits = null;
// Add Game View
player.x = 2;
player.y = 160 - 37.5;
cpu.x = 480 - 25;
cpu.y = 160 - 37.5;
ball.x = 240 - 15;
ball.y = 160 - 15;
// Score
playerScore = new Text('0', 'bold 20px Arial', '#A3FF24');
playerScore.x = 211;
playerScore.y = 20;
cpuScore = new Text('0', 'bold 20px Arial', '#A3FF24');
cpuScore.x = 262;
cpuScore.y = 20;
stage.addChild(playerScore, cpuScore, player, cpu, ball);
stage.update();
// Start Listener
bg.onPress = startGame;
}
Again, a pretty straightforward function that places the objects on the screen and adds a mouseEvent to the background image, so that when the user clicks it the game will start (we will call the startGame function).
Let’s review the startGame function:
function startGame(e)
{
bg.onPress = null;
stage.onMouseMove = movePaddle;
Ticker.addListener(tkr, false);
tkr.tick = update;
}
Here, as you can see, in addition to adding an onMouseMove event that will move our paddle. We add the tick event, which will call the update function in each frame.
Let’s review the movePaddle and reset functions:
function movePaddle(e)
{
// Mouse Movement
player.y = e.stageY;
}
/* Reset */
function reset()
{
ball.x = 240 - 15;
ball.y = 160 - 15;
player.y = 160 - 37.5;
cpu.y = 160 - 37.5;
stage.onMouseMove = null;
Ticker.removeListener(tkr);
bg.onPress = startGame;
}
In movePaddle, we basically place the user’s paddle at the mouse’s y-coordinate.
In reset, we do something similar to addGameView, except here we don’t add any graphic elements since they are already on the screen.
Using the alert function we’ll display the winning and losing popup:
function alert(e)
{
Ticker.removeListener(tkr);
stage.onMouseMove = null;
bg.onPress = null
if(e == 'win')
{
win.x = 140;
win.y = -90;
stage.addChild(win);
Tween.get(win).to({y: 115}, 300);
}
else
{
lose.x = 140;
lose.y = -90;
stage.addChild(lose);
Tween.get(lose).to({y: 115}, 300);
}
}
Step 7: The Game Loop
Now, for the last part of our tutorial we’ll work on the update function (which occurs in every frame of the game – similar to Flash’s onEnterFrame):
function update()
{
// Ball Movement
ball.x = ball.x + xSpeed;
ball.y = ball.y + ySpeed;
// Cpu Movement
if(cpu.y < ball.y) {
cpu.y = cpu.y + 4;
}
else if(cpu.y > ball.y) {
cpu.y = cpu.y - 4;
}
// Wall Collision
if((ball.y) < 0) { ySpeed = -ySpeed; SoundJS.play('wall'); };//Up
if((ball.y + (30)) > 320) { ySpeed = -ySpeed; SoundJS.play('wall');};//down
/* CPU Score */
if((ball.x) < 0)
{
xSpeed = -xSpeed;
cpuScore.text = parseInt(cpuScore.text + 1);
reset();
SoundJS.play('enemyScore');
}
/* Player Score */
if((ball.x + (30)) > 480)
{
xSpeed = -xSpeed;
playerScore.text = parseInt(playerScore.text + 1);
reset();
SoundJS.play('playerScore');
}
/* Cpu collision */
if(ball.x + 30 > cpu.x && ball.x + 30 < cpu.x + 22 && ball.y >= cpu.y && ball.y < cpu.y + 75)
{
xSpeed *= -1;
SoundJS.play('hit');
}
/* Player collision */
if(ball.x <= player.x + 22 && ball.x > player.x && ball.y >= player.y && ball.y < player.y + 75)
{
xSpeed *= -1;
SoundJS.play('hit');
}
/* Stop Paddle from going out of canvas */
if(player.y >= 249)
{
player.y = 249;
}
/* Check for Win */
if(playerScore.text == '10')
{
alert('win');
}
/* Check for Game Over */
if(cpuScore.text == '10')
{
alert('lose');
}
}
Looks scary, doesn’t it? Don’t worry, we’ll review each part and discuss it.
// Ball Movement ball.x = ball.x + xSpeed; ball.y = ball.y + ySpeed;
In each frame, the ball will move according to its x and y speed values
// Cpu Movement
if((cpu.y+32) < (ball.y-14)) {
cpu.y = cpu.y + cpuSpeed;
}
else if((cpu.y+32) > (ball.y+14)) {
cpu.y = cpu.y - cpuSpeed;
}
Here we have the basic AI of the computer, in which the computer’s paddle simply follows the ball without any special logic. We just compare the location of the center of the paddle (which is why we add 32 pixels to the cpu Y value) to the location of the ball, with a small offset, and move the paddle up or down as necessary.
if((ball.y) < 0) { //top
ySpeed = -ySpeed;
SoundJS.play('wall');
};
if((ball.y + (30)) > 320) { //bottom
ySpeed = -ySpeed;
SoundJS.play('wall');
};
If the ball hits the top border or the bottom border of the screen, the ball changes direction and we play the Wall Hit sound.
/* CPU Score */
if((ball.x) < 0)
{
xSpeed = -xSpeed;
cpuScore.text = parseInt(cpuScore.text + 1);
reset();
SoundJS.play('enemyScore');
}
/* Player Score */
if((ball.x + (30)) > 480)
{
xSpeed = -xSpeed;
playerScore.text = parseInt(playerScore.text + 1);
reset();
SoundJS.play('playerScore');
}
The score login is simple: if the ball passes the left or right borders it increases the score of the player or CPU respectively, plays a sound, and resets the location of the objects using the reset function we’ve discussed earlier.
/* CPU collision */
if(ball.x + 30 > cpu.x && ball.x + 30 < cpu.x + 22 && ball.y >= cpu.y && ball.y < cpu.y + 75)
{
xSpeed *= -1;
SoundJS.play('hit');
}
/* Player collision */
if(ball.x <= player.x + 22 && ball.x > player.x && ball.y >= player.y && ball.y < player.y + 75)
{
xSpeed *= -1;
SoundJS.play('hit');
}
Here we deal with collisions of the ball with the paddles; every time the ball hits one of the paddles, the ball changes direction and a sound is played
if(player.y >= 249)
{
player.y = 249;
}
If the player’s paddle goes out of bounds, we place it back within the bounds.
/* Check for Win */
if(playerScore.text == '10')
{
alert('win');
}
/* Check for Game Over */
if(cpuScore.text == '10')
{
alert('lose');
}
In this snippet, we check whether either of the players’ score has reached 10 points, and if so we display the winning or losing popup to the player (according to his winning status).
Conclusion
That’s it, you’ve created an entire pong game using CreateJS. Thank you for taking the time to read this tutorial.
View full post on Activetuts+

The web moves fast – so fast that our original EaselJS tutorial is already out of date! In this tutorial, you’ll learn how to use the newest CreateJS suite by creating a simple Pong clone.
Final Result Preview
Let’s take a look at the final result we will be working towards:
Click to play
This tutorial is based on Carlos Yanez’s Create a Pong Game in HTML5 With EaselJS, which in turn built on his Getting Started With EaselJS guide. The graphics and sound effects are all taken from the former tutorial.
Step 1: Create
index.htmlThis will be our main
index.htmlfile:<!DOCTYPE html> <html> <head> <title>Pong</title> <style>/* Removes Mobile Highlight */ *{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style> <script src="http://code.createjs.com/easeljs-0.4.2.min.js"></script> <script src="http://code.createjs.com/tweenjs-0.2.0.min.js"></script> <script src="http://code.createjs.com/soundjs-0.2.0.min.js"></script> <script src="http://code.createjs.com/preloadjs-0.1.0.min.js"></script> <script src="http://code.createjs.com/movieclip-0.4.1.min.js"></script> <script src="assets/soundjs.flashplugin-0.2.0.min.js"></script> <script src="Main.js"></script> </head> <body onload="Main();"> <canvas id="PongStage" width="480" height="320"></canvas> </body> </html>As you can see, it’s pretty short and consists mainly of loading the CreateJS libraries.
Since the release of CreateJS (which basically bundles all the separate EaselJS libraries) we no longer have to download the JS files and host them on our website; the files are now placed in a CDN (Content Delivery Network) which allows us to load these files remotely as quickly as possible.
Let’s review the code:
<style>/* Removes Mobile Highlight */ *{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style>This line removes the mobile highlight which may appear when you trying to play the game on mobile. (The mobile highlight causes the canvas object to get highlighted and thus ignore your finger movements.)
Next up, we have the loading of the CreateJS libraries:
This code loads the JS files from the CreateJS CDN and it basically allows us to use any of the CreateJS functions in our code
Next, we will load the SoundJS Flash plugin, which provides sound support for browsers that don’t support HTML5 Audio. This is done by using a SWF (a Flash object) to load the sounds.
In this case we will not use the CDN; instead, we’ll download the SoundJS library from http://createjs.com/#!/SoundJS/download and place the
soundjs.flashplugin-0.2.0.min.jsandFlashAudioPlugin.swffiles in a local folder namedassets.Last among the JS files, we’ll load the
Main.jsfile which will contain all the code to our game:Finally, let’s place a Canvas object on our stage.
Now we can start working on the game code.
Step 2: The Variables
Our game code will be inside a file named
Main.js, so create and save this now.First of all, let’s define variables for all the graphic objects in the game:
I’ve added a comment for each variable so that you’ll know what we’ll be loading in that variable
Next up, the scores:
We’ll, need variables for the speed of the ball:
You can change these values to whatever you want, if you’d like to make the game easier or harder.
If you’re a Flash developer, you know that Flash’s
onEnterFrameis very useful when creating games, as there are things that need to happen in every given frame. (If you’re not familiar with this idea, check out this article on the Game Loop.)We have an equivalent for
onEnterFramein CreateJS, and that is thetickerobject, which can run code every fraction of a second. Let’s create the variable that will link to it:Next we have the preloader, which will use the new PreloadJS methods.
preloader– will contain the PreloadJS object.manifest– will hold the list of files we need to load.totalLoaded– this variable will hold the number of files already loaded.Last but not least in our list of variables, we have
TitleView, which will hold several graphics within in order to display them together (like a FlashDisplayObjectContainer).Let’s move on to the Main function…
Step 3: The Main() Function
This function is the first function that runs after all the JS files from the
index.htmlare loaded. But what’s calling this function?Well, remember this line from the
index.htmlfile?This code snippet states that once the HTML (and JS libraries) are loaded, the
Mainfunction should run.Let’s review it:
function Main() { /* Link Canvas */ canvas = document.getElementById('PongStage'); stage = new Stage(canvas); stage.mouseEventsEnabled = true; /* Set The Flash Plugin for browsers that don't support SoundJS */ SoundJS.FlashPlugin.BASE_PATH = "assets/"; if (!SoundJS.checkPlugin(true)) { alert("Error!"); return; } manifest = [ {src:"bg.png", id:"bg"}, {src:"main.png", id:"main"}, {src:"startB.png", id:"startB"}, {src:"creditsB.png", id:"creditsB"}, {src:"credits.png", id:"credits"}, {src:"paddle.png", id:"cpu"}, {src:"paddle.png", id:"player"}, {src:"ball.png", id:"ball"}, {src:"win.png", id:"win"}, {src:"lose.png", id:"lose"}, {src:"playerScore.ogg", id:"playerScore"}, {src:"enemyScore.ogg", id:"enemyScore"}, {src:"hit.ogg", id:"hit"}, {src:"wall.ogg", id:"wall"} ]; preloader = new PreloadJS(); preloader.installPlugin(SoundJS); preloader.onProgress = handleProgress; preloader.onComplete = handleComplete; preloader.onFileLoad = handleFileLoad; preloader.loadManifest(manifest); /* Ticker */ Ticker.setFPS(30); Ticker.addListener(stage); }Let’s break down each part:
canvas = document.getElementById('PongStage'); stage = new Stage(canvas); stage.mouseEventsEnabled = true;Here we link the
PongStageCanvas object from theindex.htmlfile to the canvas variable, and then create a Stage object from that canvas. (The stage will allow us to place objects on it.)mouseEventsEnabledenables us to use mouse events, so we can detect mouse movements and clicks./* Set The Flash Plugin for browsers that don't support SoundJS */ SoundJS.FlashPlugin.BASE_PATH = "assets/"; if (!SoundJS.checkPlugin(true)) { alert("Error!"); return; }Here we configure where the Flash sound plugin resides for those browsers in which HTML5 Audio is not supported
manifest = [ {src:"bg.png", id:"bg"}, {src:"main.png", id:"main"}, {src:"startB.png", id:"startB"}, {src:"creditsB.png", id:"creditsB"}, {src:"credits.png", id:"credits"}, {src:"paddle.png", id:"cpu"}, {src:"paddle.png", id:"player"}, {src:"ball.png", id:"ball"}, {src:"win.png", id:"win"}, {src:"lose.png", id:"lose"}, {src:"playerScore.ogg", id:"playerScore"}, {src:"enemyScore.ogg", id:"enemyScore"}, {src:"hit.ogg", id:"hit"}, {src:"wall.ogg", id:"wall"} ];In the manifest variable we place an array of files we want to load (and provide a unique ID for each one
preloader = new PreloadJS(); preloader.installPlugin(SoundJS); preloader.onProgress = handleProgress; preloader.onComplete = handleComplete; preloader.onFileLoad = handleFileLoad; preloader.loadManifest(manifest);Here we configure the preloader object using PreloadJS. PreloadJS is a new addition to the CreateJS libraries and quite a useful one.
We create a new PreloadJS object and place it in the
preloadervariable, then assign a method to each event (onProgress,onComplete,onFileLoad). Finally we use thepreloaderto load the manifest we created earlier.Here we add the Ticker object to the stage and set the frame rate to 30 FPS; we’ll use it later in the game for the
enterFramefunctionality.Step 4: Creating the Preloader Functions
function handleProgress(event) { //use event.loaded to get the percentage of the loading } function handleComplete(event) { //triggered when all loading is complete } function handleFileLoad(event) { //triggered when an individual file completes loading switch(event.type) { case PreloadJS.IMAGE: //image loaded var img = new Image(); img.src = event.src; img.onload = handleLoadComplete; window[event.id] = new Bitmap(img); break; case PreloadJS.SOUND: //sound loaded handleLoadComplete(); break; } }Let’s review the functions:
handleProgress– In this function you’ll be able to follow the percentage of the loading progress using this parameter:event.loaded. You could use this to create for example a progress bar.handleComplete– This function is called once all the files have been loaded (in case you want to place something there).handleFileLoad– Since we load two types of files – images and sounds – we have this function that will handle each one separately. If it’s an image, we create a bitmap image and place it in a variable (whose name is the same as the ID of the loaded image) and then call thehandleLoadCompletefunction (which we’ll write next); if it’s a sound then we just call thehandleLoadCompleteimmediately.Now let’s discuss the
handleLoadCompletefunction I just mentioned:function handleLoadComplete(event) { totalLoaded++; if(manifest.length==totalLoaded) { addTitleView(); } }It’s a pretty straightforward function; we increase the
totalLoadedvariable (that holds the number of assets loaded so far) and then we check if the number of items in our manifest is the same as the number of loaded assets, and if so, go to the Main Menu screen.Step 5: Creating the Main Menu
function addTitleView() { //console.log("Add Title View"); startB.x = 240 - 31.5; startB.y = 160; startB.name = 'startB'; creditsB.x = 241 - 42; creditsB.y = 200; TitleView.addChild(main, startB, creditsB); stage.addChild(bg, TitleView); stage.update(); // Button Listeners startB.onPress = tweenTitleView; creditsB.onPress = showCredits;Nothing special here. We place the images of the Background, Start Button and Credits Button on the stage and link
onPressevent handlers to the Start and Credits buttons.Here are the functions that display and remove the credits screen and the
tweenTitleViewwhich starts the game:function showCredits() { // Show Credits credits.x = 480; stage.addChild(credits); stage.update(); Tween.get(credits).to({x:0}, 300); credits.onPress = hideCredits; } // Hide Credits function hideCredits(e) { Tween.get(credits).to({x:480}, 300).call(rmvCredits); } // Remove Credits function rmvCredits() { stage.removeChild(credits); } // Tween Title View function tweenTitleView() { // Start Game Tween.get(TitleView).to({y:-320}, 300).call(addGameView); }Step 6: The Game Code
We’ve reached the main part of this tutorial which is the code of the game itself.
First of all, we need to add all the required assets to the stage, so we do that in the
addGameViewfunction:function addGameView() { // Destroy Menu & Credits screen stage.removeChild(TitleView); TitleView = null; credits = null; // Add Game View player.x = 2; player.y = 160 - 37.5; cpu.x = 480 - 25; cpu.y = 160 - 37.5; ball.x = 240 - 15; ball.y = 160 - 15; // Score playerScore = new Text('0', 'bold 20px Arial', '#A3FF24'); playerScore.x = 211; playerScore.y = 20; cpuScore = new Text('0', 'bold 20px Arial', '#A3FF24'); cpuScore.x = 262; cpuScore.y = 20; stage.addChild(playerScore, cpuScore, player, cpu, ball); stage.update(); // Start Listener bg.onPress = startGame; }Again, a pretty straightforward function that places the objects on the screen and adds a mouseEvent to the background image, so that when the user clicks it the game will start (we will call the
startGamefunction).Let’s review the
startGamefunction:function startGame(e) { bg.onPress = null; stage.onMouseMove = movePaddle; Ticker.addListener(tkr, false); tkr.tick = update; }Here, as you can see, in addition to adding an
onMouseMoveevent that will move our paddle. We add thetickevent, which will call theupdatefunction in each frame.Let’s review the
movePaddleandresetfunctions:function movePaddle(e) { // Mouse Movement player.y = e.stageY; } /* Reset */ function reset() { ball.x = 240 - 15; ball.y = 160 - 15; player.y = 160 - 37.5; cpu.y = 160 - 37.5; stage.onMouseMove = null; Ticker.removeListener(tkr); bg.onPress = startGame; }In
movePaddle, we basically place the user’s paddle at the mouse’s y-coordinate.In
reset, we do something similar toaddGameView, except here we don’t add any graphic elements since they are already on the screen.Using the
alertfunction we’ll display the winning and losing popup:function alert(e) { Ticker.removeListener(tkr); stage.onMouseMove = null; bg.onPress = null if(e == 'win') { win.x = 140; win.y = -90; stage.addChild(win); Tween.get(win).to({y: 115}, 300); } else { lose.x = 140; lose.y = -90; stage.addChild(lose); Tween.get(lose).to({y: 115}, 300); } }Step 7: The Game Loop
Now, for the last part of our tutorial we’ll work on the
updatefunction (which occurs in every frame of the game – similar to Flash’sonEnterFrame):function update() { // Ball Movement ball.x = ball.x + xSpeed; ball.y = ball.y + ySpeed; // Cpu Movement if(cpu.y < ball.y) { cpu.y = cpu.y + 4; } else if(cpu.y > ball.y) { cpu.y = cpu.y - 4; } // Wall Collision if((ball.y) < 0) { ySpeed = -ySpeed; SoundJS.play('wall'); };//Up if((ball.y + (30)) > 320) { ySpeed = -ySpeed; SoundJS.play('wall');};//down /* CPU Score */ if((ball.x) < 0) { xSpeed = -xSpeed; cpuScore.text = parseInt(cpuScore.text + 1); reset(); SoundJS.play('enemyScore'); } /* Player Score */ if((ball.x + (30)) > 480) { xSpeed = -xSpeed; playerScore.text = parseInt(playerScore.text + 1); reset(); SoundJS.play('playerScore'); } /* Cpu collision */ if(ball.x + 30 > cpu.x && ball.x + 30 < cpu.x + 22 && ball.y >= cpu.y && ball.y < cpu.y + 75) { xSpeed *= -1; SoundJS.play('hit'); } /* Player collision */ if(ball.x <= player.x + 22 && ball.x > player.x && ball.y >= player.y && ball.y < player.y + 75) { xSpeed *= -1; SoundJS.play('hit'); } /* Stop Paddle from going out of canvas */ if(player.y >= 249) { player.y = 249; } /* Check for Win */ if(playerScore.text == '10') { alert('win'); } /* Check for Game Over */ if(cpuScore.text == '10') { alert('lose'); } }Looks scary, doesn’t it? Don’t worry, we’ll review each part and discuss it.
In each frame, the ball will move according to its x and y speed values
// Cpu Movement if((cpu.y+32) < (ball.y-14)) { cpu.y = cpu.y + cpuSpeed; } else if((cpu.y+32) > (ball.y+14)) { cpu.y = cpu.y - cpuSpeed; }Here we have the basic AI of the computer, in which the computer’s paddle simply follows the ball without any special logic. We just compare the location of the center of the paddle (which is why we add 32 pixels to the cpu Y value) to the location of the ball, with a small offset, and move the paddle up or down as necessary.
if((ball.y) < 0) { //top ySpeed = -ySpeed; SoundJS.play('wall'); }; if((ball.y + (30)) > 320) { //bottom ySpeed = -ySpeed; SoundJS.play('wall'); };If the ball hits the top border or the bottom border of the screen, the ball changes direction and we play the Wall Hit sound.
/* CPU Score */ if((ball.x) < 0) { xSpeed = -xSpeed; cpuScore.text = parseInt(cpuScore.text + 1); reset(); SoundJS.play('enemyScore'); } /* Player Score */ if((ball.x + (30)) > 480) { xSpeed = -xSpeed; playerScore.text = parseInt(playerScore.text + 1); reset(); SoundJS.play('playerScore'); }The score login is simple: if the ball passes the left or right borders it increases the score of the player or CPU respectively, plays a sound, and resets the location of the objects using the
resetfunction we’ve discussed earlier./* CPU collision */ if(ball.x + 30 > cpu.x && ball.x + 30 < cpu.x + 22 && ball.y >= cpu.y && ball.y < cpu.y + 75) { xSpeed *= -1; SoundJS.play('hit'); } /* Player collision */ if(ball.x <= player.x + 22 && ball.x > player.x && ball.y >= player.y && ball.y < player.y + 75) { xSpeed *= -1; SoundJS.play('hit'); }Here we deal with collisions of the ball with the paddles; every time the ball hits one of the paddles, the ball changes direction and a sound is played
if(player.y >= 249) { player.y = 249; }If the player’s paddle goes out of bounds, we place it back within the bounds.
/* Check for Win */ if(playerScore.text == '10') { alert('win'); } /* Check for Game Over */ if(cpuScore.text == '10') { alert('lose'); }In this snippet, we check whether either of the players’ score has reached 10 points, and if so we display the winning or losing popup to the player (according to his winning status).
Conclusion
That’s it, you’ve created an entire pong game using CreateJS. Thank you for taking the time to read this tutorial.
In this mini-series, exclusive to Tuts+ Premium members, you’ll learn how to use Flash to build a Facebook Graph API application that can create slideshows for your public pages. The final part shows you how to actually retrieve and display the information from Facebook.
Premium Preview
Click to try the app on Facebook.
In the previous part of this series, we created the event slider generator. In this part we’ll make the actual slider for the images (and later, for the events).
You’ll need to be logged in to Facebook in order to see this demo: https://apps.facebook.com/activetuts_tabmaker/
Read the Full Tutorial
Premium members can access the full tutorial right away!
If you’re not yet a Premium member, you can still read the first few steps for free.
Tuts+ Premium Membership
We run a Premium membership system which periodically gives members access to extra tutorials, like this one, from across the whole Tuts+ network. If you’re a Premium member, you can log in and read the tutorial. If you’re not a member, you can of course join today!
Also, don’t forget to follow @envatoactive on twitter, circle us on Google+, like us on Facebook, and grab the Activetuts+ RSS Feed to stay up to date with the latest tutorials and articles.
Earlier today, Wilson Lim showed you how to create a gravity-based game called Flux. In this Workshop, Matt Porter critiques Flux, explaining what it would take to turn that simple demo into a real game.
Play the Game
Use the left and right arrow keys to manoeuvre your ship, the up and down arrow keys to increase or reduce the size of the magnetic field it produces, and the space bar to reverse the polarity. Collect the white crystals to increase your fuel supply – but avoid the red ones, because they use it up. Don’t hit a rock, or it’s game over.
Please bear in mind that this demo was created specifically for a tutorial, rather than for release on a Flash portal, so it’s naturally less polished than games we’ve featured in past critiques.
Introduction
Flux is a barebones game. It has one core mechanic, and very basic controls. That being said, even the most basic aspects of a game can require careful balance. With a few small tweaks, we can make something that’s simple and frustrating into something that might not necessarily be a blast, but will be a much smoother and more enjoyable experience.
The core aim of Flux is to sustain energy for your ship by collecting white crystals, while at the same time avoiding red crystals. In addition, you must also avoid rocks, which end your game immediately upon contact. To mix things up a bit, the ship is equipped with a magnet of sorts, that can attract or deflect the negative crystals, but has no effect on the rocks of instant doom.
You can move your ship horizontally, but not vertically, using the left and right arrow keys. The last bit of spice in this otherwise currently bland game is that you can increase or decrease your magnet’s area of effect, by pressing the up and down arrow keys respectively.
So there you have it, that’s Flux. While there’s very little to this game, we’re about to break it down into more than the average eyes see, and come up with some simple, yet game changing, alterations.
Getting Started
The first and most immediate flaw in Flux is that there isn’t really a title screen.
Flux’s starting screen
There’s no instructions, no place to check high score, no title, no music… you’re just tossed onto a bland screen with a Start Game button. Pretty bad start, but okay, let’s move on.
Upon pressing the button, we now see our ship, and some objects in our face. Let’s just assume that we know what to do right off, and that there is a rock (instant death if it hits us) coming right at us (that’ll happen when positions are 100% random). We quickly get our hands onto the keyboard (we started using the mouse, and there were no instructions, so we’re not actually prepared for this), and attempt to dodge the rock.
Oh no, our ship isn’t moving! Does WASD not work? Well, actually, they don’t work (which is a horrible decision, as WASD and arrow keys should always both be supported where possible) – but believe it or not, that’s not our issue either.
The real issue is that the game area doesn’t have focus. We actually need to click the player area to gain control, even though we just pressed the Start Game button. Looks like we just blew up from an oncoming rock. So here we are, literally five seconds into the game, and all we’ve done is see a bland title screen and die from absolute unfairness. Congratulations, you’ve just lost the majority of all your players right off, and most of them have down-voted your game if it’s being played on a major portal such as Newgrounds or Kongregate.
A likely response from a Newgrounds gamer
While the above is an absolute mess, it’s actually quite easily fixed.
Pixel Purge‘s title screen, for comparison
First off, we add a simple title screen. The quality can range greatly, but at the very least, we need a title, a play button, and something related to scores – whether it’s a list already on screen, or a button to let us view them all.
Next, we make sure that the play area has keyboard focus after the Start Game button is pressed. If this were my game, I would then choose to display the controls of the game to the player through pictures and text, and not just a wall of text. For good measure, I’d save a variable to a shared object that says the player has seen the controls, and I would never display them at the start of a game again (unless a “delete all data” button was pressed).
Now that the player has a good initial first impression, with a decent menu, and controls shown to them, we’d start the game. Here I would make sure that the game didn’t actually start until the player moved the ship. This would accomplish two things: first, we’d know that they have their hands on the keyboard and are ready to play; and second, they wouldn’t die from a randomly generated flying space rock within the first two seconds of the game.
Nothing that I just listed above is hard, but it’s just changed that poor initial first impression of this game to “this has potential, let’s see what happens”.
Gameplay
Now that we’ve taken care of analyzing the first impression, let’s focus a bit on the actual gameplay.
Flux’s play screen
The first thing I noticed when playing Flux, is that the movement was extremely stiff, and felt very amateur. It’s obvious that the movement is locked to a set speed, and that there’s no acceleration or easing. If we were to add some of that then not only would the game feel better and look more smooth, but our control would be far more precise, making for a much more enjoyable experience.
The magnet mechanic is actually pretty neat, but it’s a bit rough around the edges. The first thing I noticed, is that there are no restrictions on how big or small the attract radius can be. This allows us to go negative, and eventually appear to be positive again, but the magnet won’t actually do anything. This could leave various impressions on the player, from them being confused to them thinking the game looks unprofessional, so it’s important that we put restrictions in place.
One More Time
Assuming that players actually have fun with the game the way it is (admittedly doubtful beyond the first play or two), we still have some fixing up to do. When the player dies, they aren’t really left with any incentive to play again. The game over screen should be used as an opportunity to tempt the player to play again – and simply having a button to let them do so is not what I mean.
Flux’s game over screen
In this case, the game over screen should still show the player’s score, but also their all-time best score. If their previous play was their best, this should be related to the player both visually and audibly.
Pixel Purge’s game over screen, for comparison
If we wanted to take things even further, we’d toss in Facebook / Twitter buttons, and allow the player to brag about their accomplishment with friends.
What Else?
As I’ve mentioned, even with the critical flaws fixed, and the controls a bit polished, the game is still a bit boring. While there are hundreds if not thousands of directions in which to take a game this basic, I’ll just focus on some of the simpler yet still effective changes.
The first addition I would add is powerups. We’ve already got collision code we use for collecting crystals, so adding powerups is not that hard from a technical standpoint. For example, we could add a powerup that turns every red crystal into a white one. With a cool visual effect, and an awesome sound effect, grabbing one of these would be quite rewarding.
The next thing I would do, is add crystals of different sizes, where the bigger the crystal, the harder it is to attract or repel. Give the larger crystals a higher score value, and a more rewarding sound, and you’ve now got some substance to your game. You’ll find players act extra risky around instant-kill death rocks, just so they can attempt to pull in a giant white crystal.
Speaking of instant-kill death rocks, why not add a way to get rid of them? With the simple press of a button (say, the X key), let the player shoot directly ahead – for a cost. To make the game more interesting, firing your weapon would require some of your energy, so it would have to be used sparingly. We could also add a powerup that explodes all rocks on the screen, or that makes the player invincible so that they can hit them without worry for a limited time.
If I really wanted to add a level of complexity to the game, I’d make it so that all crystals gained would be stored as currency, and after each game (or level), the player could spend those points on upgrading stats. These stats could range from maximum magnet radius, to increased energy regeneration from white crystals; the possibilities are nearly endless.
All of the biggest remaining issues are related to polish, and as many developers will tell you, this can take a long time to get right.
Some of the more major aspects of polish that are needed are as follows:
Conclusion
As you can see, it didn’t take too much to really rip this game apart, and then point out some simple but effective ways to fix it up.
There are enormous benefits to being able to fix up such a basic game. In reality, everything that Flux has is no larger or more complex than one individual portion of any other larger game. If we look at game development like this, we then see the benefit of being able to fix up such a simple game. We’re essentially able to view our bigger projects as small pieces, and we can then polish each piece up individually until it’s a smooth and enjoyable experience on its own. In the end, we’ll be left with a full-fledged game, comprised of many small, extremely well built pieces.
What do you think should be added to Flux? Post your suggestions in the comments – or you could even follow the tutorial and make the changes yourself…
In this tutorial, I’ll explain the major steps and workflow for creating a simple space survival game, based on the gravity mechanic explained in a previous tutorial. This game is written in AS3 using FlashDevelop.
Play the Game
Use the left and right arrow keys to manoeuvre your ship, the up and down arrow keys to increase or reduce the size of the magnetic field it produces, and the space bar to reverse the polarity. Collect the white crystals to increase your fuel supply – but avoid the red ones, because they use it up. Don’t hit a rock, or it’s game over!
In this tutorial, we won’t actually create the full game displayed above; we’ll just get started on it, by making a very simple version with primitive graphics and just one type of object. However, by the end, you should have learned enough to be able to add the other features yourself!
The game itself is very simple in its current state – take a look at this critique for tips on how you can take it from a simple demo to a full game!
Let’s Get Started!
Set up a new AS3 project in FlashDevelop, and set its dimensions to 550x600px.
package { [SWF(width = "550", height = "600")] public class Main extends Sprite { } }Step 1: Identifying the Game Objects
There are six objects in particle that you can identify from playing the game above:
Of course you can add in any other object to make the game more interactive or add a new feature. For this tutorial we’ll just make
Step 2: The
EnergyClassFrom the objects we identified, four of them actually work in exactly the same way: by falling from top to bottom.
They are:
In this tutorial, we’re only going to make the “energy supply” objects, out of the four above. So let’s begin by creating these objects and making them fall down, with a random spawning position and speed.
Start by creating an
Energyclass:package { import flash.display.MovieClip; import flash.events.Event; public class Energy extends MovieClip { private var rSpeed:Number = 0; public function Energy(speed:Number) { graphics.beginFill(0x321312); graphics.drawCircle(0, 0 , 8); rSpeed = speed; } // we will call this every frame public function move():void { this.y += rSpeed; //rotation speed is linked to moving speed this.rotation += rSpeed / 8; } } }Step 3: The
GameScreenClassThis class will eventually control most of the aspects of our game, including the player movement and the game loop.
Create the class:
package { public class GameScreen extends MovieClip { public function GameScreen() { } } }That’s all we need for now.
Step 4: Update The Main Class
We’ll now create an instance of
GameScreenwithinMain:package { import flash.display.Sprite; import flash.events.Event; [SWF(width = "550", height = "600")] public class Main extends Sprite { private var game:GameScreen; public function Main():void { // don't display a yellow rectangle on the screen at startup stage.stageFocusRect = false; game = new GameScreen(); addChild(game); // give keyboard focus to the game screen immediately stage.focus = game; } } }Why bother? Well, this way, it’ll be easier to add extra screens later if we want to (like a preloader, a title screen, a game over screen…).
Step 5: Introducing a Manager Class
To avoid the
GameScreenclass becoming too much of a mess, we’ll use separate classes to manage each object.Each manager class will contain all the functions that relate to, and interact with, a particular object. Here’s the
EnergyManagerclass:package { import flash.display.MovieClip; public class EnergyManager { // this Vector will store all instances of the Energy class private var energyList:Vector.<Energy> private var gameScreen:GameScreen; public function EnergyManager(gs:GameScreen) { gameScreen = gs; energyList = new Vector.<Energy>; } } }Note that we require a reference to the GameScreen to be passed to the constructor, and we store this reference in a private variable. We also set up a Vector to store references to all the energy objects.
So far the class contain no other functions; we will add them in later.
Step 6: Creating Energy
Add the below function for creating energy, this is just a function; we will call the function later from
GameScreenClass:public function createEnergy(number:int):void { var energy:Energy; for (var i:int = 0; i < number; i++) { energy = new Energy(4); gameScreen.addEnergyToScreen(energy); energyList.push(energy); energy.x = Calculation.generateRandomValue(30, 520); energy.y = Calculation.generateRandomValue( -150, -20); } }We create a new energy supply with a speed of 4, add it to the display list (via the GameScreen), add it to the Vector of all energy objects that we just created, and set its position to a random point within certain bounds.
The
Calculation.generateRandomValue(#, #)is a static function we haven’t written yet, so let’s do that now. Create a new class calledCalculationand add this function:public static function generateRandomValue(min:Number, max:Number):Number { var randomValue:Number = min + (Math.random() * (max - min)); return randomValue; }This function will generate a random number between the two values passed to it. For more information on how it works, see this Quick Tip. Since this is a static function, we don’t need to create an instance of
Calculationin order to call it.Now, what’s that
addEnergyToScreen()function? We haven’t defined that yet, so let’s do it now. Add this toGameScreen:public function addEnergyToScreen(energy:Energy):void { addChild(energy); }It just adds the passed instance of energy to the display list. Let’s also make a corresponding function to remove a given energy object from the screen:
public function removeEnergyFromScreen(energy:Energy):void { if (energy.parent == this) { removeChild(energy); } }Step 7: Spawning Energy
Let’s set a timer that defines the interval for each spawning. This code goes in
GameScreen‘s constructor function:So, every three seconds, the timer will call
spawnEnergy(). Let’s write that function now:private function spawnEnergy(e:TimerEvent):void { energyM.createEnergy(4); // create 4 energies }Step 8: Creating Player
Let’s use another, bigger circle to represent the player. Feel free to import an image to use instead:
public function Player() { graphics.beginFill(0x7ebff1); graphics.drawCircle(0, 0, 20);Add this code to
GameScreento add the player to the screen:So far we should have a few energy supplies falling few seconds, and the player appearing in the middle of the screen:
Step 9: Moving the Player
There are basically two ways to apply movement:
true. In each frame update, “moving right” istrue, we increase the object’s x-value.Using direct update each frame– when the right arrow key is pressed, an object is told to move right immediately, by increasing its x-value.The second method does not lead to smooth movement when the key is continuously pressed, but the first method does – so we shall use the first method.
There are three simple steps to doing this:
addEventListener(Event.ENTER_FRAME, update); addEventListener(KeyboardEvent.KEY_DOWN, KeyDownHandler); addEventListener(KeyboardEvent.KEY_UP, KeyUpHandler); } private function KeyDownHandler(e:KeyboardEvent):void { if (e.keyCode == Keyboard.RIGHT) { moveRight = true; } if (e.keyCode == Keyboard.LEFT) { moveLeft = true; } if (e.keyCode == Keyboard.SPACE) { if (isGravityPushing == true) { isGravityPushing = false; } else { isGravityPushing = true; } } } private function KeyUpHandler(e:KeyboardEvent):void { if (e.keyCode == Keyboard.RIGHT) { moveRight = false; } if (e.keyCode == Keyboard.LEFT) { moveLeft = false; } }Don’t forget to first create a function listen from the enter frame event, “updating” :
//call this function every frame private function update(e:Event):void if (moveRight == true) { player.x += 6; } if (moveLeft == true) { player.x -= 6; }Keep the player within the bounds of the screen:
if (player.x >= 525) { moveRight = false; } if (player.x <= 20) { moveLeft = false; }Here’s how all that looks, in place:
package { import flash.display.MovieClip; import flash.events.Event; import flash.events.TimerEvent; import flash.ui.Keyboard; import flash.utils.Timer; import flash.events.KeyboardEvent; public class GameScreen { public var player:Player; private var energyM:EnergyManager; private var moveRight:Boolean = false; private var moveLeft:Boolean = false; private var isGravityPushing:Boolean = true; private var returnedPower:int = 0; private var scoreText:Text; private var totalScore:int=0; private var score:Text; public function GameScreen() { scoreText = new Text("Score :"); addChild(scoreText); energyM = new EnergyManager; var spawnTimer:Timer = new Timer(3000, 0); spawnTimer.addEventListener(TimerEvent.TIMER, spawnEnergy); spawnTimer.start(); player = new Player; addChild(player); player.x = 275; player.y = 450; addEventListener(Event.ENTER_FRAME, update); addEventListener(KeyboardEvent.KEY_DOWN, KeyDownHandler); addEventListener(KeyboardEvent.KEY_UP, KeyUpHandler); } private function KeyDownHandler(e:KeyboardEvent):void { if (e.keyCode == Keyboard.RIGHT) { moveRight = true; } if (e.keyCode == Keyboard.LEFT) { moveLeft = true; } if (e.keyCode == Keyboard.SPACE) { if (isGravityPushing == true) { isGravityPushing = false; }else if (isGravityPushing == false) { isGravityPushing = true; } } } private function KeyUpHandler(e:KeyboardEvent):void { if (e.keyCode == Keyboard.RIGHT) { moveRight = false; } if (e.keyCode == Keyboard.LEFT) { moveLeft = false; } } private function update(e:Event):void { if (player.x >= 525) { moveRight = false; } if (player.x <= 20) { moveLeft = false; } if (moveRight == true) { player.x += 6; } if (moveLeft == true) { player.x -= 6; } } } }Step 10: Move the Energy Supplies
At the moment, the energy supplies are spawning but not moving. We’ll use the
GameScreen.update()function to make them move, since it runs every frame.Add this code to
GameScreen.update():Now of course we need to make the
EnergyManager.moveAll()function, so add this toEnergyManager.as:public function moveAll():void { for (var i:int = 0; i < energyList.length; i++) { var energyS:Energy = energyList[i]; energyS.move(); } }Step 10: Collision Detection
We will need to check for collisions between each energy object and the player. (If you develop the game further, you’ll need to check this for asteroids and energy consumers, but not for stars.)
The best place to handle these checks is inside the
EnergyManager, triggered every frame by theGameScreen.One thing to consider: the collision checks will be between two circles, so
hitTestObject()is not ideal. Instead, we’ll be using the method explained in this tutorial.We can write the function as below:
public function checkCollision(p:Player):int { // energy transferred due to collision var energyTransfer:int = 0; for (var i:int = 0; i < energyList.length; i++) { var energyS:Energy = energyList[i]; var newX:Number = p.x - energyS.x; var newY:Number = p.y - energyS.y; var distance:Number = Math.sqrt(newX * newX + newY * newY); if (distance <= 28) { gameScreen.removeEnergyFromScreen(energyS); energyList.splice(i, 1); // for this simple game, we'll always transfer 1 unit // but you could alter this based on speed of collision // or any other factor energyTransfer = 1; } } return energyTransfer; }EnergySis short for Energy Supply.You could alter Line 51 to
energyTransfer += 1, to allow the player to absorb more than one energy object at once. It’s up to you – try it out and see how it affects the game.Step 11: Call Collision Detection Routine
We need to check for collisions every frame, so we should call the function we just wrote from
GameScreen.update().First, we need to create an integer variable to store the energy transfer value from the collision detection function. We’ll use this value for increasing the ship’s energy and adding to the player’s score.
Step 12: Newton’s Law of Gravitation
Before we go into creating the game mechanic for the ‘Push’ and ‘Pull’ function of the ship, I would like to introduce the physics concept on which the mechanic is based.
The idea is to attract the object towards the player by means of a force. Newton’s Law of Universal Gravitation gives us a great (and simple) mathematical formula we can use for this, where the force is of course the gravitational force:
G is just a number, and we can set it to whatever we like. Similarly, we can set the masses of each object in the game to any values that we like. Gravity occurs across infinite distances, but in our game, we’ll have a cut-off point (denoted by the white circle in the demo from the start of the tutorial).
The two most important things to note about this formula are:
Step 13: Revising Math Concepts
Before we start coding the game mechanics for the ‘Push’ and ‘Pull’ function, let’s be clear on what we want it to do:
Essentially, we want A (the player) to exert a certain force on B (a crystal), and move B towards A based on that force.
We should revise a few concepts:
Math.atan2(B.y - A.y, B.x - A.x).B.x += (Force*Math.cos(angle));B.y += (Force*Math.sin(angle));For more information, see the tutorials Gravity in Action and Trigonometry for Flash Game Developers.
Step 14: Implementing Push and Pull
Based on the previous explanation, we can come up with an outline for our code that attracts each crystal to the ship:
Sample Code:
public function gravityPull(p:Player): void { for (var i:int = 0; i < energyList.length; i++) { var energyS:Energy = energyList[i]; var nX:Number = (p.x - energyS.x); var nY:Number = (p.y - energyS.y); var angle:Number = Math.atan2(nY, nX); var r:Number = Math.sqrt(nX * nX + nY * nY); if (r <= 250) { var f:Number = (4 * 50 * 10) / (r * r); energyS.x += f * Math.cos(angle); energyS.y += f * Math.sin(angle); } } }Here’s a timelapse showing how this looks:
Note that the energy moves faster the closer it gets to the ship, thanks to the r-squared term.
We can implement the pushing function just by making the force negative:
public function gravityPull(p:Player): void { for (var i:int = 0; i < energyList.length; i++) { var energyS:Energy = energyList[i]; var nX:Number = (p.x - energyS.x); var nY:Number = (p.y - energyS.y); var angle:Number = Math.atan2(nY, nX); var r:Number = Math.sqrt(nX * nX + nY * nY); if (r <= 250) { var f:Number = (4 * 50 * 10) / (r * r); energyS.x -= f * Math.cos(angle); energyS.y -= f * Math.sin(angle); } } }Here the object moves more slowly as it gets further away from the player, since the force gets weaker.
Step 15: Apply the Mechanic
Of course that you will need this function to be run each frame by
GameScreen– but before that, we will need to use a Boolean function to toggle between the two functions:We are going to use true for ‘Push’ and false for ‘Pull’.
Inside
KeyDownHandler():if (e.keyCode == Keyboard.SPACE) { if (isGravityPushing == true) { isGravityPushing = false; } else if (isGravityPushing == false) { isGravityPushing = true; } }Afterwards, you will have to check the Boolean each frame. Add this to
update():if (isGravityPushing == true) { energyM.gravityPull(player); } if (isGravityPushing == false) { energyM.gravityPush(player); }Step 16: Modification
You might find that the movement doesn’t look so nice. This could be because the force is not quite ideal, or because of the r-squared term.
I’d like to alter the formula like so:
As you can see, I’ve reduced the value of “G” to 0.8, and changed the force to depend simply on the distance between the objects, rather than the distance squared.
Try it out and see if you enjoy the change. You can always alter it however you like.
Step 17: The Text Class
We will need to show some text on the screen, for showing the score and the ship’s remaining power.
For this purpose, we’ll build a new class,
Text:package { import flash.display.MovieClip; import flash.text.TextField; import flash.events.Event; import flash.text.TextFormat; import flash.text.TextFormatAlign; public class Text extends MovieClip { public var _scoreText:TextField= new TextField(); public function Text(string:String) { var myScoreFormat:TextFormat = new TextFormat(); //Format changeable myScoreFormat.size = 24; myScoreFormat.align = TextFormatAlign.LEFT; myScoreFormat.color = (0x131313); _scoreText.defaultTextFormat = myScoreFormat; _scoreText.text = string; addChild(_scoreText); } public function updateText(string:String) { _scoreText.text = string; } } }It’s very simple; it’s basically a MovieClip with a text field inside.
Step 18: Adding Power for Player
To give the game some challenge, we’ll make the ship’s power get used up slowly, so that the player has to collect energy objects in order to recharge.
To make the ship’s power appear on the ship itself, we can simply add an instance of
Textto the ship object’s display list.Declare these variables within the
Shipclass:We’ll need to keep the amount of power (both stored and displayed) updated every frame, so add this new function to
Player:First, in the constructor:
// add a new text object if it doesn't already exist if (!powerText) { powerText = new Text(String(int(totalPower))); addChild(powerText); powerText.x -= 20; //Adjust position powerText.y -= 16; }And then…
public function updatePower():void { // fps = 24, so this makes power decrease by 1/sec totalPower -= 1 / 24; powerText.updateText(String(int(totalPower))); }The power will decrease every frame by 1/24th of a unit, meaning it’ll decrease by one full unit every second.
We need to make this run every frame, so add this line to
GameScreen.update():Step 19: Make Energy Increase Power
When the ship collides with an energy object, we want it to increase its power.
In
GameScreen.update(), add the highlighted line:Remember you can alter how much power is returned in the
EnergyManager.checkCollision()function.Step 20: Setting Up the Score
Again, we will need the text class. This time, we’ll display “Score” and then the value.
Here, we will need three more variables:
Declare these in
GameScreenclass:In the constructor, add this code:
scoreText = new Text("Score :"); addChild(scoreText); score = new Text(String(totalScore)); addChild(score); score.x = scoreText.x + 100; //Positioning it beside the "Score : " Text. score.y += 2;Now, in the
update()function, add this:That’s it – we’ve created a basic version of the above game!
Take a look (you may need to reload the page):
Extra Features and Polishing
Space Background
Maybe you would also like a background with an embedded image and stars. Add this to your
Mainclass:Now create the
Starclass:package assets { import flash.display.MovieClip; import flash.events.Event; public class Star extends MovieClip { private var speed:Number; public function Star(alpha:Number, size:Number, speed1:Number) { graphics.beginFill(0xCCCCCC); graphics.drawCircle(0, 0, size); speed = speed1; } // make sure you call this every frame private function moveDown():void { this.y += speed; if (this.y >= 600) { this.y = 0; } } } }In the
Main()constructor, add this to create the stars:for (var i:int = 0; i < numOfStars; i++) { createStars(); }Here’s the actual
createStars()function:private function createStars():void { var star:Star = new Star( Math.random(), Calculations.getRandomValue(1, 2), Calculations.getRandomValue(2, 5) ); //random alpha, size and speed addChild(star); star.x = Calculations.getRandomValue(0, 550); star.y = Calculations.getRandomValue(0, 600); }With random alpha, size, position, and speed, a pseudo-3D background can be generated.
Range indicator
A range indicator circle can be made by simply creating another circle and adding it to the ship’s display list, just like how you added the power indicator text. Make sure the circle is centred on the ship, and has a radius equal to the ship’s push/pull range.
Add transparancy (alpha value) to the circle with the below code:
Try adding extra controls that make the range increase or decrease when the up and down arrow keys are pressed.
Conclusion
I hope you enjoyed this tutorial! Please do leave your comments.
Next: Read this critique for a guide to taking Flux from a simple demo to a full game!
There are several methods used to produce menus within Unity, the main two being the built in GUI system and using GameObjects with Colliders that respond to interactions with the mouse. Unity’s GUI system can be tricky to work with so we’re going to use the GameObject approach which I think is also a bit more fun for what we’re trying to achieve here.
Final Result Preview
Please view the full post to see the Unity content.
Step 1: Determine Your Game Resolution
Before designing a menu you should always determine what resolution you are going to serve it to.
Open the Player settings via the top menu, Edit > Project Settings > Player and enter your default screen width and height values into the inspector. I chose to leave mine as the default 600x450px as shown below.
You then need to adjust the size of your Game view from the default "Free Aspect" to "Web (600 x 450)", else you could be positioning your menu items off the screen.
Step 2: Choosing a Menu Background
As you will have seen in the preview, our menu scene is going to have our game environment in the background so that when you click Play you enter seamlessly into the game.
To do this you need to position your player somewhere in the scene where you like the background and round up the Y rotation value. This is so it’s easier to remember and to replicate later, so that the transition can be seamless from the menu into the game.
Let’s now get on with the creation of the menu scene!
Step 3: Creating the Menu Scene
Make sure your scene is saved and is called "game" – you’ll see why later.
Select the game scene within the Project view and duplicate it using Ctrl/Command + D, then rename the copy to "menu" and double-click it to open it.
Note: You can confirm which scene is open by checking the top of the screen, it should say something like "Unity – menu.unity – ProjectName – Web Player / Stand Alone". You don’t want to start deleting parts accidently from your game scene!
Now select and delete the GUI and PickupSpawnPoints GameObjects from the Hierarchy. Expand the "Player" and drag the "Main Camera" so that it’s no longer a child of the Player, then delete the Player.
Next, select the terrain and remove the Terrain Collider Component by right-clicking and selecting Remove Component.
Finally, select the "Main Camera" and remove the MouseLook Component by right-clicking and selecting Remove Component.
If you run the game now nothing should happen and you shouldn’t be able to move at all. If you can move or rotate then redo the above steps.
Step 4: Adjusting the Build Settings
Currently when you build or play your game the only level included within that build is the "game" scene. This means that the menu scene will never appear. So that we can test our menu, we’ll adjust the build settings now.
From the top menu select File > Build Settings and drag the menu scene from your Project View into the Build Settings’ "Scenes In Build" list as shown below
(Make sure you rearrange the scenes to put "menu.unity" at the top, so that it’s the scene that’s loaded first when the game is played.)
Perfect!
Step 5: Adding the Play Button
We’re going to use 3D Text for our menu, so go ahead and create a 3D Text GameObject via the top menu: GameObject > Create Other > 3D Text, then rename it "PlayBT". Set the Y rotation of the PlayBT text to match the Y rotation value of your Main Camera so that it’s facing directly at it and therefore easily readable.
With the PlayBT selected, change the Text Mesh text property from Hello World to "Play", reduce the Character Size to 0.1 and increase the Font Size to 160 to make the text crisper.
Note: If you want to use a font other than the default, either select the font before creating the 3D Text or drag the Font onto the 3DText’s TextMesh’s Font property and then drag the Fonts "Font Material" onto the Mesh Renderers Materials list, overwriting the existing Font Material. Quite a hassle I know!
Finally, add a Box Collider via the top menu: Component > Physics > Box Collider. Resize the Box Collider to fit the text if it doesn’t fit it nicely.
At this point in the tutorial you really need to have both the Scene and Game Views open at the same time since you are now going to move the PlayBT within the Scene View so that it’s centred within your Game View as shown below. I recommend first positioning it horizontally using a top down view and then revert to using a perspective view to position it vertically using the axis handles.
So that’s our Play button all set up. Now let’s make it play!
Step 6: The Mouse Handler Script
Create a new JavaScript script within your scripts folder, rename it "MenuMouseHandler" and add it as a Component of the PlayBT GameObject by either dragging in directly onto PlayBT or by selecting PlayBT and dragging the script onto it’s Inspector.
Replace the default code with the following:
/** Mouse Down Event Handler */ function OnMouseDown() { // if we clicked the play button if (this.name == "PlayBT") { // load the game Application.LoadLevel("game"); } }We’re using the MonoBehaviour OnMouseDown(…) function, invoked every time the BoxCollider is clicked by the mouse. We check whether the button clicked is called "PlayBT", and if so we use Application.LoadLevel(…) to load the "game" scene.
Enough talk – go run it and watch your game come to life when you click Play!
Note: If you click Play and have found yourself with a build settings error, don’t fret; you just need to check your build settings – revisit Step 4.
Step 7: Ending the Game
So the menu to start the game is great but the game technically never ends since when the timer runs out nothing happens… let’s fix that now.
Open the CountdownTimer script and at the bottom of the
Tick()function add the following line:Application.LoadLevel("menu");Now re-run your game and when the timer runs out you’ll be taken back to the menu! Easy Peasy!
There we go – a basic menu added to our game. Now let’s make it a little more user friendly with a help screen to explain how to play.
Step 8: Adding the Help Button
The help button is identical to the PlayBT in practically every way, so go ahead and duplicate the PlayBT, rename it to HelpBT and position it below the Play button. Adjust the text property to say "Help" rather than "Play" and perhaps make the Font Size a little smaller as shown below – I used 100.
Now open the MenuMouseHandler script and add the following
else ifblock to your existingifstatement.// if we clicked the help button else if (this.name == "HelpBT") { // rotate the camera to view the help "page" }If you check the preview you’ll see that when you click Help the camera rotates around to show the help menu. So, how do we do that?
Step 9: God Save iTween
Our camera rotation can all be done nice and cleanly in one line, thanks to iTween. Without iTween life wouldn’t be nearly as fun. As the name may give away it’s a tweening engine, built for Unity. It’s also free.
Go ahead and open iTween within the Unity Asset store, then click Download/Import and import it all into your project. Once it’s imported you need to move the iTween/Plugins directory into the root of your Assets folder.
You’re now all set to tween your life away!
Step 10: Rotating the Camera
Grab a piece of paper and a pen (or open a blank document) and make a note of your Main Camera’s Y rotation value, as circled below.
Within the scene view rotate the camera around in whichever direction you like around the Y axis so that the Play and Help text are out of view and so that you’ve got a decent background for your help page. You can see mine below: I rotated from -152 to -8.
Return to your MenuMouseHandler script and add the following line within the
else ifstatement:We use Camera.main to retrieve the main camera (defined by the "MainCamera" tag) from the scene and use iTween.RotateTo(…) to rotate the camera to a specific angle – in my case
-8.(You need to replace the
-8within the above line with your camera’s current rotation.)Now go back to your scene and return your camera back to its original rotation that you wrote down at the start of this section, so that it’s facing the PlayBT. Run your game, click Help and the camera should spin around. Woo!
Note: If you get an error about iTween not existing then you haven’t imported it properly – revisit Step 9.
Now let’s build our Help page.
Step 11: Building the Help Page
Rotate your camera back to the Y rotation of your help page – in my case
-8.Now we’re going to add a little explanation text as well as some more text to explain the different pickups and their scores. Finally, we’ll add a Back button to return to the main menu. You can arrange your page in whatever way you wish so feel free to get creative. Here we go…
Duplicate the HelpBT, rename it HelpHeader, set its rotation to that of your camera, change the Anchor value to "upper center" and reduce the Font Size – I used 60.
Next, copy and paste the below paragraph into the
textproperty:"Collect as many Cubes as you can within the time limit.
Watch out though, they change over time!
Note: It’s worth noting that you can’t type multiline text into the text property; you have to type it in another program and then copy and paste it since pressing enter/return assigns the field.
Finally remove the Box Collider and MenuMouseHandler Components within the Inspector since this text won’t need to be clickable. Hopefully you end up with something like this:
Now drag a pickup prefab into the scene and position it on the screen. I put mine as shown below.
Next, duplicate the HelpHeader, rename it to HelpPowerups, change the Anchor to "upper-left" and copy and paste the below paragraph into the text field.
"Green: + 2 Points
Pink: +/- Random Points
Blue: Random Speed Boost"
Reposition it so you have something like the below.
All that’s left now is to add a Back button to return to the main menu.
Step 12: The Help Page Back Button
Duplicate the HelpBT, rename it BackBT, change its text to "Back", set its rotation to that of your camera and use the Scene View to reposition it within the Game View. I placed mine in the bottom corner as shown here:
Now we just need to update our MenuMouseHandler script to handle mouse clicks from the BackBT as well. Add the following
else ifblock to the bottom of theOnMouseDown()ifstatements:// if we clicked the Back button else if (this.name == "BackBT") { // rotate the camera to view the menu "page" iTween.RotateTo(Camera.main.gameObject, Vector3(0, -152, 0), 1.0); }This is nearly identical to the previous iTween statement, the only difference being the angle the camera is rotated to –
-152in my case. You need to change this number to the original Y rotation of your camera was (the one you wrote down, remember?)Now all you need to do it set your camera back to its original rotation – the value you just added to the iTween statement – so that it’s facing the main menu again.
Run the game and your camera should spin round to reveal the help page and spin back round to the main menu. Congratulations, you’ve finished!
Conclusion
I hope you’ve enjoyed this Getting Started with Unity Session!
In this part we’ve covered using GameObjects as menu items and the incredibly powerful tweening library, iTween.
If you want an extra challenge, try using iTween to change the text colour on MouseOver and then back again on MouseExit. (You’ll find a list of Mouse handlers on this page.)
Or add an iTween CameraFade and then fade it out when the timer runs out, then load then menu – rather than abruptly ending the game. You could then delay the call to
Application.LoadLevel(...)usingyield WaitForSeconds(...).Let me know how you get on in the comments!
We’ve tackled drawing curves, and finding their quadratic and cubic roots, as well as handy applications for using quadratic roots within games. Now, as promised, we’ll look at applications for finding cubic roots, as well as curves’ gradients and normals, like making objects bounce off curved surfaces. Let’s go!
Example
Let’s take a look one practical use of this math:
In this demo, the “ship” bounces off the edges of the SWF and the curve. The yellow dot represents the closest point to the ship that lies on the curve. You can adjust the shape of the curve by dragging the red dots, and adjust the movement of the ship using the arrow keys.
Step 1: Shortest Distance to a Curve
Let’s consider the scenario where a point is located near a quadratic curve. How do you calculate the shortest distance between the point and the curve?
Well, let’s start with Pythagoras’s Theorem.
\[
Let\ the\ point\ be\ (x_p,\ y_p)\\
and\ call\ the\ closest\ point\ on\ the\ curve\ (x_c,\ y_c)\\
Then:\\
z^2 = x^2 + y^2\\
z^2 = (x_c-x_p)^2 + (y_c-y_p)^2\\
Given\ y_c=ax_c^2 + bx_c + c,\\
z^2 = (x_c-x_p)^2 + [(ax_c^2 + bx_c + c) -y_p]^2
\]
You can see that we have substituted \(y_c\) with the quadratic equation. At a glance, we can see the highest power is 4. Thus, we have a quartic equation. All we need to do is to find a minimum point in this equation to give us the shortest distance from a point to a quadratic curve.
But before that, we’ll need to understand gradients on a curve…
Step 2: Gradient of a Curve
Before we look at the problem of minimizing a quartic equation, let’s try to understand gradients of a curve. A straight line has only one gradient. But a quadratic curve’s gradient depends on which point on the curve we refer to. Check out the Flash presentation below:
Drag the red dots around to change the quadratic curve. You can also play with the slider’s handle to change the position of blue dot along x. As the blue dot changes, so will the gradient drawn.
Step 3: Gradient Through Calculus
This is where calculus will come in handy. You may have guessed that differentiating a quadratic equation would give you the gradient of the curve.
\[
f(x) = ax^2+bx+c\\
\frac{df(x)}{dx} = 2ax+b
\]
So \(\frac{df(x)}{dx}\) is the gradient of a quadratic curve, and it’s dependant on the \(x\) coordinate. Well, good thing we’ve got a method to handle this:
diff1(x:Number)will return the value after a single differentiation.To draw the gradient, we’ll need an equation to represent the line, \(y=mx+c\). The coordinate of the blue point \((x_p, y_p)\) will be substituted into the \(x\) and \(y\), and the gradient of the line found through differentiation will go into \(m\). Thus the y-intercept of line, \(c\) can be calculated through some algebra work.
Check out the AS3:
Step 4: Coordinate Systems
Always bear in mind of the inverted y-axis of Flash coordinate space as shown in the image below. At first glance, the diagram on right may seem like a negative gradient – but due to the inverted y-axis, it’s actually a positive gradient.
The same goes for the minimum point as indicated below. Because of the inverted y-axis, the minimum point in Cartesian coordinate space (at (0,0)) looks like a maximum in Flash coordinate space. But by referring to the location of origin in Flash coordinate space relative to the quadratic curve, it’s actually a minimum point.
Step 5: Rate of Change for Gradient
Now let’s say I’m interested in finding the lowest point on a curve – how do I proceed? Check out the image below (both figures are in the same coordinate space).
In order to get the minimum point, we’ll just equate \(\frac{df(x)}{dx} = 0\), since by definition we’re looking for the point where the gradient is zero. But as shown above, it turns out that the maximum point on a curve also satisfies this condition. So how do we discriminate between these two cases?
Let’s try differentiation of the second degree. It’ll give us the rate of change of the gradient.
\[
\frac{df(x)}{dx} = 2ax+b\\
\frac{df^2(x)}{dx^2} = 2a
\]
I’ll explain with reference to the image below (drawn in Cartesian coordinate space). We can see that, as we increment along the x-axis, the gradient changes from negative to positive. So the rate of change should be a positive value.
We can also see that when \(\frac{df^2(x)}{dx^2}\) is positive, there’s a minimum point on the curve. Conversely if the rate is negative, a maximum point is present.
Step 6: Back to the Problem
Now we are ready to solve the problem presented in Step 1. Recall the quartic equation (where the highest degree is 4) we arrived at:
\[
z^2 = (x_c-x_p)^2 + [(ax_c^2 + bx_c + c) -y_p]^2
\]
The same quartic equation, plotted
Remember, we are interested to find the minimum point on this curve, because the corresponding point on the original quadratic curve will be the point that’s at the minimum distance from the red dot.
So, let’s differentiate the quartic function to get to gradient of this curve, and then equate the gradient of this quartic function to zero. You will see that the gradient is actually a cubic function. I’ll refer interested readers to Wolfram’s page; for this tutorial, I’ll just pluck the result of their algebra workings:
\[
\frac{d(z^2)}{dx}=
2(x_c-x_p) + 2(ax_c^2 + bx_c + c - y_p)(2ax_c+b)\\
\frac{d(z^2)}{dx}= 2a^2(x_c)^3+3ab(x_c)^2+(b^2+2ac-2ay_p+1)(x_c)+(bc-by_p-x_p)\\
Equate\ gradient\ to\ 0\\
\frac{d(z^2)}{dx}=0\\
2a^2(x_c)^3+3ab(x_c)^2+(b^2+2ac-2ay_p+1)(x_c)+(bc-by_p-x_p)=0\\
Compare\ with\ cubic\ equation\\
Ax^3+Bx^2+Cx+D=0\\
A = 2a^2\\
B=3ab\\
C=b^2+2ac-2ay_p+1\\
D=bc-by_p-x_p
\]
Solve for the roots of this (rather messy) cubic function and we’ll arrive at the coordinates of the three blue points as indicated above.
Next, how do we filter our results for the minimum point? Recall from the previous step that a minimum point has a rate of change that’s positive. To get this rate of change, differentiate the cubic function that represents gradient. If the rate of change for the given blue point is positive, it’s one of the minimum points. To get the minimum point, the one that we’re interested in, choose the point with the highest rate of change.
Step 7: Sample of Output
So here’s a sample implementation of the idea explained above. You can drag the red dots around to customise your quadratic curve. The blue dot can also be dragged. As you move the blue dot, the yellow one will be repositioned so that the distance between the blue and yellow dots will be minimum among all points on the curve.
As you interact with the Flash presentation, there may be times where three yellow dots appear all at once. Two of these, faded out, refer to the roots obtained from the calculation but rejected because they are not the closest points on the curve to the blue dot.
Step 8: ActionScript Implementation
So here’s the ActionScript implementation of the above. You can find the full script in
Demo2.as.First of all, we’ll have to draw the quadratic curve. Note that the matrix
m2will be referred to for further calculation.private function redraw_quadratic_curve():void { var cmd:Vector.<int> = new Vector.<int>; var coord:Vector.<Number> = new Vector.<Number>; //redraw curve; m1 = new Matrix3d( curve_points[0].x * curve_points[0].x, curve_points[0].x, 1, 0, curve_points[1].x * curve_points[1].x, curve_points[1].x, 1, 0, curve_points[2].x * curve_points[2].x, curve_points[2].x, 1, 0, 0,0,0,1 ); m2 = new Matrix3d( curve_points[0].y, 0, 0, 0, curve_points[1].y, 0, 0, 0, curve_points[2].y, 0, 0, 0, 0,0,0,1 ) m1.invert(); m2.append(m1); quadratic_equation.define(m2.n11, m2.n21, m2.n31); for (var i:int = 0; i < stage.stageWidth; i+=2) { if (i == 0) cmd.push(1); else cmd.push(2); coord.push(i, quadratic_equation.fx_of(i)); } graphics.clear(); graphics.lineStyle(1); graphics.drawPath(cmd, coord); }And here’s the one that implements the mathematical concept explained.
c1refers to a point randomly positioned on stage.private function recalculate_distance():void { var a:Number = m2.n11; var b:Number = m2.n21; var c:Number = m2.n31; /*f(x) = Ax^3 + Bx^2 +Cx + D */ var A:Number = 2*a*a var B:Number = 3*b*a var C:Number = b*b + 2*c*a - 2*a*c1.y +1 var D:Number = c * b - b * c1.y - c1.x quartic_gradient = new EqCubic(); quartic_gradient.define(A, B, C, D); quartic_gradient.calcRoots(); roots = quartic_gradient.roots_R; var chosen:Number = roots[0]; if (!isNaN(roots[1]) && !isNaN(roots[2])) { //calculate gradient and rate of gradient of all real roots var quartic_rate:Vector.<Number> = new Vector.<Number>; for (var i:int = 0; i < roots.length; i++) { if (!isNaN(roots[i])) quartic_rate.push(quartic_gradient.diff1(roots[i])); else roots.splice(i, 1); } //select the root that will produce the shortest distance for (var j:int = 1; j < roots.length; j++) { //the rate that corresponds with the root must be the highest positive value //because that will correspond with the minimum point if (quartic_rate[j] > quartic_rate[j - 1]) { chosen = roots[j]; } } //position the extra roots in demo position_extras(); } else { //remove the extra roots in demo kill_extras(); } intersec_points[0].x = chosen intersec_points[0].y = quadratic_equation.fx_of(chosen); }Step 9: Example: Collision Detection
Let’s make use of this concept to detect the overlap between a circle and a curve.
The idea is simple: if the distance between the the blue dot and the yellow dot is less than blue dot’s radius, we have a collision. Check out the demo below. The interactive items are the red dots (to control the curve) and the blue dot. If the blue dot is colliding with the curve, it will fade out a little.
Step 10: ActionScript Implementation
Well, the code is quite simple. Check out the full source in
CollisionDetection.as.graphics.moveTo(intersec_points[0].x, intersec_points[0].y); graphics.lineTo(c1.x, c1.y); var distance:Number= Math2.Pythagoras( intersec_points[0].x, intersec_points[0].y, c1.x, c1.y) if (distance < c1.radius) c1.alpha = 0.5; else c1.alpha = 1.0; t.text = distance.toPrecision(3);Step 11: Bouncing Off the Curve
So now that we know when collision will occur, let’s try to program some collision response. How about bouncing off the surface? Check out the Flash presentation below.
You can see the ship (triangle shape), is surrounded by a circle (translucent blue). Once the circle collides with the curve, the ship will bounce off the surface.
Step 12: Controlling the Ship
Here’s the ActionScript to control the ship.
public function CollisionDetection2() { /** * Instantiation of ship & its blue-ish circular area */ ship = new Triangle(); addChild(ship); ship.x = Math.random() * stage.stageWidth; ship.y = stage.stageHeight * 0.8; c1 = new Circle(0x0000ff, 15); addChild(c1); c1.alpha = 0.2; /** * Ship's velocity */ velo = new Vector2D(0, -1); updateShip(); stage.addEventListener(KeyboardEvent.KEY_DOWN, handleKey); stage.addEventListener(KeyboardEvent.KEY_UP, handleKey); stage.addEventListener(Event.EXIT_FRAME, handleEnterFrame); /** * The curve and the calculations */ quadratic_equation = new EqQuadratic(); curve_points = new Vector.<Circle>; populate(curve_points, 0xff0000, 3); intersec_points = new Vector.<Circle>; populate(intersec_points, 0xffff00, 3, false); redraw_quadratic_curve(); } private function handleKey(e:KeyboardEvent):void { if (e.type == "keyDown") { if (e.keyCode == Keyboard.UP) isUp = true; else if (e.keyCode == Keyboard.DOWN) isDown = true; if (e.keyCode == Keyboard.LEFT) isLeft = true; else if (e.keyCode == Keyboard.RIGHT) isRight = true; } if (e.type == "keyUp") { if (e.keyCode == Keyboard.UP) isUp = false; else if (e.keyCode == Keyboard.DOWN) isDown = false; if (e.keyCode == Keyboard.LEFT) isLeft = false; else if (e.keyCode == Keyboard.RIGHT) isRight = false; } } private function handleEnterFrame(e:Event):void { /** * Control the magnitude */ if (isUp) velo.setMagnitude(Math.min(velo.getMagnitude()+0.2, 3)); else if(isDown) velo.setMagnitude(Math.max(velo.getMagnitude()-0.2, 1)); /** * Control the direction */ if (isRight) velo.setAngle(velo.getAngle() + 0.03); else if (isLeft) velo.setAngle(velo.getAngle() - 0.03); recalculate_distance(); if (distance < c1.radius) bounce(); updateShip(); } /** * Update ship's position, orientation and it's area (the blue-ish circle) */ private function updateShip():void { ship.x += velo.x; ship.y += velo.y; ship.rotation = Math2.degreeOf(velo.getAngle()); c1.x = ship.x; c1.y = ship.y; if (ship.x > stage.stageWidth || ship.x < 0) velo.x *= -1; if (ship.y > stage.stageHeight || ship.y < 0) velo.y *= -1; }You can see that the keyboard controls are updating flags to indicate whether the left, up, right, or down keys are being pressed. These flags will be captured by the enterframe event handler and update the magnitude and direction of the ship.
Step 13: Calculating the Reflection Vector
I’ve already covered the vector calculation of reflection vector in this post. Here, I shall just cover how to obtain the normal vector from gradient.
\[
\frac{df(x)}{dx}=gradient\\
line\ gradient=\frac{y}{x}\\
Assume\ gradient=0.5\\
y=0.5\\
x=1\\
Vector\ of\ left\ normal=
\begin{bmatrix}-1 \\0.5\end{bmatrix}\\
Vector\ of\ right\ normal=
\begin{bmatrix}1 \\-0.5\end{bmatrix}
\]
Step 14: ActionScript Implementation
So the ActionScript below will implement the mathematical concept explained in the previous step. Check out the highlighted lines:
private function bounce():void { var gradient:Number = quadratic_equation.diff1(intersec_points[0].x); var grad_vec:Vector2D = new Vector2D(1, gradient); var left_norm:Vector2D = grad_vec.getNormal(false); var right_norm:Vector2D = grad_vec.getNormal(); var chosen_vec:Vector2D; if (velo.dotProduct(left_norm) > 0) chosen_vec = left_norm else chosen_vec = right_norm var chosen_unit:Vector2D = chosen_vec.normalise(); var proj:Number = velo.dotProduct(chosen_unit); chosen_unit.scale(-2*proj); velo = velo.add(chosen_unit); }Conclusion
Well, thanks for your time! If you’ve found this useful, or have any questions, do leave some comments.
This week, Matt Porter critiques Jelly Escape, a popular new platformer developed by TawStudio Entertainment. Platformers are a dime a dozen on Flash portals, so let’s see what this one brings to the table…
Play the Game
Play the game at full size over on TawStudio’s site!
Overview
The web has always had an abundance of platformer games, thanks to Flash. Unfortunately, most of them lack any kind of serious polish. Fortunately, Jelly Escape isn’t one of them; it’s an incredibly well built platformer, and it’s clear that the developers did their homework and studied industry hit and obvious inspiration: Super Meat Boy.
Gameplay
Jelly Escape’s main focus is simple, quick, addicting platforming, and achieves this quite effectively. The game has a number of tricks up its sleeve to engage the player beyond the first few minutes, and the overall level of polish means players are likely to stick with the game long enough to get to that point.
The most immediate, and engaging aspect of the game to me, is the retro feel. While there has been a surplus of retro-inspired games since the introduction of great Flash game frameworks such as Flixel and FlashPunk, most don’t take that retro-magic to the same level.
As soon as I saw the opening animation where quarters were being popped into an arcade machine, I was hooked. Even if you’re not a hardcore fan of classic gaming, the added story is a very welcome addition, and is sure to please most.
Level Select
The second thing I noticed and was impressed with in Jelly Escape was the sheer number of levels the game had to offer. As soon as you start the game, you can see that there at least 60 – that’s a lot of levels! These are split into four sections of fifteen levels each, so I was pretty convinced that variety wouldn’t be an issue.
While I do love the sheer number of levels offered, I do think all great platformers should have a world map of sorts since, to me, exploration is one of my favorite aspects of gaming. Not only is arranging 60 levels as boxes with numbers a tad boring, it straight up tells you how many levels there are, and doesn’t leave any mystery for the player. I personally love unlocking a new world when I think a game is about to be complete, and this setup just doesn’t allow for that.
Another possible negative aspect of telling the player how many levels there are, is that they may find themselves slightly bored part way through your game and, knowing they’re nowhere near the end, just stop playing. If they thought that they might be nearly finished, they might just stick around another few levels to finish it up. During this time they may become bored and leave anyways, complete the game, or they might just hit your next big game mechanic that adds a breath of fresh air into their gaming experience, keeping them around that much longer.
There are a few levels that can be unlocked through your actions…
A proper compromise to having a full blown world map, versus a simple menu setup, would be to unlock new sets of levels upon the completion of a previous set. Jelly Escape has four sets, but perhaps only one needs to be seen at first; the unlocking of the others could have been used to spice up the player’s experience. Even if my love for exploration isn’t shared by all, it’s important as a developer to understand the various experiences that any gamer enjoys, so that you can be sure to fit in as many positive experiences as your game’s design will allow, and please as large an audience as possible.
Rewards and Stats
One of the strongest points of Jelly Escape is how much – and how often – the player is rewarded. Some of these rewards are given by playing the game well, while others are a bit more creative and are rewarded for completing actions such as following the developers on Twitter and Facebook.
While I’m generally against gimmicks, the unlockables are purely bonus material, and truly don’t take away from the game if you pass them up – so no harm done. It’s actually a great move on the developers part, and I’ll probably use it myself in future games, as I like their implementation.
In addition to the unlockables, the game also keeps track of a ton of stats, such as your best times and lowest death count for each level. Small features like this make for great replay value, and add a lot to the game.
Like many games of this style, Jelly Escape keeps track of how many of a certain collectible you get in each level. This information is shown on the level select screen, and is easily to understand. Unlike certain games, this rating actually matters. Far too many games will give you a “3 star rating” system of sorts, just for the hell of it, with no real incentive for the player to go back and master levels, but Jelly Escape offers two incentives to collect the energy bolts.
The first, which is my favorite, is that you can find characters locked up behind a gate within select levels (as shown on the level select screen), and if you’ve collected enough of the energy collectibles, you can set them free. Once free, you can use their appearance as your jelly’s skin, which is a pretty awesome addition, especially since some of the skins are based off of characters from other popular games.
The second, is that collecting enough bolts will unlock additional challenge levels. I played a few of these myself, and they definitely had an “unlockable level” kind of feel, much like the “secret” zone found in Super Mario World. Between these two awesome incentives, I definitely felt encouraged to replay levels, rather than to just rush through the game.
Level Design
Level design is a strong point of Jelly Escape, as it should be with any platformer. There aren’t too many mechanics in the game, but what’s there is used very well.
I never found the level design to be too repetitive or bland, and was actually impressed with the overall diversity. In addition, the difficulty curve seemed to be rather balanced, as few levels stood out as too hard, but it did feel like the difficulty was slowly climbing. To make things even better, levels are littered with checkpoints so that death isn’t overly punishing: an excellent addition that makes this game far more friendly with its casual target market. Plus, I never encountered any levels that were strictly tedious and not fun.
While the level design is quite well done, some core design choices of the game do take away from the overall fun factor. The game has a rather zoomed out feel, because all of the levels are presented within a single screen. While this seems to be accepted among many platformers, I feel that a more zoomed in view, with a camera for scrolling, is the better approach. To me, this allows for more precise movement, and gives a hint of exploration during gameplay, as you can’t see everything at once.
On the other hand, the game design often requires the levels to fit in a single screen – for example, falling through the bottom of the level simply brings you back to the top, and some levels are designed around this – so perhaps this is just a matter of my preferences getting in the way.
I will note that Super Meat Boy isn’t limited to a single screen however, and I feel that if it had been, it would greatly deter from the experience. Perhaps this was a deliberate design choice, perhaps scrolling levels were simply a technical complexity the developers didn’t want to mess with; either way, I still feel a more zoomed-in experience would benefit the game’s design.
Graphics
The graphics in Jelly Escape are retro, but kick things up with with massive attention to small details in visual effects. Touching walls covers them with your jelly’s slime, and spits out particle effects. Dying makes the edges of the screen tweak out. There’s a neat lighting effect in place where you can see better around your character, and collecting energy within a level increases this effect significantly.
The game is also very animation complete, something that is often lacking in indie platformers. The player is excellently animated, energy collectibles are animated, springs are animated; everything that should have an animation, does. All menus are very clean, and all menu items have a hover effect, which makes for a very polished game. While you may think that menus are nearly worthless in a game as simple as a platformer, retaining a polished and positive image consistently throughout the project is extremely important. If you’re not willing to polish your audio-mute button, don’t bother polishing your enemy animations, as a lack of consistency breaks the entire image.
All in all, the graphical quality of this greatly excels because of the extreme attention to small details. No single effect is a game changer on its own, but together, they make this game look ten times better than it would without them.
Audio
While the audio is nothing amazing in Jelly Escape, it’s still quite well done. I find the music very fitting to the somewhat dark and ominous atmosphere, and I personally feel it’s a very good fit.
As was the case with animations and effects for grpahics, Jelly Escape is extremely complete in the audio department. There’s sounds for literally everything. Jumping has a sound, landing has a sound, enemies hitting walls, opening gates, hitting buttons, dying, hovering over menu items; everything in the entire game has a sound, as it should be.
To further add polish, the toggle music and sound buttons are pretty awesome looking too. Each is its own small audio visualizer, which I found very neat. This is most definitely not needed, but if I noticed it, others did too, so take note of the positive effects such features can have on a players mode, and how it can effect their perception of an author’s hard work.
Conclusion
Jelly Escape may have its flaws, but I haven’t played a web platformer this polished and addicting since MoneySeize, and those were great (albeit somewhat frustrating) times.
Yikes!
Jelly Escape doesn’t take many shortcuts. A ton of unlockables, a huge array of levels, and badges on Kongregate make the addicting qualities of the game shine brighter than ever.
I definitely plan on revisiting the game to finish up those last 30 levels or so, as I’m a sucker for platformers; how far did you play before losing interest?
In this Premium series, you’ll learn how to use Flash to build a Facebook Graph API application that can create slideshows for your public pages. This third part covers generating a code to load the events from a Facebook Page.
Premium Preview
Click to try the app on Facebook.
In the second part, we created the embed code for the album (basically finding out the album ID from which we load the photos). This time, we are going to use the Graph API to get a list of a page’s events, based on its Page ID.
You’ll need to be logged in to Facebook in order to see this demo: https://apps.facebook.com/activetuts_tabmaker/
Read the Full Tutorial
Premium members can access the full tutorial right away!
If you’re not yet a Premium member, you can still read the first few steps for free.
Tuts+ Premium Membership
We run a Premium membership system which periodically gives members access to extra tutorials, like this one, from across the whole Tuts+ network. If you’re a Premium member, you can log in and read the tutorial. If you’re not a member, you can of course join today!
Also, don’t forget to follow @envatoactive on twitter, circle us on Google+, like us on Facebook, and grab the Activetuts+ RSS Feed to stay up to date with the latest tutorials and articles.
In the first tutorial of this series, we took a look at drawing curves using equations and AS3. Now, we’re going to tackle solving those equations to find the roots of a curve – that is, the places where the curve crosses a given straight line. We can use this to predict collisions with curved surfaces, and to avoid “tunnelling” in Flash games.
Step 1: Quadratic Roots
First, time for some quick math revision. In this tutorial, we’ll just accept and apply the methods we’ll use, but interested readers can refer to Wikipedia’s page on quadratic equations for information about the mathematical deriviations.
So \(f(x)\) is a quadratic function. If \(f(x)\) is equivalent to 0, \(x\) can be obtained by this formula:
\[Given\ f(x)\ = \ ax^2+bx+c,\ \]
\[f(x)\ =\ 0,\ x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} \]
\(b^2 – 4ac\) is called the discriminant of the formula. If the discriminant is negative, the square root of the discriminant will produce imaginary roots, which we can’t plot. Conversely, if the discriminat is positive, you will have real number roots and you’ll be able to plot them onto the screen.
Step 2: Visualising Quadratic Roots
So what are roots? Well in our context they are nothing more than intersection points between the quadratic curve and a line. For example, suppose we are interested to find the intersection point(s) of the following set of equations:
\(
f(x)\ = \ ax^2+bx+c \\
g(x)\ = \ 0
\)
This is a typical scenario of looking for the intersection point(s) between a quadratic curve and the x-axis (because the x-axis is the line where
y==0). Since by definition the intersection point(s) are shared by \(f(x)\) and \(g(x)\), we can conclude that \(f(x) = g(x)\) for the values ofxthat we are looking for.It’s then a trivial operation where you just substitute the functions and then apply the formula from Step 1 to obtain the roots. Now there are several possibilities we can anticipate as shown below.
(As you can see, “imaginary roots” means, for our purposes, that the curve doesn’t ever cross the x-axis.)
Now let’s consider the case where \(g(x)\) is more than just a mundane horizontal line. Let’s say it’s a slanted line, \(g(x)\ =\ mx\ +\ d\). Now when we equate both functions, we’ll need to do a little precalculation before the formula can be effectively applied.
\[
ax^2\ +\ bx + c\ =\ mx\ +\ d\\
ax^2\ +\ (\ b\ - m)\ x + (c\ -\ d)\ =\ 0
\]
I’ve included an interactive Flash presentation below so feel free to drag the red and blue dots. Yellow dots indicate the intersection points. You may need to position the curve and line to intersect each other in order for the yellow dots to appear.
Step 3: Plotting This With ActionScript
The full script can be found in
Demo1.as; here I’ll just explain a crucial extract of the code. Let’s look at the AS3 for drawing the curve and line:private function redraw():void { var cmd:Vector.<int> = new Vector.<int>; var coord:Vector.<Number> = new Vector.<Number>; //redraw curve; m1 = new Matrix3d( curve_points[0].x * curve_points[0].x, curve_points[0].x, 1, 0, curve_points[1].x * curve_points[1].x, curve_points[1].x, 1, 0, curve_points[2].x * curve_points[2].x, curve_points[2].x, 1, 0, 0,0,0,1 ); m2 = new Matrix3d( curve_points[0].y, 0, 0, 0, curve_points[1].y, 0, 0, 0, curve_points[2].y, 0, 0, 0, 0,0,0,1 ) m1.invert(); m2.append(m1); quadratic_equation.define(m2.n11, m2.n21, m2.n31); for (var i:int = 0; i < stage.stageWidth; i+=2) { if (i == 0) cmd.push(1); else cmd.push(2); coord.push(i, quadratic_equation.fx_of(i)); } //draw line n1 = new Matrix(); n1.a = line_points[0].x; n1.c = 1; n1.b = line_points[1].x; n1.d = 1; n2 = new Matrix(); n2.a = line_points[0].y; n2.c = 0; n2.b = line_points[1].y; n2.d = 0; n1.invert(); n2.concat(n1); var x:Number = stage.stageWidth //y = mx + c cmd.push(1); coord.push(0, n2.a * 0 + n2.b); cmd.push(2); coord.push(x, n2.a * x + n2.b); graphics.clear(); graphics.lineStyle(1); graphics.drawPath(cmd, coord); }The bulk of ActionScript for drawing the curve from line 80~104 is largely borrowed from the previous tutorial, so I shall just explain a little about the code for drawing a line.
In the Flash presentation above, there are two interactive blue dots. Each of these has coordinates, and with both dots, a line is formed. Since both dots lie on the same line, they share a common slope and y-intercept to form one general line equation:
\[
y\ =\ mx\ +\ d,\\
m\ =\ slope,\ d\ =\ y-intercept
\]
We can use a little algebra to solve for the two unknowns, \(m\) and \(d\). Given the coordinates of the two blue dots as \((x_1,\ y_1)\) and \((x_2,\ y_2)\):
\(
y_1 = mx_1 + d\\
y_2 = mx_2 + d\\
\)
\(
\begin{bmatrix}y_1 \\y_2\end{bmatrix} =
\begin{bmatrix}x_1 & 1\\x_2 & 1\end{bmatrix}
\begin{bmatrix}m \\d\end{bmatrix} \\
\)
\(
\begin{bmatrix}x_1 & 1\\x_2 & 1\end{bmatrix}^{-1}
\begin{bmatrix}y_1 \\y_2\end{bmatrix} =
\begin{bmatrix}x_1 & 1\\x_2 & 1\end{bmatrix}^{-1}
\begin{bmatrix}x_1 & 1\\x_2 & 1\end{bmatrix}
\begin{bmatrix}m \\d\end{bmatrix} \\
\)
\(
\begin{bmatrix}x_1 & 1\\x_2 & 1\end{bmatrix}^{-1}
\begin{bmatrix}y_1 \\y_2\end{bmatrix} =
I
\begin{bmatrix}m \\d\end{bmatrix}
\)
(Note that a matrix with superscripted -1 refers to inverse of that matrix.)
So using this, \(m\) and \(d\) are calculated. We can can now draw the line by joining the coordinates \((0, y_3)\) and \((stage.stageWidth, y_4)\). How do you find \(y_3\) and \(y_4\)? Well now that \(m\), \(x\) and \(d\) are known, we can simply put all these values into the line general equation,
\(y\ =\ mx\ +\ d\)
..to get those \(y\)s.
Step 4: Calculate the Quadratic Roots
To calculate the position of the intersecting points, we shall use the formula from Step 1. This is done in
EqQuadratic.asas the functions shown below:/**Read-only * Discriminant of equation */ public function get discriminant():Number { //B*B-4*A*C return _B * _B - 4 * _A * _C; } /** * Performs calculation to obtain roots */ public function calcRoots():void { var disc:Number = this.discriminant //handle imaginary roots if (disc < 0) { disc *= -1; var component_real:Number = -_B / (2 * _A); var component_imaginary:Number = Math.sqrt(disc) / (2 * _A); _root_i[0] = (component_real + "+ i" + component_imaginary).toString(); _root_i[1] = (component_real + "- i" + component_imaginary).toString(); } //handle real roots else { var sqrt:Number = Math.sqrt(disc); _root_R[0] = ( -_B + sqrt) / (2 * _A); _root_R[1] = ( -_B - sqrt) / (2 * _A); } }Further details of
EqQuadratic.as:Step 5: Plotting This With ActionScript
An example of utilising this
EqQuadratic.asis inDemo1.as. After the initiation ofEqQuadratic, we shall use it to calculate the roots. Then, after validating the presence of real roots, we’ll use them to plot the yellow dots.Now the roots refer to only the \(x\) component of the coordinates. To obtain the \(y\)s, guess what? Again, we put the values of \(m\), \(d\) (calculated earlier in Step 3) and \(x\) (from the roots) into the line general equation, \(y\ =\ mx\ +\ d\). Check out the corresponding code in line 135 and 136.
private function recalculate_reposition():void { quadratic_equation.define(m2.n11, m2.n21 - n2.a, m2.n31 - n2.b); quadratic_equation.calcRoots(); var roots:Vector.<Number> = quadratic_equation.roots_R; if (!isNaN(roots[0]) && !isNaN(roots[1])) { intersec_points[0].x = roots[0]; intersec_points[0].y = n2.a * roots[0] + n2.b intersec_points[1].x = roots[1]; intersec_points[1].y = n2.a * roots[1] + n2.b } else { intersec_points[0].x = -100; intersec_points[0].y = -100; intersec_points[1].x = -100; intersec_points[1].y = -100; } }Step 6: Cubic Roots
Cubic roots, not surprisingly, are the intersection points between a cubic curve and a line. But a cubic curve is a little different to a quadratic curve, and in this respect the possibilities for where intersections could be located are different.
The image below shows a cubic curve intersecting with the x-axis:
Again, here’s a little Flash presentation for you to experiment with. Red and blue dots can be dragged while the yellow ones just indicate the intersection points.
Step 7: General Formula for Cubic Roots
The general formula to find a cubic curve was discovered by Cardano. Although I’m enticed to elaborate on the details, I’ll just point interested readers to the following links:
Anyway, the
EqCubic.asclass implements this formula to resolve roots of cubic functions along with other mathematical utility functions. Generally all the attributes and methods forEqCubic.asfollow the desciption as tabled in Step 4, because both classesEqQuadratic.asandEqCubic.asimplement one common interface,IEquation.as, except for the details listed below.defineroots_R,root_iStep 8: Plotting This With ActionScript
Here’s the Actionscript implementation for the Flash presentation from Step 5. The full code is in
Demo3.as.private function redraw():void { var cmd:Vector.<int> = new Vector.<int>; var coord:Vector.<Number> = new Vector.<Number>; //redraw curve; m1 = new Matrix3d( curve_points[0].x * curve_points[0].x * curve_points[0].x, curve_points[0].x * curve_points[0].x, curve_points[0].x, 1, curve_points[1].x * curve_points[1].x * curve_points[1].x, curve_points[1].x * curve_points[1].x, curve_points[1].x, 1, curve_points[2].x * curve_points[2].x * curve_points[2].x, curve_points[2].x * curve_points[2].x, curve_points[2].x, 1, curve_points[3].x * curve_points[3].x * curve_points[3].x, curve_points[3].x * curve_points[3].x, curve_points[3].x, 1 ); m2 = new Matrix3d( curve_points[0].y, 0, 0, 0, curve_points[1].y, 0, 0, 0, curve_points[2].y, 0, 0, 0, curve_points[3].y, 0, 0, 0 ) m1.invert(); m2.append(m1); cubic_equation.define(m2.n11, m2.n21, m2.n31, m2.n41); for (var i:int = 0; i < stage.stageWidth; i+=2) { if (i == 0) cmd.push(1); else cmd.push(2); coord.push(i, cubic_equation.fx_of(i)); } //draw line n1 = new Matrix(); n1.a = line_points[0].x; n1.c = 1; n1.b = line_points[1].x; n1.d = 1; n2 = new Matrix(); n2.a = line_points[0].y; n2.c = 0; n2.b = line_points[1].y; n2.d = 0; n1.invert(); n2.concat(n1); var x:Number = stage.stageWidth //y = mx + c cmd.push(1); coord.push(0, n2.a * 0 + n2.b); cmd.push(2); coord.push(x, n2.a * x + n2.b); graphics.clear(); graphics.lineStyle(1); graphics.drawPath(cmd, coord); }Again, the ActionScript commands to draw a cubic curve are exactly the same as explained in my previous article, whereas Actionscript commands to draw the line are already explained in Step 3 of this one.
Now let’s move on to calculating and positioning the cubic roots:
private function recalculate_reposition():void { cubic_equation.define(m2.n11, m2.n21 , m2.n31 - n2.a, m2.n41 - n2.b); cubic_equation.calcRoots(); var roots:Vector.<Number> = cubic_equation.roots_R; for (var i:int = 0; i < roots.length; i++) { if (!isNaN(roots[i])) { intersec_points[i].x = roots[i]; intersec_points[i].y = n2.a * roots[i] + n2.b } else { intersec_points[i].x = -100; intersec_points[i].y = -100; } } }After instantiating
cubic_equationin the constructor we proceed on to define its coefficients, calculate the roots, and store the roots in a variable.One little note on the roots: there are a maximum of three real roots for a cubic equation, but not all real roots are present in all situation as some roots may be imaginary. So what happens when there’s only one real root, for example? Well, one of the array of roots called from
cubic_equation.roots_Rwill be a real number, while all the others will be Not a Number (NaN). Check out the highlighted ActionScript for this.Step 9: Predicting Where an Object Will Collide With Curved Surface
A great application of calculating roots is projecting a collision point onto curved surface, as show below. Use the left and right arrow keys to steer the moving ship, and press up to accelerate. You will notice that collision points which would have happened in the past are slightly dimmed.
Step 10: Implementation
The idea is similar to that in my tutorial about predicting collision points. However, instead of colliding with a straight line, we’re now using a curved line. Let’s check out the code.
The snippet below is called every frame:
private function update(e:Event):void { //Steering left and right if (control == 1) velo = velo.rotate(Math2.radianOf(-5)); else if (control == 2) velo = velo.rotate(Math2.radianOf(5)); //manipulating velocity var currVelo:Number = velo.getMagnitude(); if (increase == 0) { currVelo -= 0.5; currVelo = Math.max(currVelo, 1); //lower bound for velocity } else if (increase == 1) { currVelo += 0.5; currVelo = Math.min(currVelo, 5); //upper bound for velocity } velo.setMagnitude(currVelo); //update velocity ship.x += velo.x; ship.y += velo.y; ship.rotation = Math2.degreeOf(velo.getAngle()); //reflect when ship is out of stage if (ship.x <0 || ship.x > stage.stageWidth) velo.x *= -1; if (ship.y <0 || ship.y > stage.stageHeight) velo.y *= -1; redraw(); recalculate(); }The core code lies in
redrawandrecalculate. Let’s first see what’s inredraw. It’s the same one we had been using in previous demos. One little note on drawing the line. We saw in previous demos that two dots are needed to draw get the equation. Well, here we only have one ship. So to get the second point, just add the ship’s velocity to its current position. I’ve highlighted the code for convenience.private function redraw():void { var cmd:Vector.<int> = new Vector.<int>; var coord:Vector.<Number> = new Vector.<Number>; //redraw curve; m1 = new Matrix3d( w1.x * w1.x, w1.x, 1, 0, w2.x * w2.x, w2.x, 1, 0, w3.x * w3.x, w3.x, 1, 0, 0,0,0,1 ); m2 = new Matrix3d( w1.y, 0, 0, 0, w2.y, 0, 0, 0, w3.y, 0, 0, 0, 0,0,0,1 ) m1.invert(); m2.append(m1); quadratic_equation.define(m2.n11, m2.n21, m2.n31); minX = Math.min(w1.x, w2.x, w3.x); maxX = Math.max(w1.x, w2.x, w3.x); for (var i:int = minX; i < maxX; i+=2) { if (i == minX) cmd.push(1); else cmd.push(2); coord.push(i, quadratic_equation.fx_of(i)); } n1 = new Matrix(); n1.a = ship.x; n1.c = 1; n1.b = ship.x + velo.x; n1.d = 1; n2 = new Matrix(); n2.a = ship.y; n2.c = 0; n2.b = ship.y + velo.y; n2.d = 0; n1.invert(); n2.concat(n1); var x:Number = stage.stageWidth //y = mx + c cmd.push(1); coord.push(0, n2.a * 0 + n2.b); cmd.push(2); coord.push(x, n2.a * x + n2.b); graphics.clear(); graphics.lineStyle(1); graphics.drawPath(cmd, coord); }Now for
recalculate, I’ve done a little vector calculation to check whether the point is behind or in front of the ship. Check out the highlighted code:private function recalculate():void { quadratic_equation.define(m2.n11, m2.n21 - n2.a, m2.n31 - n2.b); quadratic_equation.calcRoots(); var roots:Vector.<Number> = quadratic_equation.roots_R; for (var i:int = 0; i < roots.length; i++) { var reposition:Sprite = getChildByName("c" + i) as Sprite //conditions: //real root, value of x within the range if (!isNaN(roots[i]) && roots[i] > minX && roots[i] < maxX) { reposition.x = roots[i]; reposition.y = n2.a * roots[i] + n2.b; //discriminating between future and already happened collision point var vec:Vector2D = new Vector2D(reposition.x - ship.x, reposition.y - ship.y); if (velo.dotProduct(vec) < 0) reposition.alpha = 0.4; else reposition.alpha = 1 } else { reposition.x = -100; reposition.y = -100; } } }Step 11: Time-Based Collision Detection
Another great application is not quite as obvious as the first. To perform a more accurate collision detection, instead of basing our conclusion on the distance between two objects, we’ll uae their time to impact. Why? Because “tunneling” can happen if we use distance based collision detection:
Consider a collision detection algorithm that’s based on distance for two circles. Of the four frames shown, only frame 2.15 successfully detected collision between two circles. Why? Because the current distance between the gray and the red circles’ centers is less than the sum of both circles’ radii.
(Readers interested on more details on this topic can refer to this article.)
\[distance\ between\ circles\ < \ radius_{red}\ +\ radius_{gray}\]
The problem is caused by how Flash proceeds by one discrete frame at a time, which means that only frames 1, 2, and 3 will be successfully captured, and not the moments in between thos snapshots of time. Now the gray and red circles did not collide in these frames according to a distance-based calculation, so the red circle tunnels right through the gray one!
To fix this, we need a way to see the collision that occurred between frames 2 and 3. We need to calculate the time to impact between two circles. For example, once we check that time to impact is less than 1 frame at frame 2, this means that once Flash proceeds 1 frame forward collision or even tunneling will have definitely had taken place.
\[if\ time\ to\ impact,\ t,\ fulfills\ 0\ < \ t\ <\ 1,\ tunneling\ will\ happen\ next\ frame\]
The question is, how do we calculate this time?
Step 12: Precalculations
I’ll try to show my method as simply as possible.
Given the scenario above, the two gray and red circles are currently located at \((x_{gray},\ y_{gray})\) and \((x_{red},\ y_{red})\). They are moving at \(v_{gray}\) and \(v_{red}\) respectively, and set on a collision path. We are interested to calculate the time taken, \(t\), for them to reach positions \((x’_{gray},\ y’_{gray})\) and \((x’_{red},\ y’_{red})\), indicated by the translucent gray and red circles, where the collision occurred.
\[
displacement_{future} = displacement_{present}+velocity*time\\
x'_{gray}=x_{gray}+v_{gray_x}*t\ ...(eq.\ 1)\\
y'_{gray}=y_{gray}+v_{gray_y}*t\ ...(eq.\ 2)\\
x'_{red}=x_{red}+v_{red_x}*t\ ...(eq.\ 3)\\
y'_{red}=y_{red}+v_{red_y}*t\ ...(eq.\ 4)
\]
Note that I’ve derived the horizontal and vertical components of \(v_{gray}\) into \(v_{gray_x}\) and \(v_{gray_y}\). Same goes with velocity of the red circle; check out this Quick Tip to get to know how these components are derived.
We still lack one relationship to bind all these equations together. Let’s see the image below.
The other relationship goes back to Pythagoras. When both circles meet, the distance between both centers is exactly \(rad_{gray}\) plus \(rad_{red}\).
\[
Pythagoras'\ Theorem,\ z^2=x^2+y^2\\
(rad_{gray}+rad_{red})^2=(x'_{gray}-x'_{red})^2+(y'_{gray}-y'_{red})^2\ ...(eq.\ 5)\\
\]
This is where you substitute equations 1~4 into equation 5. I understand its quite daunting mathematically, so I’ve separated it out into Step 13. Feel free to skip it to arrive at the result at Step 14.
Step 13 (Optional): Mathematical Rigour
First, we establish the following identities.
\[
Identity,\\
(a+b)^2=a^2+2ab+b^2\ ...(id.\ 1)\\
(a-b)^2=a^2-2ab+b^2\ ...(id.\ 2)\\
\]
At all times, bear in mind that all mathematical symbols represent a constant except for time, \(t\), which is the subject of interest.
\(x_{gray},\ v_{gray_x},\ y_{red}, \) and so on are all defined in the scenario.
Next, we’ll try to break our problem down term by term:
\[
(rad_{gray}+rad_{red})^2=(x'_{gray}-x'_{red})^2+(y'_{gray}-y'_{red})^2\\
Consider\ term\ (x'_{gray}-x'_{red})^2\ and\ utilising\ id.\ 2\\
(x'_{gray}-x'_{red})^2 = (x'_{gray})^2-2(x'_{gray})(x'_{red})+(x'_{red})^2\\
\]
\[
Consider\ term\ (x'_{gray})^2\\
(x'_{gray})^2\\
=(x_{gray}+v_{gray_x}*t)^2,\ utilise\ id.\ 1\\
=(x_{gray})^2+2(x_{gray})(v_{gray_x}*t)+(v_{gray_x}*t)^2
\]
\[
Consider\ term\ -2(x'_{gray})(x'_{red})\\
-2(x'_{gray})(x'_{red})\\
=-2(x_{gray}+v_{gray_x}*t)(x_{red}+v_{red_x}*t)\\
=-2[(x_{gray})(x_{red})+(x_{gray})(v_{red_x}*t)+(v_{gray_x}*t)(x_{red})+(v_{gray_x}*t)(v_{red_x}*t)]\\
=-2(x_{gray})(x_{red})-2(x_{gray})(v_{red_x}*t)-2(v_{gray_x}*t)(x_{red})-2(v_{gray_x}*t)(v_{red_x}*t)
\]
\[
Consider\ term\ (x'_{red})^2\\
(x'_{red})^2\\
=(x_{red}+v_{red_x}*t)^2,\ utilise\ id.\ 1\\
=(x_{red})^2+2(x_{red})(v_{red_x}*t)+(v_{red_x}*t)^2
\]
Now at a glance, we can easily see the highest power of \(t\) is 2. So we have ourselves a quadratic equation. Let’s collect all the coefficients contributed by these three terms according to their powers.
Let’s analyse the coefficients with \(t^2\) and \(t^0\).
\[
(v_{gray_x})^2-2(v_{gray_x})(v_{red_x})+(v_{red_x})^2,\ recall\ id.\ 2\\
(v_{gray_x})^2-2(v_{gray_x})(v_{red_x})+(v_{red_x})^2 = (v_{gray_x}-v_{red_x})^2
\]
\[
(x_{gray})^2-2(x_{gray})(x_{red})+(x_{red})^2,\ recall\ id.\ 2\\
(x_{gray})^2-2(x_{gray})(x_{red})+(x_{red})^2 = (x_{gray}-x_{red})^2
\]
And that of \(t\).
\[
Simplify\\
a=(x_{gray}),\ b=(v_{gray_x})\\
c=(v_{red_x}),\ d=(x_{red})\\
2ab-2ac-2bd+2dc\\
=2[ab-ac-bd+dc]\\
=2[a(b-c)-d(b-c)]\\
=2[(b-c)(a-d)]\\
Resubstitute\\
2[(b-c)(a-d)] = 2(v_{gray_x}-v_{red_x})(x_{gray}-x_{red})
\]
Let’s summarise in term of \((x’_{gray}-x’_{red})^2\)
\[
(x'_{gray}-x'_{red})^2\\
=(v_{gray_x}-v_{red_x})^2*t^2+2(v_{gray_x}-v_{red_x})(x_{gray}-x_{red})*t +(x_{gray}-x_{red})^2
\]
Note that this only caters for one term in \(eq.\ 5\). We’ll need to perform the same process for another term \((y’_{gray}-y’_{red})^2\). Since they have the same algebraic form, the result should also be the same.
\[
(y'_{gray}-y'_{red})^2\\
=(v_{gray_y}-v_{red_y})^2*t^2+2(v_{gray_y}-v_{red_y})(y_{gray}-y_{red})*t +(y_{gray}-y_{red})^2
\]
Thus after rearrangement in terms of \(t\), \(eq.\ 5\) should be as follow.
\[
(rad_{gray}+rad_{red})^2=(x'_{gray}-x'_{red})^2+(y'_{gray}-y'_{red})^2\\
p=v_{gray_x}-v_{red_x}\\
q=x_{gray}-x_{red}\\
r=v_{gray_y}-v_{red_y}\\
s=y_{gray}-y_{red}\\
(p^2+r^2)*t^2+2(pq+rs)*t+(q^2+s^2-(rad_{gray}+rad_{red})^2) = 0
\]
Step 14: The Result
So from the previous step, through rigourous algebra we arrived at the following formula:
\[
p=v_{gray_x}-v_{red_x}\\
q=x_{gray}-x_{red}\\
r=v_{gray_y}-v_{red_y}\\
s=y_{gray}-y_{red}\\
(p^2+r^2)*t^2+2(pq+rs)*t+(q^2+s^2-(rad_{gray}+rad_{red})^2) = 0
\]
Now this is one huge quadratic formula. We’ll try to group the coefficients into that accepted by
EqQuadratic. Compare the two forms:\[
ax^2+bx+c = 0\\
(p^2+r^2)*t^2+2(pq+rs)*t+(q^2+s^2-(rad_{gray}+rad_{red})^2) = 0\\
a = p^2+r^2)\\
b = 2(pq+rs)\\
c = (q^2+s^2-(rad_{gray}+rad_{red})^2)
\]
Step 15: Sample Implementation
So here’s a Flash presentation to demonstrate the idea. You will see two particles on the stage, one gray and the other red. Both are connected to an arrow, indicating the magnitude and direction of velocity.
To alter the velocity of the particles, press:
Finally, to toggle visibility of the arrows, press "V"
Step 16: A Note on the Quadratic Roots
There are two roots to the quadratic equation. In this context, we are interested in the real roots. However if the two particles are not set on collision path (both paths are parallel to each other), then imaginary roots will be produced instead of real roots. In this case, both real roots will remain
NaN.If both particles are set on a collision path, we will get two real roots. But what do these two roots represent?
Recall in Step 12 that we made used of Pythagoras’s Theorem to tie \((x’_{gray},\ y’_{gray})\) and \((x’_{red},\ y’_{red})\) together into an equation. Well, there are two situations where the distance between two circles’ centers are exactly the sum of both radii: one before collision and one after collision. Take a look at this image:
So which one do we choose? Obviously the first because we’re not interested in the instance after collision. So we should always choose the lesser value of both roots and evaluate it. If the value is positive and less than 1, a collision will happen during the next frame. If the value is negative, the collision happened in the past.
Step 17: The ActionScript Explained
Let’s look at the Actionscript implemented for this example. First, the variables.
Then the calculation of roots. You may want to cross check the following ActionScript with the variables above.
Here’s how you should interpret the roots:
//if no real roots are available, then they are on not on collision path if (isNaN(roots[0]) && isNaN(roots[1])) { t.text = "Particles not on collision path." } else { var time:Number = Math.min(roots[0], roots[1]) var int_time:int = time * 1000; time = int_time / 1000; t.text = "Frames to impact: "+time.toString() + "\n"; if(time>1) t.appendText("Particles are closing...") else if (time > 0 && time < 1) t.appendText("Particles WILL collide next frame!") else if (time < 0) t.appendText("Collision had already happened."); }Conclusion
So now we’ve studied quadratic and cubic roots in ActionScript, as well as taking a close look at two examples of how we can use the quadratic roots.
Thanks for taking the time to read this tutorial. Do leave a comment if you see other applications of quadratic roots (or any errors!)
It’s been about 6 months since we launched our much improved New Tuts+ Premium complete with courses and eBooks. In that time we’ve added 97 hours of video training, 36 eBooks and of course our premium tutorial library has continued to grow and is rapidly apporaching 1000 tutorials, including lots of fantastic Activetuts+ content. Now, for a limited time only, thanks to PayPal we’re offering anyone who joins Tuts+ Premium with a yearly membership via the PayPal service, a $50 cash back to their PayPal account. It’s a good way to get an even better deal on Tuts+ Premium yearly subscriptions!
Join Tuts+ Premium
If you’re serious about skilling up, head over to Tuts+ Premium and invest in yourself and your development! We’re constantly adding more content and it’s only getting better and better, so buying a yearly subscription is worth it. Plus we have a money back guarantee in the first month, so if you discover it’s not for you, you can always bail out without charge (and of course without the $50 cash!) If you enjoy our free Tuts+, you’ll definitely find Tuts+ Premium a valuable edition.
Read the terms below and Join Tuts+ Premium today!
Terms – read these!
Disclaimer:PayPal is not responsible for the promotion offered by Envato and you should read the Terms & Conditions below. The PayPal service is provided by PayPal Australia Pty Limited (ABN 93 111 195 389) which holds an Australian Financial Services Licence, number 304962. Before deciding to sign-up for or use the PayPal service you should consider the Combined Financial Services Guide and Product Disclosure Statement, available at http://www.paypal.com.au.
Upgrades
If you are an existing monthly Tuts+ Premium member, and interested in upgrading to a yearly membership to take advantage of this offer, be sure to cancel your existing monthly subscription via PayPal. Also, thank you for being a member!!