Stage3D First Person Shooter Development Notes: Part 1

Stage3D First Person Shooter Development Notes: Part 1

Learning Stage 3D was the impetus behind starting our R&D project, Dead Coats, a first person shooter game where you are Ben Franklin killing red coat zombies with your electric musket. We challenged ourselves to conceptualize, design, and develop the game within a three week time frame, and amidst some speculation, we knocked it out of the park (see video)...

We landed on using Flare3D for the 3D framework. It allowed us to target Flash Player 11 (Molehill) and Stage3D to take full advantage of the GPU. With Flare3D still in prerelease and Flash Player 11 having only been available for a couple weeks, there was little to no support on some of the issues that we came across. So we wanted to share our development workflow and R&D findings here to spare other intrepid developers some of our pain.

The Development Environment

We normally use Flash Develop as our code editor of choice, and work with Flash Professional for all assets and publishing the final build. Unfortunately, at the time Flash Pro wasn’t updated to publish to FP11 (and still isn’t at the time of this writing), so we knew we had to use the Flex 4.5 SDK. To assist us was the new release of Flash Develop 4 which had the new Flex SDK as part of the initial install. After coming across this blog post, we had our development process set up and were ready to start getting our hands dirty. I also recommend checking the Flare3D tutorial page for some additional examples.

The 3D Environment

Flare3D offers an exporter plug-in for 3D Studio Max. With this plug-in, we were able to export models from 3DS Max as a .f3d file that included all materials and animation. After modeling the world map, there were a few things we needed to setup to allow Flash to communicate with certain objects and materials.

Naming Objects:

Your f3d file can be made up of multiple objects. For Dead Coats, we had the entire world in one f3d file, including the ground, sky, buildings, fences, etc. In order for ActionScript to reference a particular object within the model, we needed to name all of our objects that we wanted to reference. For example, we added a few markers in the world map for positioning purposes. So we added a simple box shape with the name "bottomLeft_pin".

See Referencing objects in ActionScript below.

Naming Materials:

Same as with naming objects, we reference a particular material in ActionScript by making sure we name our materials. For example, we needed to reference material used for the sky dome, so it wouldn't be affected by the lights.

See Referencing Materials in ActionScript below.

Collision Objects:

Ben Franklin couldn’t simply walk through walls, so we needed to apply some collision detection to the game. Flare3D has a built in method for collision detection which goes through all of the geometry to see if the camera has collided with it. We had a lot of geometry that would never need to get tested for collision, like tree branches, roofs of buildings, hills outside of the game play area, etc. Since there is no reason to test this unneeded geometry, we created objects with much simpler geometry around all of the collision areas, and had ActionScript use that for its collision detection. This resulted in much better performance. See the green boxes around the game elements in the image below.

See Adding Collision Detection below.

Exporting the 3d Model:

Now that everything is setup as far as object naming, material naming, and collision objects, are concerned, we are ready to export the model. Through the simple Flare3D exporter there are only a few options to check.

Get more info about these settings here.

Working in Flash Develop

Once you've got your models setup, you're going to want to import them, reference the objects within them, create controls for moving around in your game, and do some basic collision detection. Hey, what a coincidence, so did we! Here's what we did...

Importing the model:

To bring assets into the Flare3D scene, you should pause scene rendering, add an event listener to the scene for asset download completion, and load external textures and 3D models using Flare3D's "scene.addTextureFromFile( url )" and "scene.addChildFromFile( url )" methods. Once you get the event, re-parent the objects and resume the scene.

Importing the World Model

scene = new Scene3D( this ); // create a new Scene3D object
scene.antialias = 10; // bump up the anti-aliasing to make the scene look crisper. This is a good candidate property for a "quality" slider
scene.addEventListener( Scene3D.COMPLETE_EVENT, assetCompleteHandler); // listen for the scene to finish downloading external assets
scene.pause(); // pause scene redering

containerPivot = new Pivot3D( "container" ); // create a new container Pivot3D to add scene content to
containerPivot.parent = scene; // parent the scene to the container

// class property "worldPivot" defined above as: 'var worldPivot:Pivot3D;'
worldPivot = scene.addChildFromFile( _path + "WorldMap.f3d" ); // set the Pivot3D for the world to the top-node of the WorldMap.f3d object hierarchy
worldT

And then in the assetCompleteHandler function:

Parsing the World Model

private function assetCompleteHandler( evt:Event ):void {
  // STEP 1: Parse the world pivot
  containerPivot.addChild( worldPivot ); // same as: worldPivot.parent = containerPivot;

  // STEP 2: ???
  if( Math.random() ){
    true;
  }

  // STEP 3: PROFIT!
  scene.resume();
}

Referencing Objects in ActionScript:

So we can now access the object we named "bottomLeft_pin" in ActionScript . We are able to get that object, get its position, and then hide it so we don’t see it in the game:

Referencing a 3D object

var bl:Pivot3D = _world.getChildByName( "bottomLeft_pin" );
var _bottomLeft: Vector3D = bl.getPosition();
bl.visible = false;

Referencing Materials in ActionScript:

Once we've brought in the model, we need to dynamically setup properties of it (hiding geometry, replacing static textures with dynamic ones, etc.). Here, we are turning off the lighting effects for the sky dome material.

Referencing the materials on a model

// turn off lights on the sky material
var skyMaterial:Shader3D = Shader3D( worldPivot.getMaterialByName( "sky" ) );
skyMaterial.enableLights = false;

Adding Collision Detection:

Collision detection can easily be done through the SphereCollision Flare3D class. Basically, you surround the object you want to test with a collider sphere of a certain radius. This looks something like:

Creating the SphereCollision object for the camera

// get the simplified collision geometry from the world model
var collisionObjects:Pivot3D = Pivot3D( world.getChildByName( "CollisionObjects" ) );
collisionObjects.visible = false; // turn the collision objects off

// create the collision manager for detecting when Ben hits the world
var collisions:SphereCollision = new SphereCollision( scene.camera, 10 );
collisions.addCollisionWith( collisionObjects );

Then inside the game update function, detect collisions using a collisions.slider() function. When collisions are detected with the target object's bounding sphere, the target object is slid in a normal direction (oh, that's what slider() means). So here's what that means:

Creating the SphereCollision object for the camera

private function detectCollisions():void {
// once, we move the player, test if it is colliding with something.
  if ( collisions.slider( 5 ) )
  {
    // if it collided with something, get the first collision data.
    var info:CollisionInfo = collisions.data[0];
    trace( info.mesh.name );
  }
}

This is overkill for our game, since all we're doing when we detect a collision is pushing the camera back from the object that it collided with. For this, all you have to do is the collisions.slider( precision ) call and you're done. However, if you had an object that had an effect when someone walked over it (i.e. a campfire, pressure sensor, or molten magma) you could do your handling there by detecting the collision object's name and testing against the obstacle object names.

Adding first person nav controls:

There is a lot to go over here, but the two main parts are to 1) make the mouse look and 2) generate the velocity components based on the current direction. I won't go over all of the setup for these, as it can get tedious and the naming should clarify what everything is, but here is the basic math.

Mouse Look equations

/* Performed on update */
var deltaX:Number = mouseDeltaX; // get the amount the mouse has moved since the last frame in the x direction
var deltaY:Number = mouseInverted ? -mouseDeltaY : mouseDeltaY; // Do the same for the y direction, except if the mouse should be inverted negate the change value

cameraRotationX += deltaY * mouseSensitivity; // update the up-down rotation around the X axis based on the mouse sensitivity setting
cameraRotationY += deltaX * mouseSensitivity; // update the left-right rotation likewise
NeoMath.clamp( cameraRotationX, -90, 40 ); // clamp the up-down rotation value so that you can't look too far down or up

And then here is the movement function that relies on the camera's left-right rotation value and the player's velocity to determine their next position.

Mouse Look equations

/* Performed on update. Note: These equations don't take into account terrain with elevation. To do that, you would need to factor in the slope of the terrain the player was currently over. */
scene.camera.x += Math.sin( NeoMath.RADIANS * cameraRotationY ) * velocity.z + Math.sin( NeoMath.RADIANS * cameraRotationY + Math.PI * 0.5 ) * velocity.x;
scene.camera.z += Math.cos( NeoMath.RADIANS * cameraRotationY ) * velocity.z + Math.cos( NeoMath.RADIANS * cameraRotationY + Math.PI * 0.5 ) * velocity.x;

Conclusion

Developing Dead Coats was an extremely fun project for us. We got our feet wet with a true 3D FPS and learned a lot along the way. What we shared here is anything but a mind-blowing secret that we have conjured up. Just what we found and implemented for this R&D project. Hopefully there is something here worth taking away that will help you in your FPS endeavors. Look forward to seeing more of our notes on Dead Coats in the next few weeks, specifically on modeling and animation rigging in 3DS Max.