Advertisement

Use Citrus to Build a Simple Flash Platformer

by

Ever wanted to make a platformer, but not too keen on writing all the code from scratch? The Citrus Engine doesn't eliminate code entirely, but it makes it much simpler, with plenty of useful 2D game features built right in. In this tutorial, we'll use it to build a simple platform game.


Step 1: Brief Overview

In this tutorial we'll use a popular Flash Game Engine, called Citrus, and a series of ActionScript classes to create a platformer game.


Step 2: Flash Document Settings

Open Flash and create a 420x280px document. Set the Frame rate to 30fps.


Step 3: Interface

We'll use these great pixel graphics by Guillaume Moreau. You can download them from opengameart.org.


Step 4: Background

For the background, change the stage color to #99D9EA, or use the Rectangle Tool (R) to draw a rectangle of that color.


Step 5: Alert

An alert will be shown when the player completes the level or dies; it will display a game over message and the score. Use the Rectangle Tool to create it and set its instance name to AlertView. Mark the Export for ActionScript box and give it the same class name.

There are many other objects in the game (for example: a title screen, the various symbols for each sprite, the terrain). Rather than explaining how to create them here, I suggest you download the source files and look at what objects are in the FLA's Library. You can either use the same symbols in your own game, or create new ones!


Step 6: Embed Font

We'll use a bitmap font in the game; since we are going to use the font dynamically we will need to embed it in the application. I'm using the fonts 04b11 and Arcade Classic.

Select a dynamic text field with the font of your preference and click the Embed... button in the Properties Panel.

Select/add all the necesary characters and click OK.


Step 7: Sounds

We'll use Sound Effects to enhance the feeling of the game. The sounds used in this example were generated using as3sfxr and are included in the source files.


Step 8: TweenNano

We'll use a different tween engine from the default included in Flash. This will increase performace as well as being easier to use.

You can download TweenNano from its official website.


Step 9: Citrus Engine

We'll use the Citrus Engine to power our game.

What is the Citrus Engine? Learn more from its website:

The Citrus Engine is a professional-grade, scalable Flash game engine built for industry-quality games. It is built upon modern Flash programming practices, allowing you to focus on making your game awesome! It comes built-in with a "platformer" starter-kit, which you can use to easily make awesome 2D sidescrolling games.


Step 10: Set Up Citrus Engine

Go to the download page and get the necessary files. Open Flash Preferences, select ActionScript from the list on the left and click on ActionScript 3.0 settings..., then add the path to the sources as shown in the image above.


Step 11: Set Document Class

We'll make our application interactive by using an external class. Add its name (Main) to the Class field in the Publish section of the Properties panel to associate the FLA with the Main document class.


Step 12: 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 13: Class Structure

Create your basic class structure to begin writing your code.

 
package  
{	 
	public class Main 
	{ 
		public function Main():void 
		{ 
			// constructor code 
		} 
	} 
}

Step 14: Main.as

The Main class will prepare the Citrus Engine to handle the first level.

 
package 
{ 
	import com.citrusengine.core.CitrusEngine; 
	 
	public final class Main extends CitrusEngine 
	{ 
		public final function Main():void 
		{ 
			super(); 
			state = new Level(); 
		} 
	} 
}

This class extends the CitrusEngine class and sets the game's "state" to Level, which is the name of the class that will contain all our game's behaviour.


Step 15: Level.as

The State class is one of the core classes of Citrus Engine, and you should extend this class to create the game logic of your levels or states. In this example the Level class extends State to create the game's first level.

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


Step 16: Required Classes

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

 
import flash.display.MovieClip; 
import flash.display.Sprite; 
import flash.events.MouseEvent; 
import com.citrusengine.core.CitrusEngine; 
import com.citrusengine.core.State; 
import com.citrusengine.physics.Box2D; 
import com.citrusengine.objects.platformer.*; 
import com.citrusengine.objects.*; 
import com.citrusengine.math.MathVector; 
import flash.events.Event; 
import flash.geom.Rectangle; 
import flash.ui.Keyboard; 
import flash.events.KeyboardEvent; 
import flash.text.TextField; 
import flash.text.TextFormat; 
import com.greensock.TweenNano; 
import com.greensock.easing.Expo; 
import flash.net.navigateToURL; 
import flash.net.URLRequest; 
import flash.filters.GlowFilter; 
import flash.filters.BitmapFilter;

Step 17: Variables

These are the variables we'll use; read the comments in the code to know more about them. (Some of their names are self-explanatory, so there's no comment.)

 
private var levelView:LevelView = new LevelView();    //sprite from the Library 
private var hero:Hero; 
private var hearts:Vector.<Sprite> = new Vector.<Sprite>(); 
private var tf:TextFormat = new TextFormat('ArcadeClassic', 17, 0xFFFFFF, null, null, null, null, null, 'right'); 
private var scoreTF:TextField = new TextField(); 
private var gemSnd:GemSnd = new GemSnd();    //Snd = Sound 
private var goalSnd:GoalSnd = new GoalSnd(); 
private var hitSnd:HitSnd = new HitSnd(); 
private var jumpSnd:JumpSnd = new JumpSnd(); 
private var loseSnd:LoseSnd = new LoseSnd(); 
private var baddySnd:BaddySnd = new BaddySnd();

Step 18: Constructor

The constructor is a function that runs when an object is created from a class, this code is the first to execute when you make an instance of an object, loads once the game starts if part of the document class.

It calls the necessary functions to start the game. Check those functions in the next steps.

 
public final function Level():void 
{ 
	//Code 
}

Step 19: Pause Game

We'll start by pausing the game; this will prevent the engine from adding the graphics while the Level View sprite is on stage.

 
public final function Level():void 
{ 
	super();    //default setup code 
	CitrusEngine.getInstance().playing = false;    //actually pause game 
}

Step 20: Add Level View

This will add the Level View sprite to the stage, as well as a mouse listener to remove it.

 
override public function initialize():void 
{ 
	/* Level Start View */ 
	 
	addChild(levelView); 
	levelView.addEventListener(MouseEvent.MOUSE_UP, startLevel); 
}

Step 21: Start Level

The next function runs when the Level View is clicked, and will handle the logic to start the game.

 
private final function startLevel(e:MouseEvent):void 
{

Step 22: Remove Level View

This code destroys the Level View instance.

 
levelView.removeEventListener(MouseEvent.MOUSE_UP, startLevel); 
removeChild(levelView); 
levelView = null;

Step 23: Start GameState (Unpause)

Now we unpause the game and initialize the level.

 
super.initialize(); 
CitrusEngine.getInstance().playing = true;

Step 24: Start Box2D

The following code starts the Box2D engine that handles the physics of Citrus Engine.

 
var box2D:Box2D = new Box2D('Box2d'); 
add(box2D); 
//box2D.visible = true; //uncomment to see the debug graphics of Box2D

Step 25: Add Graphic Objects

These lines will create the entire level, read through the code to understand the Citrus Engine instantiation system (which is really easy!).

(All view properties are the class names of symbols from the FLA Library: Bg, TerrainPart1, WaterX3, etc.)

 
var leftWall:Platform = new Platform('LeftWall', {width:1, height:280, x:0, y:110}); 
add(leftWall); 
 
var rightWall:Platform = new Platform('RightWall', {width:1, height:280, x:726, y:100}); 
add(rightWall); 
 
var bg:CitrusSprite = new CitrusSprite('Bg', {view:Bg, x:0, y:20}); 
add(bg); 
 
var terrain1:Platform = new Platform('Terrain1', {width:422, height:32, x: 211, y:264, view:TerrainPart1}); 
add(terrain1); 
 
var oneWay1:Platform = new Platform('OneWay1', {width:92, height:32, x:184, y:232, oneWay:true, view:OneWay1}); 
add(oneWay1); 
 
var gem:Coin = new Coin('Gem',{width:11, height:10, x:186, y:136, view:Gem}); 
add(gem); 
 
var water:Sensor = new Sensor('Water', {width:92, height:32, x: 468, y:264, view:WaterX3}); 
add(water); 
 
var terrain2:Platform = new Platform('Terrain2', {width:214, height:32, x:621, y:264, view:TerrainPart2}); 
add(terrain2); 
 
var baddy:Baddy = new Baddy('Baddy', {x:300, y:200, leftBound:250, rightBound:350, view:Enemy}); 
add(baddy); 
 
var movingPlatform:MovingPlatform = new MovingPlatform('MP',{width:32, height:8, x:436, y:232, startX:436, startY:232, endX:500, endY:232, view:PlatformClip, speed:0.9}); 
add(movingPlatform); 
 
var oneWay2:Platform = new Platform('OneWay2', {width:127, height:32, x:663, y:232, oneWay:true, view:OneWay2}); 
add(oneWay2); 
 
var oneWay3:Platform = new Platform('OneWay3', {width:64, height:32, x:695, y:200, oneWay:true, view:OneWay3}); 
add(oneWay3); 
 
var door:Sensor = new Sensor('Door', {width:20, height:28, x:695, y:202, view:Door}); 
add(door); 
 
hero = new Hero('Hero', {x:30, y:234, width:19, height:26, view:HeroClip, jumpHeight:9, maxVelocity:2, hurtVelocityX:2}); 
add(hero);

As you can see, in each case you create an instance of the type of object you want and use the parameters to specify its position, size, graphic or skin (this is the view parameter) and other useful elements. Then we add it to the Citrus stage using the add() method.

Let's stop here to make a quick test and make sure that our game code is working:

Remember that the Milestones are included in the source files, so if for some reason your file doesn't mimic this one take a look at the source to see what can be causing that. (And keep in mind that some lines must be commented out as some functions haven't been created yet.)


Step 26: Add Signals

Citrus Engine uses Signals to handle event type interaction. You can learn more about signals on this Activetuts+ tutorial.

 
gem.onBeginContact.addOnce(function(e:*){gemSnd.play();scoreTF.text = String(int(scoreTF.text) + 50);}); 
door.onBeginContact.addOnce(levelComplete); 
 
hero.onTakeDamage.add(hurtHero); 
hero.onGiveDamage.addOnce(killBaddy); 
hero.onJump.add(function(){jumpSnd.play()}); 
hero.onGiveDamage.addOnce(function(){baddySnd.play()});

Step 27: Stop Hero Animation

Our Hero MovieClip will play by default unless we prevent it. This code handles that, and you will also learn how to access the MovieClip that serves as the art for your Citrus Object.

 
this.view.getArt(hero).content.stop(); //State(Level).SpriteView.SpriteArt.MovieClip.stop 
 
gameListeners(); 
addIndicators();

Step 28: Game Listeners

This code adds EnterFrame and Keyboard listeners that will be used in our game. You can read about the corresponding handler functions in the next steps.

 
private final function gameListeners(action:String = 'add'):void 
{ 
	if(action == 'add') 
	{ 
		stage.addEventListener(Event.ENTER_FRAME, gameLogic); 
		stage.addEventListener(KeyboardEvent.KEY_DOWN, animateWalk); 
		stage.addEventListener(KeyboardEvent.KEY_UP, stopWalk); 
	} 
	else 
	{ 
		stage.removeEventListener(Event.ENTER_FRAME, gameLogic); 
		stage.removeEventListener(KeyboardEvent.KEY_DOWN, animateWalk); 
		stage.removeEventListener(KeyboardEvent.KEY_UP, stopWalk); 
	} 
}

Step 29: Add Hearts

Hearts will represent our Hero's health. The next code adds three hearts to the stage and stores them in a Vector to use them later outside this function.

 
private final function addIndicators():void 
{ 
	/* Hearts */ 
	 
	for(var i:int = 0; i < 3; i++) 
	{ 
		var heart:Heart = new Heart(); 
		heart.y = 5; 
		heart.x = 5 + (i * heart.width); 
		addChild(heart); 
		hearts.push(heart); 
	}

Step 30: Add Score

The Score TextField is created by this code. We use filters to add the black stroke around the letters.

 
	/* Score */ 
	 
	scoreTF.x = 320; 
	scoreTF.defaultTextFormat = tf; 
	scoreTF.text = '0'; 
	var filter:BitmapFilter = new GlowFilter(0, 1, 2, 2); 
	var filter2:BitmapFilter = new GlowFilter(0, 1, 1, 1); 
	scoreTF.filters = [filter, filter2]; 
	addChild(scoreTF); 
}

Step 31: Handle Camera

Here we set up the camera, which will follow our Hero if its position is past the center X of the stage.

 
private final function gameLogic(e:Event):void 
{ 
	/* Handle Camera */ 
	 
	if(hero.x >= stage.stageWidth * 0.5) 
	{ 
		view.setupCamera(hero, new MathVector(stage.stageWidth * 0.5, 234), new Rectangle(0, 0, 726, 228), null); 
	}

Step 32: Check for Fall

This code checks if our hero has fallen into the water, and if so it plays the lose sound and calls an alert.

 
	/* Check if hero fell */ 
	 
	if(hero.y > stage.stageHeight) 
	{ 
		loseSnd.play(); 
		alert('lose'); 
	} 
}

Step 33: Animate Hero Walk

Our hero walking animation is started when the left or right arrow keys are pressed.

 
private final function animateWalk(e:KeyboardEvent):void 
{ 
	if(e.keyCode == 37 || e.keyCode == 39) 
	{ 
		this.view.getArt(hero).content.play(); 
	} 
}

Step 34: Stop Walk Animation

When the keys are released the animation stops.

 
private final function stopWalk(e:KeyboardEvent):void 
{ 
	if(e.keyCode == 37 || e.keyCode == 39) 
	{ 
		this.view.getArt(hero).content.gotoAndStop(1); 
	} 
}

Let's stop here to make another test and check that our game code is working:

Keep in mind again that some lines have been commented out as not all functions have been created yet.

Remember that the Milestones are included in the source files, so if for some reason your file doesn't mimic this one take a look at the source to see what could be causing that.


Step 35: Hurt Hero

The hero should take damage if the baddy touches him; the next lines remove one heart and play the hurt sound. An alert is called when the hero is out of health.

 
private final function hurtHero():void 
{ 
	removeChild(hearts[hearts.length-1]); 
	hearts.splice(hearts.length-1, 1); 
	 
	hitSnd.play(); 
	 
	if(hearts.length <= 0) 
	{ 
		loseSnd.play(); 
		alert('lose'); 
	} 
}

Step 36: Kill Baddy

You can kill a baddy by jumping on it. When this happens the score increases.

 
private final function killBaddy():void 
{ 
	scoreTF.text = String(int(scoreTF.text) + 100); 
}

Step 37: Level Complete

The level ends when the hero reaches the door. A sound will be played and an alert will be called; you can see the alert code in the next step.

 
private final function levelComplete(e:*):void 
{ 
	goalSnd.play(); 
	alert(); 
}

Step 38: Alert

This function will stop the game and show the game over message, and also adds a mouse listener to reset the game when clicked.

 
private final function alert(gameState:String = 'win'):void 
{ 
	gameListeners('rmv'); 
	CitrusEngine.getInstance().playing = false; 
	this.view.getArt(hero).content.gotoAndStop(1); 
	 
	var alert:AlertView = new AlertView(); 
	 
	alert.x = stage.stageWidth * 0.5; 
	alert.y = stage.stageHeight * 0.5; 
	alert.scoreTF.text = scoreTF.text; 
	alert.addEventListener(MouseEvent.MOUSE_UP, restart); 
	 
	if(gameState == 'lose') 
	{ 
		alert.titleTF.text = 'Level Failed!'; 
	} 
	 
	addChild(alert); 
	TweenNano.from(alert, 0.6, {scaleX: 0.2, scaleY: 0.2, ease:Expo.easeOut}); 
}

Step 39: Restart

This code will reload the SWF when the game over message is clicked, restoring all initial values and returning to the initial screen.

 
private final function restart(e:MouseEvent):void 
{ 
	navigateToURL(new URLRequest(stage.loaderInfo.url), '_level0'); 
}

Step 40: Final Test

We are now ready to do a final test to our game and check that everything works as expected.


Conclusion

Experiment with this powerful game engine and create your own games!

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

Advertisement