logo
468x60-2-495


  • Home
  • Privacy Policy
  • About
search
top
Mar 11, 2012 Posted on Mar 11, 2012 in Hints and Tips | 10 comments

Build a Classic Snake Game in AS3

In this tutorial I would like to show you how easy it is to create a classic “Snake” game in Flash. I will try to explain everything easily, step by step, so that you can develop the game further to your needs! The Game will be developed in AS3 and I will use the FlashDevelop IDE.


Introduction

The game won’t be complex. Whenever we hit a wall, it will restart the game. After eating an apple the snake will grow, and a ‘new’ Apple will appear. (Actually, it will be the same apple, but I’ll explain this later.)

One of the most important aspects of the game is the code’s reaction to KEY_DOWN events. The snake will only then change its direction after a tick has passed, not immediately after a keypress. This means that, if the snake is going right, and you press down and left very fast, the snake will go down, not down AND left. Without this ‘feature’ the snake would allow us to go left while we are going right, which would mean it hit itself.


Let’s Look at the Game Already!

Let’s take a look at the final result we will be working towards:


Step 1: Creating the Project

In FlashDevelop, create a new Project, and inside the ‘src’ folder create a ‘com’ folder. In the ‘com’ folder create a new class, and call it ‘Element.as’.

Set the dimensions of the project to 600x600px.

The FlashDevelop project structure

Step 2: Wait… What’s an Element?

The snake is make up of blue squares, which I call elements. We will create an Element Class, which draws the element. The red apple is going to be an element too, so we will extend the code with a few more lines.

Therefore we won’t create a new class for the apple. (But if you really want to, you can.)


Step 3: Writing the Element Class

The Element class creates a square. It doesn’t draw it on the stage, it just creates it. The registration point of the element – the position referred to by its x- and y-coordinates – is in the top-left.

After opening the Element.as you will see something like this:

package com
{
	/**
	 * ...
	 * @author Fuszenecker Zsombor
	 */
	public class Element
	{

		public function Element()
		{

		}

	}
}

First we need this to extend the Shape class, so we can use the graphics object to draw the square. After this, create two variables: one for the direction (if it’s part of the snake), and one for the score value (if it’s an apple), and then change the parameters of the constructor function:

package com
{
	import flash.display.Shape;

	public class Element extends Shape
	{
		protected var _direction:String;
		//IF IT IS AN APPLE ->
		protected var _catchValue:Number;

		//color,alpha,width,height
		public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
		{

		}
	}
}

Now fill the function with some code:

package com
{
	import flash.display.Shape;

	public class Element extends Shape
	{
		protected var _direction:String;
		//IF IT IS AN APPLE ->
		protected var _catchValue:Number;

		//color,alpha,width,height
		public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
		{
			graphics.lineStyle(0, _c, _a);
			graphics.beginFill(_c, _a);
			graphics.drawRect(0, 0, _w, _h);
			graphics.endFill();

			_catchValue = 0;
		}
	}
}

Now, whenever we create an element, it will draw a rectangle and set the score value of the element to 0 by default. (It won’t put the rectangle on stage, it just draws it within itself. Notice that we have not called the addChild() function.)

Let’s finish this class and then we can finally test how much we have done already:

package com
{
	import flash.display.Shape;

	public class Element extends Shape
	{
		protected var _direction:String;
		//IF IT IS AN APPLE ->
		protected var _catchValue:Number;

		//color,alpha,width,height
		public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
		{
			graphics.lineStyle(0, _c, _a);
			graphics.beginFill(_c, _a);
			graphics.drawRect(0, 0, _w, _h);
			graphics.endFill();

			_catchValue = 0;
		}

		//ONLY USED IN CASE OF A PART OF THE SNAKE
		public function set direction(value:String):void
		{
			_direction = value;
		}
		public function get direction():String
		{
			return _direction;
		}

		//ONLY USED IN CASE OF AN APPLE
		public function set catchValue(value:Number):void
		{
			_catchValue = value;
		}
		public function get catchValue():Number
		{
			return _catchValue;
		}
	}

}

We created four functions to change the directions and the value of the apple. We achieved this by using setters and getters. More about Setters/Getters in this article!


Step 4: Testing the Element Class

Open Main.as now.

Import the com.Element class and create an Element in the init() function:

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import com.Element;

	public class Main extends Sprite
	{
		public function Main()
		{
			if(stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}

		private function init(e:Event = null):void
		{
			var testElement:Element = new Element(0x00AAFF, 1, 10, 10);
			testElement.x = 50;
			testElement.y = 50;
			this.addChild(testElement);

		}

	}
}

First we create the testElement variable which holds our element. We create a new Element and assign that to our testElement variable. Note the arguments we passed: first we give it a color, then the alpha, width and height. If you look in the Element class’s Element function, you can see how it uses this data to draw the rectangle.

After creating the Element, we position it and put it on the stage!


Step 5: Setting Up the Variables

Look at the following code. I wrote the functions of the variables next to them (notice that we imported the necessary classes too):

package
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.ui.Keyboard;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.Event;

	import com.Element;

	public class Main extends Sprite
	{

		//DO NOT GIVE THESE VARS A VALUE HERE!
		//Give them their values in the init() function.
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
		private var markers_vector:Vector.<Object>; //the markers are held in here
		private var timer:Timer;
		private var dead:Boolean;
		private var min_elements:int; //holds how many parts the snake should have at the beginning
		private var apple:Element; //Our apple
		private var space_value:Number; //space between the snake's parts
		private var last_button_down:uint; //the keyCode of the last button pressed
		private var flag:Boolean; //is it allowed to change direction?
		private var score:Number;
		private var score_tf:TextField; //the Textfield showing the score

		public function Main()
		{
			if(stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}

		private function init(e:Event = null):void
		{
			snake_vector = new Vector.<Element>;
			markers_vector = new Vector.<Object>;
			space_value = 2; //There will be 2px space between every Element
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired! This will set the SPEED of the snake
			dead = false;
			min_elements = 10; //We will begin with 10 elements.
			apple = new Element(0xFF0000, 1, 10, 10); //red, not transparent, width:10, height: 10;
			apple.catchValue = 0; //pretty obvious - the score of the apple
			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable
			score = 0;
			score_tf = new TextField(); //this is the TextField which shows our score.
			this.addChild(score_tf);
		}
	}
}

The most important variable is the snake_vector. We will put every Element of the snake in this Vector.

Then there is the markers_vector. We will use markers to set the direction of the snake’s parts. Each object in this Vector will have a position and a type. The type will tell us whether the snake should go right, left, up, or down after ‘hitting’ the object. (They won’t collide, only the position of the markers and the snake’s parts will be checked.)

As an example, if we press DOWN, an object will be created. The x and y of this object will be the snake’s head’s x and y coordinates, and the type will be “Down”. Whenever the position of one of the snake’s Elements is the same as this object’s, the snakes elements direction will be set to “Down”.

Please read the comments next to the variables to understand what the other variables do!


Step 6: Writing the attachElement() Function

The attachElement() function will take four parameters: the new snake element, the x and y coordinates, and the direction of the last part of the snake.

private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
{

}

Before we put the element on the stage we should position it. But for this we need the direction of the snake’s last element, to know whether the new element has to be above, under, or next to this.

After checking the direction and setting the position, we can add it to the stage.

private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
{
	if (dirOfLast == "R")
	{
		who.x = lastXPos - snake_vector[0].width - space_value;
		who.y = lastYPos;
	}
	else if(dirOfLast == "L")
	{
		who.x = lastXPos + snake_vector[0].width + space_value;
		who.y = lastYPos;
	}
	else if(dirOfLast == "U")
	{
		who.x = lastXPos;
		who.y = lastYPos + snake_vector[0].height + space_value;
	}
	else if(dirOfLast == "D")
	{
		who.x = lastXPos;
		who.y = lastYPos - snake_vector[0].height - space_value;
	}
	this.addChild(who);
}

Now we can use this function in the init() function:

for(var i:int=0;i<min_elements;++i)
{
	snake_vector[i] = new Element(0x00AAFF,1,10,10);
	snake_vector[i].direction = "R"; //The starting direction of the snake
	if (i == 0)//first snake element
	{
		//you have to place the first element on a GRID. (now: 0,0)
		//[possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
		attachElement(snake_vector[i],0,0,snake_vector[i].direction)
		snake_vector[0].alpha = 0.7;
	}
	else
	{
		attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
	}
}

We create the first 10 Elements, and set the direction of them to ‘R’ (right). If it is the first element, we call attachElement() and we change its alpha a bit (so the “head” is a slightly lighter color).

If you wish to set the position somewhere else, then please keep the following in mind: the snake has to be placed on a grid, otherwise it would look bad and would not work. If you wish to change the x and y position you can do it the following way:

Setting the x position: (snake_vector[0].width+space_value)*[UINT], where you should replace [UINT] with a positive integer.

Setting the y position: (snake_vector[0].height+space_value)*[UINT], where you should replace [UINT] with a positive integer.

Let’s change it to this:

if (i == 0)//first snake element
{
	//you have to place the first element on a GRID. (now: 0,0)
	//[possible x positions: (snake_vector[0].width+space_value)*<UINT>]
	attachElement(
		snake_vector[i],
		(snake_vector[0].width+space_value)*20,
		(snake_vector[0].height+space_value)*10,
		snake_vector[i].direction
	);
	snake_vector[0].alpha = 0.7;
}

And the snake’s first element is set onto the 20th space in the x-grid and 10th space in the y-grid.

This is what we’ve got so far:

package
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.ui.Keyboard;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.Event;

	import com.Element;

	public class Main extends Sprite
	{

		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
		private var markers_vector:Vector.<Object>; //the markers are held in here
		private var timer:Timer;
		private var dead:Boolean;
		private var min_elements:int; //holds how many parts should the snake have at the beginning
		private var apple:Element; //Our apple
		private var space_value:Number; //space between the snake parts
		private var last_button_down:uint; //the keyCode of the last button pressed
		private var flag:Boolean; //is it allowed to change direction?
		private var score:Number;
		private var score_tf:TextField; //the Textfield showing the score

		public function Main()
		{
			if(stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}

		private function init(e:Event = null):void
		{
			snake_vector = new Vector.<Element>;
			markers_vector = new Vector.<Object>;
			space_value = 2;
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!
			dead = false;
			min_elements = 10; //We will begin with 10 elements.
			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;
			apple.catchValue = 0; //pretty obvious
			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable
			score = 0;
			score_tf = new TextField(); //this is the TextField which shows our score.
			this.addChild(score_tf);

			for(var i:int=0;i<min_elements;++i)
			{
				snake_vector[i] = new Element(0x00AAFF,1,10,10);
				snake_vector[i].direction = "R"; //The starting direction of the snake
				if (i == 0)//first snake element
				{
					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
					attachElement(snake_vector[i], (snake_vector[0].width + space_value) * 20, (snake_vector[0].height + space_value) * 10, snake_vector[i].direction);
					snake_vector[0].alpha = 0.7;
				}
				else
				{
					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
				}
			}
		}	

		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
		{
			if (dirOfLast == "R")
			{
				who.x = lastXPos - snake_vector[0].width - space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "L")
			{
				who.x = lastXPos + snake_vector[0].width + space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "U")
			{
				who.x = lastXPos;
				who.y = lastYPos + snake_vector[0].height + space_value;
			}
			else if(dirOfLast == "D")
			{
				who.x = lastXPos;
				who.y = lastYPos - snake_vector[0].height - space_value;
			}
			this.addChild(who);
		}
	}
}


Step 7: Writing the placeApple() Function

This function does the following:

  1. It checks whether the apple was caught. For this we will use the caught parameter, and set its default value to true, in case we don’t pass any value as parameters in the future. If it was caught, it adds 10 to the apple’s score value (so the next apple is worth more).
  2. After this the apple has to be repositioned (we don’t create new apples) at a random grid position.
  3. If it is placed on the snake, we should place it somewhere else.
  4. If it is not on the stage yet, we place it there.

private function placeApple(caught:Boolean = true):void
{
	if (caught)
		apple.catchValue += 10;

	var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
	var randomX:Number = Math.floor(Math.random()*boundsX);

	var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
	var randomY:Number = Math.floor(Math.random()*boundsY);

	apple.x = randomX * (apple.width + space_value);
	apple.y = randomY * (apple.height + space_value);

	for(var i:uint=0;i<snake_vector.length-1;i++)
	{
		if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
			placeApple(false);
	}
	if (!apple.stage)
		this.addChild(apple);
}

There will be some math here, but if you think it through you should understand why it is so. Just draw it out on some paper if necessary.

  • boundsX will hold how many elements could be drawn in one row.
  • randomX takes this boundsX, multiplies it with a Number between zero and one, and floors it. If boundsX is 12 and the random Number is 0.356, then floor(12*0.356) is 4, so the apple will be placed on the 4th spot on the x-grid.
  • boundsY will hold how many elements can be drawn in one column.
  • randomY takes this boundsY, multiplies it with a Number between zero and one, and floors it.
  • Then we set the x and y position to these numbers.

In the for loop, we check whether the apple’s new x and y positions are identical to any of the snake_vectors elements. If so, we call the placeApple() function again (recursive function), and set the parameter of it to false. (Meaning that the apple was not caught, we just need to reposition it)

(apple.stage) returns true if the apple is on the stage. we use the ‘!’ operator to invert that value, so if it is NOT on the stage, we place it there.

The last thing we need to do is call the placeApple() function at the end of the init() function.

private function init(e:Event = null):void
{
    /*
	.
	.
	.
	*/

	placeApple(false);
}

Notice that we pass false as the parameter. It’s logical, because we didn’t catch the apple in the init() function yet. We will only catch it in the moveIt() function.

Now there are only three more functions to write: the directionChanged(), moveIt() and the gameOver() functions.


Step 8: Starting the moveIt() Function

The moveIt() function is responsible for all of the movement. This function will check the boundaries and check whether there is an object at the x and y position of the snake’s head. It will also look for the apple at this position.

For all of this, we will use our timer variable.

Add two more lines in the end of the init() function:

timer.addEventListener(TimerEvent.TIMER,moveIt);
timer.start();

Look at the comments in the sourcecode, to see which block of code does what.

		private function moveIt(e:TimerEvent):void
		{
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				//This code runs if the snakes heads position and the apples position are the same
			}

			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
			{
				//This block runs if the snakes head is out of the stage (hitting the walls)
			}

			for (var i:int = 0; i < snake_vector.length; i++)
			{
				/*
					START OF FOR BLOCK
					This whole 'for' block will run as many times, as many elements the snake has.
					If there are four snake parts, this whole for cycle will run four times.
				*/

				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
				{
					//If the snakes heads position is the same as any of the snake parts, this block will run (Checking the collision with itself).
				}

				if (markers_vector.length > 0)
				{
					//if there are direction markers, this code runs
				}

				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.
				switch (DIRECTION)
				{
					//Sets the new position of the snakes part
				}

				/*
					END OF FOR BLOCK
				*/
			}

		}

Now we need to code the movement. For this we jump into the switch block, which will run on every snake part, because of the for loop.

First we need to check the direction of the current element.

				switch (DIRECTION)
				{
					case "R" :
						//Here we need to set the new x position for the current part
						break;
					case "L" :
						//Here we need to set the new x position for the current part
						break;
					case "D" :
						//Here we need to set the new y position for the current part
						break;
					case "U" :
						//Here we need to set the new y position for the current part
						break;
				}

When the direction of the part is set to “R”, for instance, we need to add something to its current X position (the space_value plus the width of the snake part).

With this in mind, we can fill it out:

				switch (DIRECTION)
				{
					case "R" :
						snake_vector[i].x += snake_vector[i].width + space_value;
						break;
					case "L" :
						snake_vector[i].x -= snake_vector[i].width + space_value;
						break;
					case "D" :
						snake_vector[i].y += snake_vector[i].height + space_value;
						break;
					case "U" :
						snake_vector[i].y -= snake_vector[i].width + space_value;
						break;
				}

After testing the code, you should see that the snake is moving, and going off the stage and never stops. (You may need to refresh the page – or just click here to load it in a new window.)

So we need to stop the snake


Step 9: Writing the gameOver() Function

This function is going to be the shortest. We just clear the stage and restart it:

private function gameOver():void
{
	dead = true;
	timer.stop();
	while (this.numChildren)
		this.removeChildAt(0);
	timer.removeEventListener(TimerEvent.TIMER,moveIt);
	init();
}

That’s it. We set the dead variable to true, stop the movement with the timer, remove every child of the class and call the init() function, like we just started the game.

Now, let’s get back to the moveIt() function.


Step 10: Continuing the moveIt() Function

We will use the gameOver() function in two places. The first is when we check if the head is out of bounds, and the second is when the snake hits itself:

		private function moveIt(e:TimerEvent):void
		{
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				//This code runs if the snakes heads position and the apples position are the same
			}

			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
			{
				gameOver();
			}

			for (var i:int = 0; i < snake_vector.length; i++)
			{
				/*
					START OF FOR BLOCK
					This whole 'for' block will run as many times, as many elements the snake has.
					If there are four snake parts, this whole for cycle will run four times.
				*/

				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
				{
					//If the snakes heads position is the same as any of the snake parts, this block will run
					gameOver();
				}

				if (markers_vector.length > 0)
				{
					//if there are direction markers, this code runs
				}

				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.
				switch (DIRECTION)
				{
					case "R" :
						snake_vector[i].x += snake_vector[i].width + space_value;
						break;
					case "L" :
						snake_vector[i].x -= snake_vector[i].width + space_value;
						break;
					case "D" :
						snake_vector[i].y += snake_vector[i].height + space_value;
						break;
					case "U" :
						snake_vector[i].y -= snake_vector[i].width + space_value;
						break;
				}

				/*
					END OF FOR BLOCK
				*/
			}

		}

This is the code we have now:

package
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.ui.Keyboard;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.Event;

	import com.Element;

	public class Main extends Sprite
	{

		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
		private var markers_vector:Vector.<Object>; //the markers are held in here
		private var timer:Timer;
		private var dead:Boolean;
		private var min_elements:int; //holds how many parts should the snake have at the beginning
		private var apple:Element; //Our apple
		private var space_value:Number; //space between the snake parts
		private var last_button_down:uint; //the keyCode of the last button pressed
		private var flag:Boolean; //is it allowed to change direction?
		private var score:Number;
		private var score_tf:TextField; //the Textfield showing the score

		public function Main()
		{
			if(stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}

		private function init(e:Event = null):void
		{
			snake_vector = new Vector.<Element>;
			markers_vector = new Vector.<Object>;
			space_value = 2;
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!
			dead = false;
			min_elements = 10; //We will begin with 10 elements.
			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;
			apple.catchValue = 0; //pretty obvious
			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable
			score = 0;
			score_tf = new TextField(); //this is the TextField which shows our score.
			this.addChild(score_tf);

			for(var i:int=0;i<min_elements;++i)
			{
				snake_vector[i] = new Element(0x00AAFF,1,10,10);
				snake_vector[i].direction = "R"; //The starting direction of the snake
				if (i == 0)//first snake element
				{
					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
					attachElement(snake_vector[i], (snake_vector[0].width + space_value) * 20, (snake_vector[0].height + space_value) * 10, snake_vector[i].direction);
					snake_vector[0].alpha = 0.7;
				}
				else
				{
					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
				}
			}

			placeApple(false);
			timer.addEventListener(TimerEvent.TIMER, moveIt);
			timer.start();
		}	

		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
		{
			if (dirOfLast == "R")
			{
				who.x = lastXPos - snake_vector[0].width - space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "L")
			{
				who.x = lastXPos + snake_vector[0].width + space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "U")
			{
				who.x = lastXPos;
				who.y = lastYPos + snake_vector[0].height + space_value;
			}
			else if(dirOfLast == "D")
			{
				who.x = lastXPos;
				who.y = lastYPos - snake_vector[0].height - space_value;
			}
			this.addChild(who);
		}

		private function placeApple(caught:Boolean = true):void
		{
			if (caught)
				apple.catchValue += 10;

			var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
			var randomX:Number = Math.floor(Math.random()*boundsX);

			var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
			var randomY:Number = Math.floor(Math.random()*boundsY);

			apple.x = randomX * (apple.width + space_value);
			apple.y = randomY * (apple.height + space_value);

			for(var i:uint=0;i<snake_vector.length-1;i++)
			{
				if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
					placeApple(false);
			}
			if (!apple.stage)
				this.addChild(apple);
		}		

		private function moveIt(e:TimerEvent):void
		{
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				//This code runs if the snakes heads position and the apples position are the same
			}

			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
			{
				gameOver();
			}

			for (var i:int = 0; i < snake_vector.length; i++)
			{
				/*
					START OF FOR BLOCK
					This whole 'for' block will run as many times, as many elements the snake has.
					If there are four snake parts, this whole for cycle will run four times.
				*/

				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
				{
					//If the snakes heads position is the same as any of the snake parts, this block will run
					gameOver();
				}

				if (markers_vector.length > 0)
				{
					//if there are direction markers, this code runs
				}

				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.
				switch (DIRECTION)
				{
					case "R" :
						snake_vector[i].x += snake_vector[i].width + space_value;
						break;
					case "L" :
						snake_vector[i].x -= snake_vector[i].width + space_value;
						break;
					case "D" :
						snake_vector[i].y += snake_vector[i].height + space_value;
						break;
					case "U" :
						snake_vector[i].y -= snake_vector[i].width + space_value;
						break;
				}

				/*
					END OF FOR BLOCK
				*/
			}

		}

		private function gameOver():void
		{
			dead = true;
			timer.stop();
			while (this.numChildren)
				this.removeChildAt(0);
			timer.removeEventListener(TimerEvent.TIMER,moveIt);
			//stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
			init();
		}

	}
}


Step 11: The directionChanged() Function

We want to listen to the keyboard, so we can actually control the snake. For this we need to put some code into the init() function and the gameOver() function.

Put this at the end of the init() function (setting up the listener function):

stage.addEventListener(KeyboardEvent.KEY_DOWN,directionChanged);

And this at the end of the gameOver() function:

stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);

Now create a new function:

private function directionChanged(e:KeyboardEvent):void
{
	var m:Object = new Object(); //MARKER OBJECT
	//this will be added to the markers_vector, and have the properties x,y, and type
	//the type property will show us the direction. if it is set to right, whenever a snake's part hits it,
	//the direction of that snake's part will be set to right also

	if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT)
	{
		//If we pressed the LEFT arrow,
		//and it was not the last key we pressed,
		//and the last key pressed was not the RIGHT arrow either...
		//Then this block of code will run
	}
	markers_vector.push(m); //we push the object into a vector, so we can acces to it later (in the moveIt() function)
}

What goes into the if block?

  • The direction of the head should be rewritten.
  • The marker object has to be set correctly.
  • The last_button variable should be set to the last button pressed.

if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
{
	snake_vector[0].direction = "L";
	m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
	last_button_down = Keyboard.LEFT;
}

Repeat this three more times, and we will have this:

private function directionChanged(e:KeyboardEvent):void
		{
			var m:Object = new Object(); //MARKER OBJECT

			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT)
			{
				snake_vector[0].direction = "L";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
				last_button_down = Keyboard.LEFT;
			}
			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT)
			{
				snake_vector[0].direction = "R";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
				last_button_down = Keyboard.RIGHT;
			}
			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN)
			{
				snake_vector[0].direction = "U";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
				last_button_down = Keyboard.UP;
			}
			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP)
			{
				snake_vector[0].direction = "D";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
				last_button_down = Keyboard.DOWN;
			}
			markers_vector.push(m);
		}

We need one more thing to test it. In the moveIt() function we have something like this:

				if (markers_vector.length > 0)
				{
					//if there are direction markers, this code runs
				}

Here we need another for loop, to check every snake’s part against every marker on the stage, and check whether they collide. If they do, we need to set the snake’s part’s direction to the marker’s type. If it’s the last snake part which collides with the marker, we need to remove the marker from the markers_vector, too, so the snake parts don’t collide with it any more.

				if (markers_vector.length > 0)
				{
					for(var j:uint=0;j < markers_vector.length;j++)
					{
						if(snake_vector[i].x == markers_vector[j].x && snake_vector[i].y == markers_vector[j].y)
						{
							//setting the direction
							snake_vector[i].direction = markers_vector[j].type;
							if(i == snake_vector.length-1)
							{
								//if its the last snake_part
								markers_vector.splice(j, 1);
							}
						}
					}
				}

Now if you play with it it looks okay, but there is a bug in there. Remember what i said at the beginning of the tutorial?

For instance, if the snake is going to the right and you press the down-left combo very fast, it will hit itself and restart the game.

How do we correct this? Well it’s easy. We have our flag variable, and we will use that for this. We will only be able to change the directions of the snake when this is set to true (Default is false, check the init() function for that).

So we need to change the directionChanged() function a little. The if blocks’ heads should be changed: add a && flag clause at the end of every ‘if’.

		private function directionChanged(e:KeyboardEvent):void
		{
			var m:Object = new Object(); //MARKER OBJECT

			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
			{
				snake_vector[0].direction = "L";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
				last_button_down = Keyboard.LEFT;
				flag = false;
			}
			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT && flag)
			{
				snake_vector[0].direction = "R";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
				last_button_down = Keyboard.RIGHT;
				flag = false;
			}
			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN && flag)
			{
				snake_vector[0].direction = "U";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
				last_button_down = Keyboard.UP;
				flag = false;
			}
			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP && flag)
			{
				snake_vector[0].direction = "D";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
				last_button_down = Keyboard.DOWN;
				flag = false;
			}
			markers_vector.push(m);
		}

If you test it now, it won’t work because the flag is always false.

When do we need to set it to true then?

After a move/tick we can allow the users to change directions, we just don’t want to change it twice in one tick. So put this at the very end of the moveIt() function:

flag = true;

Now test it, and there is no bug any more.


Step 12: Finishing the Game

Now the only thing we need to do is the ‘apple-check’

Remember this at the very beginning of the moveIt() function?

			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				//This code runs if the snake's head's position and the apple's position are the same
			}

This is what we need to do in there:

  • Call the placeApple() function. (We don’t set the parameter to false; we leave it as it is. The default is true.)
  • Show the current score
  • Attach a new element to the snake’s last part.

			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				//calling the placeApple() function
				placeApple();
				//show the current Score
				score += apple.catchValue;
				score_tf.text = "Score:" + String(score);
				//Attach a new snake Element
				snake_vector.push(new Element(0x00AAFF,1,10,10));
				snake_vector[snake_vector.length-1].direction = snake_vector[snake_vector.length-2].direction; //lastOneRichtung
				//attachElement(who,lastXPos,lastYPos,lastDirection)
				attachElement(snake_vector[snake_vector.length-1],
									  (snake_vector[snake_vector.length-2].x),
									  snake_vector[snake_vector.length-2].y,
									  snake_vector[snake_vector.length-2].direction);
			}

Now everything should work fine. Try it out:

Here is the whole Main class again:

			package
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.ui.Keyboard;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.Event;

	import com.Element;

	public class Main extends Sprite
	{
		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function
		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
		private var markers_vector:Vector.<Object>; //the markers are held in here
		private var timer:Timer;
		private var dead:Boolean;
		private var min_elements:int; //holds how many parts should the snake have at the beginning
		private var apple:Element; //Our apple
		private var space_value:Number; //space between the snake parts
		private var last_button_down:uint; //the keyCode of the last button pressed
		private var flag:Boolean; //is it allowed to change direction?
		private var score:Number;
		private var score_tf:TextField; //the Textfield showing the score

		public function Main()
		{
			if(stage)
				addEventListener(Event.ADDED_TO_STAGE, init);
			else
				init();
		}

		private function init(e:Event = null):void
		{
			snake_vector = new Vector.<Element>;
			markers_vector = new Vector.<Object>;
			space_value = 2;
			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!
			dead = false;
			min_elements = 1;
			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;
			apple.catchValue = 0; //pretty obvious
			last_button_down = Keyboard.RIGHT; //The starting direction of the snake (only change it if you change the 'for cycle' too.)
			score = 0;
			score_tf = new TextField();
			this.addChild(score_tf);

			//Create the first <min_elements> Snake parts
			for(var i:int=0;i<min_elements;++i)
			{
				snake_vector[i] = new Element(0x00AAFF,1,10,10);
				snake_vector[i].direction = "R"; //The starting direction of the snake
				if (i == 0)
				{
					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
					attachElement(snake_vector[i],0,0,snake_vector[i].direction)
					snake_vector[0].alpha = 0.7;
				}
				else
				{
					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
				}
			}

			placeApple(false);
			timer.addEventListener(TimerEvent.TIMER,moveIt);
			stage.addEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
			timer.start();
		}

		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
		{
			if (dirOfLast == "R")
			{
				who.x = lastXPos - snake_vector[0].width - space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "L")
			{
				who.x = lastXPos + snake_vector[0].width + space_value;
				who.y = lastYPos;
			}
			else if(dirOfLast == "U")
			{
				who.x = lastXPos;
				who.y = lastYPos + snake_vector[0].height + space_value;
			}
			else if(dirOfLast == "D")
			{
				who.x = lastXPos;
				who.y = lastYPos - snake_vector[0].height - space_value;
			}
			this.addChild(who);
		}

		private function placeApple(caught:Boolean = true):void
		{
			if (caught)
				apple.catchValue += 10;

			var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
			var randomX:Number = Math.floor(Math.random()*boundsX);

			var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
			var randomY:Number = Math.floor(Math.random()*boundsY);

			apple.x = randomX * (apple.width + space_value);
			apple.y = randomY * (apple.height + space_value);

			for(var i:uint=0;i<snake_vector.length-1;i++)
			{
				if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
					placeApple(false);
			}
			if (!apple.stage)
				this.addChild(apple);
		}

		private function moveIt(e:TimerEvent):void
		{
			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
			{
				placeApple();
				//show the current Score
				score += apple.catchValue;
				score_tf.text = "Score:" + String(score);
				//Attach a new snake Element
				snake_vector.push(new Element(0x00AAFF,1,10,10));
				snake_vector[snake_vector.length-1].direction = snake_vector[snake_vector.length-2].direction; //lastOneRichtung
				attachElement(snake_vector[snake_vector.length-1],
									  (snake_vector[snake_vector.length-2].x),
									  snake_vector[snake_vector.length-2].y,
									  snake_vector[snake_vector.length-2].direction);
			}
			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
			{
				gameOver();
			}

			for (var i:int = 0; i < snake_vector.length; i++)
			{
				if (markers_vector.length > 0)
				{
					for(var j:uint=0;j < markers_vector.length;j++)
					{
						if(snake_vector[i].x == markers_vector[j].x && snake_vector[i].y == markers_vector[j].y)
						{
							snake_vector[i].direction = markers_vector[j].type;
							if(i == snake_vector.length-1)
							{
								markers_vector.splice(j, 1);
							}
						}
					}
				}
				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
				{
					gameOver();
				}

				//Move the boy
				var DIRECTION:String = snake_vector[i].direction;
				switch (DIRECTION)
				{
					case "R" :
						snake_vector[i].x += snake_vector[i].width + space_value;
						break;
					case "L" :
						snake_vector[i].x -= snake_vector[i].width + space_value;
						break;
					case "D" :
						snake_vector[i].y += snake_vector[i].height + space_value;
						break;
					case "U" :
						snake_vector[i].y -= snake_vector[i].width + space_value;
						break;
				}

			}

			flag = true;
		}

		private function gameOver():void
		{
			dead = true;
			timer.stop();
			while (this.numChildren)
				this.removeChildAt(0);
			timer.removeEventListener(TimerEvent.TIMER,moveIt);
			stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
			init();
		}

		private function directionChanged(e:KeyboardEvent):void
		{
			var m:Object = new Object(); //MARKER OBJECT

			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
			{
				snake_vector[0].direction = "L";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
				last_button_down = Keyboard.LEFT;
				flag = false;
			}
			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT && flag)
			{
				snake_vector[0].direction = "R";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
				last_button_down = Keyboard.RIGHT;
				flag = false;
			}
			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN && flag)
			{
				snake_vector[0].direction = "U";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
				last_button_down = Keyboard.UP;
				flag = false;
			}
			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP && flag)
			{
				snake_vector[0].direction = "D";
				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
				last_button_down = Keyboard.DOWN;
				flag = false;
			}
			markers_vector.push(m);
		}

	}

}


Step 13: Summing It All Up

Congratulations! You have just created a nice game. Now you can develop it further, and create a super apple or something. For that I recommend using another function called placeSuperApple() and a new class named SuperApple. Whenever you catch a super apple, the snakes parts could lengthen by three elements, perhaps. This could be set with setters/getters in the SuperApple class.

If you wish to do this, and you get stuck somewhere, just leave me a comment here.

Thank you for your time!



View full post on Activetuts+

banner ad

10 Responses to “Build a Classic Snake Game in AS3”

  1. Anthony Lombardo says:
    March 11, 2012 at 7:56 pm

    With the advent of tools like Adobe Edge and libraries like EaselJS, more resources are becoming available for developers looking to create interactive HTML5 content. Many of these tools are being geared specifically for Flash developers to make the transition from ActionScript to HTML5 canvas a smooth one. This article will overview oCanvas, an HTML5 library that developers might not only find invaluable but also very easy to use.


    HTML5 Canvas Background

    Before we dive into exploring oCanvas, let’s quickly set the scene for how HTML5 canvas works. If you want a more thorough explanation on how to use HTML5 canvas, check out this tutorial.

    If you know ActionScript, you already know a lot of JavaScript, which is where the real power resides when working with canvas. We use the HTML5 drawing API to create our content along with some good ol’ JavaScript to make things interactive and dynamic. But when we combine the two, the approach behind how we go about putting together our code is a bit different than what we are used to with ActionScript.

    In short, to use the native Canvas API, we draw pixels onto the drawing context of the canvas. But the key thing to remember is that we are working with the entire canvas, not just a single shape or image that we have drawn. Every time we want to alter something we have drawn we have to redraw the entire canvas. If we want to animate something we have to redraw the canvas over and over in our JavaScript to make it appear that things are moving.

    This notion is very similar to traditional animation, where animators had to draw each and every pose in their sequence and have the camera move through them very quickly to simulate movement. But if you’re used to tree-like structures such as the DOM, or the display list in Actionscript, this notion can be hard to get your head around. This rinse and repeat approach to programming is much different than working with objects for most developers.


    Introducing oCanvas

    Luckily for those of us who are so accustomed to working with objects, oCanvas brings that familiar approach to HTML5 canvas. oCanvas is a JavaScript library developed by Johannes Koggdal with the intention of making it easier to develop with HTML5 canvas. It allows you to work directly with objects, modify their properties and hook up events to them all while handling the nitty-gritty behind the scenes stuff for you. As put best by Johannes on his blog:

    My goal has always been to make it really easy for people to build canvas things based on objects. I decided on the name oCanvas as a contraction of “object canvas”.


    Get the Library

    Download oCanvas

    To start using oCanvas we need to include a copy of the library on our HTML page. We can either reference the CDN-hosted file, or host a local copy ourselves. Jump over to the oCanvas website and you can either download a copy of the library or grab the reference to the CDN-hosted version. The current version is 2.0 and was released only a few weeks ago, which addressed many of the bugs that were in the initial release. On the site there is a minified production version, which is good to use when you’re ready to deploy your project. There is also a development version, which is uncompressed but is better for debugging. I like to directly link to the hosted version for faster loading and caching by the browser.

    
    
    <script src="http://cdnjs.cloudflare.com/ajax/libs/ocanvas/2.0.0/ocanvas.min.js"></script&gt;
    

    Initial Code Setup

    After you make a reference to oCanvas, next we need to setup a canvas element in the body of our HTML and create a reference to it for use in our Javascript.

    
    
    <canvas id="canvas" width="800" height="600"></canvas>
    

    As always, if you place your script above the canvas element, you need to wrap it in a function so you know the DOM is ready. There’s a couple ways to go here. You can either create your own function and then call it in your body element when it loads, like this:

    
    
    function Main()	{
    	// your oCanvas code
    }
    
    <body onload="Main();">
    

    Or you can wrap your code within oCanvas’s built-in domReady() method. This is the equivalent to jQuery’s $(document).ready(). In oCanvas we use this:

    
    
    oCanvas.domReady(function () {
    	//Your Code Here
    });
    

    Note: You could use jQuery’s $(document).ready() method if you wanted to.


    Initialize an Instance of oCanvas

    This piece of code is absolutely necessary and is the first thing you must write when using oCanvas.

    
    
    var canvas = oCanvas.create({
    	canvas: "#canvas",
    	background: "#0cc",
    	fps: 60
    });
    

    In this code we store a reference to the canvas element within our document and get access to the core instance, which will enable you to start creating objects. The create() method takes an object as its argument which controls how oCanvas will work. There are numerous properties to pass into the create() method, but the only mandatory one is the canvas property: a CSS selector that must point to a canvas element within the DOM.

    The other properties passed in the above code are the background and fps properties. The background property allows you to apply a background to the canvas, which can be CSS color values, gradients and images. If it’s omitted, the canvas will be transparent. The fps property sets the number of frames per second any animation will run at. The default is 30 fps.

    Note: While we overview many of the features in oCanvas, I recommend checking out the library’s documentation to get a better understanding of each section.


    Display Objects

    Display Objects

    There are numerous types of display objects you can create with oCanvas. You can create shapes such as rectangles, ellipses, polygons and lines along with images, text and even sprite sheets. To create a new display object we use oCanvas’s Display Module, and specify what kind of display object we want to create as well as some basic properties – like so:

    
    
    var box  = canvas.display.rectangle({
    	x: 50,
    	y: 150,
    	width: 50,
    	height: 50,
    	fill: "#000"
    });
    

    Then to add it to the display we call a familiar method for you Flash developers…

    Good Ol’ addChild()

    Yes an oldie but a goodie, which makes adding objects to oCanvas a familiar process. So to add our box to the canvas, we would write:

    
    
    canvas.addChild(box);
    

    Just like in ActionScript, addChild() adds the specified object as a child of the caller. And in turn the child’s x and y will be relative to its parent. So in this case we are making box a child of the canvas, which we could simplify like this:

    
    
    box.add();
    

    The add() method also adds the object to the canvas — which is really the same thing as canvas.addChild(box). But addChild() is most useful for adding an object as a child to an already created display object, like:

    
    
    var square  = canvas.display.rectangle({ x: 0, y: 0, width: 10, height: 10, fill: "#990000"});
    box.addChild(square);
    

    Let’s take a look at some of the different types of display objects you can create in oCanvas.

    Shapes

    You already saw a square, but we can use the rectangle display object to create a lot of things. Here’s a rectangle with a blue stroke:

    
    
    var rectangle = canvas.display.rectangle({
    	x: 500,
    	y: 100,
    	width: 100,
    	height: 200,
    	fill: "#000",
    	stroke:"outside 2px blue"
    });
    

    The fill property can take any valid CSS color, along with CSS gradients and even image patterns.

    To create an ellipse we would write:

    
    
    var ellipse = canvas.display.ellipse({
    	x: 100,
    	y: 100,
    	radius_x: 20,
    	radius_y: 30,
    	fill: "rgba(255, 0, 0, 0.5)"
    });
    

    If you want a full circle, just replace the radius_x and radius_y properties with a single radius property.

    Creating any kind of regular polygon is just as easy — all you have to do is specify the number of sides and the radius you want your shape to have. To create a triangle:

    
    
    var triangle = canvas.display.polygon({
    	x: 320,
    	y: 145,
    	sides: 3,
    	radius: 50,
    	fill: "#406618"
    });
    

    How about a pentagon?

    
    
    var pentagon = canvas.display.polygon({
    	x: 200,
    	y: 50,
    	sides: 5,
    	rotation: 270,
    	radius: 40,
    	fill: "#790000"
    });
    

    To accomplish that with the HTML5 canvas API, you would have to draw a bunch of paths and try figure out what x and y positions to join them at. I attempted to draw an octagon for comparison’s sake but as you can see below I gave up quite easily. Not quite sure what this is supposed to be.

    
    
    var canvas = $("#canvas");
    var ctx = canvas.get(0).getContext("2d");
    ctx.fillStyle = '#000';
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(100,50);
    ctx.lineTo(50, 100);
    ctx.lineTo(0, 90);
    ctx.closePath();
    ctx.fill();
    

    Images

    Creating display objects with images doesn’t get any simpler than in oCanvas. Just specify an x and y position and the path to the image file:

    
    
    var tree = canvas.display.image({
    	x: 100,
    	y: 350,
    	image: "tree.png"
    });
    

    A nice feature of the image display object is the tile property, which lets you easily create a grid of the same image instead of drawing it over and over.

    Text

    oCanvas contains a text display object and handles font styling just like CSS does.

    
    
    var text = canvas.display.text({
    	x: 70,
    	y: 300,
    	align: "center",
    	font: "bold 18px sans-serif",
    	text: "oCanvas Rocks",
    	fill: "purple"
    });
    

    You can use many of the other text properties you’re familiar with from CSS. Check out the documentation on text for more.


    Properties and Methods

    All display objects inherit a common group of properties and methods. Some of the most common display object properties are: x, y, width, height, rotation, scalingX, scalingY, opacity, shadow (uses CSS-box-shadow syntax) and zIndex. You can check out this link for a full list of the base properties and methods. Let’s take a look at some other noteworthy ones.

    Origin

    This method is a big time-saver because it let’s you easily set the origin inside the object. In other words, it allows you to set the registration point of the object. If you ever tried to perform a rotation from the center with the HTML5 Canvas API, you know how big of a headache it can be. You have to do a slew of actions of saving the drawing state, translating the canvas, performing your rotation and then restoring the drawing state. With the origin property you can easily define an object’s origin:

    
    
    var obj  = canvas.display.image({
    	x: 270,
    	y: 270,
    	origin: {
    		x: "center",
    		y: "center"
    	}
    });
    

    This would draw the image from its center; if we were to rotate the object, it would rotate from its center, too. Besides “center” you could also pass in “left” or “right” for the x and “top” or “bottom” for the y positions. In addition to using the predefined keywords, you could also supply positive or negative numbers as values of where to draw the object. The default origin for all display objects is defined at its top left.

    You can also use the setOrigin() method at any time to define an object’s origin:

    
    
    obj.setOrigin("left", "bottom")
    

    ID

    A display object’s id, which is really a read-only property, corresponds to where the object exists in the draw list — which you can think of as the display list. I find it to be very useful because it can serve as a unique identifier in certain situations when you might be seeking out a specific object in your code. Consider a basic snippet like this:

    
    
    getId(box.id)
    function getId(id) {
    	if (id==9) console.log("CORRECT ! " + id)
    	else console.log("WRONG! " + id)
    }
    

    Composition

    The composition property is the equivalent of globalCompositeOperation within the native Canvas API. If you’re not familiar with it, it basically determines how pixels are rendered when drawn onto already existing pixels on the canvas. I encourage you to read up on the different compositing operations you can set, but with oCanvas you can simply set the operation you want by passing it as a string:

    
    
    var shape  = canvas.display.rectangle({
    	x: 270,
    	y: 270,
    	width: 180,
    	height: 80,
    	fill: "#ff6900",
    	composition:"destination-atop"
    });
    

    There are many different operations you can pass in but I think one of the neat things you can do with the composition property is create masks between different display objects. Check out the file named masks.html in the download package. If you ever relied on creating layer masks in your Flash applications, you’ll enjoy this one.

    Methods of Note

    Since we mentioned rotating objects earlier, you can quickly rotate an object with the rotate() and rotateTo() methods:

    
    
    obj.rotate(45);
    

    You can also simply set the rotation property:

    
    
    obj.rotation=45;
    

    There’s also the move() and moveTo() methods which, like their names suggest, allows you to move an object by a specified amount of pixels for the former and toa specified x and y position for the latter.

    
    
    obj.moveTo(100, 100)
    

    The same idea works for the scale() and scaleTo() methods():

    
    
    obj.scale(1.25, 0.25)
    obj.scaleTo(1.5, 1.5)
    

    We mentioned addChild() before; let’s not forget about removeChild() and removeChildAt(). And like the add() method, we can do the opposite with remove().

    Another really useful method is clone(), which allows you to duplicate a display object and all of its properties.

    
    
    var box  = canvas.display.rectangle({ x: 50, y: 150, width: 50, height: 50, fill: "#000"});
    var box2 = box.clone(x:200)
    

    Events

    A big plus to oCanvas is that you can add events to specific objects. The oCanvas contains many methods and properties for easily handling mouse, keyboard and even touch events all with one simple method.

    Bind()

    If you’re familiar with jQuery, you probably already know where I’m going with this.

    
    
    canvas.bind("click tap", function () {
    	canvas.background.set("#efefef");
    });
    

    All this does is change the background color of the canvas, but notice how we pass in “click tap” — easily allowing us to add support for both mouse and touch devices.

    Besides click events, you can also listen for other mouse events:mousedown, mouseup, mousemove, mouseenter, mouseleave and dblclick.

    A simple rollover effect might look like this:

    
    
    box.bind("mouseenter", function () {
    	canvas.background.set("#333");
    }).bind("mouseleave", function () {
    	canvas.background.set("#000");
    });
    

    This is an example of chaining functions — which (not to sound like a broken record) is another jQuery feature leveraged in oCanvas.

    But instead of altering the canvas when a mouse event occurs, what about altering an actual display object? This is still HTML5 Canvas after all, so we must remember to call an important method to tell the canvas to update itself.

    canvas.redraw()

    The redraw() method (which is actually part of the Draw Module, not the Events Module) redraws the canvas with all the display objects that have been added. So if want to perform an action on a particular object and have the rest of the draw list stay intact, we must add this one simple line of code to our functions:

    
    
    square.bind("click tap", function () {
    	square.x+=50;
    	canvas.redraw();
    });
    

    Unbind()

    What good is an event listener if we can’t remove it?

    
    
    rectangle.bind("click tap", function onClick ()
    	{
    		this.fill="#FF9933";
    		canvas.redraw();
    		rectangle.unbind("click tap", onClick)
    	});
    

    How About a Quick Drag and Drop?

    We don’t need the bind() method for this one. We just write:

    
    
    circle.dragAndDrop();
    

    That’s probably the quickest and easiest drag and drop code you’ll ever write.

    Note on events: When you’re working with events, it’s natural to want to get as much information as possible about the event. Luckily, we can still do so when working with oCanvas. For example if we take the click handler a few lines up and log the event to the console we can see all the properties we have from the event.

    
    
    rectangle.bind("click tap", function onClick (e)
    {
    	this.fill="#FF9933";
    	canvas.redraw();
    	rectangle.unbind("click tap", onClick);
    	console.log(e);
    });
    
    Firefox console log

    Keyboard and Touch Events

    Besides mouse events, oCanvas has entire modules dedicated to keyboard and touch events with their own unique methods and properties. These events are also handled with the bind() method. The events system in oCanvas is a very broad topic, so I encourage taking a look at the events section in the documentation and experimenting.


    Timeline

    With the Timeline Module we can set up our main loop for our application. If you were creating a game, this would essentially be your game loop. I like to think of it as the equivalent of an ENTER_FRAME in Flash.

    It’s simple to set up — we just call the setLoop function and chain the start() method to it:

    
    
    canvas.setLoop(function () {
    	triangle.rotation+=5;
    }).start();
    

    If we wanted to tie the setLoop() function to an event – say to a mouse click – we could do something like this:

    
    
    canvas.setLoop(function () {
    	triangle.rotation+=5;
    })
    
    button.bind("click tap", function () {
    	canvas.timeline.start()
    });
    

    And we could stop the timeline by simply calling:

    
    
    canvas.timeline.stop();
    

    Animation

    Using setLoop() is the way to go for animations that will occur over a long period of time and to handle constant updates you have to make throughout your application. But oCanvas has built in methods to handle more simpler and basic animations that are commonly needed. These methods are also practically taken verbatim from jQuery.

    Animate()

    The animate() method works just like it does in jQuery. If you’re not familiar with this side of jQuery, think if it like a tweening engine like TweenMax or Tweener for Flash. You can animate any property that can be set by a numeric value:

    
    
    circle.animate(
    	{
    		y: circle.y - 300,
    		scalingX:.5,
    		scalingY: .5
    	},
    	"short",
    	"ease-in",
    	function () {
    		circle.fill = "#45931e";
    		canvas.redraw();
    	}
    );
    

    Here we animate the circle’s y position and overall size, apply some easing, and when it is finished we run a callback function that changes its fill color. But don’t forget to call redraw().

    fadeIn(), fadeOut(), and fadeTo()

    To fade an object in and out we could simply call:

    
    
    square.fadeIn();
    square.fadeOut();
    

    To fade the opacity to a specific value, we’d use fadeTo():

    
    
    square.fadeTo(.6);
    

    You can also define the duration, easing and provide a callback function for these methods in the same way you would with the animate() method.


    Scenes

    oCanvas contains a very useful Scenes Module that allows you to easily separate your application into different states. Game developers might appreciate this one because it’s a simple approach to breaking down your game into different sections. Even old school Flash animators might liken the Scenes Module to the Scenes panel, which allows you to literally create different scenes within a Flash project.

    To create a scene in oCanvas we call the create() method to return a scenes object:

    
    
    var intro = canvas.scenes.create("intro", function () {
    	// add display objects here
    });
    

    Within the create() method we pass in two arguments: the name of the scene as a string, and a function where we add the display object we want to add to that scene.

    
    
    var introText = canvas.display.text({
    	x: canvas.width / 2,
    	y: canvas.height/2,
    	align: "center",
    	font: "bold 36px sans-serif",
    	text: "Introduction",
    	fill: "#133035"
    });
    var intro = canvas.scenes.create("intro", function () {
    	this.add(introText);
    });
    

    Now we have to load our scene and those objects will be added to the display:

    
    
    canvas.scenes.load("intro");
    

    Notice we pass in the name we gave the scene when we created it.

    And of course we can unload a scene at any time:

    
    
    canvas.scenes.unload("intro");
    

    Imagine how much of a time-saver this could be if you used scenes and event handlers together.


    oCanvas vs. EaselJS

    The only real downside to oCanvas is that it hasn’t gained as much traction in the development community as you might guess – or at least it seems that way for now. Part of that reason for this, I think, is because of the popularity of EaselJS. There seems to be alot more awareness and resources out there for EaselJS than there are for oCanvas — which is hard to believe since the latter was first released in March 2011, but for some reason it has flown under the radar.

    I’ve been using both libraries for quite some time now, and I can honestly say I am big fan of both. EaselJS definitely feels more like you are using ActionScript and if you’re a Flash developer will be easy to pick up. And as we’ve seen, oCanvas could pass for jQuery’s long lost brother in many ways. So if you’re a pure ActionScripter, you might just naturally gravitate towards EaselJS— especially since Easel was written specifically to appeal to Flash developers.

    However, I’ve been using Actionscript much longer than jQuery and I personally find oCanvas simpler to use and less verbose to write. And even though EaselJS is pretty easy to use itself, the simple syntax in oCanvas just makes it such a welcome tool.

    But besides the simpler syntax, oCanvas and EaselJS in many ways are are pretty much interchangeable. Both libraries can accomplish more or less the same tasks and there’s very little difference in performance, if any. However I do notice the Ticker function in EaselJS runs a little more smoothly than oCanvas’ setLoop function (though that could just be a browser-based difference).

    EaselJS does have much more of an extensive API — especially when it comes to drawing and effects. And if you take into account TweenJS and SoundJS, Easel is definitely a more complete tool — especially if you’re used to using an application like Flash that offers fine-tune control over your projects. But if you’re new to the whole HTML5 game, you’re likely to hit the ground running with oCanvas much faster. When I was first introduced to oCanvas, I found it so much fun to play with. Everything is already there for you — all the necessary methods and events to start creating, manipulating and animating objects right away.


    Wrapping Up

    Whichever library you prefer, oCanvas and EaselJS are only the beginning of what I think will be an influx of tools and resources to allow developers to easily create browser-based applications. The features of oCanvas detailed in this article barely scratch the surface of what could be created using this very powerful library.

    By no means though is oCanvas (or any library for that matter) a reason not to learn and use the native HTML5 Canvas API. But if you find yourself in a situation where all your former Flash clients are now looking for you to create HTML5 apps (like mine were) and you don’t have time to learn something like the unfriendly transformation matrix in the native Canvas API — oCanvas can definitely ease the learning curve.


  2. Andre Cavallari says:
    March 11, 2012 at 7:58 pm

    In this tutorial I’ll show you how to create a menu like Apple’s Dock using AS3 classes. We will create a single AS file that will perform all the magic, extending it to add new features.


    March of 2010


    Final Result Preview

    First, let’s take a look at what we’ll be creating. Roll your mouse over the icons to see how they move and scale.


    Step 1: Create a New ActionScript File

    Begin by creating a new ActionScript file and saving it as “DockItem.as”. I’m saving mine at c:/macmenu/org/effects/DockItem.as.

    Note that our document root (where the .fla lives) will be c:/macmenu; the folder /org/effects will form the package for the DockItem class.


    Step 2: Create a New FLA

    Create a new ActionScript 3.0 Flash File and open it, so that we have both DockItem.as and this .fla file opened. Save this .fla in the root folder (the DockItem.as is at c:/macmenu/org/effects, so our site root is c:/macmenu) the /org/effects is the package of DockItem Object and we save the .fla as c:/macmenu/macmenu.fla.


    Step 3: Import Icons

    Now we import or draw some icons to the .fla. I’ve imported some icons I have here from an Illustrator file, but you can of course draw your own and apply a gradient to them.


    Step 4: Begin Converting Icons to Symbols

    Select any icon and click Modify > Convert To Symbol.

    In the box that opens, give it a name (I named this symbol “Star”) and pay attention to the registration point; it needs to be bottom center. For the class use the same name (remember that you can’t use spaces) and for the Base class, use org.effects.DockItem (the class that we’ll create). Also, make sure your Type is set to Movie Clip.

    Then, align all the objects to the bottom: select all, click Window > Align, make sure the button "To stage" is unselected (otherwise it will align at the botton of the stage), then click the top-right button in this panel to align all the objects.


    Step 5: Convert All Icons to Symbols

    We can have as many buttons as we want, so let’s convert all our icons to symbols. Remember to give them a name and a Class, set all their registration points to bottom center and set the Base class to org.effects.DockItem.

    See below for how our library and the icons should look; note the space between them, it’s important for creating a good effect.


    Step 6: Start Coding the DockItem Class

    If we test the movie now it will throw an error saying that an ActionScript file must have at least one external and visible definition; that’s because all our menu items are extending the DockItem class, which we haven’t yet written. Let’s write it now…

    Start creating the package by extending the Sprite class (we will extend Sprite since we don’t have a timeline animation.)

    package org.effects{
    
    	import flash.display.Sprite;
    
    	public class DockItem extends Sprite{
    	}
    }
    

    At this point we have our DockItem extending the Sprite class, so if you test it now it will work, but you’ll see no effects.

    (Confused? Not used to coding with classes? Check out this Quick Tip on using a document class for an introduction.)


    Step 7: Import Necessary Classes

    Now we will import all the necessary classes. A custom class is being used here, the TweenLite class, which you can download from GreenSock.com. When you’ve downloaded TweenLite, extract it to your /macmenu/ folder (so you will have a folder /macmenu/com/greensock/).

    package org.effects{
    
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import com.greensock.TweenLite; //http://www.greensock.com/tweenlite
    	import com.greensock.plugins.TweenPlugin;
    	import com.greensock.plugins.TintPlugin;
    
    	public class DockItem extends Sprite{
    	}
    }
    

    I’ve imported the Sprite class because it’s what we are extending; if you have animations on the timeline, extend the MovieClip class. We will use the Event class when the custom object is added to stage and we’ll use the MouseEvent when checking the distance of each icon from the mouse.


    Step 8: Declare Necessary Variables

    During this step we’ll declare the necessary variables:

    package org.effects{
    
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import com.greensock.TweenLite;
    	import com.greensock.plugins.TweenPlugin;
    	import com.greensock.plugins.TintPlugin;
    
    	public class DockItem extends Sprite{
    
    		private var _initPosition:Number;
    		public var maxXDistance:Number;
    		public var maxYDistance:Number;
    		public var maxScale:Number;
    
    	}
    }
    

    Note that I used the _initPosition as private: it just sets the initial x-position of the icon. The distance of the mouse will always be measured from this point, because the actual x-position of the item will always be changing.

    maxXDistance is the maximum x-distance over which the mouse will affect the icon, maxYDistance is the maximum y-distance over which mouse will affect the icon and maxScale is the maximum scale that will be added to the icon (for example, if you set it to 2, the maximum scale the object can reach is 3.)

    I’ve used public variables for the last three so we can change them at runtime.


    Step 9: Coding the Constructor Function

    The constructor function must have the same name as the class (and therefore the same name as the file), hence DockItem():

    package org.effects{
    
       import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import com.greensock.TweenLite;
    	import com.greensock.plugins.TweenPlugin;
    	import com.greensock.plugins.TintPlugin;
    
    	public class DockItem extends Sprite{
    
    		private var _initPosition:Number;
    		public var maxXDistance:Number;
    		public var maxYDistance:Number;
    		public var maxScale:Number;
    
    		public function DockItem($maxXDistance:Number=60,$maxYDistance:Number=30,$maxScale:Number=2):void{
    			maxXDistance=$maxXDistance;
    			maxYDistance=$maxYDistance;
    			maxScale=$maxScale;
    			if(stage) init();
    			else addEventListener(Event.ADDED_TO_STAGE,init);
    			addEventListener(Event.REMOVED_FROM_STAGE,end);
    		}
    
    	}
    }
    

    Why do we have some parameters here? This allows us to use different combinations of distances and scales: we can have a short distance with a very big scale or a long distance with a small scale. Also, we can determine the y distance within which the mouse will affect the icon.

    As we are extending the Sprite class we can add children or even code a custom class for each icon extending the DockItem class, so if we extend it we can use the super() function to pass the new parameters to the superclass. We can then use the DockItem class anytime and anywhere.

    In this step we set the maxXDistance variable, maxYDistance variable and the maxScale variable to the values passed as parameters. Also, we check if the object is on the stage – if not, we add an Event to check when it is. We also add another event listener to detect when the icon is removed from the stage. We’ll add a MOUSE_MOVE event to the stage to get the distance, so it’s important to know whether it’s on the stage.


    Step 10: The Init() Function

    This is the function that will be run once the icon is created and added to the stage. In the init() function we just add an MouseEvent.MOUSE_MOVE listener to the stage, set the _initPosition variable to the x value of the object, and listen for the mouse leaving the area of stage.

    package org.effects{
    
       import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import com.greensock.TweenLite;
    	import com.greensock.plugins.TweenPlugin;
    	import com.greensock.plugins.TintPlugin;
    
    	public class DockItem extends Sprite{
    
    		private var _initPosition:Number;
    		public var maxXDistance:Number;
    		public var maxYDistance:Number;
    		public var maxScale:Number;
    
    		public function DockItem($maxXDistance:Number=60,$maxYDistance:Number=30,$maxScale:Number=2):void{
    			maxXDistance=$maxXDistance;
    			maxYDistance=$maxYDistance;
    			maxScale=$maxScale;
    			if(stage) init();
    			else addEventListener(Event.ADDED_TO_STAGE,init);
    			addEventListener(Event.REMOVED_FROM_STAGE,end);
    		}
    
    		private function init(e:Event=null):void{
    			_initPosition=x;
    			stage.addEventListener(MouseEvent.MOUSE_MOVE,mouseMove);
    			stage.addEventListener(Event.MOUSE_LEAVE,mouseLeave);
    		}
    
    	}
    }
    

    Step 11: The Mouse Functions

    When the mouse moves over the stage, this function (triggered by the MOUSE_MOVE event we added a listener for in the last step) will check the mouse position of the parent object and measure the distance from the object to the mouse parent position.

    We use parent.mouseX because that gets us the x-position of the mouse relative to whichever object contains the icon, rather than relative to the registration point of the icon.

    We also tween the icons back to their starting positions if the mouse leaves the stage in the mouseLeave() handler.

    package org.effects{
    
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import com.greensock.TweenLite;
    	import com.greensock.plugins.TweenPlugin;
    	import com.greensock.plugins.TintPlugin;
    
    	public class DockItem extends Sprite{
    
    		private var _initPosition:Number;
    		public var maxXDistance:Number;
    		public var maxYDistance:Number;
    		public var maxScale:Number;
    
    		public function DockItem($maxXDistance:Number=60,$maxYDistance:Number=30,$maxScale:Number=2):void{
    			maxXDistance=$maxXDistance;
    			maxYDistance=$maxYDistance;
    			maxScale=$maxScale;
    			if(stage) init();
    			else addEventListener(Event.ADDED_TO_STAGE,init);
    			addEventListener(Event.REMOVED_FROM_STAGE,end);
    		}
    
    		private function init(e:Event=null):void{
    			_initPosition=x;
    			stage.addEventListener(MouseEvent.MOUSE_MOVE,mouseMove);
    			stage.addEventListener(Event.MOUSE_LEAVE,mouseLeave);
    		}
    
    		private function mouseMove(e:MouseEvent):void{
    			var yDistance:Number=Math.abs(parent.mouseY-y);
    			if(yDistance>maxYDistance){
    				if(_initPosition==x) return;
    				else{
    					TweenLite.to(this,.3,{x:_initPosition,scaleX:1,scaleY:1});
    					return;
    				}
    			}
    			//get the difference between the parent mouse x position and the initial position of the object
    			var xDistance:Number=parent.mouseX-_initPosition;
    
    			//check if the distance of the mouse from the object is more than max distance, it can't be bigger...
    			xDistance = xDistance > maxXDistance ? maxXDistance : xDistance;
    
    			//check if the distance is lower than the negative of the max distance, it can't be lower...
    			xDistance = xDistance < -maxXDistance ? -maxXDistance : xDistance;
    
    			//create a variable for the position, assuming that the x position must be the initial position plus the distance of the mouse, but it can't be more than the max distance.
    			var posX=_initPosition-xDistance;
    
    			//we get the scale proportion here, it goes from 0 to maxScale variable
    			var scale:Number=(maxXDistance-Math.abs(xDistance))/maxXDistance;
    
    			//the minimum scale is 1, the original size, and the max scale will be maxScale variable + 1
    			scale=1+(maxScale*scale);
    
    			//here we use a Tween to set the new position according to the mouse position
    			TweenLite.to(this,.3,{x:posX,scaleX:scale,scaleY:scale});
    		}
    
    		private function mouseLeave(e:Event):void{
    			TweenLite.to(this,.3,{x:_initPosition,scaleX:1,scaleY:1});
    		}
    	}
    }
    

    First, we check the y distance (vertical distance between the icon and the mouse); if it’s further away than the range we set with the maxYDistanceVariable, then we check whether the icon is back in its original position, and, if not, we tween it there. The return keyword breaks out of the function, so none of the rest of the code will be run in this case.

    If the mouse is close to the icon vertically, we use some maths to figure out a new scale and position for the icon based on its horizontal distance from the mouse, then tween it to those values.


    Step 12: The End() Function

    If we remove the object from the stage, we need to remove the mouseMove and mouseLeave listeners; if not we can get errors every time the mouse is moved. This function is the handler for the REMOVED_FROM_STAGE listener we added earlier, so will be triggered when the object is removed.

    package org.effects{
    
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import com.greensock.TweenLite;
    	import com.greensock.plugins.TweenPlugin;
    	import com.greensock.plugins.TintPlugin;
    
    	public class DockItem extends Sprite{
    
    		private var _initPosition:Number;
    		public var maxXDistance:Number;
    		public var maxYDistance:Number;
    		public var maxScale:Number;
    
    		public function DockItem($maxXDistance:Number=60,$maxYDistance:Number=30,$maxScale:Number=2):void{
    			maxXDistance=$maxXDistance;
    			maxYDistance=$maxYDistance;
    			maxScale=$maxScale;
    			if(stage) init();
    			else addEventListener(Event.ADDED_TO_STAGE,init);
    			addEventListener(Event.REMOVED_FROM_STAGE,end);
    		}
    
    		private function init(e:Event=null):void{
    			_initPosition=x;
    			stage.addEventListener(MouseEvent.MOUSE_MOVE,mouseMove);
    			stage.addEventListener(Event.MOUSE_LEAVE,mouseLeave);
    		}
    
    		private function mouseMove(e:MouseEvent):void{
    			var yDistance:Number=Math.abs(parent.mouseY-y);
    			if(yDistance>maxYDistance){
    				if(_initPosition==x) return;
    				else{
    					TweenLite.to(this,.3,{x:_initPosition,scaleX:1,scaleY:1});
    					return;
    				}
    			}
    			//get the difference between the parent mouse x position and the initial position of the object
    			var xDistance:Number=parent.mouseX-_initPosition;
    
    			//check if the distance of the mouse from the object is more than max distance, it can't be bigger...
    			xDistance = xDistance > maxXDistance ? maxXDistance : xDistance;
    
    			//check if the distance is lower than the negative of the max distance, it can't be lower...
    			xDistance = xDistance < -maxXDistance ? -maxXDistance : xDistance;
    
    			//create a variable for the position, assuming that the x position must be the initial position plus the distance of the mouse, but it can't be more than the max distance.
    			var posX=_initPosition-xDistance;
    
    			//we get the scale proportion here, it goes from 0 to maxScale variable
    			var scale:Number=(maxXDistance-Math.abs(xDistance))/maxXDistance;
    
    			//the minimum scale is 1, the original size, and the max scale will be maxScale variable + 1
    			scale=1+(maxScale*scale);
    
    			//here we use a Tween to set the new position according to the mouse position
    			TweenLite.to(this,.3,{x:posX,scaleX:scale,scaleY:scale});
    		}
    
    		private function mouseLeave(e:Event):void{
    			TweenLite.to(this,.3,{x:_initPosition,scaleX:1,scaleY:1});
    		}
    
    		private function end(e:Event=null):void{
    			stage.removeEventListener(MouseEvent.MOUSE_MOVE,mouseMove);
    			stage.removeEventListener(Event.MOUSE_LEAVE,mouseLeave);
    		}
    
    	}
    }
    

    All we do in this function is remove the event listener from the stage.


    Step 13: Test It!

    At this point we can already test it; it will work since each object is linked with the Base class DockItem. However, we don’t have a bounding box for clicking (if we set our object’s buttonMode property to true, we’ll see that we can click it only when it’s over the actual graphic.)


    Step 14: Start Turning Icons Into Buttons

    So far we can see the effect working, so now let’s turn each item into a button. We’ll create a new ActionScript file and this one will extend the DockItem – let’s name it DockButton. Its package will be the same as DockItem (org.effects), so we’ll save itb in the same folder as DockItem.as (example: c:/macmenu/org/effects/DockButton.as)


    Step 15: Change the Base Class

    Now we change the base class of each object in the library. We are currently using org.effects.DockItem as Base class, let’s now use org.effects.DockButton.

    If we test it now, there will be an error. This is because DockButton.as is still empty, so let’s code it.


    Step 16: Start Coding DockButton.as

    OK, now we’ll extend the DockItem class, because we want to use everything that we have in DockItem and add some more tricks (allowing it to act as a button), but we don’t want to add the new features to DockItem directly. This way, if we want to use the DockItem as anything other than a Button later on, we can, but if we want to use it as a Button we can use the DockButton.

    package org.effects{
    
    	public class DockButton extends DockItem{
    	}
    }
    

    If we test our project now, it will work, but it will work exactly as the DockItem as we haven’t yet added anything new.


    Step 17: Import Classes for DockButton

    Let’s import some things we will need to extend the DockItem. As we are extending the DockItem we don’t need to import the classes that are already there, since we wont use them directly in DockButton.

    package org.effects{
    
    	import flash.geom.Rectangle;
    
    	public class DockButton extends DockItem{
    	}
    }
    

    I’ve imported the Rectangle class, but why? It’s because we will use the bounding box of our object to create a fake background, to allow the button to be clickable even if the mouse isn’t precisely over a colored area. Let’s create a background graphic with alpha 0 (transparent), so we will have a square to click.


    Step 18: Constructor for DockButton

    Since we need to create a bounding box for DockButton, we will get its own bounds, that’s why we imported the flash.geom.Rectangle class

    package org.effects{
    
    	import flash.geom.Rectangle;
    
    	public class DockButton extends DockItem{
    
    		public function DockButton():void{
    			buttonMode=true;
    			mouseChildren=false;
    			var bounds:Rectangle=getBounds(this);
    			this.graphics.beginFill(0,0);
    			this.graphics.drawRect(bounds.x,bounds.y,bounds.width,bounds.height);
    			this.graphics.endFill();
    		}
    	}
    }
    

    What we have done? We created a constructor which first sets the object’s buttonMode to true, so our DockButton will be treated as a Button. Then we set mouseChildren to false, so mouse events will come from the DockButton object, not any other object inside it. Next we get the bounds of the object using getBounds() and draw a transparent rectangle using the graphics object. (The graphics property comes with the Sprite class, and we extended Sprite to make our DockItem object. Now we’ve extended our DockItem to make our DockButton object, DockButton has everything from the Sprite class and the DockItem class.)


    Step 19: Check Everything and Test It

    OK, let’s perform a check:

    • We need a .fla file (example: c:/macmenu/macmenu.fla).
    • In the same folder as the .fla file we need to have another folder: /org/effects (example: c:/macmenu/org/effects).
    • Inside this folder we need to have two .as documents (DockItem.as and DockButton.as)
    • Within the .fla, each item in the library must be linked to a class, and the base class of each item must be org.effects.DockButton.

    If it’s all OK, test the movie…

    (At this point, if you want to put the folder org/effects in your classpath you can, so you won’t need to copy this folder to each project you create and use the DockItem or DockButton.)


    Step 20: Change the Color on Mouse Over

    Why not change the color of the button when the mouse passes over it? In this section I will teach how. For this we will use the TweenLite engine again to give some tint to the object. However, we are already using TweenLite in the DockItem object and we are extending this object at DockButton. We want to extend DockButton to change the color, but we can’t use TweenLite anymore in the same object since the new TweenLite object will overwrite the other one (even with the property overwrite:false in TweenLite it will reduce the performance a lot if we use it directly in the same object). All is not lost; we have an icon inside each object of the library and we can apply the tint to that.

    To do this, let’s create another ActionScript File, but now save this one at the same folder as the .fla with the name “OverButton.as” (example: c:/macmenu/OverButton.as.)


    Step 21: Coding the OverButton Object

    First we create the package and import the necessary classes; since we saved the OverButton.as file in the same folder of the .fla file the package will be top level, so there’s no need to write “package org.effects”:

    package{
    
    	import org.effects.DockButton;
    	import flash.display.DisplayObject;
    	import flash.events.MouseEvent;
    	import com.greensock.TweenLite;
    	import com.greensock.plugins.TweenPlugin;
    	import com.greensock.plugins.TintPlugin;
    
    	public class OverButton extends DockButton{
    	}
    }
    

    OK, so we’re extending DockButton this time and we’ve imported the DisplayObject class because we will treat the icon as a DisplayObject. We’ve also imported MouseEvent which we’ll use to check when the mouse is over the icon and when it’s out. We also have TweenLite to create some tween effects with the color.


    Step 22: OverButton Constructor

    package{
    
    	import org.effects.DockButton;
    	import flash.display.DisplayObject;
    	import flash.events.MouseEvent;
    	import com.greensock.TweenLite;
    	import com.greensock.plugins.TweenPlugin;
    	import com.greensock.plugins.TintPlugin;
    
    	public class OverButton extends DockButton{
    
    		private var _object:DisplayObject;
    
    		public function OverButton():void{
    			_object=this.getChildAt(0) as DisplayObject;
    			this.addEventListener(MouseEvent.MOUSE_OVER, mouseOver);
    			this.addEventListener(MouseEvent.MOUSE_OUT, mouseOut);
    			TweenPlugin.activate([TintPlugin]);
    		}
    	}
    }
    

    Why have we created a private var _object as DisplayObject? Our actual icon is stored in this variable (that’s what line 13 does) and is treated as a DisplayObject; we will use the color effect on our icon, not in the whole object.

    We add the event listeners of the mouse to check when the mouse is over and when the mouse is out.


    Step 23: Coding Mouse Functions

    Since we have created the listeners for mouse over and mouse out, we will now create their functions:

    package{
    
    	import org.effects.DockButton;
    	import flash.display.DisplayObject;
    	import flash.events.MouseEvent;
    	import com.greensock.TweenLite;
    	import com.greensock.plugins.TweenPlugin;
    	import com.greensock.plugins.TintPlugin;
    
    	public class OverButton extends DockButton{
    
    		private var _object:DisplayObject;
    
    		public function OverButton():void{
    			_object=this.getChildAt(0) as DisplayObject;
    			this.addEventListener(MouseEvent.MOUSE_OVER, mouseOver);
    			this.addEventListener(MouseEvent.MOUSE_OUT, mouseOut);
    			TweenPlugin.activate([TintPlugin]);
    		}  
    
    		private function mouseOver(e:MouseEvent):void{
    			new TweenLite(_object,.5,{tint:0x990099});
    		}
    
    		private function mouseOut(e:MouseEvent):void{
    			new TweenLite(_object,.5,{tint:null});
    		}
    	}
    }
    

    Note that we are using the TweenLite on _object now, not on “this” any more. That’s because the OverButton extends the DockButton which extends the DockItem where there is already a TweenLite being used. Also, in DockButton we have a fake alpha 0 background that doesn’t need to be painted.

    For the tint property of TweenLite I used a color code of 0×990099, which is a medium purple; if you use null as the value the tint will be removed softly.


    Step 24: Change the Base Classes

    At this point if you test the movie, you won’t see any color change, because we need to change the base class of each object in the library again. Open the Library once more in the .fla (Window > Library). Right-click each object and change its base class to OverButton (not org.effects.OverButton, because the class file is not in the /org/effects folder).

    OK, now you can test it!

    Conclusion

    In this tutorial I’ve explained about extending objects. The actual dock effect is pure math – it’s distance calculations, scale settings – but it’s important we see in the code that we cant use the “x” property as position reference, because the “x” property is changed every time. I hope now you all have a better understanding of the “extends” keyword, and can appreciate how the calculations are done here. Thanks for reading :)


  3. Christer Kaitila says:
    March 11, 2012 at 8:57 pm

    In this tutorial series (part free, part Premium) we’ll create a high-performance 2D shoot-em-up using the new hardware-accelerated Stage3D rendering engine. We will be taking advantage of several hardcore optimization techniques to achieve great 2D sprite rendering performance. In this part, we’ll build a high-performance demo that draws hundreds of moving sprites on-screen at once.


    Final Result Preview

    Let’s take a look at the final result we will be working towards: a high-performance 2D sprite demo that uses Stage3D with optimizations that include a spritesheet and object pooling.


    Introduction: Flash 11 Stage3D

    If you’re hoping to take your Flash games to the next level and are looking for loads of eye-candy and amazing framerate, Stage3D is going to be your new best friend.

    The incredible speed of the new Flash 11 hardware accelerated Stage3D API is just begging to be used for 2D games. Instead of using old-fashioned Flash sprites on the DisplayList or last-gen blitting techniques as popularized by engines such as FlashPunk and Flixel, the new breed of 2D games uses the power of your video card’s GPU to blaze through rendering tasks at up to 1000x the speed of anything Flash 10 could manage.

    Although it has 3D in its name, this new API is also great for 2D games. We can render simple geometry in the form of 2D squares (called quads) and draw them on a flat plane. This will enable us to render tons of sprites on screen at a silky-smooth 60fps.

    We’ll make a side-scrolling shooter inspired by retro arcade titles such as R-Type or Gradius in ActionScript using Flash 11′s Stage3D API. It isn’t half as hard as some people say it is, and you won’t need to learn assembly language AGAL opcodes.

    In this 6-part tutorial series, we are going to program a simple 2D shoot-’em-up that delivers mind-blowing rendering performance. We are going to build it using pure AS3, compiled in FlashDevelop (read more about it here). FlashDevelop is great because it is 100% freeware – no need to buy any expensive tools to get the best AS3 IDE around.


    Step 1: Create a New Project

    If you don’t already have it, be sure to download and install FlashDevelop. Once you’re all set up (and you’ve allowed it to install the latest version of the Flex compiler automatically), fire it up and start a new “AS3 Project.”

    Create an .AS3 project using FlashDevelop

    FlashDevelop will create a blank template project for you. We’re going to fill in the blanks, piece-by-piece, until we have created a decent game.


    Step 2: Target Flash 11

    Go into the project menu and change a few options:

    1. Target Flash 11.1
    2. Change the size to 600x400px
    3. Change the background color to black
    4. Change the FPS to 60
    5. Change the SWF filename to a name of your choosing
    Project properties

    Step 3: Imports

    Now that our blank project is set up, let’s dive in and do some coding. To begin with, we will need to import all the Stage3D functionality required. Add the following to the very top of your Main.as file.

    
    
    // Stage3D Shoot-em-up Tutorial Part 1
    // by Christer Kaitila - http://www.mcfunkypants.com
    // Created for active.tutsplus.com
    
    package
    {
    	[SWF(width = "600", height = "400", frameRate = "60", backgroundColor = "#000000")]
    
    	import flash.display3D.*;
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageQuality;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	import flash.events.ErrorEvent;
    	import flash.geom.Rectangle;
    	import flash.utils.getTimer;
    

    Step 4: Initialize Stage3D

    The next step is to wait for our game to appear on the Flash stage. Doing things this way allows for the future use of a preloader. For simplicity, we will be doing most of our game in a single little class that inherits from the Flash Sprite class as follows.

    
    
    public class Main extends Sprite
    {
    private var _entities : EntityManager;
    private var _spriteStage : LiteSpriteStage;
    private var _gui : GameGUI;
    private var _width : Number = 600;
    private var _height : Number = 400;
    public var context3D : Context3D;
    
    // constructor function for our game
    public function Main():void
    {
    	if (stage) init();
    	else addEventListener(Event.ADDED_TO_STAGE, init);
    }
    
    // called once Flash is ready
    private function init(e:Event = null):void
    {
    	removeEventListener(Event.ADDED_TO_STAGE, init);
    	stage.quality = StageQuality.LOW;
    	stage.align = StageAlign.TOP_LEFT;
    	stage.scaleMode = StageScaleMode.NO_SCALE;
    	stage.addEventListener(Event.RESIZE, onResizeEvent);
    	trace("Init Stage3D...");
    	_gui = new GameGUI("Simple Stage3D Sprite Demo v1");
    	addChild(_gui);
    	stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
    	stage.stage3Ds[0].addEventListener(ErrorEvent.ERROR, errorHandler);
    	stage.stage3Ds[0].requestContext3D(Context3DRenderMode.AUTO);
    	trace("Stage3D requested...");
    }
    

    After setting some stage-specific properties, we request a Stage3D context. This can take a while (a fraction of a second) as your video card is configured for hardware rendering, so we need to wait for the onContext3DCreate event.

    We also want to detect any errors that may occur, especially since Stage3D content does not run if the HTML embed code that loads your SWF doesn’t include the parameter "wmode=direct". These errors can also happen if the user is running an old version of Flash or if they don’t have a video card capable of handling pixel shader 2.0.


    Step 5: Handle Any Events

    Add the following functions that detect any events that might be triggered as specified above. In the case of errors due to running old Flash plugins, in future versions of this game we might want to output a message and remind the user to upgrade, but for now this error is simply ignored.

    For users with old video cards (or drivers) that don’t support shader model 2.0, the good news is that Flash 11 is smart enough to provide a software renderer. It doesn’t run very fast but at least everyone will be able to play your game. Those with decent gaming rigs will get fantastic framerate like you’ve never seen in a Flash game before.

    
    
    // this is called when the 3d card has been set up
    // and is ready for rendering using stage3d
    private function onContext3DCreate(e:Event):void
    {
    	trace("Stage3D context created! Init sprite engine...");
    	context3D = stage.stage3Ds[0].context3D;
    	initSpriteEngine();
    }
    
    // this can be called when using an old version of Flash
    // or if the html does not include wmode=direct
    private function errorHandler(e:ErrorEvent):void
    {
    	trace("Error while setting up Stage3D: "+e.errorID+" - " +e.text);
    }
    
    protected function onResizeEvent(event:Event) : void
    {
    	trace("resize event...");
    
    	// Set correct dimensions if we resize
    	_width = stage.stageWidth;
    	_height = stage.stageHeight;
    
    	// Resize Stage3D to continue to fit screen
    	var view:Rectangle = new Rectangle(0, 0, _width, _height);
    	if ( _spriteStage != null ) {
    		_spriteStage.position = view;
    	}
    	if(_entities != null) {
    		_entities.setPosition(view);
    	}
    }
    

    The event handling code above detects when Stage3D is ready for hardware rendering and sets the variable context3D for future use. Errors are ignored for now. The resize event simply updates the size of the stage and batch rendering system dimensions.


    Step 6: Init the Sprite Engine

    Once the context3D has been received, we are ready to start the game running. Continuing with Main.as, add the following.

    
    
    private function initSpriteEngine():void
    {
    	// init a gpu sprite system
    	var stageRect:Rectangle = new Rectangle(0, 0, _width, _height);
    	_spriteStage = new LiteSpriteStage(stage.stage3Ds[0], context3D, stageRect);
    	_spriteStage.configureBackBuffer(_width,_height);
    
    	// create a single rendering batch
    	// which will draw all sprites in one pass
    	var view:Rectangle = new Rectangle(0,0,_width,_height)
    	_entities = new EntityManager(stageRect);
    	_entities.createBatch(context3D);
    	_spriteStage.addBatch(_entities._batch);
    	// add the first entity right now
    	_entities.addEntity();
    
    	// tell the gui where to grab statistics from
    	_gui.statsTarget = _entities; 
    
    	// start the render loop
    	stage.addEventListener(Event.ENTER_FRAME,onEnterFrame);
    }
    

    This function creates a sprite rendering engine (to be implemented below) on the stage, ready to use the full size of your flash file. We then add the entity manager and batched geometry system (which we will discuss below). We are now able to give a reference to the entity manager to our stats GUI class so that it can display some numbers on screen regarding how many sprites have been created or reused. Lastly, we start listening for the ENTER_FRAME event, which will begin firing at a rate of up to 60 times per second.


    Step 7: Start the Render Loop

    Now that everything has been initialized, we are ready to play! The following function will be executed every single frame. For the purposes of this first tech demo, we are going to add one new sprite on stage each frame. Because we are going to implement an object pool (which you can read more about in this tutorial) instead of inifinitely creating new objects until we run out of RAM, we are going to be able to reuse old entities that have moved off screen.

    After spawning another sprite, we clear the stage3D area of the screen (setting it to pure black). Next we update all the entities that are being controlled by our entity manager. This will move them a little more each frame. Once all sprites have been updated, we tell the batched geometry system to gather them all up into one large vertex buffer and bast them on screen in a single draw call, for efficiency. Finally, we tell the context3D to update the screen with our final render.

    
    
    		// this function draws the scene every frame
    		private function onEnterFrame(e:Event):void
    		{
    			try
    			{
    				// keep adding more sprites - FOREVER!
    				// this is a test of the entity manager's
    				// object reuse "pool"
    				_entities.addEntity();
    
    				// erase the previous frame
    				context3D.clear(0, 0, 0, 1);
    
    				// move/animate all entities
    				_entities.update(getTimer());
    
    				// draw all entities
    				_spriteStage.render();
    
    				// update the screen
    				context3D.present();
    			}
    			catch (e:Error)
    			{
    				// this can happen if the computer goes to sleep and
    				// then re-awakens, requiring reinitialization of stage3D
    				// (the onContext3DCreate will fire again)
    			}
    		}
    	} // end class
    } // end package
    

    That’s it for the inits! As simple as it sounds, we have now created a template project that is ready to blast out an insane number of sprites. We are not going to use any vector art. We aren’t going to put any old-fashioned Flash sprites on the stage apart from the Stage3D window and a couple of GUI overlays. All the work of rendering our in-game graphics is going to be handled by Stage3D, so that we can enjoy improved performance.


    Going Deeper: Why Is Stage3D So Fast?

    Two reasons:

    1. It uses hardware acceleration, meaning that all drawing commands are sent to the 3D GPU on your video card in the same way that XBOX360 and PlayStation3 games get rendered.
    2. These rendering commands are processed in parallel to the rest of your ActionScript code. This means that once the commands are sent to your video card, all rendering is done at the same time as other code in your game is running – Flash doesn’t have to wait for them to be finished. While pixels are being blasted onto your screen, Flash gets to do other things like handle the player input, play sounds and update enemy positions.
    3. That said, many Stage3D engines seem to get bogged down by a few hundred sprites. This is because they have been programmed without regard to the overhead that each draw command adds. When Stage3D first came out, some of the first 2D engines would draw each and every sprite individually in one giant (slow and inefficient) loop. Since this article is all about extreme optimization for a next-gen 2D game with fabulous framerate, we are going to implement an extremely efficient rendering system that buffers all geometry into one big batch so we can draw everything in only one or two commands.


      How to Be Hardcore: Optimize!

      Hardcore gamedevs love optimizations. In order to blast the most sprites on screen with the fewest number of state changes (such as switching textures, selecting a new vertex buffer, or having to update the transform once for each and every sprite on screen), we are going to take advantage of the following three performance optimizations:

    1. object pooling
    2. spritesheet (texture atlas)
    3. batched geometry

    These three hardcore gamedev tricks are the key to getting awesome FPS in your game. Let’s implement them now. Before we do, we need to create some of the tiny classes that these techniques will make use of.


    Step 8: The Stats Display

    If we’re going to be doing tons of optimizations and using Stage3D in an attempt to achieve blazingly fast rendering performance, we need a way to keep track of the statistics. A few little benchmarks can go a long way to prove that what we’re doing is having a positive effect on the framerate. Before we go farther, create a new class called GameGUI.as and implement a super-simple FPS and stats display as follows.

    
    
    // Stage3D Shoot-em-up Tutorial Part 1
    // by Christer Kaitila - http://www.mcfunkypants.com
    
    // GameGUI.as
    // A typical simplistic framerate display for benchmarking performance,
    // plus a way to track rendering statistics from the entity manager.
    
    package
    {
    	import flash.events.Event;
    	import flash.events.TimerEvent;
    	import flash.text.TextField;
    	import flash.text.TextFormat;
    	import flash.utils.getTimer;
    
    	public class GameGUI extends TextField
    	{
    		public var titleText : String = "";
    		public var statsText : String = "";
    		public var statsTarget : EntityManager;
    		private var frameCount:int = 0;
    		private var timer:int;
    		private var ms_prev:int;
    		private var lastfps : Number = 60;
    
    		public function GameGUI(title:String = "", inX:Number=8, inY:Number=8, inCol:int = 0xFFFFFF)
    		{
    			super();
    			titleText = title;
    			x = inX;
    			y = inY;
    			width = 500;
    			selectable = false;
    			defaultTextFormat = new TextFormat("_sans", 9, 0, true);
    			text = "";
    			textColor = inCol;
    			this.addEventListener(Event.ADDED_TO_STAGE, onAddedHandler);
    
    		}
    		public function onAddedHandler(e:Event):void {
    			stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
    		}
    
    		private function onEnterFrame(evt:Event):void
    		{
    			timer = getTimer();
    
    			if( timer - 1000 > ms_prev )
    			{
    				lastfps = Math.round(frameCount/(timer-ms_prev)*1000);
    				ms_prev = timer;
    
    				// grab the stats from the entity manager
    				if (statsTarget)
    				{
    					statsText =
    						statsTarget.numCreated + ' created ' +
    						statsTarget.numReused + ' reused';
    				}
    
    				text = titleText + ' - ' + statsText + " - FPS: " + lastfps;
    				frameCount = 0;
    			}
    
    			// count each frame to determine the framerate
    			frameCount++;
    
    		}
    	} // end class
    } // end package
    

    Step 9: The Entity Class

    We are about to implement an entity manager class that will be the “object pool” as described above. We first need to create a simplistic class for each individual entity in our game. This class will be used for all in-game objects, from spaceships to bullets.

    Create a new file called Entity.as and add a few getters and setters now. For this first tech demo, this class is merely an empty placeholder without much functionality, but in later tutorials this is where we will be implementing much of the gameplay.

    
    
    // Stage3D Shoot-em-up Tutorial Part 1
    // by Christer Kaitila - http://www.mcfunkypants.com
    
    // Entity.as
    // The Entity class will eventually hold all game-specific entity logic
    // for the spaceships, bullets and effects in our game. For now,
    // it simply holds a reference to a gpu sprite and a few demo properties.
    // This is where you would add hit points, weapons, ability scores, etc.
    
    package
    {
    	public class Entity
    	{
    		private var _speedX : Number;
    		private var _speedY : Number;
    		private var _sprite : LiteSprite;
    		public var active : Boolean = true;
    
    		public function Entity(gs:LiteSprite = null)
    		{
    			_sprite = gs;
    			_speedX = 0.0;
    			_speedY = 0.0;
    		}
    		public function die() : void
    		{
    			// allow this entity to be reused by the entitymanager
    			active = false;
    			// skip all drawing and updating
    			sprite.visible = false;
    		}
    		public function get speedX() : Number
    		{
    			return _speedX;
    		}
    		public function set speedX(sx:Number) : void
    		{
    			_speedX = sx;
    		}
    		public function get speedY() : Number
    		{
    			return _speedY;
    		}
    		public function set speedY(sy:Number) : void
    		{
    			_speedY = sy;
    		}
    		public function get sprite():LiteSprite
    		{
    			return _sprite;
    		}
    		public function set sprite(gs:LiteSprite):void
    		{
    			_sprite = gs;
    		}
    	} // end class
    } // end package
    

    Step 10: Make a Spritesheet

    An important optimization technique we are going to use is the use of a spritesheet – sometimes referred to as a Texture Atlas. Instead of uploading dozens or hundreds of individual images to video RAM for use during rendering, we are going to make a single image that holds all the sprites in our game. This way, we can use a single texture to draw tons of different kinds of enemies or terrain.

    Using a spritesheet is a considered a best practice by veteran gamedevs who need to ensure their games run as fast as possible. The reason it speeds things up so much is much the same as the reason why we are going to use geometry batching: instead of having to tell the video card over and over to use a particular texture to draw a particular sprite, we can simply tell it to always use the same texture for all draw calls.

    This cuts down on “state changes” which are extremely costly in terms of time. We no longer need to say “video card, start using texture 24… now draw sprite 14″ and so on. We just say “draw everything using this one texture” in a single pass. This can increase performance by an order of magnitude.

    For our example game we will be using a collection of legal-to-use freeware images by the talented DanC, which you can get here. Remember that if you use these images you should credit them in your game as follows: “Art Collection Title” art by Daniel Cook (Lostgarden.com).

    Using Photoshop (or GIMP, or whatever image editor you prefer), cut and paste the sprites your game will need into a single PNG file that has a transparent background. Place each sprite on an evenly-spaced grid with a couple pixels of blank space between each. This small buffer is required to avoid any “bleeding” of edge pixels from adjacent sprites that can occur due to bilinear texture filtering that happens on the GPU. If each sprite is touching the next, your in-game sprites may have unwanted edges where they should be completely transparent.

    For optimization reasons, GPUs work best with images (called textures) that are square and whose dimensions are equal to a power of two and evenly divisible by eight. Why? Because of the way that the pixel data is accessed, these magic numbers happen to align in VRAM in just the right way to be fastest to access, because the data is often read in chunks.

    Therefore, ensure that your spritesheet is either 64×64, 128×128, 256×256, 512×512 or 1024×1024. As you might expect, the smaller the better – not just in terms of performance but because a smaller texture will naturally keep your game’s final SWF smaller.

    Here is the spritesheet that we will be using for our example. “Tyrian” art by Daniel Cook (Lostgarden.com).

    The spritesheet image
    Right-click to download

    Step 11: The Entity Manager

    The first optimization technique we’re going to take advantage of to achieve blazing performance is the use of “object pools”. Instead of constantly allocating more ram for objects like bullets or enemies, we’re going to make a reuse pool that recycles unused sprites over and over again.

    This technique ensures that RAM use stays very low and GC (garbage collection) hiccups rarely occur. The result is that framerate will be higher and your game will run smoothly no matter how long you play.

    Create a new class in your project called EntityManager.as and implement a simple recycle-on-demand mechanism as follows.

    
    
    // Stage3D Shoot-em-up Tutorial Part 1
    // by Christer Kaitila - http://www.mcfunkypants.com
    
    // EntityManager.as
    // The entity manager handles a list of all known game entities.
    // This object pool will allow for reuse (respawning) of
    // sprites: for example, when enemy ships are destroyed,
    // they will be re-spawned when needed as an optimization
    // that increases fps and decreases ram use.
    // This is where you would add all in-game simulation steps,
    // such as gravity, movement, collision detection and more.
    
    package
    {
    	import flash.display.Bitmap;
    	import flash.display3D.*;
    	import flash.geom.Point;
    	import flash.geom.Rectangle;
    
    	public class EntityManager
    	{
    		// the sprite sheet image
    		private var _spriteSheet : LiteSpriteSheet;
    		private const SpritesPerRow:int = 8;
    		private const SpritesPerCol:int = 8;
    		[Embed(source="../assets/sprites.png")]
    		private var SourceImage : Class;
    
    		// a reusable pool of entities
    		private var _entityPool : Vector.<Entity>;
    
    		// all the polygons that make up the scene
    		public var _batch : LiteSpriteBatch;
    
    		// for statistics
    		public var numCreated : int = 0;
    		public var numReused : int = 0;
    
    		private var maxX:int;
    		private var minX:int;
    		private var maxY:int;
    		private var minY:int;
    
    		public function EntityManager(view:Rectangle)
    		{
    			_entityPool = new Vector.<Entity>();
    			setPosition(view);
    		}
    

    Step 12: Set Boundaries

    Our entity manager is going to recycle entities when they move off the left edge of the screen. The function below is called during inits or when the resize event is fired. We add a few extra pixels to the edges so that sprites don’t suddenly pop in or out of existence.

    
    
    public function setPosition(view:Rectangle):void
    {
    	// allow moving fully offscreen before looping around
    	maxX = view.width + 32;
    	minX = view.x - 32;
    	maxY = view.height;
    	minY = view.y;
    }
    

    Step 13: Set Up the Sprites

    The entity manager runs this function once at startup. It creates a new geometry batch using the spritesheet image that was embedded in our code above. It sends the bitmapData to the spritesheet class constructor, which will be used to generate a texture that has all the available sprite images on it in a grid. We tell our spritesheet that we’re going to use 64 different sprites (8 by 8) on the one texture. This spritesheet will be used by the batch geometry renderer.

    If we wanted, we could use more than one spritesheet, by initializing additional images and batches as required. In the future, this might be where you create a second batch for all terrain tiles that go underneath your spaceship sprites. You could even implement a third batch which is layered on top of everything for fancy particle effects and eye candy. For now, this simple tech demo only needs a single spritesheet texture and geometry batch.

    
    
    
    		public function createBatch(context3D:Context3D) : LiteSpriteBatch
    		{
    			var sourceBitmap:Bitmap = new SourceImage();
    
    			// create a spritesheet with 8x8 (64) sprites on it
    			_spriteSheet = new LiteSpriteSheet(sourceBitmap.bitmapData, 8, 8);
    
    			// Create new render batch
    			_batch = new LiteSpriteBatch(context3D, _spriteSheet);
    
    			return _batch;
    		}
    

    Step 14: The Object Pool

    This is where the entity manager increases performance. This one optimization (an object reuse pool) will allow us to only create new entities on demand (when there aren’t any inactive ones that can be reused). Note how we reuse any sprites that are currently marked as inactive, unless they are all currently being used, in which case we spawn a new one. This way, our object pool only every holds as many sprites as are even visible at the same time. After the first few seconds that our game has been running, the entity pool will remain constant – rarely will a new entity need to be created once there are enough to handle what’s going on on-screen.

    Continue adding to EntityManager.as as follows:

    
    
    		// search the entity pool for unused entities and reuse one
    		// if they are all in use, create a brand new one
    		public function respawn(sprID:uint=0):Entity
    		{
    			var currentEntityCount:int = _entityPool.length;
    			var anEntity:Entity;
    			var i:int = 0;
    			// search for an inactive entity
    			for (i = 0; i < currentEntityCount; i++ )
    			{
    				anEntity = _entityPool[i];
    				if (!anEntity.active && (anEntity.sprite.spriteId == sprID))
    				{
    					//trace('Reusing Entity #' + i);
    					anEntity.active = true;
    					anEntity.sprite.visible = true;
    					numReused++;
    					return anEntity;
    				}
    			}
    			// none were found so we need to make a new one
    			//trace('Need to create a new Entity #' + i);
    			var sprite:LiteSprite;
    			sprite = _batch.createChild(sprID);
    			anEntity = new Entity(sprite);
    			_entityPool.push(anEntity);
    			numCreated++;
    			return anEntity;
    		}
    
    		// for this test, create random entities that move
    		// from right to left with random speeds and scales
    		public function addEntity():void
    		{
    			var anEntity:Entity;
    			var randomSpriteID:uint = Math.floor(Math.random() * 64);
    			// try to reuse an inactive entity (or create a new one)
    			anEntity = respawn(randomSpriteID);
    			// give it a new position and velocity
    			anEntity.sprite.position.x = maxX;
    			anEntity.sprite.position.y = Math.random() * maxY;
    			anEntity.speedX = (-1 * Math.random() * 10) - 2;
    			anEntity.speedY = (Math.random() * 5) - 2.5;
    			anEntity.sprite.scaleX = 0.5 + Math.random() * 1.5;
    			anEntity.sprite.scaleY = anEntity.sprite.scaleX;
    			anEntity.sprite.rotation = 15 - Math.random() * 30;
    		}
    

    The functions above are run whenever a new sprite needs to be added on screen. The entity manager scans the entity pool for one that is currently not in use and returns it when possible. If the list is fully of active entities, a brand new one needs to be created.


    Step 15: Simulate!

    The final function that is the responsibility of our entity manager is the one that gets called every frame. It is used to do any simulation, AI, collision detection, physics or animation as required. For the current simplistic tech demo, it simply loops through the list of active entities in the pool and updates their positions based on velocity. Each entity is moved according to their current velocity. Just for fun, they are set to spin a little each frame as well.

    Any entity that goes past the left side of the screen is “killed” and is marked as inactive and invisible, ready to be reused in the functions above. If an entity touches the other three screen edges, the velocity is reversed so it will “bounce” off that edge. Continue adding to EntityManager.as as follows:

    
    
    		// called every frame: used to update the simulation
    		// this is where you would perform AI, physics, etc.
    		public function update(currentTime:Number) : void
    		{
    			var anEntity:Entity;
    			for(var i:int=0; i<_entityPool.length;i++)
    			{
    				anEntity = _entityPool[i];
    				if (anEntity.active)
    				{
    					anEntity.sprite.position.x += anEntity.speedX;
    					anEntity.sprite.position.y += anEntity.speedY;
    					anEntity.sprite.rotation += 0.1;
    
    					if (anEntity.sprite.position.x > maxX)
    					{
    						anEntity.speedX *= -1;
    						anEntity.sprite.position.x = maxX;
    					}
    					else if (anEntity.sprite.position.x < minX)
    					{
    						// if we go past the left edge, become inactive
    						// so the sprite can be respawned
    						anEntity.die();
    					}
    					if (anEntity.sprite.position.y > maxY)
    					{
    						anEntity.speedY *= -1;
    						anEntity.sprite.position.y = maxY;
    					}
    					else if (anEntity.sprite.position.y < minY)
    					{
    						anEntity.speedY *= -1;
    						anEntity.sprite.position.y = minY;
    					}
    				}
    			}
    		}
    	} // end class
    } // end package
    

    Step 16: The Sprite Class

    The final step to get everything up and running is to implement the four classes that make up our “rendering engine” system. Because the word Sprite is already in use in Flash, the next few classes will use the term LiteSprite, which is not just a catchy name but implies the lightweight and simplistic nature of this engine.

    To begin, we will create the simple 2D sprite class that our entity class above refers to. There will be many sprites in our game, each of which is collected into a large batch of polygons and rendered in a single pass.

    Create a new file in your project called LiteSprite.as and implement some getters and setters as follows. We could probably get away with simply using public variables, but in future versions changing some of these values will require running some code first, so this technique will prove invaluable.

    
    
    // Stage3D Shoot-em-up Tutorial Part 1
    // by Christer Kaitila - http://www.mcfunkypants.com
    
    // LiteSprite.as
    // A 2d sprite that is rendered by Stage3D as a textured quad
    // (two triangles) to take advantage of hardware acceleration.
    // Based on example code by Chris Nuuja which is a port
    // of the haXe+NME bunnymark demo by Philippe Elsass
    // which is itself a port of Iain Lobb's original work.
    // Also includes code from the Starling framework.
    // Grateful acknowledgements to all involved.
    
    package
    {
        import flash.geom.Point;
        import flash.geom.Rectangle;
    
        public class LiteSprite
        {
            internal var _parent : LiteSpriteBatch;
            internal var _spriteId : uint;
            internal var _childId : uint;
            private var _pos : Point;
            private var _visible : Boolean;
            private var _scaleX : Number;
            private var _scaleY : Number;
            private var _rotation : Number;
            private var _alpha : Number;
    
            public function get visible() : Boolean
            {
                return _visible;
            }
            public function set visible(isVisible:Boolean) : void
            {
                _visible = isVisible;
            }
    		public function get alpha() : Number
    		{
    			return _alpha;
    		}
    		public function set alpha(a:Number) : void
    		{
    			_alpha = a;
    		}
            public function get position() : Point
            {
                return _pos;
            }
            public function set position(pt:Point) : void
            {
                _pos = pt;
            }
            public function get scaleX() : Number
            {
                return _scaleX;
            }
            public function set scaleX(val:Number) : void
            {
                _scaleX = val;
            }
            public function get scaleY() : Number
            {
                return _scaleY;
            }
            public function set scaleY(val:Number) : void
            {
                _scaleY = val;
            }
            public function get rotation() : Number
            {
                return _rotation;
            }
            public function set rotation(val:Number) : void
            {
                _rotation = val;
            }
            public function get rect() : Rectangle
            {
                return _parent._sprites.getRect(_spriteId);
            }
            public function get parent() : LiteSpriteBatch
            {
                return _parent;
            }
            public function get spriteId() : uint
            {
                return _spriteId;
            }
            public function set spriteId(num : uint) : void
            {
                _spriteId = num;
            }
            public function get childId() : uint
            {
                return _childId;
            }
    
            // LiteSprites are typically constructed by calling LiteSpriteBatch.createChild()
            public function LiteSprite()
            {
                _parent = null;
                _spriteId = 0;
                _childId = 0;
                _pos = new Point();
                _scaleX = 1.0;
                _scaleY = 1.0;
                _rotation = 0;
                _alpha = 1.0;
                _visible = true;
            }
        } // end class
    } // end package
    

    Each sprite can now keep track of where it is on screen, as well as how big it is, how transparent, and what angle it is facing. The spriteID property is a number used during rendering to look up which UV (texture) coordinate needs to be used as the source rectangle for the pixels of the spritesheet image it uses.


    Step 17: The Spritesheet Class

    We now need to implement a mechanism to process the spritesheet image that we embedded above and use portions of it on all our rendered geometry. Create a new file in your project called LiteSpriteSheet.as and begin by importing the functionality required, defining a few class variables and a constructor function.

    
    
    // Stage3D Shoot-em-up Tutorial Part 1
    // by Christer Kaitila - http://www.mcfunkypants.com
    
    // LiteSpriteSheet.as
    // An optimization used to improve performance, all sprites used
    // in the game are packed onto a single texture so that
    // they can be rendered in a single pass rather than individually.
    // This also avoids the performance penalty of 3d stage changes.
    // Based on example code by Chris Nuuja which is a port
    // of the haXe+NME bunnymark demo by Philippe Elsass
    // which is itself a port of Iain Lobb's original work.
    // Also includes code from the Starling framework.
    // Grateful acknowledgements to all involved.
    
    package
    {
        import flash.display.Bitmap;
        import flash.display.BitmapData;
        import flash.display.Stage;
        import flash.display3D.Context3D;
        import flash.display3D.Context3DTextureFormat;
        import flash.display3D.IndexBuffer3D;
        import flash.display3D.textures.Texture;
        import flash.geom.Point;
        import flash.geom.Rectangle;
        import flash.geom.Matrix;
    
        public class LiteSpriteSheet
        {
            internal var _texture : Texture;
    
            protected var _spriteSheet : BitmapData;
            protected var _uvCoords : Vector.<Number>;
            protected var _rects : Vector.<Rectangle>;
    
            public function LiteSpriteSheet(SpriteSheetBitmapData:BitmapData, numSpritesW:int = 8, numSpritesH:int = 8)         {
                _uvCoords = new Vector.<Number>();
                _rects = new Vector.<Rectangle>();
    			_spriteSheet = SpriteSheetBitmapData;
    			createUVs(numSpritesW, numSpritesH);
    		}
    

    The class constructor above is given a BitmapData for our spritesheet as well as the number of sprites that are on it (in this demo, 64).


    Step 18: Chop It Up

    Because we are using a single texture to store all of the sprite images, we need to divide the image into several parts (one for each sprite on it) when rendering. We do this by assigning different coordinates for each vertex (corner) of each quad mesh used to draw a sprite.

    These coordinates are called UVs, and each goes from 0 to 1 and represents where on the texture stage3D should start sampling pixels when rendering. The UV coordinates and pixel rectangles are stored in an array for later using during rendering so that we don't have to calculate them every frame. We also store the size and shape of each sprite (which in this demo are all identical) so that when we rotate a sprite we know its radius (which is used to keep the pivot in the very centre of the sprite).

    
    
    
            // generate a list of uv coordinates for a grid of sprites
    		// on the spritesheet texture for later reference by ID number
    		// sprite ID numbers go from left to right then down
    		public function createUVs(numSpritesW:int, numSpritesH:int) : void
    		{
    			trace('creating a '+_spriteSheet.width+'x'+_spriteSheet.height+
    				' spritesheet texture with '+numSpritesW+'x'+ numSpritesH+' sprites.');
    
    			var destRect : Rectangle;
    
    			for (var y:int = 0; y < numSpritesH; y++)
    			{
    				for (var x:int = 0; x < numSpritesW; x++)
    				{
    					_uvCoords.push(
    						// bl, tl, tr, br
    						x / numSpritesW, (y+1) / numSpritesH,
    						x / numSpritesW, y / numSpritesH,
    						(x+1) / numSpritesW, y / numSpritesH,
    						(x + 1) / numSpritesW, (y + 1) / numSpritesH);
    
    					    destRect = new Rectangle();
    						destRect.left = 0;
    						destRect.top = 0;
    						destRect.right = _spriteSheet.width / numSpritesW;
    						destRect.bottom = _spriteSheet.height / numSpritesH;
    						_rects.push(destRect);
    				}
    			}
            }
    
            public function removeSprite(spriteId:uint) : void
            {
                if ( spriteId < _uvCoords.length ) {
                    _uvCoords = _uvCoords.splice(spriteId * 8, 8);
                    _rects.splice(spriteId, 1);
                }
            }
    
            public function get numSprites() : uint
            {
                return _rects.length;
            }
    
            public function getRect(spriteId:uint) : Rectangle
            {
                return _rects[spriteId];
            }
    
            public function getUVCoords(spriteId:uint) : Vector.<Number>
            {
                var startIdx:uint = spriteId * 8;
                return _uvCoords.slice(startIdx, startIdx + 8);
            }
    

    Step 19: Generate Mipmaps

    Now we need to process this image during the init. We are going to upload it for use as a texture by your GPU. As we do so, we are going to create smaller copies that are called "mipmaps". Mip-mapping is used by 3d hardware to further speed up rendering by using smaller versions of the same texture whenever it is seen from far away (scaled down) or, in true 3D games, when it is being viewed at an oblique angle. This avoids any "moiree" effects (flickers) than can happen if mipmapping is not used. Each mipmap is half the width and height as the previous.

    Continuing with LiteSpriteSheet.as, let's implement the routine we need that will generate mipmaps and upload them all to the GPU on your video card.

    
    
            public function uploadTexture(context3D:Context3D) : void
            {
                if ( _texture == null ) {
                    _texture = context3D.createTexture(_spriteSheet.width, _spriteSheet.height, Context3DTextureFormat.BGRA, false);
                }
    
                _texture.uploadFromBitmapData(_spriteSheet);
    
                // generate mipmaps
                var currentWidth:int = _spriteSheet.width >> 1;
                var currentHeight:int = _spriteSheet.height >> 1;
                var level:int = 1;
                var canvas:BitmapData = new BitmapData(currentWidth, currentHeight, true, 0);
                var transform:Matrix = new Matrix(.5, 0, 0, .5);
                while ( currentWidth >= 1 || currentHeight >= 1 ) {
                    canvas.fillRect(new Rectangle(0, 0, Math.max(currentWidth,1), Math.max(currentHeight,1)), 0);
                    canvas.draw(_spriteSheet, transform, null, null, null, true);
                    _texture.uploadFromBitmapData(canvas, level++);
                    transform.scale(0.5, 0.5);
                    currentWidth = currentWidth >> 1;
                    currentHeight = currentHeight >> 1;
                }
            }
        } // end class
    } // end package
    

    Step 20: Batched Geometry

    The final hardcore optimization we are going to implement is a batched geometry rendering system. This "batched geometry" technique is often used in particle systems. We are going to use it for everything. This way, we can tell your GPU to draw everything in one go instead of naively sending hundreds of draw commands (one for each sprite on screen).

    In order to minimize the number of draw calls and rendering everything in one go, we will be batching all game sprites into a long list of (x,y) coordinates. Essentially, the geometry batch is treated by your video hardware as a single 3D mesh. Then, once per frame, we will upload the entire buffer to Stage3D in a single function call. Doing things this way is far faster than uploading the individual coordinates of each sprite separately.

    Create a new file in your project called LiteSpriteBatch.as and begin by including all the imports for functionality it will need, the class variables it will use, and the constructor as follows:

    
    
    // Stage3D Shoot-em-up Tutorial Part 1
    // by Christer Kaitila - http://www.mcfunkypants.com
    
    // LiteSpriteBatch.as
    // An optimization used to increase performance that renders multiple
    // sprites in a single pass by grouping all polygons together,
    // allowing stage3D to treat it as a single mesh that can be
    // rendered in a single drawTriangles call.
    // Each frame, the positions of each
    // vertex is updated and re-uploaded to video ram.
    // Based on example code by Chris Nuuja which is a port
    // of the haXe+NME bunnymark demo by Philippe Elsass
    // which is itself a port of Iain Lobb's original work.
    // Also includes code from the Starling framework.
    // Grateful acknowledgements to all involved.
    
    package
    {
        import com.adobe.utils.AGALMiniAssembler;
    
        import flash.display.BitmapData;
        import flash.display3D.Context3D;
        import flash.display3D.Context3DBlendFactor;
        import flash.display3D.Context3DCompareMode;
        import flash.display3D.Context3DProgramType;
        import flash.display3D.Context3DTextureFormat;
        import flash.display3D.Context3DVertexBufferFormat;
        import flash.display3D.IndexBuffer3D;
        import flash.display3D.Program3D;
        import flash.display3D.VertexBuffer3D;
        import flash.display3D.textures.Texture;
        import flash.geom.Matrix;
        import flash.geom.Matrix3D;
        import flash.geom.Point;
        import flash.geom.Rectangle;
    
        public class LiteSpriteBatch
        {
            internal var _sprites : LiteSpriteSheet;
            internal var _verteces : Vector.<Number>;
            internal var _indeces : Vector.<uint>;
            internal var _uvs : Vector.<Number>;
    
            protected var _context3D : Context3D;
            protected var _parent : LiteSpriteStage;
            protected var _children : Vector.<LiteSprite>;
    
            protected var _indexBuffer : IndexBuffer3D;
            protected var _vertexBuffer : VertexBuffer3D;
            protected var _uvBuffer : VertexBuffer3D;
            protected var _shader : Program3D;
            protected var _updateVBOs : Boolean;
    
            public function LiteSpriteBatch(context3D:Context3D, spriteSheet:LiteSpriteSheet)
            {
                _context3D = context3D;
                _sprites = spriteSheet;
    
                _verteces = new Vector.<Number>();
                _indeces = new Vector.<uint>();
                _uvs = new Vector.<Number>();
    
                _children = new Vector.<LiteSprite>;
                _updateVBOs = true;
                setupShaders();
                updateTexture();
            }
    

    Step 21: Batch Parent and Children

    Continue by implementing getters and setters and functionality for handling the addition of any new sprites to the batch. The parent refers to the sprite stage object used by our game engine, while the children are all the sprites in this one rendering batch. When we add a child sprite, we add more data to the list of verteces (which supplies the locations on screen of that particular sprite) as well as the UV coordinates (the location on the spritesheet texture that this particular sprite is stored at). When a child sprite is added or removed from the batch, we set a boolean variable to tell our batch system that the buffers need to be re-uploaded now that they have changed.

    
    
            public function get parent() : LiteSpriteStage
            {
                return _parent;
            }
    
            public function set parent(parentStage:LiteSpriteStage) : void
            {
                _parent = parentStage;
            }
    
            public function get numChildren() : uint
            {
                return _children.length;
            }
    
            // Constructs a new child sprite and attaches it to the batch
            public function createChild(spriteId:uint) : LiteSprite
            {
                var sprite : LiteSprite = new LiteSprite();
                addChild(sprite, spriteId);
                return sprite;
            }
    
            public function addChild(sprite:LiteSprite, spriteId:uint) : void
            {
                sprite._parent = this;
                sprite._spriteId = spriteId;
    
                // Add to list of children
                sprite._childId = _children.length;
                _children.push(sprite);
    
                // Add vertex data required to draw child
                var childVertexFirstIndex:uint = (sprite._childId * 12) / 3;
                _verteces.push(0, 0, 1, 0, 0,1, 0, 0,1, 0, 0,1); // placeholders
                _indeces.push(childVertexFirstIndex, childVertexFirstIndex+1, childVertexFirstIndex+2,
    			    childVertexFirstIndex, childVertexFirstIndex+2, childVertexFirstIndex+3);
    
                var childUVCoords:Vector.<Number> = _sprites.getUVCoords(spriteId);
                _uvs.push(
                    childUVCoords[0], childUVCoords[1],
                    childUVCoords[2], childUVCoords[3],
                    childUVCoords[4], childUVCoords[5],
                    childUVCoords[6], childUVCoords[7]);
    
                _updateVBOs = true;
            }
    
            public function removeChild(child:LiteSprite) : void
            {
                var childId:uint = child._childId;
                if ( (child._parent == this) && childId < _children.length ) {
                    child._parent = null;
                    _children.splice(childId, 1);
    
                    // Update child id (index into array of children) for remaining children
                    var idx:uint;
                    for ( idx = childId; idx < _children.length; idx++ ) {
                        _children[idx]._childId = idx;
                    }
    
                    // Realign vertex data with updated list of children
                    var vertexIdx:uint = childId * 12;
                    var indexIdx:uint= childId * 6;
                    _verteces.splice(vertexIdx, 12);
                    _indeces.splice(indexIdx, 6);
                    _uvs.splice(vertexIdx, 8);
    
                    _updateVBOs = true;
                }
            }
    

    Step 22: Set Up the Shader

    A shader is a set of commands that is uploaded directly to your video card for extremely fast rendering. In Flash 11 Stage3D, you write them in a kind of assembly language called AGAL. This shader needs only be created once, at startup. You don't need to understand assembly language opcodes for this tutorial. Instead, simply implement the creation of a vertex program (which calculates the locations of your sprites on screen) and a fragment program (which calculates the color of each pixel) as follows.

    
    
            protected function setupShaders() : void
            {
                var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
                vertexShaderAssembler.assemble( Context3DProgramType.VERTEX,
                    "dp4 op.x, va0, vc0 \n"+ // transform from stream 0 to output clipspace
                    "dp4 op.y, va0, vc1 \n"+ // do the same for the y coordinate
                    "mov op.z, vc2.z    \n"+ // we don't need to change the z coordinate
                    "mov op.w, vc3.w    \n"+ // unused, but we need to output all data
                    "mov v0, va1.xy     \n"+ // copy UV coords from stream 1 to fragment program
                    "mov v0.z, va0.z    \n"  // copy alpha from stream 0 to fragment program
                );
    
                var fragmentShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
                fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT,
                    "tex ft0, v0, fs0 <2d,clamp,linear,mipnearest> \n"+ // sample the texture
                    "mul ft0, ft0, v0.zzzz\n" + // multiply by the alpha transparency
                    "mov oc, ft0 \n" // output the final pixel color
                );
    
                _shader = _context3D.createProgram();
                _shader.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode );
            }
    
            protected function updateTexture() : void
            {
                _sprites.uploadTexture(_context3D);
            }
    

    Step 23: Move the Sprites Around

    Just before being rendered, each sprite's vertex coordinates on screen will have most likely changed as the sprite moves around or rotates. The following function calculates where each vertex (corner of the geometry) needs to be. Because each quad (the square that makes up one sprite) has four vertices each, and each vertex needs an x, y and z coordinate, there are twelve values to update. As a little optimization, if the sprite is not visible we simply write zeroes into our vertex buffer to avoid doing unnecessary calculations.

    
    
            protected function updateChildVertexData(sprite:LiteSprite) : void
            {
                var childVertexIdx:uint = sprite._childId * 12;
    
                if ( sprite.visible ) {
                    var x:Number = sprite.position.x;
                    var y:Number = sprite.position.y;
                    var rect:Rectangle = sprite.rect;
                    var sinT:Number = Math.sin(sprite.rotation);
                    var cosT:Number = Math.cos(sprite.rotation);
    				var alpha:Number = sprite.alpha;
    
                    var scaledWidth:Number = rect.width * sprite.scaleX;
                    var scaledHeight:Number = rect.height * sprite.scaleY;
                    var centerX:Number = scaledWidth * 0.5;
                    var centerY:Number = scaledHeight * 0.5;
    
                    _verteces[childVertexIdx] = x - (cosT * centerX) - (sinT * (scaledHeight - centerY));
                    _verteces[childVertexIdx+1] = y - (sinT * centerX) + (cosT * (scaledHeight - centerY));
    				_verteces[childVertexIdx+2] = alpha;
    
                    _verteces[childVertexIdx+3] = x - (cosT * centerX) + (sinT * centerY);
                    _verteces[childVertexIdx+4] = y - (sinT * centerX) - (cosT * centerY);
    				_verteces[childVertexIdx+5] = alpha;
    
                    _verteces[childVertexIdx+6] = x + (cosT * (scaledWidth - centerX)) + (sinT * centerY);
                    _verteces[childVertexIdx+7] = y + (sinT * (scaledWidth - centerX)) - (cosT * centerY);
    				_verteces[childVertexIdx+8] = alpha;
    
                    _verteces[childVertexIdx+9] = x + (cosT * (scaledWidth - centerX)) - (sinT * (scaledHeight - centerY));
                    _verteces[childVertexIdx+10] = y + (sinT * (scaledWidth - centerX)) + (cosT * (scaledHeight - centerY));
    				_verteces[childVertexIdx+11] = alpha;
    
                }
                else {
                    for (var i:uint = 0; i < 12; i++ ) {
                        _verteces[childVertexIdx+i] = 0;
                    }
                }
            }
    

    Step 24: Draw the Geometry

    Finally, continue adding to the LiteSpriteBatch.as class by implementing the drawing function. This is where we tell stage3D to render all the sprites in a single pass. First, we loop through all known children (the individual sprites) and update the verterx positions based on where they are on screen. We then tell stage3D which shader and texture to use, as well as set the blend factors for rendering.

    What is a blend factor? It defines whether or not we should use transparency, and how to deal with transparent pixels on our texture. You could change the options in the setBlendFactors call to use additive blanding, for example, which looks great for particle effects like explosions, since pixels will increase the brightness on screen as they overlap. In the case of regular sprites, all we want is to draw them at the exact color as stored in our spritesheet texture and to allow transparent regions.

    The final step in our draw function is to update the UV and index buffers if the batch has changed size, and to always upload the vertex data because our sprites are exected to be constantly moving. We tell stage3D which buffers to use and finally render the entire giant list of geometry as if it were a single 3D mesh, so that it gets drawn using a single, fast, drawTriangles call.

    
    
    
            public function draw() : void
            {
                var nChildren:uint = _children.length;
                if ( nChildren == 0 ) return;
    
                // Update vertex data with current position of children
                for ( var i:uint = 0; i < nChildren; i++ ) {
                    updateChildVertexData(_children[i]);
                }
    
                _context3D.setProgram(_shader);
                _context3D.setBlendFactors(Context3DBlendFactor.ONE,
    			    Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
                _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,
    			    0, _parent.modelViewMatrix, true);
                _context3D.setTextureAt(0, _sprites._texture);
    
                if ( _updateVBOs ) {
    				_vertexBuffer = _context3D.createVertexBuffer(_verteces.length/3, 3);
    				_indexBuffer = _context3D.createIndexBuffer(_indeces.length);
    				_uvBuffer = _context3D.createVertexBuffer(_uvs.length/2, 2);
    				_indexBuffer.uploadFromVector(_indeces, 0, _indeces.length); // indices won't change
    				_uvBuffer.uploadFromVector(_uvs, 0, _uvs.length / 2); // child UVs won't change
    				_updateVBOs = false;
    			}
    
                // we want to upload the vertex data every frame
    			_vertexBuffer.uploadFromVector(_verteces, 0, _verteces.length / 3);
                _context3D.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
                _context3D.setVertexBufferAt(1, _uvBuffer, 0, Context3DVertexBufferFormat.FLOAT_2);
    
                _context3D.drawTriangles(_indexBuffer, 0,  nChildren * 2);
            }
        } // end class
    } // end package
    

    Step 25: The Sprite Stage Class

    The final class required by our fancy (and speedy) hardware-accelerated sprite rendering engine is the sprite stage class. This stage, much like the traditional Flash stage, holds a list of all the batches that are used for your game. In this first demo, our stage will only be using a single batch of sprites, which itself only uses a single spritesheet.

    Create one last file in your project called LiteSpriteStage.as and begin by creating the class as follows:

    
    
    // Stage3D Shoot-em-up Tutorial Part 1
    // by Christer Kaitila - http://www.mcfunkypants.com
    
    // LiteSpriteStage.as
    // The stage3D renderer of any number of batched geometry
    // meshes of multiple sprites. Handles stage3D inits, etc.
    // Based on example code by Chris Nuuja which is a port
    // of the haXe+NME bunnymark demo by Philippe Elsass
    // which is itself a port of Iain Lobb's original work.
    // Also includes code from the Starling framework.
    // Grateful acknowledgements to all involved.
    
    package
    {
        import flash.display.Stage3D;
        import flash.display3D.Context3D;
        import flash.geom.Matrix3D;
        import flash.geom.Rectangle;
    
        public class LiteSpriteStage
        {
            protected var _stage3D : Stage3D;
            protected var _context3D : Context3D;
            protected var _rect : Rectangle;
            protected var _batches : Vector.<LiteSpriteBatch>;
            protected var _modelViewMatrix : Matrix3D;
    
            public function LiteSpriteStage(stage3D:Stage3D, context3D:Context3D, rect:Rectangle)
            {
                _stage3D = stage3D;
                _context3D = context3D;
                _batches = new Vector.<LiteSpriteBatch>;
    
                this.position = rect;
            }
    

    Step 26: The Camera Matrix

    In order to know exactly where on screen each sprite needs to go, we will track the location and size of the rendering window. During our game's initializations (or if it changes) we create a model view matrix which is used by Stage3D to transform the internal 3D coordinates of our geometry batches to the proper on-screen locations.

    
    
    
            public function get position() : Rectangle
            {
                return _rect;
            }
    
            public function set position(rect:Rectangle) : void
            {
                _rect = rect;
                _stage3D.x = rect.x;
                _stage3D.y = rect.y;
                configureBackBuffer(rect.width, rect.height);
    
                _modelViewMatrix = new Matrix3D();
                _modelViewMatrix.appendTranslation(-rect.width/2, -rect.height/2, 0);
                _modelViewMatrix.appendScale(2.0/rect.width, -2.0/rect.height, 1);
            }
    
            internal function get modelViewMatrix() : Matrix3D
            {
                return _modelViewMatrix;
            }
    
            public function configureBackBuffer(width:uint, height:uint) : void
            {
                 _context3D.configureBackBuffer(width, height, 0, false);
            }
    

    Step 27: Handle Batches

    The final step in the creation of our Stage3D game demo is to handle the addition and removal of geometry batches as well as a loop that calls the draw function on each batch. This way, when our game's main ENTER_FRAME event is fired, it will move the sprites around on screen via the entity manager and then tell the sprite stage system to draw itself, which in turn tells all known batches to draw.

    Because this is a heavily optimized demo, there will only be one batch in use, but this will change in future tutorials as we add more eye candy.

    
    
    		public function addBatch(batch:LiteSpriteBatch) : void
            {
                batch.parent = this;
                _batches.push(batch);
            }
    
            public function removeBatch(batch:LiteSpriteBatch) : void
            {
                for ( var i:uint = 0; i < _batches.length; i++ ) {
                    if ( _batches[i] == batch ) {
                        batch.parent = null;
                        _batches.splice(i, 1);
                    }
                }
            }
    
    		// loop through all batches
    		// (this demo uses only one)
    		// and tell them to draw themselves
    		public function render() : void
    		{
    			for ( var i:uint = 0; i < _batches.length; i++ ) {
    				_batches[i].draw();
    			}
    		}
        } // end class
    } // end package
    

    Step 28: Compile and Run!

    We're almost done! Compile your SWF, fix any typos, and check out the graphical goodness. You should have a demo that looks like this:

    Screenshot of our final .SWF in action. Sprite-o-licious!

    If you are having difficulties compiling, note that this project needs a class that was made by Adobe which handles the compilation of AGAL shaders, which is included in the source code .zip file download.

    Just for reference, and to ensure that you've used the correct filenames and locations for everything, here is what your FlashDevelop project should look like:

    The project window - what each file is named.

    Tutorial Complete: You Are Awesome

    That's it for tutorial one in this series! Tune in next week to watch the game slowly evolve into a great-looking, silky-smooth 60 FPS shoot-em-up. In the next part, we will implement player controls (using the keyboard to move around) and add some movement, sounds and music to the game.

    I'd love to hear from you regarding this tutorial. I warmly welcome all readers to get in touch with me via twitter: @mcfunkypants or my blog mcfunkypants.com or on Google+ any time. I'm always looking for new topics to write future tutorials on, so feel free to request one. Finally, I'd love to see the games you make using this code!

    Thanks for reading. See you next week. Good luck and HAVE FUN!


  4. Ashish Bogawat says:
    March 11, 2012 at 9:52 pm

    We’re starting a new community project: every week, we’ll post a web app or game and ask you all for your feedback on what it gets right, what it gets wrong, and how we could all learn from its design choices. We’ll also frequently offer our own thoughts. This week, Ashish Bogawat gives us a rundown of the New York Times’s app for Chrome.


    Introduction

    It wasn’t long ago that Google took the desktop web browser market by storm with Chrome. Along with a whole bunch of fresh ideas and relentless focus on lightning fast browsing, the feature that caught all other browsers unaware was the Chrome Web Store. What started with a whole lot of overhyped links to websites and a handful of specially designed browser-based apps has quickly turned into an avalanche of native applications that seem to take better advantage of the browser’s capabilities with every release, making Chrome the clear leader in the browser space as far as third-party develop support is concerned.

    The New York Times app was one of the first native apps on Chrome – and was, for a long time, their showcase for the future of web-based rich internet applications. Built specifically to take advantages of the browser’s innards, the app took news reading in a browser to an entirely new level and set the trend for others to follow. It is only apt, then, that we start this series with the NYTimes Chrome app.

    I would like to divide this critique into two broad sections: what I think works very well in the app from a user experience standpoint, and what doesn’t. The focus here is purely on the the interface and interaction design, and does not attempt to get into the technicalities of how the app was developed – something I would rather leave to the experts.


    What I Like About the App

    The first thing that strikes you when you launch the app is how clean, simple and snappy it feels. The designers have clearly spent a lot of time ensuring that the one thing that stays in focus the most is the content – the news.

    A good 90% of the screen real estate is dedicated to the content which, by default, contains the top news stories of the moment. On the right, the Sections list is clearly stated, yet unobtrusive. Since I am not a paid subscriber to the NY Times, the lock icons clearly tell me that I’m only going to have restricted access to anything other than the Top News category.

    The design for the content is a nice hybrid between traditional newspaper layouts and more modern, for-screen interactive layouts. Bigger stories get more space on the left with stories getting smaller and smaller as they go towards the right and demand less importance. Since the interactive medium affords designers the possibility of giving readers a glimpse of the content before delving deeper, a much more voluminous scattering of stories is possible here than in a printed newspaper, and the app takes good advantage of this ability.

    The layout also adapts to some extent to the content it represents. The Photos section, for example, is much more a collage of images than a grid of stories since there’s little to read there. While clicking a story opens up the full text of that news item, clicking a photo pulls up a slideshow of related images.

    Of course, the app also scores a lot of points in giving the reader the ability to control their news reading experience. If you don’t like the default layout, there are a whole bunch of alternative styles available from the ‘Layout’ link in the bottom right. Sure, none of these are drastically different from each other, but for the audience of this app – long time readers who are likely to spend a decent amount of time within it – the subtle variations can mean the difference between easy reading and frustration.

    The ability to navigate through the entire app with keyboard shortcuts (primarily the arrow keys) is a huge advantage. Again, this is a big win for avid readers. As with pretty much every reading app worth its while, the abilities to bookmark and share stories, and to customize the font size when reading long articles are common sense inclusions, and nicely implemented.


    What Doesn’t Work as Well

    As with most apps, though, not everything is perfectly where it should be. Subtle design decisions like the save icon (+) in the top-right corner of each story synopsis, the color changes on rollover or the lock icons to denote premium content are good as long as one is savvy enough to figure out what they might mean. And yes, one might argue that computer literacy – at least in a country like the US – is at a stage where non-savvy users are rare. But my problem remains that affordances for interactive elements in a design this flat pose a learning curve to some extent for all users.

    From a user interface design perspective, some decisions make very little sense. At first glance, the right-side panel looks like an accordion where I expect the Layout, My Account and Shortcuts to open up inside the panel to reveal details like the Sections do. Instead, they are all links that behave entirely differently when clicked. No hover states for pretty much anything in this panel as well as the bottom panel in the story view are glaring omissions that don’t make any sense. The page navigation arrows don’t even have a cursor change to indicate that they are active!

    Another problem which is possibly unique to me is that some of the keyboard shortcuts just don’t work. When in the main view, hitting the / key is supposed to switch me over to the selecting articles using arrow keys, but I just can’t seem to get it to work. Maybe I’m doing something wrong or my browser/OS combination is at fault, but I expect the designers to have tested the most common scenarios (mine is a simple Chrome Beta on Windows 7 setup that’s far from rare) before releasing the app.

    There are also other bugs like blank pages at the end of stories, which leads one to wonder what level of testing goes into the release of an app of this magnitude.


    Wrap-Up

    So yes, the app has its pros and cons. All in all, though, I like what it achieves with its brave attempt to bring the best of both worlds to the platform. News on the web has come a long way since the days of scanned PDFs of newspapers playing through clumsy Flash widgets, and the future seems only brighter. Now to wait and see which publication one-ups NY Times in building something truly revolutionary.


    Your Turn

    What do you think of NYTimes for Chrome? Share your constructive criticism in the comments below!

    And if you’ve got a browser app or game that you’d like the Activetuts+ community to do a critique on, submit it here. We’re looking forward to seeing what you’ve built.


  5. Tyler Seitz says:
    March 11, 2012 at 10:34 pm

    Flash games are very much the bread and butter of indie pop-nerd culture. If you consider the slices of bread the menu and the game itself, what is left? The butter – the very substance that makes the bread taste that much more delicious. And in terms of a Flash game, what comes in between menus and games are the transitions!


    Final Result Preview

    This is an example pattern of the transition effect that we will be working towards:


    Step 1: Setting Up

    Per usual we need to create a new Flash File (ActionScript 3.0). Set its width to 400px, its height to 200px, and the frame rate to 30fps. The background color can be left as the default. Save the file; it can be named whatever you please. I named mine Transitions.fla.

    Next we need to create a document class. Go to your Flash file’s properties and set its class to Transitions. Then create the document class:

    
    
    package {
    	import flash.display.*;
    	import flash.events.*;
    
    	public class Transitions extends MovieClip {
    		static public var val:Number = new Number();
    		static public var transitionAttached:Boolean = new Boolean();
    		public function Transitions() {
    			val = 0;
    			transitionAttached = false;
    		}
    	}
    }
    

    The code just introduced two variables. The first will be used to select the effect’s pattern, and the second will be used to check against having multiple instances of the effect on the stage.


    Step 2: Creating the Square Sprite

    Our next step is to create the sprite that will be used as each square for the transition. Create a new class and save it as Square.as:

    
    
    package{
    	import flash.display.*;
    	import flash.events.*;
    
    	public class Square extends Sprite{
    		public var squareShape:Shape = new Shape();
    		public function Square(){
    
    		}
    	}
    }
    

    We use the squareShape variable to draw our shape inside the Sprite. Draw a rectangle 40px by 40px (Which is the full size) and set its scale to 0.1, a tenth of its size – this will aid us in the effect later:

    
    
    addChild(squareShape);
    squareShape.graphics.beginFill(0x000000,1);
    squareShape.graphics.drawRect(-20,-20,40,40);
    squareShape.graphics.endFill();
    this.scaleX = 0.1;
    this.scaleY = 0.1;
    

    Step 3: Creating the Effect

    Create another new class for the effect itself. Once we are finished, adding the effect to the stage will be very simple:

    
    
    package{
    	import flash.display.*;
    	import flash.events.*;
    	import flash.utils.*;
    
    	public class FadeEffect extends Sprite{
    		public var currentFadeOut:int = 00;
    		public var currentSquares:int = 01;
    		public var pauseTime:int = 01;
    		public var tempNum:int = 00;
    		public var fading:String = "in";
    		public var fadeinTimer:Timer = new Timer(100);
    		public var fadeoutTimer:Timer = new Timer(100);
    		public var fadeArray:Array = [
                           //top
                           [[01,01,01,01,01,01,01,01,01,01],
                            [02,02,02,02,02,02,02,02,02,02],
                            [03,03,03,03,03,03,03,03,03,03],
                            [04,04,04,04,04,04,04,04,04,04],
                            [05,05,05,05,05,05,05,05,05,05]],
                           //bottom
                           [[05,05,05,05,05,05,05,05,05,05],
                            [04,04,04,04,04,04,04,04,04,04],
                            [03,03,03,03,03,03,03,03,03,03],
                            [02,02,02,02,02,02,02,02,02,02],
                            [01,01,01,01,01,01,01,01,01,01]]];
    		public var squaresArray:Array = new Array();
    		public function FadeEffect(){
    
    		}
    	}
    }
    

    You are probably thinking “that is a heck of a lot of variables, what all are they used for?”:

    • currentFadeOut - used as a check for tempNum to see how many squares are to be scaled
    • currentSquares - the current value indicating which squares should be attached and/or scaled
    • pauseTime - a simple integer to give a slight pause in between transitions and removing itself
    • tempNum - used to check what numbers in the array are to be scaled
    • fading - a string to check if the transition is fading in or out
    • fadeinTimer - a timer that is called to begin the fading in of the current value of currentSquares
    • fadeoutTimer - another timer that is called to begin the fading out of the current value of currentSquares
    • fadeArray - the 3D array that contains all the transition patterns
    • squaresArray - an array for the Square sprites

    Our effect will begin by initiating an event listener for fadeInTimer and starting it. We also need to add an event listener to continuously scale all of the sprites to their correct sizes. Use the following code inside the constructor:

    
    
    fadeinTimer.addEventListener("timer", fadeSquaresInTimer);
    fadeinTimer.start();
    addEventListener(Event.ENTER_FRAME, enterFrame);
    

    The next step is to create those two event listeners. We will start with the easier of the two, the enterFrame function:

    
    
    public function enterFrame(e:Event){
    	for each(var s1 in squaresArray){
    		tempNum+=1;
    		if(fading=="in"){
    			if(s1.scaleX<=1){
    				s1.scaleX+=0.05;
    				s1.scaleY+=0.05;
    			}
    		}else if(fading=="out"){
    			if(tempNum<=currentFadeOut){
    				if(s1.scaleX>=0.1){
    				s1.scaleX-=0.05;
    				s1.scaleY-=0.05;
    				}else{
    					if(s1.visible == true){
    						s1.visible = false;
    					}
    				}
    			}
    		}
    	}
    	tempNum=00;
    }
    

    It may not make total sense right now, but this should help shed some light.

    • s1 is the instance name that will be given to the Squares when we create them in a later function.
    • They are added to an array called squaresArray to keep track of the number of them and we perform the same operation for every object in the array.
    • Next we increase tempNum (used in the fading out if-statement) which is used to scale the sqaures in the order that they were added to the array. This means it is not pattern dependant and will work with any pattern.

    After that…

    • We check if fading is true or not.
    • If true, it scales all the squares up until they reach their full size (they begin scaling immediately after currentSquares increases).
    • Once it begins fading out things become a little trickier; we only scale down the squares that are lower than the current value of currentFadeOut (these are the ones that should be scaling, all others should remain at full scale until the value increases).
    • Once they have scaled down to a tenth of the size we make those squares invisible (they will be deleted with the whole effect).

    It’s time to add the event listener for the timer:

    
    
    public function fadeSquaresInTimer(e:Event){
    	fadeSquaresIn(fadeArray[Transitions.val]);
    	currentSquares+=1;
    }
    

    At first glance it looks less complicated, but you should notice that we are calling a function with the fadeArray as the parameter. Which pattern is selected from the array depends on what you set val equal to in the Transitions class; right now it should use the first pattern because val is set to 0.

    The next step is to create the fadeSquaresIn function that is called from the previous timer:

    
    
    public function fadeSquaresIn(s:Array){
    	for (var row=0; row<s[0].length; row++) {
    		for (var col=0; col<s.length; col++) {
    
    		}
    	}
    }
    

    First thing that we accomplish is iterating through the selected pattern. We start at row 1, colomn 1 and cycle through every colomn until the end of the row has been reached. Then we move onto the next row and repeat the process.

    The next thing to do is compare the current item in the array to the value of currentSquares:

    
    
    if(int(s[col][row]) == currentSquares){
    
    }
    

    If they are equivalent we add a square, position it accordingly, and push it onto the squaresArray so that it can be scaled:

    
    
    var s1:Sprite = new Square();
    s1.x = 20+(row*40);
    s1.y = 20+(col*40);
    addChild(s1);
    squaresArray.push(s1);
    

    We are almost done with this function, we just have to perform a check for when there are the same number of squares as there are items in the pattern. We do so by adding the following if-statement outside both for-loops:

    
    
    if(squaresArray.length == (s[0].length * s.length)){
    	fadeinTimer.stop();
    	addEventListener(Event.ENTER_FRAME, pauseBetween);
    }
    

    Self explanatory – we stopped the timer and called an event listener for the pause between fading in and fading out. That function is used to initiate the fading out and may also be used to cause change in your game:

    
    
    public function pauseBetween(e:Event){
    	pauseTime+=1;
    	if(pauseTime==60){
    		currentSquares=01;
    		fading="out";
    		fadeoutTimer.addEventListener("timer", fadeSquaresOutTimer);
    		fadeoutTimer.start();
    		removeEventListener(Event.ENTER_FRAME, pauseBetween);
    	}
    }
    

    We won’t spend much time on this function due to its simplicity. Here we increase the value of pauseTime, and once it equals 60 (meaning two seconds have passed) we set the value of currentSquares back to 1, set fading to "out" so that the squares can scale backwards, remove the listener for pauseBetween() itself, and add an event listener for this new function:

    
    
    public function fadeSquaresOutTimer(e:Event){
    	fadeSquaresOut(fadeArray[Transitions.val]);
    	currentSquares+=1;
    }
    

    This works much like fadeSquaresInTimer(), though this time we are calling the function fadeSquaresOut():

    
    
    public function fadeSquaresOut(s:Array){
    	for (var row=0; row<s[0].length; row++) {
    		for (var col=0; col<s.length; col++) {
    			if(int(s[col][row]) == currentSquares){
    				currentFadeOut+=1;
    			}
    		}
    	}
    }
    

    We cycle through, but this time when we find an equivalent item we increase the value of currentFadeOut so that the next item in the squaresArray can begin fading out.

    Almost finished now; all that’s left is to stop the timer and remove the effect. Add this if-statement outside of the two for-loops:

    
    
    if(currentFadeOut == (s[0].length * s.length)){
    	fadeoutTimer.stop();
    	pauseTime=01;
    	addEventListener(Event.ENTER_FRAME, delayedRemove);
    }
    

    This checks whether all of the items have begun fading out. If so, it then stops the timer, sets pauseTime back to 1 and adds an event listener for the function delayedRemove():

    
    
    public function delayedRemove(e:Event){
    	pauseTime+=1;
    	if(pauseTime==30){
    		Transitions.transitionAttached = false;
    		removeEventListener(Event.ENTER_FRAME, delayedRemove);
    		stage.removeChild(this);
    	}
    }
    

    Like before we increase the value of pauseTime, and once it equals 30 (1 second) we set the boolean back to false so that the effect can be added once again. We remove this event listener and we remove this effect from the stage.


    Step 4: Adding the Effect

    Now comes the easy part. Add the following code inside the document class constructor to add the effect:

    
    
    if(transitionAttached == false){
    	transitionAttached = true;
    	var f1:Sprite=new FadeEffect;
    	stage.addChild(f1);
    }
    

    Step 5: Creating More Patterns

    Feel free to create your own patterns! It’s extremely simple, just create a new 2D array inside the 3D array. Here is the array that I have created (just replace your 3D array with it). It includes 8 different transitions:

    
    
    [//top
    [[01,01,01,01,01,01,01,01,01,01],
    [02,02,02,02,02,02,02,02,02,02],
    [03,03,03,03,03,03,03,03,03,03],
    [04,04,04,04,04,04,04,04,04,04],
    [05,05,05,05,05,05,05,05,05,05]],
    //bottom
    [[05,05,05,05,05,05,05,05,05,05],
    [04,04,04,04,04,04,04,04,04,04],
    [03,03,03,03,03,03,03,03,03,03],
    [02,02,02,02,02,02,02,02,02,02],
    [01,01,01,01,01,01,01,01,01,01]],
    //left
    [[01,02,03,04,05,06,07,08,09,10],
    [01,02,03,04,05,06,07,08,09,10],
    [01,02,03,04,05,06,07,08,09,10],
    [01,02,03,04,05,06,07,08,09,10],
    [01,02,03,04,05,06,07,08,09,10]],
    //right
    [[10,09,08,07,06,05,04,03,02,01],
    [10,09,08,07,06,05,04,03,02,01],
    [10,09,08,07,06,05,04,03,02,01],
    [10,09,08,07,06,05,04,03,02,01],
    [10,09,08,07,06,05,04,03,02,01]],
    //top-left
    [[01,02,03,04,05,06,07,08,09,10],
    [02,03,04,05,06,07,08,09,10,11],
    [03,04,05,06,07,08,09,10,11,12],
    [04,05,06,07,08,09,10,11,12,13],
    [05,06,07,08,09,10,11,12,13,14]],
    //top-right
    [[10,09,08,07,06,05,04,03,02,01],
    [11,10,09,08,07,06,05,04,03,02],
    [12,11,10,09,08,07,06,05,04,03],
    [13,12,11,10,09,08,07,06,05,04],
    [14,13,12,11,10,09,08,07,06,05]],
    //bottom-left
    [[05,06,07,08,09,10,11,12,13,14],
    [04,05,06,07,08,09,10,11,12,13],
    [03,04,05,06,07,08,09,10,11,12],
    [02,03,04,05,06,07,08,09,10,11],
    [01,02,03,04,05,06,07,08,09,10]],
    //bottom-right
    [[14,13,12,11,10,09,08,07,06,05],
    [13,12,11,10,09,08,07,06,05,04],
    [12,11,10,09,08,07,06,05,04,03],
    [11,10,09,08,07,06,05,03,03,02],
    [10,09,08,07,06,05,04,03,02,01]]];
    

    You can change the value of Transitions.val to choose another pattern – for example, if val is 3, the transition will sweep in from the right.


    Conclusion

    Thanks for taking the time to read this tutorial. If you have any questions please leave a comment below. And if you would like a challenge, try making the effect fade in with one pattern and fade out with an opposing one.


  6. David Appleyard says:
    March 11, 2012 at 11:19 pm

    Each month, we bring together a selection of the best tutorials and articles from across the whole Tuts+ network. Whether you’d like to read the top posts from your favourite site, or would like to start learning something completely new, this is the best place to start!


    Psdtuts+ — Photoshop Tutorials

    • Create a Baseball-Inspired Text Effect in Photoshop

      Create a Baseball-Inspired Text Effect in Photoshop

      Applying texture to a text effect can be a lot of fun. In this tutorial we will explain how to create a baseball-inspired text effect using layer styles, patterns, and brushes. Let’s get started!

      Visit Article

    • Create a Mini Planet Using Photoshop’s 3D Capabilities

      Create a Mini Planet Using Photoshop’s 3D Capabilities

      When most people think about Photoshop, they probably don’t think about 3D. What most people don’t realize, however, is that Photoshop CS5 Extended includes some powerful tools to help you render your artwork in 3D. In this tutorial we will demonstrate how to create a mini planet using Photoshop’s 3D capabilities. Let’s get started!

      Visit Article

    • Create a Coffee Cake Photo Manipulation – Tuts+ Premium Tutorial

      Create a Coffee Cake Photo Manipulation – Tuts+ Premium Tutorial

      In this Tuts+ Premium tutorial, author Stephen Petrany will demonstrate how to take pieces from multiple photos and seamlessly blend them into a "coffee cake" photo manipulation. This tutorial will also explore unique ways to work with paths and smart objects. If you are looking to take your photo manipulation skills to the next level then Log in or Join Now to get started!

      Visit Article


    • Nettuts+ — Web Development Tutorials

    • The Largest jQuery Class in the World

      The Largest jQuery Class in the World

      A couple weeks ago, Tuts+ Premium launched a free new real-time course, called “30 Days to Learn jQuery.” After signing up, each member receives an email, linking to a new video lesson for an entire month.

      Visit Article

    • How to Customize Your Command Prompt

      How to Customize Your Command Prompt

      Lately, I’ve been getting this question a lot: “how did you get your terminal to look the way it does?” If you’ve noticed my terminal and are curious about how I set it up, this is the tutorial for you! Of course, what you learn here will be enough to get you started on creating your own custom command prompt, as well!

      Visit Article

    • Attention Developers: NewRelic is your Secret Weapon

      Attention Developers: NewRelic is your Secret Weapon

      While the title of this article may sound like a cliche, hatched in the bowels of PR hell, I’m serious when I say that NewRelic is your secret weapon.

      Visit Article


    • Vectortuts+ — Illustrator Tutorials

    • How to Create a Vintage Type Postcard

      How to Create a Vintage Type Postcard

      Follow this in-depth look at the process of designing type for a vintage style postcard in Adobe Illustrator CS5. Harken back to an era when postcards were all the rage with this friendly type style. The tutorial will delve into clipping masks, using bitmap images, working with layers and type effects.

      Visit Article

    • Create a Block Game Interface in Illustrator

      Create a Block Game Interface in Illustrator

      In the following tutorial you will learn how to create a block game interface in Adobe Illustrator CS5. Vector game graphics allow for versatile artwork. The workflow presented in this tutorial will teach you how to create game graphics in Illustrator. These techniques can be applied to multiple interface design and game design projects. It’s time to jump in, learn to create these shapes, and give them colorful graphic depth.

      Visit Article

    • 25+ Illustrator Tutorials for Creating Vintage Graphics and Retro Illustration

      Illustrator Tutorials for Creating Vintage Graphics and Retro Illustration

      If youre looking to improve your vector design skills, learn how to use Illustrator on a deeper level, and discover how to create vintage vector graphics, then you’ve landed on the right article. We’ve assembled a collection of tutorials that show you how to create vintage illustrations, and retro graphics using Illustrator effects and a variety of professional workflows.

      Visit Article


    • Webdesigntuts+ — Web Design Tutorials

    • Principles for Successful Button Design

      Principles for Successful Button Design

      There are a thousand ways to design and create buttons today and you only need to spend a small amount of time looking through work on dribbble to get a sense of them. A great deal of these examples are exactly the same, but occasionally there are the odd few that feel like they’ve had a little more care and attention in their making.

      Visit Article

    • Orman Clark’s Vertical Navigation Menu: The CSS3 Version

      Orman Clark’s Vertical Navigation Menu: The CSS3 Version

      Next in the Orman Clark’s coded PSD series is his awesome looking Vertical Navigation Menu. We’ll recreate it with CSS3 and jQuery while using the minimal amount of images possible.

      Visit Article

    • Coding the SimpleAdmin Theme: Login Page

      Coding the SimpleAdmin Theme: Login Page

      It’s time to translate our admin layout into a working template. We’ll begin by setting out the markup for our Login page, then we’ll hit the stylesheets..

      Visit Article


    • Phototuts+ — Photography Tutorials

    • The Stock Market: Exploring Stock Photography

      The Stock Market: Exploring Stock Photography

      Creative professionals all over the world frequently require high quality images, but often don’t have the budget to hire a photographer for small projects. Enter stock photography: an industry where awesome photographs are out there and ripe for the using. Today, we’ll be taking a look at the wild world of the stock market – stock photography, that is.

      Visit Article

    • Lightroom 4 Beta: Packed with New Features

      Lightroom 4 Beta: Packed with New Features

      In six short years, Adoble Lightroom has changed the way many photographers manage their images. With powerful cataloging and developing features, Lightroom offers photographers the ability to customize their photo management workflow and manage the thousands of images more efficiently than ever before. Adobe’s innovation continues with Lightroom 4, which is currently in Beta. Today, we’ll be taking a look at some of the new features of the latest iteration of Lightroom.

      Visit Article

    • A Primer to Digital Medium Format Camera

      A Primer to Digital Medium Format Camera

      Over the last few months, I’ve observed a trend among several well known photographers. No longer satisfied with crop factor cameras or even 35mm equivalent full frame digital cameras, more and more photographers are jumping to digital medium format. What are the advantages offered by digital medium formats, and will you be using one anytime soon? Read on to find out.

      Visit Article


    • Cgtuts+ — Computer Graphics Tutorials

    • Achieving 3D Realism: Reception Area Render With 3D Studio Max & V-Ray, Part 1

      Achieving 3D Realism: Reception Area Render With 3D Studio Max & V-Ray, Part 1

      The following tutorial is based on a real project. This unique tutorial will take users through the real process of creating shaders with bespoke physical properties and applying textures based on real photo references.

      Visit Article

    • Create And Render A Still Life Scene In Blender, Using Cycles

      Create And Render A Still Life Scene In Blender, Using Cycles

      Today, we’ll have a brief introduction to Blender’s new rendering engine – Cycles. This tutorial will cover modeling a small and easy still life scene, setting up different types of materials used in cycles and then finally lighting and rendering the scene.

      Visit Article

    • An Introduction To UVMapping In 3d Studio Max Using The Unwrap UVW Modifier

      An Introduction To UVMapping In 3d Studio Max Using The Unwrap UVW Modifier

      So UVMapping… you hate it, I hate it. But unfortunately it’s a necessary step in the process of completing most cg projects. In this tutorial we’ll look at creating uvs using the ‘Unwrap UVW’ modifier in 3D Studio Max, and discuss what uv mapping is, why it’s necessary and some ways to approach it.

      Visit Article


    • Aetuts+ — After Effects Tutorials

    • Create The Amazing Spider-Man Title Sequence Entirely In After Effects

      Create The Amazing Spider-Man Title Sequence Entirely In After Effects

      Nancy will show us how to create the title sequence for the Amazing Spider-Man entirely in After Effects using ShapeShifter AE. She shows us how to combine Shape Layers + Layer Masks to model and animate Spideys symbol. Download the free project file and follow along. You’ll be amazed at how easy it is to create!

      Visit Article

    • 3D Transforming Text With ShapeShifter AE

      D Transforming Text With ShapeShifter AE

      In this tutorial, we will be taking a look at how to build this 3D transforming text animation using Mettle’s ShapeShifter AE plug-in. We will also be enhancing some of the elements using 3rd party plug-ins such as Trapcode Shine (CC Light Burst alternative), Frischluft’s Out of Focus (Lens Blur alternative), and RE:Vision RSMB (CC Force Motion Blur alternative).

      Visit Article

    • Create An Awesome Array Of Shattering Strings

      Create An Awesome Array Of Shattering Strings

      We’ll be starting in Cinema 4d to create text fragments and use XPresso to export the Mograph positional data to After Effects. From there, we’ll jump over into After Effects and use expressions to connect 3d nulls to 2d data points… We’ll also be using a macro in Microsoft Word to edit multiple lines of expressions. It doesn’t matter if you’re a Cinema 4d user or strictly an After Effects user, today’s tutorial should be something helpful for everyone!

      Visit Article


    • Audiotuts+ — Audio & Production Tutorials

    • The 15 Minute Mix

      The 15 Minute Mix

      Consider this your challenge for today. Take a song that you just recorded, or have been working on and mix it in 15 minutes. Shut off everything, pull the faders up and follow the following tutorial. Use a stopwatch to keep track of time and when you should be switching tasks.

      If you don’t have any sessions to try, you can use any of these 50 different multi-tracks.

      Visit Article

    • 35 Audio Tutorial Sites That Will Keep You Learning

      Audio Tutorial Sites That Will Keep You Learning

      The reason you’re here on our site is because you’re interested in audio tutorials. I think we do a great job: we have a huge number of excellent tuts – both free and premium. But we know we haven’t cornered the market. There are an amazing number of audio tut sites out there, and the number seems to grow every year. Here are 35 of the best.

      Visit Article

    • Quick Tip: Creating Skrillex Style Tech Basslines in NI Massive

      Quick Tip: Creating Skrillex Style Tech Basslines in NI Massive

      This series of quick tips will outline how you can use the ever powerful NI Massive synth to create techy basslines used by artists such as Skrillex. In this example I have used Cubase but the same principles will translate to pretty much any other DAW. Here is an example of the kind of sound you can expect to end up with at the end of this series:

      Visit Article


    • Activetuts+ — Flash, Flex & ActionScript Tutorials

    • Review: Construct 2, a Drag and Drop HTML5 Game Maker

      Review: Construct 2, a Drag and Drop HTML5 Game Maker

      Construct 2 is an HTML5 game making tool that doesn’t require any programming knowledge. You just drag and drop items around, add behaviors to them, and make them come alive with “events”.

      Visit Article

    • Number Systems: An Introduction to Binary, Hexadecimal, and More

      Number Systems: An Introduction to Binary, Hexadecimal, and More

      Ever see crazy binary numbers and wonder what they meant? Ever see numbers with letters mixed in and wonder what is going on? You’ll find out all of this and more in this article. Hexadecimal doesn’t have to be scary.

      Visit Article

    • Understanding Affine Transformations With Matrix Mathematics

      Understanding Affine Transformations With Matrix Mathematics

      Inspired by Prof. Wildberger in his lecture series on linear algebra, I intend to implement his mathematical ideas with Flash. We shall not delve into the mathematical manipulation of matrices through linear algebra: just through vectors. This understanding, although diluting the elegance of linear algebra, is enough to launch us into some interesting possibilities of 2×2 matrix manipulation. In particular, we’ll use it to apply various shearing, skewing, flipping, and scaling effects to images at runtime.

      Visit Article


    • Wptuts+ — WordPress Tutorials

    • Creating a Filterable Portfolio with WordPress and jQuery

      Creating a Filterable Portfolio with WordPress and jQuery

      Learn in this tutorial how to make a filterable Portfolio with jQuery integrated with WordPress, remember that this portfolio kind can make a big difference on your themes!

      Visit Article

    • How to Include JavaScript and CSS in Your WordPress Themes and Plugins

      How to Include JavaScript and CSS in Your WordPress Themes and Plugins

      Knowing the proper way to include JavaScript and CSS files in your WordPress themes and plugins is very important for designers and developers. If you don’t adhere to best practices, you run the risk of conflicting with other themes and plugins, and potentially creating problems that could have been easily avoided. This article is intended as a reference for playing nicely with others.

      Visit Article

    • How to Create a Simple Post Rating System With WordPress and jQuery

      How to Create a Simple Post Rating System With WordPress and jQuery

      There already are many post rating system plugins out there. Surprisingly, no one fits my needs, they either are too complicated or with too many built-in options. So, in this tutorial, you’ll learn how to build your own simple post rating functionality, directly within your theme files. There’s no need for plugin!

      Visit Article


    • Mobiletuts+ — Mobile Development Tutorials

    • Getting Started With RenderScript on Android

      Getting Started With RenderScript on Android

      RenderScript is a scripting language on Android that allows you to write high performance graphic rendering and raw computational code. Learn more about RenderScript and write your first graphics app that leverages RenderScript in this tutorial.

      Visit Article

    • PhoneGap From Scratch: Twitter & Maps

      PhoneGap From Scratch: Twitter & Maps

      Want to learn how to use PhoneGap, but don’t know where to get started? Join us as we put together ’Sculder”, not only a tribute to an excellent science fiction TV series, but a fully-fledged native mobile application for the believer in you!

      Visit Article

    • Supplementing iAd Placement with AdMob

      Supplementing iAd Placement with AdMob

      Click-based advertising within a mobile application is a great way to make some money off of your free or inexpensive applications. While there are many choices out there, many iOS developers tend to go with the iAds platform for a variety of reasons including simplicity, aesthetics, and a high CPM.

      Visit Article


  7. Fuszenecker Zsombor says:
    March 12, 2012 at 12:08 am

    In this tutorial I would like to show you how easy it is to create a classic “Snake” game in Flash. I will try to explain everything easily, step by step, so that you can develop the game further to your needs! The Game will be developed in AS3 and I will use the FlashDevelop IDE.


    Introduction

    The game won’t be complex. Whenever we hit a wall, it will restart the game. After eating an apple the snake will grow, and a ‘new’ Apple will appear. (Actually, it will be the same apple, but I’ll explain this later.)

    One of the most important aspects of the game is the code’s reaction to KEY_DOWN events. The snake will only then change its direction after a tick has passed, not immediately after a keypress. This means that, if the snake is going right, and you press down and left very fast, the snake will go down, not down AND left. Without this ‘feature’ the snake would allow us to go left while we are going right, which would mean it hit itself.


    Let’s Look at the Game Already!

    Let’s take a look at the final result we will be working towards:


    Step 1: Creating the Project

    In FlashDevelop, create a new Project, and inside the ‘src’ folder create a ‘com’ folder. In the ‘com’ folder create a new class, and call it ‘Element.as’.

    Set the dimensions of the project to 600x600px.

    The FlashDevelop project structure

    Step 2: Wait… What’s an Element?

    The snake is make up of blue squares, which I call elements. We will create an Element Class, which draws the element. The red apple is going to be an element too, so we will extend the code with a few more lines.

    Therefore we won’t create a new class for the apple. (But if you really want to, you can.)


    Step 3: Writing the Element Class

    The Element class creates a square. It doesn’t draw it on the stage, it just creates it. The registration point of the element – the position referred to by its x- and y-coordinates – is in the top-left.

    After opening the Element.as you will see something like this:

    package com
    {
    	/**
    	 * ...
    	 * @author Fuszenecker Zsombor
    	 */
    	public class Element
    	{
    
    		public function Element()
    		{
    
    		}
    
    	}
    }
    

    First we need this to extend the Shape class, so we can use the graphics object to draw the square. After this, create two variables: one for the direction (if it’s part of the snake), and one for the score value (if it’s an apple), and then change the parameters of the constructor function:

    package com
    {
    	import flash.display.Shape;
    
    	public class Element extends Shape
    	{
    		protected var _direction:String;
    		//IF IT IS AN APPLE ->
    		protected var _catchValue:Number;
    
    		//color,alpha,width,height
    		public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
    		{
    
    		}
    	}
    }
    

    Now fill the function with some code:

    package com
    {
    	import flash.display.Shape;
    
    	public class Element extends Shape
    	{
    		protected var _direction:String;
    		//IF IT IS AN APPLE ->
    		protected var _catchValue:Number;
    
    		//color,alpha,width,height
    		public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
    		{
    			graphics.lineStyle(0, _c, _a);
    			graphics.beginFill(_c, _a);
    			graphics.drawRect(0, 0, _w, _h);
    			graphics.endFill();
    
    			_catchValue = 0;
    		}
    	}
    }
    

    Now, whenever we create an element, it will draw a rectangle and set the score value of the element to 0 by default. (It won’t put the rectangle on stage, it just draws it within itself. Notice that we have not called the addChild() function.)

    Let’s finish this class and then we can finally test how much we have done already:

    package com
    {
    	import flash.display.Shape;
    
    	public class Element extends Shape
    	{
    		protected var _direction:String;
    		//IF IT IS AN APPLE ->
    		protected var _catchValue:Number;
    
    		//color,alpha,width,height
    		public function Element(_c:uint,_a:Number,_w:Number,_h:Number)
    		{
    			graphics.lineStyle(0, _c, _a);
    			graphics.beginFill(_c, _a);
    			graphics.drawRect(0, 0, _w, _h);
    			graphics.endFill();
    
    			_catchValue = 0;
    		}
    
    		//ONLY USED IN CASE OF A PART OF THE SNAKE
    		public function set direction(value:String):void
    		{
    			_direction = value;
    		}
    		public function get direction():String
    		{
    			return _direction;
    		}
    
    		//ONLY USED IN CASE OF AN APPLE
    		public function set catchValue(value:Number):void
    		{
    			_catchValue = value;
    		}
    		public function get catchValue():Number
    		{
    			return _catchValue;
    		}
    	}
    
    }
    

    We created four functions to change the directions and the value of the apple. We achieved this by using setters and getters. More about Setters/Getters in this article!


    Step 4: Testing the Element Class

    Open Main.as now.

    Import the com.Element class and create an Element in the init() function:

    package
    {
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import com.Element;
    
    	public class Main extends Sprite
    	{
    		public function Main()
    		{
    			if(stage)
    				addEventListener(Event.ADDED_TO_STAGE, init);
    			else
    				init();
    		}
    
    		private function init(e:Event = null):void
    		{
    			var testElement:Element = new Element(0x00AAFF, 1, 10, 10);
    			testElement.x = 50;
    			testElement.y = 50;
    			this.addChild(testElement);
    
    		}
    
    	}
    }
    

    First we create the testElement variable which holds our element. We create a new Element and assign that to our testElement variable. Note the arguments we passed: first we give it a color, then the alpha, width and height. If you look in the Element class’s Element function, you can see how it uses this data to draw the rectangle.

    After creating the Element, we position it and put it on the stage!


    Step 5: Setting Up the Variables

    Look at the following code. I wrote the functions of the variables next to them (notice that we imported the necessary classes too):

    package
    {
    	import flash.display.Sprite;
    	import flash.text.TextField;
    	import flash.utils.Timer;
    	import flash.events.TimerEvent;
    	import flash.ui.Keyboard;
    	import flash.events.KeyboardEvent;
    	import flash.events.MouseEvent;
    	import flash.events.Event;
    
    	import com.Element;
    
    	public class Main extends Sprite
    	{
    
    		//DO NOT GIVE THESE VARS A VALUE HERE!
    		//Give them their values in the init() function.
    		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
    		private var markers_vector:Vector.<Object>; //the markers are held in here
    		private var timer:Timer;
    		private var dead:Boolean;
    		private var min_elements:int; //holds how many parts the snake should have at the beginning
    		private var apple:Element; //Our apple
    		private var space_value:Number; //space between the snake's parts
    		private var last_button_down:uint; //the keyCode of the last button pressed
    		private var flag:Boolean; //is it allowed to change direction?
    		private var score:Number;
    		private var score_tf:TextField; //the Textfield showing the score
    
    		public function Main()
    		{
    			if(stage)
    				addEventListener(Event.ADDED_TO_STAGE, init);
    			else
    				init();
    		}
    
    		private function init(e:Event = null):void
    		{
    			snake_vector = new Vector.<Element>;
    			markers_vector = new Vector.<Object>;
    			space_value = 2; //There will be 2px space between every Element
    			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired! This will set the SPEED of the snake
    			dead = false;
    			min_elements = 10; //We will begin with 10 elements.
    			apple = new Element(0xFF0000, 1, 10, 10); //red, not transparent, width:10, height: 10;
    			apple.catchValue = 0; //pretty obvious - the score of the apple
    			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable
    			score = 0;
    			score_tf = new TextField(); //this is the TextField which shows our score.
    			this.addChild(score_tf);
    		}
    	}
    }
    

    The most important variable is the snake_vector. We will put every Element of the snake in this Vector.

    Then there is the markers_vector. We will use markers to set the direction of the snake’s parts. Each object in this Vector will have a position and a type. The type will tell us whether the snake should go right, left, up, or down after ‘hitting’ the object. (They won’t collide, only the position of the markers and the snake’s parts will be checked.)

    As an example, if we press DOWN, an object will be created. The x and y of this object will be the snake’s head’s x and y coordinates, and the type will be “Down”. Whenever the position of one of the snake’s Elements is the same as this object’s, the snakes elements direction will be set to “Down”.

    Please read the comments next to the variables to understand what the other variables do!


    Step 6: Writing the attachElement() Function

    The attachElement() function will take four parameters: the new snake element, the x and y coordinates, and the direction of the last part of the snake.

    private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
    {
    
    }
    

    Before we put the element on the stage we should position it. But for this we need the direction of the snake’s last element, to know whether the new element has to be above, under, or next to this.

    After checking the direction and setting the position, we can add it to the stage.

    private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
    {
    	if (dirOfLast == "R")
    	{
    		who.x = lastXPos - snake_vector[0].width - space_value;
    		who.y = lastYPos;
    	}
    	else if(dirOfLast == "L")
    	{
    		who.x = lastXPos + snake_vector[0].width + space_value;
    		who.y = lastYPos;
    	}
    	else if(dirOfLast == "U")
    	{
    		who.x = lastXPos;
    		who.y = lastYPos + snake_vector[0].height + space_value;
    	}
    	else if(dirOfLast == "D")
    	{
    		who.x = lastXPos;
    		who.y = lastYPos - snake_vector[0].height - space_value;
    	}
    	this.addChild(who);
    }
    

    Now we can use this function in the init() function:

    for(var i:int=0;i<min_elements;++i)
    {
    	snake_vector[i] = new Element(0x00AAFF,1,10,10);
    	snake_vector[i].direction = "R"; //The starting direction of the snake
    	if (i == 0)//first snake element
    	{
    		//you have to place the first element on a GRID. (now: 0,0)
    		//[possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
    		attachElement(snake_vector[i],0,0,snake_vector[i].direction)
    		snake_vector[0].alpha = 0.7;
    	}
    	else
    	{
    		attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
    	}
    }
    

    We create the first 10 Elements, and set the direction of them to ‘R’ (right). If it is the first element, we call attachElement() and we change its alpha a bit (so the “head” is a slightly lighter color).

    If you wish to set the position somewhere else, then please keep the following in mind: the snake has to be placed on a grid, otherwise it would look bad and would not work. If you wish to change the x and y position you can do it the following way:

    Setting the x position: (snake_vector[0].width+space_value)*[UINT], where you should replace [UINT] with a positive integer.

    Setting the y position: (snake_vector[0].height+space_value)*[UINT], where you should replace [UINT] with a positive integer.

    Let’s change it to this:

    if (i == 0)//first snake element
    {
    	//you have to place the first element on a GRID. (now: 0,0)
    	//[possible x positions: (snake_vector[0].width+space_value)*<UINT>]
    	attachElement(
    		snake_vector[i],
    		(snake_vector[0].width+space_value)*20,
    		(snake_vector[0].height+space_value)*10,
    		snake_vector[i].direction
    	);
    	snake_vector[0].alpha = 0.7;
    }
    

    And the snake’s first element is set onto the 20th space in the x-grid and 10th space in the y-grid.

    This is what we’ve got so far:

    package
    {
    	import flash.display.Sprite;
    	import flash.text.TextField;
    	import flash.utils.Timer;
    	import flash.events.TimerEvent;
    	import flash.ui.Keyboard;
    	import flash.events.KeyboardEvent;
    	import flash.events.MouseEvent;
    	import flash.events.Event;
    
    	import com.Element;
    
    	public class Main extends Sprite
    	{
    
    		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function
    		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
    		private var markers_vector:Vector.<Object>; //the markers are held in here
    		private var timer:Timer;
    		private var dead:Boolean;
    		private var min_elements:int; //holds how many parts should the snake have at the beginning
    		private var apple:Element; //Our apple
    		private var space_value:Number; //space between the snake parts
    		private var last_button_down:uint; //the keyCode of the last button pressed
    		private var flag:Boolean; //is it allowed to change direction?
    		private var score:Number;
    		private var score_tf:TextField; //the Textfield showing the score
    
    		public function Main()
    		{
    			if(stage)
    				addEventListener(Event.ADDED_TO_STAGE, init);
    			else
    				init();
    		}
    
    		private function init(e:Event = null):void
    		{
    			snake_vector = new Vector.<Element>;
    			markers_vector = new Vector.<Object>;
    			space_value = 2;
    			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!
    			dead = false;
    			min_elements = 10; //We will begin with 10 elements.
    			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;
    			apple.catchValue = 0; //pretty obvious
    			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable
    			score = 0;
    			score_tf = new TextField(); //this is the TextField which shows our score.
    			this.addChild(score_tf);
    
    			for(var i:int=0;i<min_elements;++i)
    			{
    				snake_vector[i] = new Element(0x00AAFF,1,10,10);
    				snake_vector[i].direction = "R"; //The starting direction of the snake
    				if (i == 0)//first snake element
    				{
    					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
    					attachElement(snake_vector[i], (snake_vector[0].width + space_value) * 20, (snake_vector[0].height + space_value) * 10, snake_vector[i].direction);
    					snake_vector[0].alpha = 0.7;
    				}
    				else
    				{
    					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
    				}
    			}
    		}	
    
    		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
    		{
    			if (dirOfLast == "R")
    			{
    				who.x = lastXPos - snake_vector[0].width - space_value;
    				who.y = lastYPos;
    			}
    			else if(dirOfLast == "L")
    			{
    				who.x = lastXPos + snake_vector[0].width + space_value;
    				who.y = lastYPos;
    			}
    			else if(dirOfLast == "U")
    			{
    				who.x = lastXPos;
    				who.y = lastYPos + snake_vector[0].height + space_value;
    			}
    			else if(dirOfLast == "D")
    			{
    				who.x = lastXPos;
    				who.y = lastYPos - snake_vector[0].height - space_value;
    			}
    			this.addChild(who);
    		}
    	}
    }
    


    Step 7: Writing the placeApple() Function

    This function does the following:

    1. It checks whether the apple was caught. For this we will use the caught parameter, and set its default value to true, in case we don’t pass any value as parameters in the future. If it was caught, it adds 10 to the apple’s score value (so the next apple is worth more).
    2. After this the apple has to be repositioned (we don’t create new apples) at a random grid position.
    3. If it is placed on the snake, we should place it somewhere else.
    4. If it is not on the stage yet, we place it there.

    private function placeApple(caught:Boolean = true):void
    {
    	if (caught)
    		apple.catchValue += 10;
    
    	var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
    	var randomX:Number = Math.floor(Math.random()*boundsX);
    
    	var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
    	var randomY:Number = Math.floor(Math.random()*boundsY);
    
    	apple.x = randomX * (apple.width + space_value);
    	apple.y = randomY * (apple.height + space_value);
    
    	for(var i:uint=0;i<snake_vector.length-1;i++)
    	{
    		if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
    			placeApple(false);
    	}
    	if (!apple.stage)
    		this.addChild(apple);
    }
    

    There will be some math here, but if you think it through you should understand why it is so. Just draw it out on some paper if necessary.

    • boundsX will hold how many elements could be drawn in one row.
    • randomX takes this boundsX, multiplies it with a Number between zero and one, and floors it. If boundsX is 12 and the random Number is 0.356, then floor(12*0.356) is 4, so the apple will be placed on the 4th spot on the x-grid.
    • boundsY will hold how many elements can be drawn in one column.
    • randomY takes this boundsY, multiplies it with a Number between zero and one, and floors it.
    • Then we set the x and y position to these numbers.

    In the for loop, we check whether the apple’s new x and y positions are identical to any of the snake_vectors elements. If so, we call the placeApple() function again (recursive function), and set the parameter of it to false. (Meaning that the apple was not caught, we just need to reposition it)

    (apple.stage) returns true if the apple is on the stage. we use the ‘!’ operator to invert that value, so if it is NOT on the stage, we place it there.

    The last thing we need to do is call the placeApple() function at the end of the init() function.

    private function init(e:Event = null):void
    {
        /*
    	.
    	.
    	.
    	*/
    
    	placeApple(false);
    }
    

    Notice that we pass false as the parameter. It’s logical, because we didn’t catch the apple in the init() function yet. We will only catch it in the moveIt() function.

    Now there are only three more functions to write: the directionChanged(), moveIt() and the gameOver() functions.


    Step 8: Starting the moveIt() Function

    The moveIt() function is responsible for all of the movement. This function will check the boundaries and check whether there is an object at the x and y position of the snake’s head. It will also look for the apple at this position.

    For all of this, we will use our timer variable.

    Add two more lines in the end of the init() function:

    timer.addEventListener(TimerEvent.TIMER,moveIt);
    timer.start();
    

    Look at the comments in the sourcecode, to see which block of code does what.

    		private function moveIt(e:TimerEvent):void
    		{
    			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
    			{
    				//This code runs if the snakes heads position and the apples position are the same
    			}
    
    			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
    			{
    				//This block runs if the snakes head is out of the stage (hitting the walls)
    			}
    
    			for (var i:int = 0; i < snake_vector.length; i++)
    			{
    				/*
    					START OF FOR BLOCK
    					This whole 'for' block will run as many times, as many elements the snake has.
    					If there are four snake parts, this whole for cycle will run four times.
    				*/
    
    				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
    				{
    					//If the snakes heads position is the same as any of the snake parts, this block will run (Checking the collision with itself).
    				}
    
    				if (markers_vector.length > 0)
    				{
    					//if there are direction markers, this code runs
    				}
    
    				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.
    				switch (DIRECTION)
    				{
    					//Sets the new position of the snakes part
    				}
    
    				/*
    					END OF FOR BLOCK
    				*/
    			}
    
    		}
    

    Now we need to code the movement. For this we jump into the switch block, which will run on every snake part, because of the for loop.

    First we need to check the direction of the current element.

    				switch (DIRECTION)
    				{
    					case "R" :
    						//Here we need to set the new x position for the current part
    						break;
    					case "L" :
    						//Here we need to set the new x position for the current part
    						break;
    					case "D" :
    						//Here we need to set the new y position for the current part
    						break;
    					case "U" :
    						//Here we need to set the new y position for the current part
    						break;
    				}
    

    When the direction of the part is set to “R”, for instance, we need to add something to its current X position (the space_value plus the width of the snake part).

    With this in mind, we can fill it out:

    				switch (DIRECTION)
    				{
    					case "R" :
    						snake_vector[i].x += snake_vector[i].width + space_value;
    						break;
    					case "L" :
    						snake_vector[i].x -= snake_vector[i].width + space_value;
    						break;
    					case "D" :
    						snake_vector[i].y += snake_vector[i].height + space_value;
    						break;
    					case "U" :
    						snake_vector[i].y -= snake_vector[i].width + space_value;
    						break;
    				}
    

    After testing the code, you should see that the snake is moving, and going off the stage and never stops. (You may need to refresh the page – or just click here to load it in a new window.)

    So we need to stop the snake


    Step 9: Writing the gameOver() Function

    This function is going to be the shortest. We just clear the stage and restart it:

    private function gameOver():void
    {
    	dead = true;
    	timer.stop();
    	while (this.numChildren)
    		this.removeChildAt(0);
    	timer.removeEventListener(TimerEvent.TIMER,moveIt);
    	init();
    }
    

    That’s it. We set the dead variable to true, stop the movement with the timer, remove every child of the class and call the init() function, like we just started the game.

    Now, let’s get back to the moveIt() function.


    Step 10: Continuing the moveIt() Function

    We will use the gameOver() function in two places. The first is when we check if the head is out of bounds, and the second is when the snake hits itself:

    
    
    		private function moveIt(e:TimerEvent):void
    		{
    			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
    			{
    				//This code runs if the snakes heads position and the apples position are the same
    			}
    
    			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
    			{
    				gameOver();
    			}
    
    			for (var i:int = 0; i < snake_vector.length; i++)
    			{
    				/*
    					START OF FOR BLOCK
    					This whole 'for' block will run as many times, as many elements the snake has.
    					If there are four snake parts, this whole for cycle will run four times.
    				*/
    
    				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
    				{
    					//If the snakes heads position is the same as any of the snake parts, this block will run
    					gameOver();
    				}
    
    				if (markers_vector.length > 0)
    				{
    					//if there are direction markers, this code runs
    				}
    
    				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.
    				switch (DIRECTION)
    				{
    					case "R" :
    						snake_vector[i].x += snake_vector[i].width + space_value;
    						break;
    					case "L" :
    						snake_vector[i].x -= snake_vector[i].width + space_value;
    						break;
    					case "D" :
    						snake_vector[i].y += snake_vector[i].height + space_value;
    						break;
    					case "U" :
    						snake_vector[i].y -= snake_vector[i].width + space_value;
    						break;
    				}
    
    				/*
    					END OF FOR BLOCK
    				*/
    			}
    
    		}
    

    This is the code we have now:

    package
    {
    	import flash.display.Sprite;
    	import flash.text.TextField;
    	import flash.utils.Timer;
    	import flash.events.TimerEvent;
    	import flash.ui.Keyboard;
    	import flash.events.KeyboardEvent;
    	import flash.events.MouseEvent;
    	import flash.events.Event;
    
    	import com.Element;
    
    	public class Main extends Sprite
    	{
    
    		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function
    		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
    		private var markers_vector:Vector.<Object>; //the markers are held in here
    		private var timer:Timer;
    		private var dead:Boolean;
    		private var min_elements:int; //holds how many parts should the snake have at the beginning
    		private var apple:Element; //Our apple
    		private var space_value:Number; //space between the snake parts
    		private var last_button_down:uint; //the keyCode of the last button pressed
    		private var flag:Boolean; //is it allowed to change direction?
    		private var score:Number;
    		private var score_tf:TextField; //the Textfield showing the score
    
    		public function Main()
    		{
    			if(stage)
    				addEventListener(Event.ADDED_TO_STAGE, init);
    			else
    				init();
    		}
    
    		private function init(e:Event = null):void
    		{
    			snake_vector = new Vector.<Element>;
    			markers_vector = new Vector.<Object>;
    			space_value = 2;
    			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!
    			dead = false;
    			min_elements = 10; //We will begin with 10 elements.
    			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;
    			apple.catchValue = 0; //pretty obvious
    			last_button_down = Keyboard.RIGHT; //The first direction of the snake is set in this variable
    			score = 0;
    			score_tf = new TextField(); //this is the TextField which shows our score.
    			this.addChild(score_tf);
    
    			for(var i:int=0;i<min_elements;++i)
    			{
    				snake_vector[i] = new Element(0x00AAFF,1,10,10);
    				snake_vector[i].direction = "R"; //The starting direction of the snake
    				if (i == 0)//first snake element
    				{
    					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
    					attachElement(snake_vector[i], (snake_vector[0].width + space_value) * 20, (snake_vector[0].height + space_value) * 10, snake_vector[i].direction);
    					snake_vector[0].alpha = 0.7;
    				}
    				else
    				{
    					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
    				}
    			}
    
    			placeApple(false);
    			timer.addEventListener(TimerEvent.TIMER, moveIt);
    			timer.start();
    		}	
    
    		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
    		{
    			if (dirOfLast == "R")
    			{
    				who.x = lastXPos - snake_vector[0].width - space_value;
    				who.y = lastYPos;
    			}
    			else if(dirOfLast == "L")
    			{
    				who.x = lastXPos + snake_vector[0].width + space_value;
    				who.y = lastYPos;
    			}
    			else if(dirOfLast == "U")
    			{
    				who.x = lastXPos;
    				who.y = lastYPos + snake_vector[0].height + space_value;
    			}
    			else if(dirOfLast == "D")
    			{
    				who.x = lastXPos;
    				who.y = lastYPos - snake_vector[0].height - space_value;
    			}
    			this.addChild(who);
    		}
    
    		private function placeApple(caught:Boolean = true):void
    		{
    			if (caught)
    				apple.catchValue += 10;
    
    			var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
    			var randomX:Number = Math.floor(Math.random()*boundsX);
    
    			var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
    			var randomY:Number = Math.floor(Math.random()*boundsY);
    
    			apple.x = randomX * (apple.width + space_value);
    			apple.y = randomY * (apple.height + space_value);
    
    			for(var i:uint=0;i<snake_vector.length-1;i++)
    			{
    				if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
    					placeApple(false);
    			}
    			if (!apple.stage)
    				this.addChild(apple);
    		}		
    
    		private function moveIt(e:TimerEvent):void
    		{
    			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
    			{
    				//This code runs if the snakes heads position and the apples position are the same
    			}
    
    			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
    			{
    				gameOver();
    			}
    
    			for (var i:int = 0; i < snake_vector.length; i++)
    			{
    				/*
    					START OF FOR BLOCK
    					This whole 'for' block will run as many times, as many elements the snake has.
    					If there are four snake parts, this whole for cycle will run four times.
    				*/
    
    				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
    				{
    					//If the snakes heads position is the same as any of the snake parts, this block will run
    					gameOver();
    				}
    
    				if (markers_vector.length > 0)
    				{
    					//if there are direction markers, this code runs
    				}
    
    				var DIRECTION:String = snake_vector[i].direction; //getting the direction of the current snake element.
    				switch (DIRECTION)
    				{
    					case "R" :
    						snake_vector[i].x += snake_vector[i].width + space_value;
    						break;
    					case "L" :
    						snake_vector[i].x -= snake_vector[i].width + space_value;
    						break;
    					case "D" :
    						snake_vector[i].y += snake_vector[i].height + space_value;
    						break;
    					case "U" :
    						snake_vector[i].y -= snake_vector[i].width + space_value;
    						break;
    				}
    
    				/*
    					END OF FOR BLOCK
    				*/
    			}
    
    		}
    
    		private function gameOver():void
    		{
    			dead = true;
    			timer.stop();
    			while (this.numChildren)
    				this.removeChildAt(0);
    			timer.removeEventListener(TimerEvent.TIMER,moveIt);
    			//stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
    			init();
    		}
    
    	}
    }
    


    Step 11: The directionChanged() Function

    We want to listen to the keyboard, so we can actually control the snake. For this we need to put some code into the init() function and the gameOver() function.

    Put this at the end of the init() function (setting up the listener function):

    stage.addEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
    

    And this at the end of the gameOver() function:

    stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
    

    Now create a new function:

    private function directionChanged(e:KeyboardEvent):void
    {
    	var m:Object = new Object(); //MARKER OBJECT
    	//this will be added to the markers_vector, and have the properties x,y, and type
    	//the type property will show us the direction. if it is set to right, whenever a snake's part hits it,
    	//the direction of that snake's part will be set to right also
    
    	if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT)
    	{
    		//If we pressed the LEFT arrow,
    		//and it was not the last key we pressed,
    		//and the last key pressed was not the RIGHT arrow either...
    		//Then this block of code will run
    	}
    	markers_vector.push(m); //we push the object into a vector, so we can acces to it later (in the moveIt() function)
    }
    

    What goes into the if block?

    • The direction of the head should be rewritten.
    • The marker object has to be set correctly.
    • The last_button variable should be set to the last button pressed.

    if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
    {
    	snake_vector[0].direction = "L";
    	m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
    	last_button_down = Keyboard.LEFT;
    }
    

    Repeat this three more times, and we will have this:

    private function directionChanged(e:KeyboardEvent):void
    		{
    			var m:Object = new Object(); //MARKER OBJECT
    
    			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT)
    			{
    				snake_vector[0].direction = "L";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
    				last_button_down = Keyboard.LEFT;
    			}
    			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT)
    			{
    				snake_vector[0].direction = "R";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
    				last_button_down = Keyboard.RIGHT;
    			}
    			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN)
    			{
    				snake_vector[0].direction = "U";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
    				last_button_down = Keyboard.UP;
    			}
    			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP)
    			{
    				snake_vector[0].direction = "D";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
    				last_button_down = Keyboard.DOWN;
    			}
    			markers_vector.push(m);
    		}
    

    We need one more thing to test it. In the moveIt() function we have something like this:

    				if (markers_vector.length > 0)
    				{
    					//if there are direction markers, this code runs
    				}
    

    Here we need another for loop, to check every snake’s part against every marker on the stage, and check whether they collide. If they do, we need to set the snake’s part’s direction to the marker’s type. If it’s the last snake part which collides with the marker, we need to remove the marker from the markers_vector, too, so the snake parts don’t collide with it any more.

    				if (markers_vector.length > 0)
    				{
    					for(var j:uint=0;j < markers_vector.length;j++)
    					{
    						if(snake_vector[i].x == markers_vector[j].x && snake_vector[i].y == markers_vector[j].y)
    						{
    							//setting the direction
    							snake_vector[i].direction = markers_vector[j].type;
    							if(i == snake_vector.length-1)
    							{
    								//if its the last snake_part
    								markers_vector.splice(j, 1);
    							}
    						}
    					}
    				}
    

    Now if you play with it it looks okay, but there is a bug in there. Remember what i said at the beginning of the tutorial?

    For instance, if the snake is going to the right and you press the down-left combo very fast, it will hit itself and restart the game.

    How do we correct this? Well it’s easy. We have our flag variable, and we will use that for this. We will only be able to change the directions of the snake when this is set to true (Default is false, check the init() function for that).

    So we need to change the directionChanged() function a little. The if blocks’ heads should be changed: add a && flag clause at the end of every ‘if’.

    		private function directionChanged(e:KeyboardEvent):void
    		{
    			var m:Object = new Object(); //MARKER OBJECT
    
    			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
    			{
    				snake_vector[0].direction = "L";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
    				last_button_down = Keyboard.LEFT;
    				flag = false;
    			}
    			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT && flag)
    			{
    				snake_vector[0].direction = "R";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
    				last_button_down = Keyboard.RIGHT;
    				flag = false;
    			}
    			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN && flag)
    			{
    				snake_vector[0].direction = "U";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
    				last_button_down = Keyboard.UP;
    				flag = false;
    			}
    			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP && flag)
    			{
    				snake_vector[0].direction = "D";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
    				last_button_down = Keyboard.DOWN;
    				flag = false;
    			}
    			markers_vector.push(m);
    		}
    

    If you test it now, it won’t work because the flag is always false.

    When do we need to set it to true then?

    After a move/tick we can allow the users to change directions, we just don’t want to change it twice in one tick. So put this at the very end of the moveIt() function:

    flag = true;
    

    Now test it, and there is no bug any more.


    Step 12: Finishing the Game

    Now the only thing we need to do is the ‘apple-check’

    Remember this at the very beginning of the moveIt() function?

    			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
    			{
    				//This code runs if the snake's head's position and the apple's position are the same
    			}
    

    This is what we need to do in there:

    • Call the placeApple() function. (We don’t set the parameter to false; we leave it as it is. The default is true.)
    • Show the current score
    • Attach a new element to the snake’s last part.

    			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
    			{
    				//calling the placeApple() function
    				placeApple();
    				//show the current Score
    				score += apple.catchValue;
    				score_tf.text = "Score:" + String(score);
    				//Attach a new snake Element
    				snake_vector.push(new Element(0x00AAFF,1,10,10));
    				snake_vector[snake_vector.length-1].direction = snake_vector[snake_vector.length-2].direction; //lastOneRichtung
    				//attachElement(who,lastXPos,lastYPos,lastDirection)
    				attachElement(snake_vector[snake_vector.length-1],
    									  (snake_vector[snake_vector.length-2].x),
    									  snake_vector[snake_vector.length-2].y,
    									  snake_vector[snake_vector.length-2].direction);
    			}
    

    Now everything should work fine. Try it out:

    Here is the whole Main class again:

    			package
    {
    	import flash.display.Sprite;
    	import flash.text.TextField;
    	import flash.utils.Timer;
    	import flash.events.TimerEvent;
    	import flash.ui.Keyboard;
    	import flash.events.KeyboardEvent;
    	import flash.events.MouseEvent;
    	import flash.events.Event;
    
    	import com.Element;
    
    	public class Main extends Sprite
    	{
    		//DO NOT GIVE THEM A VALUE HERE! Give them a value in the init() function
    		private var snake_vector:Vector.<Element>; //the snake's parts are held in here
    		private var markers_vector:Vector.<Object>; //the markers are held in here
    		private var timer:Timer;
    		private var dead:Boolean;
    		private var min_elements:int; //holds how many parts should the snake have at the beginning
    		private var apple:Element; //Our apple
    		private var space_value:Number; //space between the snake parts
    		private var last_button_down:uint; //the keyCode of the last button pressed
    		private var flag:Boolean; //is it allowed to change direction?
    		private var score:Number;
    		private var score_tf:TextField; //the Textfield showing the score
    
    		public function Main()
    		{
    			if(stage)
    				addEventListener(Event.ADDED_TO_STAGE, init);
    			else
    				init();
    		}
    
    		private function init(e:Event = null):void
    		{
    			snake_vector = new Vector.<Element>;
    			markers_vector = new Vector.<Object>;
    			space_value = 2;
    			timer = new Timer(50); //Every 50th millisecond, the moveIt() function will be fired!
    			dead = false;
    			min_elements = 1;
    			apple = new Element(0xFF0000, 1,10, 10); //red, not transparent, width:10, height: 10;
    			apple.catchValue = 0; //pretty obvious
    			last_button_down = Keyboard.RIGHT; //The starting direction of the snake (only change it if you change the 'for cycle' too.)
    			score = 0;
    			score_tf = new TextField();
    			this.addChild(score_tf);
    
    			//Create the first <min_elements> Snake parts
    			for(var i:int=0;i<min_elements;++i)
    			{
    				snake_vector[i] = new Element(0x00AAFF,1,10,10);
    				snake_vector[i].direction = "R"; //The starting direction of the snake
    				if (i == 0)
    				{
    					//you have to place the first element on a GRID. (now: 0,0) [possible x positions: (snake_vector[0].width+space_value)*<UINT> ]
    					attachElement(snake_vector[i],0,0,snake_vector[i].direction)
    					snake_vector[0].alpha = 0.7;
    				}
    				else
    				{
    					attachElement(snake_vector[i], snake_vector[i - 1].x, snake_vector[i - 1].y, snake_vector[i - 1].direction);
    				}
    			}
    
    			placeApple(false);
    			timer.addEventListener(TimerEvent.TIMER,moveIt);
    			stage.addEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
    			timer.start();
    		}
    
    		private function attachElement(who:Element,lastXPos:Number = 0,lastYPos:Number = 0,dirOfLast:String = "R"):void
    		{
    			if (dirOfLast == "R")
    			{
    				who.x = lastXPos - snake_vector[0].width - space_value;
    				who.y = lastYPos;
    			}
    			else if(dirOfLast == "L")
    			{
    				who.x = lastXPos + snake_vector[0].width + space_value;
    				who.y = lastYPos;
    			}
    			else if(dirOfLast == "U")
    			{
    				who.x = lastXPos;
    				who.y = lastYPos + snake_vector[0].height + space_value;
    			}
    			else if(dirOfLast == "D")
    			{
    				who.x = lastXPos;
    				who.y = lastYPos - snake_vector[0].height - space_value;
    			}
    			this.addChild(who);
    		}
    
    		private function placeApple(caught:Boolean = true):void
    		{
    			if (caught)
    				apple.catchValue += 10;
    
    			var boundsX:int = (Math.floor(stage.stageWidth / (snake_vector[0].width + space_value)))-1;
    			var randomX:Number = Math.floor(Math.random()*boundsX);
    
    			var boundsY:int = (Math.floor(stage.stageHeight/(snake_vector[0].height + space_value)))-1;
    			var randomY:Number = Math.floor(Math.random()*boundsY);
    
    			apple.x = randomX * (apple.width + space_value);
    			apple.y = randomY * (apple.height + space_value);
    
    			for(var i:uint=0;i<snake_vector.length-1;i++)
    			{
    				if(snake_vector[i].x == apple.x && snake_vector[i].y == apple.y)
    					placeApple(false);
    			}
    			if (!apple.stage)
    				this.addChild(apple);
    		}
    
    		private function moveIt(e:TimerEvent):void
    		{
    			if (snake_vector[0].x == apple.x && snake_vector[0].y == apple.y)
    			{
    				placeApple();
    				//show the current Score
    				score += apple.catchValue;
    				score_tf.text = "Score:" + String(score);
    				//Attach a new snake Element
    				snake_vector.push(new Element(0x00AAFF,1,10,10));
    				snake_vector[snake_vector.length-1].direction = snake_vector[snake_vector.length-2].direction; //lastOneRichtung
    				attachElement(snake_vector[snake_vector.length-1],
    									  (snake_vector[snake_vector.length-2].x),
    									  snake_vector[snake_vector.length-2].y,
    									  snake_vector[snake_vector.length-2].direction);
    			}
    			if (snake_vector[0].x > stage.stageWidth-snake_vector[0].width || snake_vector[0].x < 0 || snake_vector[0].y > stage.stageHeight-snake_vector[0].height || snake_vector[0].y < 0)
    			{
    				gameOver();
    			}
    
    			for (var i:int = 0; i < snake_vector.length; i++)
    			{
    				if (markers_vector.length > 0)
    				{
    					for(var j:uint=0;j < markers_vector.length;j++)
    					{
    						if(snake_vector[i].x == markers_vector[j].x && snake_vector[i].y == markers_vector[j].y)
    						{
    							snake_vector[i].direction = markers_vector[j].type;
    							if(i == snake_vector.length-1)
    							{
    								markers_vector.splice(j, 1);
    							}
    						}
    					}
    				}
    				if (snake_vector[i] != snake_vector[0] && (snake_vector[0].x == snake_vector[i].x && snake_vector[0].y == snake_vector[i].y))
    				{
    					gameOver();
    				}
    
    				//Move the boy
    				var DIRECTION:String = snake_vector[i].direction;
    				switch (DIRECTION)
    				{
    					case "R" :
    						snake_vector[i].x += snake_vector[i].width + space_value;
    						break;
    					case "L" :
    						snake_vector[i].x -= snake_vector[i].width + space_value;
    						break;
    					case "D" :
    						snake_vector[i].y += snake_vector[i].height + space_value;
    						break;
    					case "U" :
    						snake_vector[i].y -= snake_vector[i].width + space_value;
    						break;
    				}
    
    			}
    
    			flag = true;
    		}
    
    		private function gameOver():void
    		{
    			dead = true;
    			timer.stop();
    			while (this.numChildren)
    				this.removeChildAt(0);
    			timer.removeEventListener(TimerEvent.TIMER,moveIt);
    			stage.removeEventListener(KeyboardEvent.KEY_DOWN,directionChanged);
    			init();
    		}
    
    		private function directionChanged(e:KeyboardEvent):void
    		{
    			var m:Object = new Object(); //MARKER OBJECT
    
    			if (e.keyCode == Keyboard.LEFT && last_button_down != e.keyCode && last_button_down != Keyboard.RIGHT && flag)
    			{
    				snake_vector[0].direction = "L";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"L"};
    				last_button_down = Keyboard.LEFT;
    				flag = false;
    			}
    			else if (e.keyCode == Keyboard.RIGHT && last_button_down != e.keyCode && last_button_down != Keyboard.LEFT && flag)
    			{
    				snake_vector[0].direction = "R";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"R"};
    				last_button_down = Keyboard.RIGHT;
    				flag = false;
    			}
    			else if (e.keyCode == Keyboard.UP && last_button_down != e.keyCode && last_button_down != Keyboard.DOWN && flag)
    			{
    				snake_vector[0].direction = "U";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"U"};
    				last_button_down = Keyboard.UP;
    				flag = false;
    			}
    			else if (e.keyCode == Keyboard.DOWN && last_button_down != e.keyCode && last_button_down != Keyboard.UP && flag)
    			{
    				snake_vector[0].direction = "D";
    				m = {x:snake_vector[0].x, y:snake_vector[0].y, type:"D"};
    				last_button_down = Keyboard.DOWN;
    				flag = false;
    			}
    			markers_vector.push(m);
    		}
    
    	}
    
    }
    


    Step 13: Summing It All Up

    Congratulations! You have just created a nice game. Now you can develop it further, and create a super apple or something. For that I recommend using another function called placeSuperApple() and a new class named SuperApple. Whenever you catch a super apple, the snakes parts could lengthen by three elements, perhaps. This could be set with setters/getters in the SuperApple class.

    If you wish to do this, and you get stuck somewhere, just leave me a comment here.

    Thank you for your time!


  8. Carlos Yanez says:
    March 12, 2012 at 12:27 am

    In this tutorial you’ll learn how to create a spinning wheel using Flash and AS3, with an interface that’s suitable for both mouse- and touch-based devices.


    Final Result Preview

    Let’s take a look at the final result we will be working towards:

    Click and drag your mouse vertically to spin the wheel; the longer the line you drag, the faster the wheel will spin! Once it stops, the colored bar at the bottom will display the color the wheel landed on.


    Step 1: Brief Overview

    Using pre-made graphic elements we’ll create a colorful interface that will be powered by several ActionScript 3 classes.

    The user will be able to spin the wheel using a dragging gesture represented by a line on the screen; a taller line will make a faster spin.


    Step 2: Flash Document Settings

    Open Flash and create a 500x300px document. Set the frame rate to 24fps.


    Step 3: Interface

    A colorful nice looking interface will be displayed, made up of multiple shapes, MovieClips and more.
    The simple shapes were created using the Flash Pro drawing tools, and since they’re easy to duplicate I won’t explain their creation. Make sure the wheel’s rotation point is in the center.

    You can always look at the FLA in the source download files.


    Step 4: Instance Names

    The image above shows the Instance Names of the various MovieClips. Pay special attention to the wheel.p MovieClips; these are the little black lines that divide the colors in the wheel, and are inside the wheel MovieClip. They are named p1 to p10, going clockwise.


    Step 5: TweenMax

    We’ll use a different tween engine than the default one included in Flash; this will make the color transition of the colorMC symbol a lot easier.

    You can download TweenMax from the Greensock website.


    Step 6: Set Main Class

    Add the class name, Main, to the Class field in the Publish section of the Properties panel to associate the FLA with the Main document class.


    Step 7: Create a new ActionScript Class

    Create a new (Cmd + N) ActionScript 3.0 Class and save it as Main.as in your class folder.


    Step 8: Class Structure

    Create your basic class structure to begin writing your code.

    
    
    package
    {
    	import flash.display.Sprite;
    
    	public class Main extends Sprite
    	{
    		public function Main():void
    		{
    			// constructor code
    		}
    	}
    }
    

    Step 9: Required Classes

    These are the classes we’ll need to import for our class to work. The import directive makes externally defined classes and packages available to your code.

    
    
    import flash.display.Sprite;
    import flash.display.Shape;
    import flash.events.MouseEvent;
    import flash.events.Event;
    import com.greensock.TweenMax;
    

    Step 10: Variables

    These are the variables we’ll use; read the comments in the code to know more about them:

    
    
    private var speed:Number = 0; //the current speed of the wheel
    private var paddles:Vector.<Sprite> = new Vector.<Sprite>(); //a vector that holds the p1, p2 etc MCs in the stage
    private var line:Shape; //the line drawn as the gesture to move the wheel
    private var lastPaddle:String; //will detect the current value of the wheel
    

    Step 11: Constructor

    The constructor is a function that runs when an object is created from a class, and is the first to execute when you make an instance of an object. Since this is our document class, it’ll run as soon as the SWF loads.

    
    
    public final function Main():void
    {
    	//code...
    }
    

    Step 12: Paddles Vector

    First we add the various paddle MovieClips to the vector, and add the listeners – we’ll write the listeners() function next.

    
    
    public final function Main():void
    {
    	paddles.push(wheel.p1, wheel.p2, wheel.p3, wheel.p4, wheel.p5, wheel.p6, wheel.p7, wheel.p8, wheel.p9, wheel.p10);
    	listeners('add');
    }
    

    Step 13: Listeners

    This function will add or remove the listeners according to the parameter. Mouse Listeners are set to draw the line that will control the wheel.

    
    
    private final function listeners(action:String):void
    {
    	if(action == 'add')
    	{
    		stage.addEventListener(MouseEvent.MOUSE_DOWN, startDraw);
    		stage.addEventListener(MouseEvent.MOUSE_UP, spinWheel);
    	}
    	else
    	{
    		stage.removeEventListener(MouseEvent.MOUSE_DOWN, startDraw);
    		stage.removeEventListener(MouseEvent.MOUSE_UP, spinWheel);
    	}
    }
    

    Step 14: Movement Line

    The next function starts to create a line based on the current mouse position, and places it on the stage. It’s triggered when the mouse is clicked.

    
    
    private final function startDraw(e:MouseEvent):void
    {
    	line = new Shape();
    	addChild(line);
    
    	line.graphics.moveTo(mouseX, mouseY);
    	line.graphics.lineStyle(8, 0x000000, 0.3);//you can change the line color and style here
    	stage.addEventListener(MouseEvent.MOUSE_MOVE, drawLine);
    }
    

    Step 15: Draw Line

    While the mouse is moved, the line continues in that direction.

    
    
    private final function drawLine(e:MouseEvent):void
    {
    	line.graphics.lineTo(mouseX, mouseY);
    }
    

    Step 16: Spin the Wheel

    The next code runs when the mouse button is released, finishing the line. The drawing listeners are removed to avoid drawing multiple lines and the speed is calculated according to the height of the line. Finally, an EnterFrame event is called to actually rotate the wheel.

    
    
    private final function spinWheel(e:MouseEvent):void
    {
    	stage.removeEventListener(MouseEvent.MOUSE_MOVE, drawLine);
    	listeners('rm');
    
    	speed = line.height * 0.1;
    	removeChild(line);
    	line = null;
    
    	stage.addEventListener(Event.ENTER_FRAME, spin);
    }
    

    Step 17: Rotate the Wheel

    This is the function that will spin the wheel and detect what value it lands on:

    
    
    private final function spin(e:Event):void
    {
    	/* Rotate Wheel */
    
    	wheel.rotationZ += speed;
    

    Step 18: Detect Value

    Here we detect the current value of the wheel based on the last paddle it touched.

    
    
    /* Detect Value */
    
    for(var i:int = 0; i < 10; i++)
    {
    	if(indicator.hArea.hitTestObject(paddles[i]))
    	{
    		lastPaddle = paddles[i].name;
    	}
    }
    

    Step 19: Decrease Speed

    The wheel’s speed is reduced every frame to eventually stop the spinning.

    
    
    /* Decrease speed */
    
    speed -= 0.1;
    

    Step 20: Reset Wheel

    All values are reset when the wheel stops. A function that will run an action according to the final value is called.

    
    
    	/* Remove listener and reset speed when wheel stops */
    
    	if(speed <= 0)
    	{
    		stage.removeEventListener(Event.ENTER_FRAME, spin);
    		speed = 0;
    		setBarColor(lastPaddle);
    		listeners('add');
    	}
    }
    

    Step 21: Set Bar Color

    This function will run a custom action according to the last value of the wheel. In this case it changes the color of the bottom bar, but you could make it do anything else.

    
    
    function setBarColor(action:String):void
    {
    	switch(action)
    	{
    		case 'p1':
    			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0xF15D5D, tintAmount:1}});
    			break;
    		case 'p2':
    			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0xC06CA8, tintAmount:1}});
    			break;
    		case 'p3':
    			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x644D9B, tintAmount:1}});
    			break;
    		case 'p4':
    			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x5E98C6, tintAmount:1}});
    			break;
    		case 'p5':
    			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x4789C2, tintAmount:1}});
    			break;
    		case 'p6':
    			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x55C4CB, tintAmount:1}});
    			break;
    		case 'p7':
    			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x57BC80, tintAmount:1}});
    			break;
    		case 'p8':
    			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0x90CC6C, tintAmount:1}});
    		break;
    		case 'p9':
    			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0xEBE666, tintAmount:1}});
    		break;
    		case 'p10':
    			TweenMax.to(colorMC, 0.5, {colorTransform:{tint:0xF29C69, tintAmount:1}});
    			break;
    	}
    }
    

    Conclusion

    Change the code to perform your own actions!

    I hope you liked this tutorial, thank you for reading!


  9. Carlos Yanez says:
    March 12, 2012 at 12:27 am

    It’s Premium time again! To kick off our Shoot-’Em-Up Session (and to follow up on last year’s Flash and AS3 shooter tutorial), Carlos Yanez has written a tutorial that’ll teach you how to create a simple space shooter game with HTML5 and the EaselJS library.


    Premium Preview

    Let’s take a look at the final result we will be working towards:



    Click to play the demo.

    Last year, I showed you how to create a shoot-’em-up game with Flash and AS3. With HTML5′s rise in popularity (and capabilities), let’s take a look at how to do the same with HTML5, JavaScript, and EaselJS (a Flash-based library for HTML5 development).


    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 of the tutorial.


    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 download 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.


  10. Cody Sandahl says:
    March 12, 2012 at 1:00 am

    In this tutorial we will go from asking “What is Flixel?” to having an indoor room and a keyboard-controlled character in the top-down role playing game style (think Zelda).


    Final Result Preview

    Let’s take a look at the final result we will be working towards:


    Step 1: Understanding the Project Structure

    For the visual people among us, let’s see how everything will be organized so the rest will make sense.

    list of all the source files and folders used in the project

    Basically, we have all of our artwork stored in the assets folder and all of our ActionScript files stored in the src folder. If you want to use this tutorial as the basis for your own game engine, the topdown folder contains the generic stuff (a.k.a. the engine) and the tutorial folder shows how to use it.

    You’ll probably notice rather quickly that the art files have really long names. Rather than showing you a tutorial filled with compelling red boxes (the apex of my artistic ability), we will use some open source artwork from OpenGameArt. Each file is named to show the source, the artist, and the license. So, for example, armor (opengameart - Redshrike - ccby30).png means it’s an image of armor, downloaded from OpenGameArt, created by the artist known as Redshrike, and it uses the CC-BY-30 license (Creative Commons Attribution).

    Long story short – these art files can be used for any purpose as long as we link back to the site and give credit to the artist.

    Here’s a description of each source file in the project:

    • topdown/TopDownEntity.as – base class for any moveable sprites in our top-down RPG
    • topdown/TopDownLevel.as – base class for a top-down RPG level
    • tutorial/Assets.as – imports any images that we need to use in this tutorial
    • tutorial/IndoorHouseLevel.as – defines an indoor room with some objects lying around
    • tutorial/Player.as – a keyboard-controlled, animated Ranger
    • tutorial/PlayState.as – Flixel state that controls our game
    • Default.css – an empty file needed to prevent the Flex compiler from giving us a warning
    • Main.as – entry point for the application
    • Preloader.as – Flixel preloader

    Now let’s get down to business!


    Step 2: Firing Up Flixel

    Flixel is a 2D game engine for ActionScript 3. To quote the home page:

    Flixel is an open source game-making library that is completely free for personal or commercial use.

    The most important thing to know about Flixel is that it is designed to use bitmap images (raster graphics) instead of Flash-style vector graphics. You can use Flash movie clips, but it takes a little massaging. Since I don’t feel like giving a massage today, we will be using images for all our art.

    Flixel comes with a tool that creates a dummy project for you. This tool creates the three files that are in the root of our project: Default.css, Main.as, and Preloader.as. These three files form the basis for almost any project in Flixel. Since Default.css is just there to avoid a compiler warning, let’s take a look at Main.as.

    
    
    package
    {
    	import org.flixel.*;
    	import tutorial.*;
    
    	[SWF(width="480", height="480", backgroundColor="#ffffff")]
    	[Frame(factoryClass="Preloader")]
    	public class Main extends FlxGame
    	{
    		/**
    		 * Constructor
    		 */
    		public function Main() {
    			super(240, 240, PlayState, 2);
    		}
    	}
    }
    

    There are only three lines of importance here. First off, we tell Flash to use a 480×480 window with a white background. Then we tell Flash to use our Preloader class while loading. Finally, we tell Flixel to use a 240×240 window (zooming in by a factor of 2 to make things look bigger) and to use PlayState once everything is ready to go.

    Let me share a quick word about Flixel’s states. In Flixel, states are kind of like a window, but you can only have one at a time. So, for example, you could have a state for your game’s main menu (MainMenu), and when a user clicks the Start Game button you switch to PlayState. Since we want our game to just get going immediately, we just need one state (PlayState).

    Next up is Preloader.as.

    
    
    package
    {
    	import org.flixel.system.FlxPreloader;
    
    	public class Preloader extends FlxPreloader
    	{
    		/**
    		 * Constructor
    		 */
    		public function Preloader():void {
    			className = "Main";
    			super();
    		}
    	}
    }
    

    Not much to see here. Since we extend from FlxPreloader, Flixel really just takes care of it. The only thing to note is that if you changed Main to some other name, you would have to change className here on the highlighted line.

    We’re almost up to seeing something on the screen now. All we need is a Flixel state to get the ball rolling, so here’s PlayState.as.

    
    
    package tutorial
    {
    	import org.flixel.*;
    
    	/**
    	 * State for actually playing the game
    	 * @author Cody Sandahl
    	 */
    	public class PlayState extends FlxState
    	{
    		/**
    		 * Create state
    		 */
    		override public function create():void {
    			FlxG.mouse.show();
    		}
    	}
    }
    

    If you compiled this code, you’d get a marvelous black screen with a mouse cursor. Never fear, it gets better from here.


    Step 3: Creating a Basic Level

    Now that we have Flixel up and running, it’s time to make a top-down RPG level. I like to give you reusable classes so you can make your own levels, so we’ll actually create a generic level class that we can use to make something more interesting later. This is topdown/TopDownLevel.as.

    
    
    package topdown
    {
    	import org.flixel.*;
    	/**
    	 * Base class for all levels
    	 * @author Cody Sandahl
    	 */
    	public class TopDownLevel extends FlxGroup
    	{
    		/**
    		 * Map
    		 */
    		public var state:FlxState; // state displaying the level
    		public var levelSize:FlxPoint; // width and height of level (in pixels)
    		public var tileSize:FlxPoint; // default width and height of each tile (in pixels)
    		public var numTiles:FlxPoint; // how many tiles are in this level (width and height)
    		public var floorGroup:FlxGroup; // floor (rendered beneath the walls - no collisions)
    		public var wallGroup:FlxGroup; // all the map blocks (with collisions)
    		public var guiGroup:FlxGroup; // gui elements
    
    		/**
    		 * Player
    		 */
    		public var player:TopDownEntity;
    		public var playerStart:FlxPoint = new FlxPoint(120, 120);
    
    		/**
    		 * Constructor
    		 * @param	state		State displaying the level
    		 * @param	levelSize	Width and height of level (in pixels)
    		 * @param	blockSize	Default width and height of each tile (in pixels)
    		 */
    		public function TopDownLevel(state:FlxState, levelSize:FlxPoint, tileSize:FlxPoint):void {
    			super();
    			this.state = state;
    			this.levelSize = levelSize;
    			this.tileSize = tileSize;
    			if (levelSize && tileSize)
    				this.numTiles = new FlxPoint(Math.floor(levelSize.x / tileSize.x), Math.floor(levelSize.y / tileSize.y));
    			// setup groups
    			this.floorGroup = new FlxGroup();
    			this.wallGroup = new FlxGroup();
    			this.guiGroup = new FlxGroup();
    			// create the level
    			this.create();
    		}
    
    		/**
    		 * Create the whole level, including all sprites, maps, blocks, etc
    		 */
    		public function create():void {
    			createMap();
    			createPlayer();
    			createGUI();
    			addGroups();
    			createCamera();
    		}
    
    		/**
    		 * Create the map (walls, decals, etc)
    		 */
    		protected function createMap():void {
    		}
    
    		/**
    		 * Create the player, bullets, etc
    		 */
    		protected function createPlayer():void {
    			player = new TopDownEntity(playerStart.x, playerStart.y);
    		}
    
    		/**
    		 * Create text, buttons, indicators, etc
    		 */
    		protected function createGUI():void {
    		}
    
    		/**
    		 * Decide the order of the groups. They are rendered in the order they're added, so last added is always on top.
    		 */
    		protected function addGroups():void {
    			add(floorGroup);
    			add(wallGroup);
    			add(player);
    			add(guiGroup);
    		}
    
    		/**
    		 * Create the default camera for this level
    		 */
    		protected function createCamera():void {
    			FlxG.worldBounds = new FlxRect(0, 0, levelSize.x, levelSize.y);
    			FlxG.camera.setBounds(0, 0, levelSize.x, levelSize.y, true);
    			FlxG.camera.follow(player, FlxCamera.STYLE_TOPDOWN);
    		}
    
    		/**
    		 * Update each timestep
    		 */
    		override public function update():void {
    			super.update();
    			FlxG.collide(wallGroup, player);
    		}
    	}
    }
    

    All of the variables have their own descriptions in the source code, so I won’t bore you with too much repetition. I should, however, explain groups in Flixel.

    We have three groups defined here: floorGroup, wallGroup, and guiGroup. Flixel uses groups to determine in what order to render sprites (to decide what’s on top when they overlap) and to handle collisions. We want the player to be able to walk around on a floor (no collisions needed), but we also want walls and objects (collisions definitely needed) so we need two groups. We also need a separate group for our user interface (guiGroup) so we can make sure it gets rendered on top of everything else.

    Groups are rendered in the order they are added, which is determined in our addGroups() function. Since we want guiGroup to always be on top, we call add(guiGroup) after all the other groups. If you make your own groups and forget to call add(), they won’t show up on the screen.

    In our constructor, we store some useful values (like the number of tiles in the level) and call create(). The create() function shows you what goes into a Flixel level – a map, a player, an interface, groups (to control rendering order and collisions), and a camera view. Each of these gets its own function to help keep things more readable and so we can re-use common functionality. For instance, take a look at createCamera().

    
    
    /**
     * Create the default camera for this level
     */
    protected function createCamera():void {
        FlxG.worldBounds = new FlxRect(0, 0, levelSize.x, levelSize.y);
        FlxG.camera.setBounds(0, 0, levelSize.x, levelSize.y, true);
        FlxG.camera.follow(player, FlxCamera.STYLE_TOPDOWN);
    }
    

    We won’t need to change this function to make our own indoor level. Flixel has a built-in camera for top-down games (FlxCamera.STYLE_TOPDOWN). All we’re really doing here is telling the camera not to leave the level (by calling setBounds()) and telling the camera to follow the player (by calling follow()) if the level is bigger than the screen and requires scrolling. This will work for almost every kind of level, so we can keep it here rather than re-coding this for each of our levels.

    The only other thing to notice is in update().

    
    
    /**
     * Update each timestep
     */
    override public function update():void {
        super.update();
        FlxG.collide(wallGroup, player);
    }
    

    FlxG.collide(wallGroup, player) causes the player to bump into walls rather than walking through them. Since we don’t call FlxG.collide(floorGroup, player), the player can walk all over the floors with nary a collision in sight (same thing for guiGroup, too).

    Finally, we need to make PlayState use our fancy level.

    
    
    package tutorial
    {
    	import org.flixel.*;
    	import topdown.*;
    
    	/**
    	 * State for actually playing the game
    	 * @author Cody Sandahl
    	 */
    	public class PlayState extends FlxState
    	{
    		/**
    		 * Constants
    		 */
    		public static var LEVEL_SIZE:FlxPoint = new FlxPoint(240, 240); // level size (in pixels)
    		public static var BLOCK_SIZE:FlxPoint = new FlxPoint(16, 16); // block size (in pixels)
    
    		/**
    		 * Current level
    		 * NOTE: "public static" allows us to get info about the level from other classes
    		 */
    		public static var LEVEL:TopDownLevel = null;
    
    		/**
    		 * Create state
    		 */
    		override public function create():void {
    			FlxG.mouse.show();
    			// load level
    			LEVEL = new TopDownLevel(this, LEVEL_SIZE, BLOCK_SIZE);
    			this.add(LEVEL);
    		}
    	}
    }
    

    Remember to call this.add(LEVEL) unless you want to stare at a black screen forever. As the comment states, I used public static var LEVEL as a convenience for the future. Suppose you add some artificial intelligence to your game and your AI needs to know where the player is located; this way, you can call PlayState.LEVEL.player and keep things nice and easy. It’s not necessarily the prettiest way to do things, but it’ll get the job done if used sparingly.


    Step 4: Creating a Basic Entity

    An entity is something that needs to be displayed and can move around. This could be the player, a computer-controlled character, or perhaps even something like an arrow. Since there can be many entities on a level, we want a generic class that we can use to save ourselves some time. Take a look at topdown/TopDownEntity.as.

    
    
    package topdown
    {
    	import org.flixel.*;
    
    	/**
    	 * A moveable object in the game (player, enemy, NPC, etc)
    	 * @author Cody Sandahl
    	 */
    	public class TopDownEntity extends FlxSprite
    	{
    		/**
    		 * Constants
    		 */
    		public static const SIZE:FlxPoint = new FlxPoint(16, 18); // size in pixels
    
    		/**
    		 * Constructor
    		 * @param	X	X location of the entity
    		 * @param	Y	Y location of the entity
    		 */
    		public function TopDownEntity(X:Number = 100, Y:Number = 100):void {
    			super(X, Y);
    			makeGraphic(SIZE.x, SIZE.y, 0xFFFF0000); // use this if you want a generic box graphic by default
    		}
    	}
    }
    

    Notice that we extend from FlxSprite. This gives us access to much of the power behind Flixel. makeGraphic() creates a rectangular bitmap of the given size (16×18 in this case), using the color you pass in. This color is in 0xAARRGGBB format, so 0xFFFF0000 means we’re creating a solid red box (I warned you about my artistic abilities). You can mess around with this value to see how the color changes. In fact, we now have something other than a blank screen!

    our first entity - a black screen with a red box in the middle

    Still not too exciting, but at least we can see something, right?


    Step 5: Creating an Indoor Room

    I don’t know about you, but I’m tired of looking at that black background. Let’s make it look like a room. Here’s tutorial/IndoorHouseLevel.as.

    
    
    package tutorial
    {
    	import org.flixel.*;
    	import topdown.*;
    
    	/**
    	 * A basic indoor scene
    	 * @author Cody Sandahl
    	 */
    	public class IndoorHouseLevel extends TopDownLevel
    	{
    		/**
    		 * Floor layer
    		 */
    		protected static var FLOORS:Array = new Array(
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    		);
    
    		/**
    		 * Wall layer
    		 */
    		protected static var WALLS:Array = new Array(
    			1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
    			2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
    		);
    
    		/**
    		 * Constructor
    		 * @param	state		State displaying the level
    		 * @param	levelSize	Width and height of level (in pixels)
    		 * @param	blockSize	Default width and height of each tile (in pixels)
    		 */
    		public function IndoorHouseLevel(state:FlxState, levelSize:FlxPoint, blockSize:FlxPoint):void {
    			super(state, levelSize, blockSize);
    		}
    
    		/**
    		 * Create the map (walls, decals, etc)
    		 */
    		override protected function createMap():void {
    			var tiles:FlxTilemap;
    			// floors
    			tiles = new FlxTilemap();
    			tiles.loadMap(
    				FlxTilemap.arrayToCSV(FLOORS, 15), // convert our array of tile indices to a format flixel understands
    				Assets.FLOORS_TILE, // image to use
    				tileSize.x, // width of each tile (in pixels)
    				tileSize.y, // height of each tile (in pixels)
    				0, // don't use auto tiling (needed so we can change the rest of these values)
    				0, // starting index for our tileset (0 = include everything in the image)
    				0, // starting index for drawing our tileset (0 = every tile is drawn)
    				uint.MAX_VALUE // which tiles allow collisions by default (uint.MAX_VALUE = no collisions)
    			);
    			floorGroup.add(tiles);
    			// walls
    			// FFV: make left/right walls' use custom collision rects
    			tiles = new FlxTilemap();
    			tiles.loadMap(
    				FlxTilemap.arrayToCSV(WALLS, 15), // convert our array of tile indices to a format flixel understands
    				Assets.WALLS_TILE, // image to use
    				tileSize.x, // width of each tile (in pixels)
    				tileSize.y // height of each tile (in pixels)
    			);
    			wallGroup.add(tiles);
    		}
    	}
    }
    

    The first thing you notice are those two giant arrays of numbers, FLOORS and WALLS. These arrays define our map layers. The numbers are tile indices based on the artwork we’re using. I’ve zoomed in on the image we’re using for our walls to show you what I’m talking about.

    sprite sheet for the walls in our level

    Notice that zero is blank (draw nothing). The floor image, on the other hand, is just one tile, repeated, at the moment. That means we want to draw every tile (including zero). So if you look at the createMap() function, our code to load in the floor is longer than our code to load in the walls.

    We start with FlxTilemap.arrayToCSV(FLOORS, 15), which converts our big array into a format Flixel likes (CSV). The number at the end tells Flixel how many values are in each row. Next we tell Flixel which image to use (Assets.FLOORS_TILE – I’ll explain what that’s about in the next step). After defining the size of each block in the image, we have four more values for our floor than for our walls. Since we want all the tiles (including zero) drawn for our floor, we need to pass in these extra values.

    The only one that’s a little weird is the last: uint.MAX_VALUE. Every tile number (zero through the number of tiles in our image) that is above the number passed at this parameter will be marked for collisions. Everything below this number will ignore collisions by default. So, if you had a wall that the player could walk through, you could put it at the end of your image (a high index) and use this value to have Flixel ignore collisions. Since we never want any collisions to happen with the floor, we use uint.MAX_VALUE because every tile index will be below this value and will therefore not have collisions.

    Finally, we have to remember to add our tilemaps to a group or they won’t show up on the screen. Before we can run the project, though, we need to load in our artwork.


    Step 6: Loading Assets

    Since we’re using images, we need to let Flash know about them. One of the more straightforward ways to do this is by embedding them in your SWF. Here’s how we’re doing that in this project (found in tutorial/Assets.as).

    
    
    package tutorial
    {
    	import flash.utils.ByteArray;
    	/**
    	 * Embeds and imports all assets for the game
    	 * @author Cody Sandahl
    	 */
    	public class Assets
    	{
    		// sprites
    		[Embed(source = "../../assets/sprites/ranger (opengameart - Antifarea - ccby30).png")] public static var RANGER_SPRITE:Class;
    		[Embed(source = "../../assets/sprites/rug1 (opengameart - Redshrike - ccby30).png")] public static var RUG1_SPRITE:Class;
    		[Embed(source = "../../assets/sprites/rug2 (opengameart - Redshrike - ccby30).png")] public static var RUG2_SPRITE:Class;
    		[Embed(source = "../../assets/sprites/bookcase (opengameart - Redshrike - ccby30).png")] public static var BOOKCASE_SPRITE:Class;
    		[Embed(source = "../../assets/sprites/chair_down (opengameart - Redshrike - ccby30).png")] public static var CHAIRDOWN_SPRITE:Class;
    		[Embed(source = "../../assets/sprites/chair_left (opengameart - Redshrike - ccby30).png")] public static var CHAIRLEFT_SPRITE:Class;
    		[Embed(source = "../../assets/sprites/chair_right (opengameart - Redshrike - ccby30).png")] public static var CHAIRRIGHT_SPRITE:Class;
    		[Embed(source = "../../assets/sprites/chair_up (opengameart - Redshrike - ccby30).png")] public static var CHAIRUP_SPRITE:Class;
    		[Embed(source = "../../assets/sprites/table_round (opengameart - Redshrike - ccby30).png")] public static var TABLEROUND_SPRITE:Class;
    		[Embed(source = "../../assets/sprites/armor (opengameart - Redshrike - ccby30).png")] public static var ARMOR_SPRITE:Class;
    		[Embed(source = "../../assets/sprites/bed (opengameart - Redshrike - ccby30).png")] public static var BED_SPRITE:Class;
    
    		// tiles
    		[Embed(source = "../../assets/tiles/walls (opengameart - daniel siegmund - ccby30).png")] public static var WALLS_TILE:Class;
    		[Embed(source = "../../assets/tiles/floor_wood (opengameart - Redshrike - ccby30).png")] public static var FLOORS_TILE:Class;
    	}
    }
    

    I’m giving you all the artwork at once, because it’s not all that complicated once you get the hang of it. Let’s take a look at the highlighted lines. Here we are loading in two images: one for our walls and one for our floor. If you remember in the last step, we told Flixel to use Assets.WALLS_TILE and Assets.FLOORS_TILE when loading in the map layers. This is where we define those variables.

    Notice that we use a path relative to the Assets.as file. You can also embed things like XML files, SWF files, and a ton of other assets. All we need, however, are images. For more information on embedding assets in Flash, check out this article from the Nightspade blog.

    Now that we have our images embedded and accessible, we can tell PlayState.as to use our newfangled level.

    
    
    /**
     * Create state
     */
    override public function create():void {
        FlxG.mouse.show();
        // load level
        LEVEL = new IndoorHouseLevel(this, LEVEL_SIZE, BLOCK_SIZE);
        this.add(LEVEL);
    }
    

    We changed the highlighted line from using TopDownLevel to using our new IndoorHouseLevel. Now if you run the project you should see something that looks a bit more like a room.

    an indoor room with wooden floors, stone walls, and a red square player in the middle

    Step 7: Adding Objects and Eye Candy

    It might be a room, but it’s a boring room. Let’s spruce it up a bit with some furniture. First, we need some more groups and some variables inside IndoorHouseLevel.

    
    
    /**
     * Custom groups
     */
    protected var decalGroup:FlxGroup; // extra decorative elements (no collisions)
    protected var objectGroup:FlxGroup; // objects and obstacles (with collisions)
    
    /**
     * Game objects
     */
    protected var bookcase:FlxSprite;
    protected var armor:FlxSprite;
    protected var table:FlxSprite;
    protected var bed:FlxSprite;
    

    decalGroup will allow us to add some rugs (purely visual eye candy), while objectGroup will allow us to add some furniture that will get in the player’s way. The other variables are the pieces of furniture we will be adding in a moment.

    Next, we need to add these objects to the level. We’re adding the highlighted line and everything beneath it.

    
    
    /**
     * Create the map (walls, decals, etc)
     */
    override protected function createMap():void {
        var tiles:FlxTilemap;
        // floors
        tiles = new FlxTilemap();
        tiles.loadMap(
            FlxTilemap.arrayToCSV(FLOORS, 15), // convert our array of tile indices to a format flixel understands
            Assets.FLOORS_TILE, // image to use
            tileSize.x, // width of each tile (in pixels)
            tileSize.y, // height of each tile (in pixels)
            0, // don't use auto tiling (needed so we can change the rest of these values)
            0, // starting index for our tileset (0 = include everything in the image)
            0, // starting index for drawing our tileset (0 = every tile is drawn)
            uint.MAX_VALUE // which tiles allow collisions by default (uint.MAX_VALUE = no collisions)
        );
        floorGroup.add(tiles);
        // walls
        // FFV: make left/right walls' use custom collision rects
        tiles = new FlxTilemap();
        tiles.loadMap(
            FlxTilemap.arrayToCSV(WALLS, 15), // convert our array of tile indices to a format flixel understands
            Assets.WALLS_TILE, // image to use
            tileSize.x, // width of each tile (in pixels)
            tileSize.y // height of each tile (in pixels)
        );
        wallGroup.add(tiles);
        // objects
        createObjects();
    }
    
    /**
     * Add all the objects, obstacles, etc to the level
     */
    protected function createObjects():void {
        var sprite:FlxSprite;
        // create custom groups
        decalGroup = new FlxGroup();
        objectGroup = new FlxGroup();
        // decals (decorative elements that have no functionality)
        sprite = new FlxSprite(
            16, // x location
            16, // y location
            Assets.RUG1_SPRITE // image to use
        );
        decalGroup.add(sprite);
    
        sprite = new FlxSprite(
            11 * tileSize.x, // x location (using tileSize to align it with a tile)
            1.5 * tileSize.y, // y location (showing that you don't need to line up with a tile)
            Assets.RUG2_SPRITE // image to use
        );
        decalGroup.add(sprite);
        // objects and obstacles
        // NOTE: this group gets tested for collisions
        bookcase = new FlxSprite(
            32, // x location
            0, // y location (showing that you can overlap with the walls if you want)
            Assets.BOOKCASE_SPRITE // image to use
        );
        bookcase.immovable = true; // don't allow the player to move this object
        objectGroup.add(bookcase);
    
        table = new FlxSprite(192, 192, Assets.TABLEROUND_SPRITE);
        table.immovable = true;
        objectGroup.add(table);
    
        sprite = new FlxSprite(176, 192, Assets.CHAIRRIGHT_SPRITE);
        sprite.immovable = true;
        objectGroup.add(sprite);
    
        sprite = new FlxSprite(216, 192, Assets.CHAIRLEFT_SPRITE);
        sprite.immovable = true;
        objectGroup.add(sprite);
    
        armor = new FlxSprite(192, 0, Assets.ARMOR_SPRITE);
        armor.immovable = true;
        objectGroup.add(armor);
    
        bed = new FlxSprite(16, 192, Assets.BED_SPRITE);
        bed.immovable = true;
        objectGroup.add(bed);
    }
    

    I’m using an extra function, createObjects(), simply to keep things easier to read. The comments explain each individual object, but let me offer a few general observations. First, we always need to remember to call add() for each object or it won’t get displayed. In addition, we need to use the right group (mapGroup, floorGroup, decalGroup, objectGroup, etc.) when calling add() or it will mess up our render order and our collision detection.

    Also take notice of all the various ways we can decide where to place our objects and decals. We can hard code the values (like we do with the first rug), we can use tileSize to align it with the floor and wall tiles (like we do with the second rug), and we can mix and match to our heart’s content. Just know that Flixel won’t detect it if we place something off the level or overlapping another object – it assumes we know what we’re doing.

    Now we need to display our new groups in the right order and handle collisions. Add these functions to the bottom of IndoorHouseLevel.

    
    
    /**
     * Decide the order of the groups. They are rendered in the order they're added, so last added is always on top.
     */
    override protected function addGroups():void {
        add(floorGroup);
        add(wallGroup);
        add(decalGroup);
        add(objectGroup);
        add(player);
        add(guiGroup);
    }
    
    /**
     * Update each timestep
     */
    override public function update():void {
        super.update(); // NOTE: map -> player collision happens in super.update()
        FlxG.collide(objectGroup, player);
    }
    

    Since we want our new groups to render on top of the floors and walls, we need to completely re-do the addGroups() function that we had in TopDownLevel. We also need to add collision detection for our furniture in objectGroup. Once again, since we don’t call FlxG.collide() for decalGroup, the player won’t be stymied by our imposing rugs. Now our room is looking a little less vacant.

    an indoor room with wooden floors, stone walls, furniture, and a red square player in the middle

    Step 8: Creating Our Player

    I keep talking about collisions, but it’s hard to collide with an immobile red box. Over the next three steps we will add keyboard controls to our red box before finally making it a proper animated sprite. Let’s create tutorial/Player.as.

    
    
    package tutorial
    {
    	import org.flixel.*;
    	import topdown.*;
    	/**
    	 * Player-controlled entity
    	 * @author Cody Sandahl
    	 */
    	public class Player extends TopDownEntity
    	{
    		/**
    		 * Constructor
    		 * @param	X	X location of the entity
    		 * @param	Y	Y location of the entity
    		 */
    		public function Player(X:Number=100, Y:Number=100):void {
    			super(X, Y);
    		}
    	}
    }
    

    This is the skeleton we’ll be using to flesh out a more interesting player. Now that we have our custom player, we need to use it in IndoorHouseLevel. Add this function at the end of the class.

    
    
    /**
     * Create the player
     */
    override protected function createPlayer():void {
        player = new Player(playerStart.x, playerStart.y);
    }
    

    This changed from using TopDownEntity to using Player. Now let’s make this red box move around.


    Step 9: Adding Keyboard Controls

    Since we might want entities other than Player to be able to move, we’re going to add some functionality to TopDownEntity. Here’s the new version.

    
    
    package topdown
    {
    	import org.flixel.*;
    
    	/**
    	 * A moveable object in the game (player, enemy, NPC, etc)
    	 * @author Cody Sandahl
    	 */
    	public class TopDownEntity extends FlxSprite
    	{
    		/**
    		 * Constants
    		 */
    		public static const SIZE:FlxPoint = new FlxPoint(16, 18); // size in pixels
    		public static const RUNSPEED:int = 80;
    
    		/**
    		 * Constructor
    		 * @param	X	X location of the entity
    		 * @param	Y	Y location of the entity
    		 */
    		public function TopDownEntity(X:Number = 100, Y:Number = 100):void {
    			super(X, Y);
    			makeGraphic(SIZE.x, SIZE.y, 0xFFFF0000); // use this if you want a generic box graphic by default
    			// movement
    			maxVelocity = new FlxPoint(RUNSPEED, RUNSPEED);
    			drag = new FlxPoint(RUNSPEED * 4, RUNSPEED * 4); // decelerate to a stop within 1/4 of a second
    		}
    
    		/**
    		 * Update each timestep
    		 */
    		public override function update():void {
    			updateControls();
    			super.update();
    		}
    
    		/**
    		 * Check keyboard/mouse controls
    		 */
    		protected function updateControls():void {
    			acceleration.x = acceleration.y = 0; // no gravity or drag by default
    		}
    
    		/**
    		 * Move entity left
    		 */
    		public function moveLeft():void {
    			facing = LEFT;
    			acceleration.x = -RUNSPEED * 4; // accelerate to top speed in 1/4 of a second
    		}
    
    		/**
    		 * Move entity right
    		 */
    		public function moveRight():void {
    			facing = RIGHT;
    			acceleration.x = RUNSPEED * 4; // accelerate to top speed in 1/4 of a second
    		}
    
    		/**
    		 * Move entity up
    		 */
    		public function moveUp():void {
    			facing = UP;
    			acceleration.y = -RUNSPEED * 4; // accelerate to top speed in 1/4 of a second
    		}
    
    		/**
    		 * Move playe rdown
    		 */
    		public function moveDown():void {
    			facing = DOWN;
    			acceleration.y = RUNSPEED * 4; // accelerate to top speed in 1/4 of a second
    		}
    	}
    }
    

    We’ve added a new constant, RUNSPEED, that determines how quickly our entities move. Then we set maxVelocity and drag (deceleration) in our constructor. After that, we call updateControls() each frame so we can check for keyboard, mouse, or AI (depending on our needs). Finally, we add some helper functions for moving in each direction. Notice that we update facing in each of these. This is a handy way to know which animation to use later down the line.

    Now we need to actually use the keyboard inside Player. Add this function after the constructor.

    
    
    /**
     * Check for user input to control this entity
     */
    override protected function updateControls():void {
        super.updateControls();
        // check keys
        // NOTE: this accounts for someone pressing multiple arrow keys at the same time (even in opposite directions)
        var movement:FlxPoint = new FlxPoint();
        if (FlxG.keys.pressed("LEFT"))
            movement.x -= 1;
        if (FlxG.keys.pressed("RIGHT"))
            movement.x += 1;
        if (FlxG.keys.pressed("UP"))
            movement.y -= 1;
        if (FlxG.keys.pressed("DOWN"))
            movement.y += 1;
        // check final movement direction
        if (movement.x < 0)
            moveLeft();
        else if (movement.x > 0)
            moveRight();
        if (movement.y < 0)
            moveUp();
        else if (movement.y > 0)
            moveDown();
    }
    

    So every frame we check what keys are being pressed. Flixel allows us to test keys in different ways. Here we’re using pressed(), which is true for as long as the key is being held down. If we used justPressed(), it would only be true immediately after the player presses the key, even if the key is held down after that. That would be reversed if we used justReleased().

    As I state in the comments, I want to handle the case where the user is pressing left and right (for example) at the same time by not moving. Incrementing or decrementing movement.x based on which arrow is pressed allows us to do that because movement.x would be zero if both left and right were being pressed.

    If you run the project now, you should be able to move the red box around with the arrow keys and see collisions happen between the box, and the walls or furniture (but not the rugs).

    You’ll probably notice that the box doesn’t go all the way over to the left and right walls. This is a more esoteric aspect of Flixel. Flixel uses rather simple collision detection (but allows us to make it more complicated if we want to). Since the images we’re using for all the walls are the same size (16×16), Flixel uses 16×16 as the collision size even though most of the left and right wall images are transparent. Fixing that behavior is beyond the scope of this tutorial, but it can be done.

    an indoor room with wooden floors, stone walls, furniture, and a red square player colliding with a table in the corner

    Step 10: Adding Animations

    I promised we wouldn’t stick with the red box (endearing though it is), so here we go with an animated sprite. Since we probably want the ability to animate future entities, we will be adding the basic functionality to TopDownEntity instead of Player. Here are the new constructor, createAnimations(), and update() functions for TopDownEntity.

    
    
    /**
     * Constructor
     * @param	X	X location of the entity
     * @param	Y	Y location of the entity
     */
    public function TopDownEntity(X:Number = 100, Y:Number = 100):void {
        super(X, Y);
        makeGraphic(SIZE.x, SIZE.y, 0xFFFF0000); // use this if you want a generic box graphic by default
        // movement
        maxVelocity = new FlxPoint(RUNSPEED, RUNSPEED);
        drag = new FlxPoint(RUNSPEED * 4, RUNSPEED * 4); // decelerate to a stop within 1/4 of a second
        // animations
        createAnimations();
    }
    
    /**
     * Create the animations for this entity
     * NOTE: these will be different if your art is different
     */
    protected function createAnimations():void {
        addAnimation("idle_up", [1]);
        addAnimation("idle_right", [5]);
        addAnimation("idle_down", [9]);
        addAnimation("idle_left", [13]);
        addAnimation("walk_up", [0, 1, 2], 12); // 12 = frames per second for this animation
        addAnimation("walk_right", [4, 5, 6], 12);
        addAnimation("walk_down", [8, 9, 10], 12);
        addAnimation("walk_left", [12, 13, 14], 12);
        addAnimation("attack_up", [16, 17, 18, 19], 12, false); // false = don't loop the animation
        addAnimation("attack_right", [20, 21, 22, 23], 12, false);
        addAnimation("attack_down", [24, 25, 26, 27], 12, false);
        addAnimation("attack_left", [28, 29, 30, 31], 12, false);
    }
    
    /**
     * Update each timestep
     */
    public override function update():void {
        updateControls();
        updateAnimations();
        super.update();
    }
    

    FlxSprite assumes that, if we’re animating, we have multiple frames of animation stored in a single image (called a sprite sheet). While our red box doesn’t have frames to animate, the artwork we will be using does. If you use artwork that is arranged differently in your own game, you would need to change these frame numbers. Additionally, if you look at the idle animations you’ll notice that we need to pass in an array of frame indices even if we only have one.

    Here’s the ranger sprite sheet we’ll be using for our player just so you can see a little more clearly.

    all the animations used by the ranger, with frame numbers labeled

    Note that we included some blank frames in the sprite sheet. This was mainly for convenience in editing the animations since our bottom four animations (attacking) have one more frame than our top four animations (walking). Also notice that we are using the middle frame from each of the walking animations as our idle animation.

    As long as all of our entities in our game use these same frame numbers, we never have to change the animation code. If our artwork used a different number of frames to animate walking and attacking, we would have updated the frames passed into addAnimation() accordingly. Since the animations are in their own function – createAnimations(), you can also override this function to make some entities have different animations than the rest.

    We also made another new function to show the right animation: updateAnimations().

    
    
    /**
     * Based on current state, show the correct animation
     * FFV: use state machine if it gets more complex than this
     */
    protected function updateAnimations():void {
        // use abs() so that we can animate for the dominant motion
        // ex: if we're moving slightly up and largely right, animate right
        var absX:Number = Math.abs(velocity.x);
        var absY:Number = Math.abs(velocity.y);
        // determine facing
        if (velocity.y < 0 && absY >= absX)
            facing = UP;
        else if (velocity.y > 0 && absY >= absX)
            facing = DOWN;
        else if (velocity.x > 0 && absX >= absY)
            facing = RIGHT;
        else if (velocity.x < 0 && absX >= absY)
            facing = LEFT
        // up
        if (facing == UP) {
            if (velocity.y != 0 || velocity.x != 0)
                play("walk_up");
            else
                play("idle_up");
        }
        // down
        else if (facing == DOWN) {
            if (velocity.y != 0 || velocity.x != 0)
                play("walk_down");
            else
                play("idle_down");
        }
        // right
        else if (facing == RIGHT) {
            if (velocity.x != 0)
                play("walk_right");
            else
                play("idle_right");
        }
        // left
        else if (facing == LEFT) {
            if (velocity.x != 0)
                play("walk_left");
            else
                play("idle_left");
        }
    }
    

    This is more laborious than it is complicated. Basically we are calculating how much we’re moving vertically and horizontally. Whichever has more movement, we use that direction’s animation. This would come into play if you are moving at an angle and suddenly bump into something. Whichever direction you can still move in will determine the animation used.

    We have only one more thing to do before we can finally put the red box out of its misery. We need to tell Player to use the ranger sprite sheet.

    
    
    /**
     * Constructor
     * @param	X	X location of the entity
     * @param	Y	Y location of the entity
     */
    public function Player(X:Number=100, Y:Number=100):void {
        super(X, Y);
        loadGraphic(
            Assets.RANGER_SPRITE, // image to use
            true, // animated
            false, // don't generate "flipped" images since they're already in the image
            TopDownEntity.SIZE.x, // width of each frame (in pixels)
            TopDownEntity.SIZE.y // height of each frame (in pixels)
        );
    }
    

    Once again we are going to our Assets class to pull in the image we want. The comments tell you what’s going on, but let me tell you a bit about "flipped" images. Instead of generating different animations when travelling left/right and up/down, Flixel can just flip the "right" animation to make it "left" and flip the "up" animation to make it "down" (or vice versa). Our "up" and "down" animations look very different (and we already have the artwork with all the directions), so we tell Flixel not to bother with flipping the animations.

    Now we have a true indoor top-down RPG level!

    an indoor room with wooden floors, stone walls, furniture, and an animated ranger player running down the left side

    Step 11: Adding a GUI

    As a bonus, let’s see how to add GUI elements to the screen. We’re going to add a simple set of instructions at the top so users know what to do once they load up this level. Let’s add a GUI to IndoorHouseLevel.

    
    
    /**
     * Create text, buttons, indicators, etc
     */
    override protected function createGUI():void {
        var instructions:FlxText = new FlxText(0, 0, levelSize.x, "Use ARROW keys to walk around");
        instructions.alignment = "center";
        guiGroup.add(instructions);
    }
    

    This adds a text area at the top of the screen that is as wide as the level and uses center alignment. As always, it must be added to the right group for it to show up.


    Conclusion

    Now you have everything you need to create your own top-down RPG levels. Thank you for sticking with me and go make something fun!


Leave a Reply

Click here to cancel reply.

search search search search search
Find an Article
Categories
  • Flash Video Training
  • Hints and Tips
  • Recommended
Please Support Our Sponsors
Recent Posts
  • Tuts+ Community Meetup in New York!
  • HTML5 Canvas Optimization: A Practical Example
  • Recreate the Cover Flow Effect Using Flash and AS3
  • Drawing Activetuts+ to a Close
  • Intro to Dart: Creating a Marquee
Tag Cloud
2011 ActionScript Active Activetuts+ Adobe animation Basic Basix Best Build Button Character Code Create Creating Critique Custom design Effect Effects Files Flash from Game Guide HTML5 Introduction Macromedia Motion Muzzle part Player Premium Professional Quick Silverlight Simple Text Tool Tutorial Tuts+ Using Video website Workshop
About Our Site:

Hey there and welcome to "Flash Video Training Source", a resource for anybody interested in learning more about Adobe's great tool. We feature educational videos, which will help you master Adobe Flash and help you get to know all of its features. We at "Flash Video Training Source" believe that video training and video... more

Why don't you follow us on Twitter and get the latest video tutorials twitted to your account. Just click on the floating twitter bar to your right!

Go Back In Time
May 2013
M T W T F S S
« Jul    
 12345
6789101112
13141516171819
20212223242526
2728293031  
Pretty Blank Box
top

Blogroll

  • Development Blog
  • Documentation
  • Plugins
  • Suggest Ideas
  • Support Forum
  • Themes
  • WordPress Planet

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org

Archives

  • July 2012
  • June 2012
  • May 2012
  • April 2012
  • March 2012
  • February 2012
  • January 2012
  • December 2011
  • November 2011
  • October 2011
  • September 2011
  • August 2011
  • July 2011
  • June 2011
  • May 2011
  • April 2011
  • March 2011
  • February 2011
  • January 2011
  • December 2010
  • November 2010
  • October 2010
  • September 2010
  • August 2010
  • July 2010
  • June 2010
  • May 2010
  • April 2010
Powered by WordPress  |  Designed by Elegant Themes  |  Lightning Fast Hosting by Site 5 Hosting