Advertisement
Games

Creating "Flux": A Simple Flash Game With a Gravity Mechanic

by

In this tutorial, I'll explain the major steps and workflow for creating a simple space survival game, based on the gravity mechanic explained in a previous tutorial. This game is written in AS3 using FlashDevelop.


Play the Game

Use the left and right arrow keys to manoeuvre your ship, the up and down arrow keys to increase or reduce the size of the magnetic field it produces, and the space bar to reverse the polarity. Collect the white crystals to increase your fuel supply - but avoid the red ones, because they use it up. Don't hit a rock, or it's game over!

In this tutorial, we won't actually create the full game displayed above; we'll just get started on it, by making a very simple version with primitive graphics and just one type of object. However, by the end, you should have learned enough to be able to add the other features yourself!

The game itself is very simple in its current state - take a look at this critique for tips on how you can take it from a simple demo to a full game!


Let's Get Started!

Set up a new AS3 project in FlashDevelop, and set its dimensions to 550x600px.

 package 
{
	[SWF(width = "550", height = "600")]
	
	public class Main extends Sprite
	{
	
	}
}

Step 1: Identifying the Game Objects

There are six objects in particle that you can identify from playing the game above:

  • Energy supply - represented by an white oval shape object
  • Asteroid - represented by a rock-like object
  • Energy consumer - represented by a red star bounded with green light.
  • Stars - the background
  • Range indicator - represented by a white circle
  • Ship - player object

Of course you can add in any other object to make the game more interactive or add a new feature. For this tutorial we'll just make


Step 2: The Energy Class

From the objects we identified, four of them actually work in exactly the same way: by falling from top to bottom.

They are:

  • Stars
  • Energy supply
  • Energy consumer
  • Asteroid

In this tutorial, we're only going to make the "energy supply" objects, out of the four above. So let’s begin by creating these objects and making them fall down, with a random spawning position and speed.

Start by creating an Energy class:

	package  
	{
		import flash.display.MovieClip;
		import flash.events.Event;

		public class Energy extends MovieClip
		{
			private var rSpeed:Number = 0;
			
			public function Energy(speed:Number)
			{		
				graphics.beginFill(0x321312);
				graphics.drawCircle(0, 0 , 8);
				
				rSpeed = speed;
			}
			
			// we will call this every frame
			public function move():void
			{
				this.y += rSpeed;
				//rotation speed is linked to moving speed
				this.rotation += rSpeed / 8;
			}
		}
	}

Step 3: The GameScreen Class

This class will eventually control most of the aspects of our game, including the player movement and the game loop.

Create the class:

package  
{
	
	public class GameScreen extends MovieClip
	{

		public function GameScreen()
		{
		
		}
	}
}

That's all we need for now.


Step 4: Update The Main Class

We'll now create an instance of GameScreen within Main:

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

	[SWF(width = "550", height = "600")]
	 
	public class Main extends Sprite 
	{
		private var game:GameScreen;
		
		public function Main():void 
		{
			// don't display a yellow rectangle on the screen at startup
			stage.stageFocusRect = false;
			
			game = new GameScreen();
			addChild(game);
			
			// give keyboard focus to the game screen immediately
			stage.focus = game;
		}
	}
}

Why bother? Well, this way, it'll be easier to add extra screens later if we want to (like a preloader, a title screen, a game over screen...).


Step 5: Introducing a Manager Class

To avoid the GameScreen class becoming too much of a mess, we'll use separate classes to manage each object.

Each manager class will contain all the functions that relate to, and interact with, a particular object. Here's the EnergyManager class:

package  
{
	import flash.display.MovieClip;
	
	public class EnergyManager
	{
		// this Vector will store all instances of the Energy class
		private var energyList:Vector.<Energy>	
		private var gameScreen:GameScreen;
		
		public function EnergyManager(gs:GameScreen) 
		{
			gameScreen = gs;
			energyList = new Vector.<Energy>;	
		}
	}
}

Note that we require a reference to the GameScreen to be passed to the constructor, and we store this reference in a private variable. We also set up a Vector to store references to all the energy objects.

So far the class contain no other functions; we will add them in later.


Step 6: Creating Energy

Add the below function for creating energy, this is just a function; we will call the function later from GameScreen Class:

	public function createEnergy(number:int):void	
	{
		var energy:Energy;
		for (var i:int = 0; i < number; i++) {		

			energy = new Energy(4);				
				
			gameScreen.addEnergyToScreen(energy);
				
			energyList.push(energy);				
	
			energy.x = Calculation.generateRandomValue(30, 520);		
			energy.y = Calculation.generateRandomValue( -150, -20); 
		}
	}

We create a new energy supply with a speed of 4, add it to the display list (via the GameScreen), add it to the Vector of all energy objects that we just created, and set its position to a random point within certain bounds.

The Calculation.generateRandomValue(#, #) is a static function we haven't written yet, so let's do that now. Create a new class called Calculation and add this function:

	public static function generateRandomValue(min:Number, max:Number):Number
	{
		var randomValue:Number = min + (Math.random() * (max - min));
			
		return randomValue;
	}

This function will generate a random number between the two values passed to it. For more information on how it works, see this Quick Tip. Since this is a static function, we don't need to create an instance of Calculation in order to call it.

Now, what's that addEnergyToScreen() function? We haven't defined that yet, so let's do it now. Add this to GameScreen:

		public function addEnergyToScreen(energy:Energy):void
		{
			addChild(energy);
		}

It just adds the passed instance of energy to the display list. Let's also make a corresponding function to remove a given energy object from the screen:

		public function removeEnergyFromScreen(energy:Energy):void
		{
			if (energy.parent == this)
			{
				removeChild(energy);
			}
		}

Step 7: Spawning Energy

Let's set a timer that defines the interval for each spawning. This code goes in GameScreen's constructor function:

energyM = new EnergyManager(this);	//remember to pass a reference to the game screen
								
var spawnTimer:Timer = new Timer(3000, 0);
spawnTimer.addEventListener(TimerEvent.TIMER, spawnEnergy);
spawnTimer.start();

So, every three seconds, the timer will call spawnEnergy(). Let's write that function now:

	private function spawnEnergy(e:TimerEvent):void
	{
		energyM.createEnergy(4);    // create 4 energies
	}

Step 8: Creating Player

Let's use another, bigger circle to represent the player. Feel free to import an image to use instead:

public function Player() 
		{
			graphics.beginFill(0x7ebff1);
			graphics.drawCircle(0, 0, 20);

Add this code to GameScreen to add the player to the screen:

// in the variable definitions
public var player:Player;
// in the constructor function
player = new Player;
addChild(player);
player.x = 275;
player.y = 450;

So far we should have a few energy supplies falling few seconds, and the player appearing in the middle of the screen:

playerandenergy.

Step 9: Moving the Player

There are basically two ways to apply movement:

  1. Using Boolean (true/false) values - true = moving, false = not moving. When the right arrow key is pressed, the value for "moving right" will change to true. In each frame update, "moving right" is true, we increase the object's x-value.
  2. Using direct update each frame - when the right arrow key is pressed, an object is told to move right immediately, by increasing its x-value.

The second method does not lead to smooth movement when the key is continuously pressed, but the first method does - so we shall use the first method.

There are three simple steps to doing this:

  1. Create two Boolean variables, one for moving right and one for moving left.
    	private var moveRight:Boolean = false;
    	private var moveLeft:Boolean = false;
  2. Toggle the Boolean when keys are pressed or released:
    		addEventListener(Event.ENTER_FRAME, update);
    		addEventListener(KeyboardEvent.KEY_DOWN, KeyDownHandler);
    		addEventListener(KeyboardEvent.KEY_UP, KeyUpHandler);
    	}
    		
    	private function KeyDownHandler(e:KeyboardEvent):void
    	{
    		if (e.keyCode == Keyboard.RIGHT) {
    			moveRight = true;
    		}
    		if (e.keyCode == Keyboard.LEFT) {
    			moveLeft = true;
    		}
    		if (e.keyCode == Keyboard.SPACE) {
    			if (isGravityPushing == true) {
    				isGravityPushing = false;
    			} else  {
    				isGravityPushing = true;
    			}
    		}
    	}
    		
    	private function KeyUpHandler(e:KeyboardEvent):void
    	{
    		if (e.keyCode == Keyboard.RIGHT) {
    			moveRight = false;
    		}
    		if (e.keyCode == Keyboard.LEFT) {
    			moveLeft = false;
    		}
    	}
  3. Based on these Booleans, actually move the player every frame:

    Don't forget to first create a function listen from the enter frame event, "updating" :

    //call this function every frame
    private function update(e:Event):void
    	if (moveRight == true) {
    		player.x += 6;
    	}
    	if (moveLeft == true) {
    		player.x -= 6;
    	}

    Keep the player within the bounds of the screen:

    	if (player.x >= 525) {
    		moveRight = false;
    	}
    	if (player.x <= 20) {
    		moveLeft = false;
    	}

Here's how all that looks, in place:

package
{
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.TimerEvent;
    import flash.ui.Keyboard;
    import flash.utils.Timer;
    import flash.events.KeyboardEvent;
 
    public class GameScreen
    {
        public var player:Player;
 
        private var energyM:EnergyManager;
 
        private var moveRight:Boolean = false;
        private var moveLeft:Boolean = false;
        private var isGravityPushing:Boolean = true;
 
        private var returnedPower:int = 0;
 
        private var scoreText:Text;
        private var totalScore:int=0;
        private var score:Text;
 
        public function GameScreen()
        {
            scoreText = new Text("Score :");
            addChild(scoreText);
 
            energyM = new EnergyManager;
 
            var spawnTimer:Timer = new Timer(3000, 0);
            spawnTimer.addEventListener(TimerEvent.TIMER, spawnEnergy);
            spawnTimer.start();
 
            player = new Player;
            addChild(player);
            player.x = 275;
            player.y = 450;
 
            addEventListener(Event.ENTER_FRAME, update);
            addEventListener(KeyboardEvent.KEY_DOWN, KeyDownHandler);
            addEventListener(KeyboardEvent.KEY_UP, KeyUpHandler);
        }

	private function KeyDownHandler(e:KeyboardEvent):void
	{
		if (e.keyCode == Keyboard.RIGHT) {
			moveRight = true;
		}
		if (e.keyCode == Keyboard.LEFT) {
			moveLeft = true;
		}
		if (e.keyCode == Keyboard.SPACE) {
			if (isGravityPushing == true) {
				isGravityPushing = false;
			}else if (isGravityPushing == false) {
				isGravityPushing = true;
			}
		}
	}
		
	private function KeyUpHandler(e:KeyboardEvent):void
	{
		if (e.keyCode == Keyboard.RIGHT) {
			moveRight = false;
		}
		if (e.keyCode == Keyboard.LEFT) {
			moveLeft = false;
		}
	}

	private function update(e:Event):void
	{
		if (player.x >= 525) {
			moveRight = false;
		}
		if (player.x <= 20) {
			moveLeft = false;
		}
		if (moveRight == true) {
			player.x += 6;
		}
		if (moveLeft == true) {
			player.x -= 6;
		}
	}
    }
}

Step 10: Move the Energy Supplies

At the moment, the energy supplies are spawning but not moving. We'll use the GameScreen.update() function to make them move, since it runs every frame.

Add this code to GameScreen.update():

			energyM.moveAll();  // will make every energy object move

Now of course we need to make the EnergyManager.moveAll() function, so add this to EnergyManager.as:

		public function moveAll():void
		{
			for (var i:int = 0; i < energyList.length; i++) {
				var energyS:Energy = energyList[i];
				energyS.move();
			}
		}

Step 10: Collision Detection

We will need to check for collisions between each energy object and the player. (If you develop the game further, you'll need to check this for asteroids and energy consumers, but not for stars.)

The best place to handle these checks is inside the EnergyManager, triggered every frame by the GameScreen.

One thing to consider: the collision checks will be between two circles, so hitTestObject() is not ideal. Instead, we'll be using the method explained in this tutorial.

We can write the function as below:

	public function checkCollision(p:Player):int
	{
		// energy transferred due to collision
		var energyTransfer:int = 0;
		
		for (var i:int = 0; i < energyList.length; i++) {
			var energyS:Energy = energyList[i];
			
			var newX:Number = p.x - energyS.x;
			var newY:Number = p.y - energyS.y;
				
			var distance:Number = Math.sqrt(newX * newX + newY * newY);
				
			if (distance <= 28) {
				gameScreen.removeEnergyFromScreen(energyS);
				energyList.splice(i, 1);
				// for this simple game, we'll always transfer 1 unit
				// but you could alter this based on speed of collision
				// or any other factor
				energyTransfer = 1;
			}
		}
		return energyTransfer;
	}
  • Line 32: note that we pass in a reference to the player, so that we can access its position.
  • Line 38: EnergyS is short for Energy Supply.
  • Line 40 & 41: finding the difference in x- and y-coordinates between the player and the energy supply we are currently checking.
  • Line 43: calculate the distance between the objects via Pythagoras.
  • Line 45: check for collision; 28 is the sum of the two objects' radii (player radius is 20, energy radius is 8).
  • Line 46 & 47: remove energy supply from screen and from Vector.
  • Line 51: add a maximum of one unit of energy per frame.

You could alter Line 51 to energyTransfer += 1, to allow the player to absorb more than one energy object at once. It's up to you - try it out and see how it affects the game.


Step 11: Call Collision Detection Routine

We need to check for collisions every frame, so we should call the function we just wrote from GameScreen.update().

First, we need to create an integer variable to store the energy transfer value from the collision detection function. We'll use this value for increasing the ship's energy and adding to the player's score.

private var returnedPower:int = 0;
returnedPower = energyM.checkCollision(player);

Step 12: Newton's Law of Gravitation

Before we go into creating the game mechanic for the ‘Push’ and ‘Pull’ function of the ship, I would like to introduce the physics concept on which the mechanic is based.

The idea is to attract the object towards the player by means of a force. Newton’s Law of Universal Gravitation gives us a great (and simple) mathematical formula we can use for this, where the force is of course the gravitational force:

G is just a number, and we can set it to whatever we like. Similarly, we can set the masses of each object in the game to any values that we like. Gravity occurs across infinite distances, but in our game, we'll have a cut-off point (denoted by the white circle in the demo from the start of the tutorial).

The two most important things to note about this formula are:

  • The strength of the force depends on the square of the distance between the two objects (so if the objects are twice as far away, the force is one-quarter as strong).
  • The direction of the force is along the direct line connecting the two objects through space.

Step 13: Revising Math Concepts

Before we start coding the game mechanics for the 'Push' and 'Pull' function, let's be clear on what we want it to do:

frameWork

Essentially, we want A (the player) to exert a certain force on B (a crystal), and move B towards A based on that force.

We should revise a few concepts:

  • Flash works in radians rather than degrees.
  • Flash's coordinate system has its y-axis reversed: going down means an increase in y.
  • We can get the angle of the line connecting A to B using Math.atan2(B.y - A.y, B.x - A.x).
  • We can use trigonometry to figure out how much we need to move B along each axis, based on this angle and the force:
    • B.x += (Force*Math.cos(angle));
    • B.y += (Force*Math.sin(angle));
  • We can use Pythagoras's theorem to figure out the distance between the two objects:

For more information, see the tutorials Gravity in Action and Trigonometry for Flash Game Developers.


Step 14: Implementing Push and Pull

Based on the previous explanation, we can come up with an outline for our code that attracts each crystal to the ship:

  1. Find the difference in x and y between the ship and a given crystal.
  2. Find the angle between them, in radians.
  3. Find the distance between them, using Pythagoras.
  4. Check whether object is within the ship's gravitational field.
  5. If so, calculate the gravitational force, and...
  6. ...apply the force, changing the x and y values of the crystal.

Sample Code:

	public function gravityPull(p:Player): void
	{
		for (var i:int = 0; i < energyList.length; i++) {
			var energyS:Energy = energyList[i];
				
			var nX:Number = (p.x - energyS.x);
			var nY:Number = (p.y - energyS.y);
			
			var angle:Number = Math.atan2(nY, nX);
				
			var r:Number =  Math.sqrt(nX * nX + nY * nY);
				
			if (r <= 250) {
				var f:Number = (4 * 50 * 10) / (r * r); 				
				energyS.x += f * Math.cos(angle);
				energyS.y += f * Math.sin(angle);
			}
		}
	}
  • Line 53: get a reference to the player.
  • Line 55: we loop through each energy object.
  • Line 61: find the angle between the ship and the energy.
  • Line 63: find the distance between them, too.
  • Line 65: check whether the energy is within the ship's force field.
  • Line 67: use the formula:
    • 4 = G, the "gravitational constant" I've chosen.
    • 50 = m1, the mass of the ship player.
    • 10 = m2, the mass of the energy object.
  • Line 69: apply movement.

Here's a timelapse showing how this looks:

Note that the energy moves faster the closer it gets to the ship, thanks to the r-squared term.

We can implement the pushing function just by making the force negative:

	public function gravityPull(p:Player): void
	{
		for (var i:int = 0; i < energyList.length; i++) {
			var energyS:Energy = energyList[i];
				
			var nX:Number = (p.x - energyS.x);
			var nY:Number = (p.y - energyS.y);
			
			var angle:Number = Math.atan2(nY, nX);
				
			var r:Number =  Math.sqrt(nX * nX + nY * nY);
				
			if (r <= 250) {
				var f:Number = (4 * 50 * 10) / (r * r); 				
				energyS.x -= f * Math.cos(angle);
				energyS.y -= f * Math.sin(angle);
			}
		}
	}

Here the object moves more slowly as it gets further away from the player, since the force gets weaker.


Step 15: Apply the Mechanic

Of course that you will need this function to be run each frame by GameScreen - but before that, we will need to use a Boolean function to toggle between the two functions:

private var isGravityPushing:Boolean = true;  // hitting space toggles it

We are going to use true for 'Push' and false for 'Pull'.

Inside KeyDownHandler():

	if (e.keyCode == Keyboard.SPACE) {
		if (isGravityPushing == true) {
			isGravityPushing = false;
		} else if (isGravityPushing == false) {
			isGravityPushing = true;
		}
	}

Afterwards, you will have to check the Boolean each frame. Add this to update():

	if (isGravityPushing == true) {
		energyM.gravityPull(player);
	}
	if (isGravityPushing == false) {
		energyM.gravityPush(player);
	}

Step 16: Modification

You might find that the movement doesn't look so nice. This could be because the force is not quite ideal, or because of the r-squared term.

I'd like to alter the formula like so:

var f:Number = (0.8 * 50 * 10) / r;

As you can see, I've reduced the value of "G" to 0.8, and changed the force to depend simply on the distance between the objects, rather than the distance squared.

Try it out and see if you enjoy the change. You can always alter it however you like.


Step 17: The Text Class

We will need to show some text on the screen, for showing the score and the ship's remaining power.

For this purpose, we'll build a new class, Text:

package  
{
	import flash.display.MovieClip;
	import flash.text.TextField;
	import flash.events.Event;
	import flash.text.TextFormat;

	import flash.text.TextFormatAlign;

	public class Text extends MovieClip
	{
		public var _scoreText:TextField= new TextField();
		
		public function Text(string:String)
		{	
			var myScoreFormat:TextFormat = new TextFormat(); //Format changeable
			myScoreFormat.size = 24;		
			
			myScoreFormat.align = TextFormatAlign.LEFT;
			myScoreFormat.color = (0x131313);
			
			_scoreText.defaultTextFormat = myScoreFormat;
			
			_scoreText.text = string;
			
			addChild(_scoreText);
		}

		public function updateText(string:String)
		{
			_scoreText.text = string;
		}
	}
}

It's very simple; it's basically a MovieClip with a text field inside.


Step 18: Adding Power for Player

To give the game some challenge, we'll make the ship's power get used up slowly, so that the player has to collect energy objects in order to recharge.

To make the ship's power appear on the ship itself, we can simply add an instance of Text to the ship object's display list.

Declare these variables within the Ship class:

public var totalPower:Number = 100;  // ship starts with this much power
private var powerText:Text;

We'll need to keep the amount of power (both stored and displayed) updated every frame, so add this new function to Player:

First, in the constructor:

			// add a new text object if it doesn't already exist
			if (!powerText) {
				powerText = new Text(String(int(totalPower))); 
				addChild(powerText);	
				powerText.x -= 20;			//Adjust position
				powerText.y -= 16;
			}

And then...

	public function updatePower():void
		{
			// fps = 24, so this makes power decrease by 1/sec
			totalPower -= 1 / 24;		
			powerText.updateText(String(int(totalPower)));
		}

The power will decrease every frame by 1/24th of a unit, meaning it'll decrease by one full unit every second.

We need to make this run every frame, so add this line to GameScreen.update():

player.updatePower();

Step 19: Make Energy Increase Power

When the ship collides with an energy object, we want it to increase its power.

In GameScreen.update(), add the highlighted line:

returnedPower = energyM.checkCollision(player);
player.totalPower += returnedPower;

Remember you can alter how much power is returned in the EnergyManager.checkCollision() function.


Step 20: Setting Up the Score

Again, we will need the text class. This time, we'll display “Score” and then the value.

Here, we will need three more variables:

  • The "Score" text.
  • The score value text.
  • A variable to store the actual score.

Declare these in GameScreen class:

private var scoreText:Text;
private var totalScore:int = 0;
private var score:Text;

In the constructor, add this code:

scoreText = new Text("Score :");
addChild(scoreText);

score = new Text(String(totalScore));
addChild(score);
score.x = scoreText.x + 100;   //Positioning it beside the "Score : " Text.
score.y += 2;

Now, in the update() function, add this:

score.updateText(String(totalScore));

That's it - we've created a basic version of the above game!

Take a look (you may need to reload the page):


Extra Features and Polishing

Space Background

Maybe you would also like a background with an embedded image and stars. Add this to your Main class:

[Embed(source = "/../lib/SpaceBackground.jpg")]	//embed
		private var backgroundImage:Class; //This line must come immediately after the embed

		private var bgImage:Bitmap = new backgroundImage();		
		private var numOfStars:int = 70;

Now create the Star class:

package assets 
{
	import flash.display.MovieClip;
	import flash.events.Event;

	public class Star extends MovieClip
	{
		private var speed:Number;
		
		public function Star(alpha:Number, size:Number, speed1:Number) 
		{
			graphics.beginFill(0xCCCCCC);
			graphics.drawCircle(0, 0, size);
			
			speed = speed1;
		}
		
		// make sure you call this every frame
		private function moveDown():void
		{
			this.y += speed;
			
			if (this.y >= 600) {
				this.y = 0;
			}
		}
	}
}

In the Main() constructor, add this to create the stars:


for (var i:int = 0; i < numOfStars; i++) {
		createStars();
}

Here's the actual createStars() function:


private function createStars():void
{
	var star:Star = new Star(
		Math.random(), 
		Calculations.getRandomValue(1, 2), 
		Calculations.getRandomValue(2, 5)
	);  //random alpha, size and speed
			
	addChild(star);
			
	star.x = Calculations.getRandomValue(0, 550);
	star.y = Calculations.getRandomValue(0, 600);
}

With random alpha, size, position, and speed, a pseudo-3D background can be generated.

Range indicator

A range indicator circle can be made by simply creating another circle and adding it to the ship's display list, just like how you added the power indicator text. Make sure the circle is centred on the ship, and has a radius equal to the ship's push/pull range.

Add transparancy (alpha value) to the circle with the below code:

			graphics.beginFill(0xCCCCCC, 0.1);

Try adding extra controls that make the range increase or decrease when the up and down arrow keys are pressed.


Conclusion

I hope you enjoyed this tutorial! Please do leave your comments.

Next: Read this critique for a guide to taking Flux from a simple demo to a full game!

Related Posts