18 September 2011
A solid understanding of developing with ActionScript 3.0 is required. Some previous experience working with Flash Builder is recommend. Prior knowledge of Flare3D is helpful, but not necessary to complete this tutorial.
Additional required other products
Intermediate
Flare3D is a powerful engine that facilitates management of 3D content in Flash. It was designed to provide a seamless development workflow, so that you can do more with less effort.
This tutorial focuses on the ActionScript code required to build a 3D game in Flash; the process of creating and exporting the game's asset files is not described for this sample project.
After completing this tutorial, you'll know the basic concepts required to develop a complete 3D game in Flash. For this example, you'll work with the files used to build a game called Yellow Planet using Stage3D and Flare3D (see Figure 1).
You can download and play the demo at: http://www.flare3d.com/demos/yellowplanet/
The tutorial is divided into ten steps that describe the basic 3D concepts used to make and control a more sophisticated and complex system. Download the sample files provided in the Requirements section and save the uncompressed archive folder to your desktop. As you follow along with each step, you can refer to the corresponding folder in the sample files to review the class described in each tutorial section. The steps involved to develop the game build upon the previous topics in succession, so you'll learn in a progressive way.
At the time of writing, Flash Builder (and others editors) are not currently configured to publish SWF content for Flash Player 11. To accomplish this, you'll need to use a specific version of the Flex SDK and adjust some settings to output files for Flash Player 11. The files you need are listed in the requirements section. After downloading the files, follow the instructions below to setup Flash Builder to publish Stage3D and Flare3D content.
After downloading and uncompressing the sample files folder, open the flare3D folder and locate the source code named yellowplanet.
To compile SWF files that play back in Flash Player 11, you'll need to update the following project properties:
To make these changes, you'll access the project's properties and update the settings.
In order to publish files for Flash Player 11, you need to set the SWF publish setting to version 13. You also need to configure the project to use the Flex SDK version 4.5.1.
At the time of this writing, the version 13 option for SWF files is not an available setting in the ActionScript Compiler, so you'll add it manually.
-swf-version 13
When files are published for Flash Player 11, the wmode parameter must be set as direct. You must add this parameter to the HTML file that will host the application. Flash Builder automatically creates an HTML file using a template file called index.template.html.
params.wmode = "direct";
Here's an example of the updated code:
params.quality = "high";
params.bgcolor = "${bgcolor}";
params.wmode = "direct";
params.allowscriptaccess = "sameDomain";
…
This project includes some project files that are located inside the assets folder. If desired, you can open these files and review the different parts used to build the game application:
After you are familiar with the asset files and you've finished configuring Flash Builder, you're ready to move onto the next section and begin exploring how the game is developed.
YellowPlanet_01.as contains the code described in this section.
In the same way the Stage serves as a container for 2D content, you'll need to create a container to hold 3D objects. This container is called the scene.
The process of creating a new scene is easy because you can type a single line:
var scene:Scene3D = new Scene3D(container);
In the code above, the container parameter can reference any empty sprite or movie clip instance that has been added to the Stage.
Note: There's a useful version of Scene3D called Viewer3D. This object extends Scene3D by adding scene rotation and the ability to zoom using the mouse to the viewer’s functionality.
In order to load 3D models to the project, you'll use the following code:
var astronaut:Pivot3D = scene.addChildFromFile( "astronaut.f3d" );
A Pivot3D is the most basic object in Flare3D. It is similar to an empty movie clip because you can move, rotate, and translate it, as well as adding other 3D objects to it.
If you compile the project now, you'll see a little astronaut running around.
The addChildFromFile method, as its name implies, adds a new child to the scene from an external resource and returns the object as a Pivot3D container. In this example, the object is called astronaut.
Each file could contain many things, including geometry, cameras, lights, and much more. For that reason, the returned object is not the mesh itself, but a container of the entire file.
The following code sets up the Stage, creates the scene, and loads the Flare3D files that contain the 3D models:
// stage setup.
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
// In this first steps, just change the scene by creating a Viewer3D
// to be able to look around the scene.
scene = new Viewer3D(this);
// Loads the external files and stores the references into planet
// and astronaut objects.
planet = scene.addChildFromFile( "planet.f3d" );
astronaut = scene.addChildFromFile( "astronaut.f3d" );
//You can listen PROGRESS and COMPLETE events to control scene loading
scene.addEventListener( Scene3D.PROGRESS_EVENT, progressEvent );
scene.addEventListener( Scene3D.COMPLETE_EVENT, completeEvent );
YellowPlanet_02.as contains the code described in this section.
private function setupScene( pivot:Pivot3D ): At this point you've created the scene and loaded the 3D objects. In this section, you'll learn how to find objects inside the scene and apply specific behaviors to them.
Once the scene is finished loading, you can access all of the contained objects. You can get any object inside using the method getChildByName("object_name"). This method searches recursively in the hierarchy until it finds the requested object. If nothing was found, the method returns null.
For example, you can get a reference from the sky object by using the folowing line:
sky = planet.getChildByName( "sky" );
If you want to modify all of the children of an specific object, you can use the forEach method. The forEach method calls a function for each child of the specified object.
For example, review the following line:
planet.forEach( setupScene );
In the line above, setupScene is a method that receives each child as argument.
Review the code example below:
private function setupScene( pivot:Pivot3D ):void
{
if ( pivot.name == "fan" )
{
// All Pivot3D objects has a userData property
// you can use to store your own data.
pivot.userData = new Object();
pivot.userData.speed = Math.random() * 5 + 5;
// Adds to fan Vector.
fan.push( pivot );
}
}
You can control when the scene updates by using the Update event. This event is dispatched before the render, so that you can modify the scene each time this method is invoked. The updateEvent function is the main loop in the game.
In this example, updateEvent controls how the fans and the sky are rotated:
private function updateEvent(e:Event):void
{
// Update the world.
for each ( var f:Pivot3D in fan )
{
f.rotateY( f.userData.speed ); //Rotate fan blades
}
sky.rotateX(0.1); //Rotate sky in the background
}
As it plays, notice that you can control the rotation of the planet with your mouse movements. As you watch the fans rotate, think about updateEvent function and how the planet’s objects are modified inside it. After you are finished reviewing the sample project, proceed to the next section.
YellowPlanet_03.as contains the code described in this section.
Next, you'll explore how to manipulate a camera with Flare3D. You'll also learn some basic camera manipulation concepts. There are many different strategies you can use to control the position of the camera. This section explains two functions used in the 3D game that simplifies this process.
If you examine the code in YellowPlanet_03.as, you'll notice that the astronaut is wrapped inside a Pivot3D object.
This step may not seem necessary at the moment. But in the future it facilitates more control of the astronaut when required.
Review the following code to learn how to have greater control over the display of the astronaut:
// This creates an empty 3D container and adds the astronaut to it.
container = new Pivot3D();
container.addChild( astronaut );
//Adds the container to the scene too, otherwise
//the objects will not be present on the scene.
scene.addChild( container );
In the sample project game, the astronaut walks around the planet. So the camera must be able to follow him. This camera behavior can be implemented by referencing the astronaut's current position. You can use the setPositionWithReference to accomplish this task. This method is included in the Pivot3DUtils and allows you to position an object in relation to another object used as a reference. This means that you can transform any object relative to another object.
In this game, the camera is always located behind the astronaut. It maintains its position relative to the astronaut. The camera (like any other object into the scene) has an axis that defines both its position and its direction. The direction of the axis is shown in Figure 5.
The +Z points to the astronaut's direction and +Y is pointing up. These vectors are called Up, Dir, and Right (see Figure 6).
Assuming that every object has his own axis, the next line of code sets the camera up (+Y) 80 units and away (-Z) 20 units relative to the astronaut:
Pivot3DUtils.setPositionWithReference( scene.camera, 0, 80, -20, container, 0.1 );
As you see, a complex task is resolved using a single line of code by using Flare3D.
Now that the camera is in the right position, the lens needs to "look" in the right direction. Using the same strategy as using reference points to set the position of objects, you can also use reference points to look at a specific point in the game.
This method has an additional and optional parameter called 'up' that is required for this game.
Usually, the Up vector is always pointing to the up direction. In many cases, passing a null value is enough to make Flare3D calculate the vector for you (see Figure 7).
However, imagine the folowing example: If you hold your hand with your index finger pointing at an object as though it was a pistol, your thumb automatically points up, which is the desired outcome. Both the Dir and Up vectors are defined.
Now, while still pointing your index finger at the same object, try to rotate your hand. As it rotates, the Up finger (your thumb) start changing its direction. Notice that even if you always hold your hand to point in the same direction, the Up vector moves as you rotate your hand. In order to program the camera so that it knows which way is up, you'll use the up parameter.
In the sample game, the camera is positioned above in relation to the astronaut. The camera must be set to point down, in order to look at the astronaut. The Up vector of the camera should be pointing to the Dir vector of the astronaut (see Figure 8).
Pivot3DUtils.lookAtWithReference( scene.camera, 0, 0, 0, container, container.getDir(), 0.05 );
YellowPlanet_04.as contains the code described in this section.
In this section, you'll learn some concepts used to introduce interactivity to the game.
First, explore how the game manages the user's input. Flare3D includes some utilities you can use to quickly manage the keyboard or mouse input as the game is played.
In this class, the Input3D.keyDown() method is used to detect which key is pressed. The method returns a true value while the key is held down.
The utility also includes the Input3D.keyHit() method, which is similar to keyDown but returns the true value only once.
Another interesting concept to explore in the sample project code is the use of rays.
A ray is a virtual and infinite line that starts at some arbitrary point and continues infinitely in some direction.
Rays can test if an object in the game intersects before reaching the infinite. If the test is true, the ray provides additional data about the collision, including the mesh, the exact point of the collision, the normal of the face that was collided, and other data.
Rays are used in this sample project because the astronaut needs to keep running on the spherical floor while remaining aligned to it. Rays are used to achieve both tasks (see Figure 9).
In this case, the stage of the environment is basically an sphere. If desired, you can keep the astronaut aligned to the sphere using other methods, but rays are very helpful because they work with non-uniform surfaces.
new RayCollision() methodUse the following line of code to create a new RayCollision:
ray = new RayCollision();
ray.addCollisionWith( object ) methodThe following code adds the floor of the planet to test collisions with the ray:
ray.addCollisionWith( planet.getChildByName( "floor" ), false );
The second argument ("false") indicates that the collision is detected only with the floor (discarding floor’s childs).
ray.test( from, direction ) methodBegin by setting a point where the ray will start. This position should not exactly match the astronaut's position because that is so close to the floor that the ray will not intersect with anything. Instead, set a point higher than that, such as (0,100,0) and use localToGlobal to convert that point into the world coordinates.
After defining the point where the ray starts, the next task is to give it a direction. The direction should be pointing down in the direction of the astronaut. You can get that vector using the getDown() method of Pivot3D.
//define ray’s start point
var from:Vector3D = container.localToGlobal( new Vector3D( 0, 100, 0 ) );
//define ray’s direction
var dir:Vector3D = container.getDown();
// Test the ray.
if ( ray.test( from, dir ) )
{
// ray collision is true.
…
In the previous section, you learned how to test when ray collision returns a value of true. The ray collision data is available and you can use it for the game's logic. The information is stored in an specific structure. You can use a CollisionInfo object to parse the received data. In the code bellow, notice how you can see obtain the position (info.point.x, info.point.y, info.point.z) and normal orientation (info.normal) information.
// Test the ray.
if ( ray.test( from, dir ) )
{
// Get the info of the first collision.
var info:CollisionInfo = ray.data[0];
// Set the astronaut container at the collision point.
container.setPosition( info.point.x, info.point.y, info.point.z );
// Align the astronaut container to the collision normal.
container.setNormalOrientation( info.normal, 0.05 );
YellowPlanet_05.as contains the code described in this section.
The SphereCollision is a utility that works with complex geometry collisions. It is basically a virtual sphere that collides and reacts with one or many mesh objects (see Figure 10).
This sample game uses SphereCollisions to enable the astronaut to collide with the walls and obstacles around the planet. Using SphereCollision, you can wrap the astronaut in virtual sphere to collide with the game environment.
The SphereCollision utility also provides a smooth displacement when the collision ocours, which is called a slider.
The following steps illustrate how to use the SphereCollision class:
Note: RayCollision and SphereCollision are intensive math algorithms, so it's best to use them with low polygonal objects when possible. Since these utilities perform such complex mathematical equations, you'll notice a significant performance difference when running the project in debug or release mode. Additionally, SphereCollision does not work with scaled objects, so always normalize your collision objects before working with them in the game.
The first step is to declare the collision object, like this:
// The instance for the SphereCollision.
private var collisions:SphereCollision;
Later, you can create the SphereCollision using the astronaut as a target with a radius of 3 units. Remember that in Step 3, the astronaut was placed inside a Pivot3D object called "container" (see Figure 11).
By default, the virtual sphere will be positioned at the center of the pivot. In this example, the pivot of the astronaut is at his feet, so you need to apply an offset for the virtual sphere, which is the third Vector3D parameter at (0,3,0) as shown in Figure 11.
collisions = new SphereCollision( container, 3, new Vector3D( 0, 3, 0 ) );
If you remember section 3 "Reviewing the astronaut container" the astronaut character was wraped into a container. Now it’s time to explain that. The astronaut has the ability to jump. Then, in some cases, the character can jump over a fan to avoid a sure death. But, the astronaut must not jump obstacles like buildings. At this moment, you can detect a collision between the astronaut and the obstacles. If astronaut jumps the obstacles, the collision is never dispatched. For this reason the SphereCollision is added to the container instead of the astronaut. Then, when the astronaut jumps, the sphere remains in the bottom and the collision is detected (See Figure 12).
Once the collision is created, you must specify the objects that must be checked. In the sample game, these objects (Pivot3D objects) are each called "obstacle" so you need to add a collision for each of them.
if ( pivot.name == "obstacle" )
{
// If the object is an obstacle, add it to the collisions list.
collisions.addCollisionWith( pivot, false );
}
Once you move the astronaut, all of the collisions are tested. The updateEvent() function contains the following line:
collisions.slider();
If the astronaut collides with an obstacle, this method generates a displacement to fix the intersection and relocate the astronaut to the right place in the scene. This avoids issues that may cause the astronaut to become stuck in a single location and appear to be frozen in the scene.
YellowPlanet_06.as contains the code described in this section.
Now it's time to add some nice looking special effects (see Figure 13).
Particles enable you to quickly draw a large set of data simultaneously. When you create effects, you can group all of the particles and draw them together in one draw call, to avoid drawing each particle separately. As you can imagine, this strategy increases the drawing speed, because particles works a bit differently than other 3D objects and they are usually facing the camera.
Follow these steps to use particles in a 3D Flash game:
Particle3D class to use as a particle template.Particle3D class to add the desired behavior to the particles.ParticleEmiter3D class using the material and particle template.emitParticlesPerFrame, particlesLife and decrementPerFrame properties.In this example, the new emitters were created in separate classes that extend from the ParticleEmiter3D class to keep the code simple and easier to update.
First, set the variables used for particles. Notice that both smoke and fire are Texture3D objects.
private var smoke:Texture3D;
private var fire:Texture3D;
private var fireEmiter:FireEmiter;
Next, load the textures as external files.
smoke = scene.addTextureFromFile( "smoke.png" );
fire = scene.addTextureFromFile( "particle.png" );
You create the fire emiter and add it to the astronaut. The FireEmiter class extends ParticleEmiter3D, and you can find this class in the sample files folder in the following directory: src\objects
Set the parent of the FireEmiter to the astronaut. This ensures that the particles will be emited from the astronaut model.
fireEmiter = new FireEmiter( fire );
fireEmiter.parent = astronaut;
You can use SmokeEmiter the same way you work with FireEmiter. In this sample game, SmokeEmiter is associated with each fan located on the planet.
Note that the SmokeEmiter class uses the fan direction for each emiter:
// Creates a new smoke emiter and adds them to the scene.
var particles:SmokeEmiter = new SmokeEmiter( smoke );
particles.copyTransformFrom( pivot );
particles.parent = scene;
YellowPlanet_07.as contains the code described in this section.
The concepts described in this section are similar to those used when developing any other Flash game. In this step, the goal is to make the astronaut jump over the Y axis.
Because the astronaut is located inside the container object, it doesn`t matter how the orientation of the container changes. The astronaut will always move up and down over its own Y axis.
After completing this step, you'll update the logic of the game. In order to manage the flow of the game play, it's important to add some state variables. Additionally, you'll add the function gameLogics to update all of the game states as the game is played.
Add the code shown below to implement some game logics. To control the astronaut character's behavior, you'll need to store the character's state. And to control the astronaut's moment, you'll add a new variable called jumpValue.
// Game logics variables.
private var state:String = "run";
private var jumpValue:Number = 0;
The gamesLogic method is called on each Update iteration. The gamesLogic method checks the astronaut’s state. If desired, you can modify the game behavior to change how it works. For example, if the user presses the Space key, it activates the FireEmiter and updates the character's animation. Here's an example:
private function gameLogics():void
{
switch( state )
{
case "run":
if ( Input3D.keyHit( Input3D.SPACE ) )
{
jumpValue = 4;
fireEmiter.emitParticlesPerFrame = 25;
state = "jump";
container.gotoAndPlay( "jump", 3 );
}
break;
case "jump":
if ( astronaut.y == 0 )
{
state = "run";
container.gotoAndPlay( "run", 3 );
}
break;
}
}
YellowPlanet_08.as contains the code described in this section.
The game is really shaping up nicely now. You've come to the point where the fun parts are added to make the game more exciting. In this section, you'll add the code that enables the astronaut character to kill enemies, die, and fly!
These are the hazards and pitfalls that bring the game to the life. You'll continue working with the game logic, to continue incorporating new features into the game.
At this point, it's also time to move the behaviors that control the fans and mines to a new function called gameObjects.
Create a new game variable in the game logic variables named shakeFactor:
// Game logics variables.
private var state:String = "run";
private var jumpValue:Number = 0;
private var shakeFactor:Number;
You'll use the shakeFactor variable to shake the camera and create a special effect whenever the astronaut dies.
The gameObjects behave similarly to gameLogics because the gameObjects method is called on each Update iteration. This function gets the astronaut's current position and updates the game state by comparing the distance between the astronaut character and the other elements in the scene.
If the distance between the astronaut and a fan is less than fan’s radius, then the game's state is changed to "fan" and the astronaut is launched into the sky the next time the gameLogics method is called (see Figure 14).
// changes the state to "fan"
if ( Vector3D.distance( f.getPosition(), position ) < radius ) state = "fan";
If the distance between the astronaut and a mine is less than 10 units, the game logic uses the value of the state variable to set one of two possible outcomes:
m.visible = false;
shakeFactor = 2;
container.visible = false;
shakeFactor = 15;
state = "die";
Every time a mine is destroyed, it is repositioned to the other side of the planet. Then it must be re activated.
To detect if the mine is in the other side of the planet, you can compare the Up vector of each mine with the current Up vector of the astronaut. If you multiply the two vectors using dotProduct, and both vectors are pointing in the same direction, the result is a positive numeric value, which means that the astronaut and the mine are located on the same side of the planet. If the comparison determines that the vectors are pointing in different directions, the result is a negative number because the mine is located far away from the astronaut's location (see Figure 16).
//This line gets the mine Up vector
var mineUp:Vector3D = m.getUp();
//This line gets astronaut Up vector
var contUp:Vector3D = container.getUp();
//Here compare the vector and re-activate the mine if result is great than zero.
if ( mineUp.dotProduct( contUp ) < 0 ) m.visible = true;
YellowPlanet_09.as contains the code described in this section.
In this section, you'll use Flash Professional to integrate sounds and 2D content in the 3D Flash game.
The addition of sound and 2D objects add an exciting new facet to the game and make it much more enjoyable to play.
There are many different ways you can bring Flash content into the game. In this example, you can keep things simple by exporting your game assets as a component (SWC file).
Follow these next steps to export the contents of an FLA file as a component:
After exporting a SWC file, you can add the resulting library to your project and use the included assets to create new instances of them.
This part of the process is very similar to working with basic movie clip symbols and sounds.
The 2D interface for the sample game is included in the assets.swc file. You can create an instance of this object using the following lines of code:
// MovieClip and Sound objects.
private var loading:Loading = new Loading();
private var gui:GUI = new GUI();
private var sndMusic:MusicSound = new MusicSound();
private var sndCoin:CoinSound = new CoinSound();
private var sndDead:DeadSound = new DeadSound();
…
// Adds the MovieClips to the stage.
addChild( gui );
addChild( loading );
As a user plays the game, three different scores are tracked. The user's individual game points, the number of energy objects collected and the overall highest score ever are all calculated and tracked, in order to be displayed in the game's interface. The score data is updated in gameGUI() function. This function modifys the text characters in the 2D UI elements to display the current game information (see Figure 17).
// Updates the interface of the game.
private function gameGUI():void
{
// Gets the best score.
if ( score > bestScore ) bestScore = score;
gui.points.score.text = score.toString();
gui.best.score.text = bestScore.toString();
gui.energy.content.count.text = energyCount.toString();
gui.total.score.text = score.toString();
}
The game includes messages that are displayed whenever the astronaut dies or destroys a mine. These messages are comprised of 2D graphic elements too (see Figure 18).
Review the following lines of code to see how messages are placed in the correct location within the scene, using the astronaut's position as reference.
private function newPop():void
{
var pos:Vector3D = container.getScreenCoords();
var pop:Pop = new Pop();
pop.x = pos.x;
pop.y = pos.y;
addChild( pop );
}
The method getScreenCoords() converts the 3D container’s coordinates to 2D coordinates (to return a Point object).
YellowPlanet_10.as contains the code described in this section.
The objective of the game is to collect energy items that the astronaut finds while walking around the planet. When the user collects three energy items, they are promoted to the next level in the game and the astronaut's speed is increased (see Figure 19).
The energy items are an external 3D model stored in the energy.f3d file. To add the energy items to the game, you load the F3D file and add it to the scene, just like you added the other Flare3D elements.
Every time the astronaut finds an energy item, a brand new energy item is generated and placed at a random location on the planet. The new energy item's position is randomly calculated, like this:
// Pick a random position.
var currIndex:int = energyIndex;
while ( currIndex == energyIndex ) energyIndex = Math.random() * points.length;
//energy is moved to the new position
energy.copyTransformFrom( points[energyIndex] );
Every time the astronaut finds a new energy item, his speed is increased:
speedCounter++;
speed += 0.2;
gui.speed.visible = true;
gui.speed.content.value.text = speedCounter.toString();
gui.speed.gotoAndPlay(1);
energyCount = 0;
resetCounter = 120;
state = "energy";
When the astronaut takes the 3° consecutive energy item, he is temporarily unable to move for a little while and the "Next Level" alert is displayed. After this occurs, the game's state returns to "run." This type of extra detail adds more excitement to the game and results in a more enjoyable user experience.
if ( Input3D.keyDown( Input3D.RIGHT ) ) container.rotateY( 5 );
if ( Input3D.keyDown( Input3D.LEFT ) ) container.rotateY( -5 );
resetCounter--;
if ( resetCounter < 0 ) state = "run";
The 3D Flash game called Yellow Planet is included with the official Flare3D release as a tutorial. You can use the sample project assets to explore how Flare3D works. In this tutorial, you've learned about the primary tasks associated with developing the game.
Hopefully this quick introduction has inspired you to develop your own Flash games using the Flare3D technology and leverage the new features in Flash Player 11.
To learn more about working with Flare3D, visit the Flare3D website.
Also be sure to check out the Stage 3D Developer Center to find helpful articles, tutorials and sample projects to get you up to speed quickly.