Talking about Helicopter game, 3D, Actionscript 3, Flash, Game development and Users contributions.
Do you remember Roger Engelbert and his post about one game, many frameworks?
He’s back with another amazing tutorial I had the permission to share with you. You will learn step by step how to create an helicopter game using Away3D
The 2d logic
In this post I will create a Chopper like game engine where you have a helicopter flying over a city. The same basic engine allows for multiple types of games so I will try to post some of the possible variations (particularly in the way the terrain is built and used).
But all versions will start with a basic 2D collision logic. If you’ve seen my tutorial on Isometric Arches you will recognize the logic I’ve used here. But this is the final result in 2D:
Use arrow keys to move
If you look at the code you will see that I use two views to test for collision. I check for collision on a top view, which is the one shown above, and I check for collision on a side view, when I determine the altitude of the helicopter and compare it to the height of the building(s) returned in the first collision check.
Here’s the main loop:
protected function on_EnterFrame(event:Event):void {
_timeNow = getTimer();
var dt:Number;
dt = (_timeNow - _timeThen) * 0.006;
//update player
if (_moveRight) {
_player.vx += dt;
} else if (_moveLeft) {
_player.vx -= dt;
} else if (_moveUp) {
_player.vy -= dt;
} else if (_moveDown) {
_player.vy += dt;
}
_player.update(dt);
_player.place();
//update buildings
var i:int;
for (i = 0; i < _topBuildings.length; i++) {
moveBuilding(_topBuildings[i], dt);
moveBuilding(_middleBuildings[i], dt);
moveBuilding(_bottomBuildings[i], dt);
}
//track collision (best to check in a separate loop)
for (i = 0; i < _topBuildings.length; i++) {
if (_topBuildings[i].checkCollision()) {
trace(1);
}
if (_middleBuildings[i].checkCollision()) {
trace(2);
}
if (_bottomBuildings[i].checkCollision()) {
trace(3);
}
}
_timeThen = _timeNow;
}
Here’s the collision logic inside the building class:
public function checkCollision ():Building {
if (player.bounds.intersects(this.bounds)) {
if (player.altitude <= height) {
return this;
}
}
return null;
}
Away3D and cubes
The new Away3D api handles primitives and 3D objects in a way similar to how most engines do it: by separating mesh from geometry. That’s cool. And like most engines it has only one viable way to texture a Cube so that a specific material is used on a specific face: using a Cube made of Plane meshes.
The Basic Code to Set Up the View
//Embed texture images
[Embed(source="/../assets/building_blue.jpg")]
public static var Texture:Class;
[Embed(source="/../assets/uv.jpg")]
public static var UVTexture:Class;
...
var scene:Scene3D = new Scene3D();
_view = new View3D();
_view.scene = scene;
addChild(_view);
//setup the camera
_view.camera.z = -300;
_view.camera.lookAt(new Vector3D());
Here are the ways you can texture a cube in Away3D:
The Wrap Around
//TO WRAP AROUND JUST USE A REGULAR IMAGE
var material:TextureMaterial = new TextureMaterial (Cast.bitmapTexture(Texture));
var uvmaterial:TextureMaterial = new TextureMaterial (Cast.bitmapTexture(UVTexture));
_cube = new CubeGeometry ();
_cubeMesh = new Mesh (_cube, material);
scene.addChild(_cubeMesh);
The Repeat
//TO REPEAT USE TILE6 = FALSE
var material:TextureMaterial = new TextureMaterial (Cast.bitmapTexture(Texture));
var uvmaterial:TextureMaterial = new TextureMaterial (Cast.bitmapTexture(UVTexture));
_cube = new CubeGeometry ();
_cube.tile6 = false;
_cubeMesh = new Mesh (_cube, material);
scene.addChild(_cubeMesh);
The UV Map
And here is the UV map image:
//TO USE UV MAP, CREATE THE CORRECT IMAGE
var material:TextureMaterial = new TextureMaterial (Cast.bitmapTexture(Texture));
var uvmaterial:TextureMaterial = new TextureMaterial (Cast.bitmapTexture(UVTexture));
_cube = new CubeGeometry ();
_cubeMesh = new Mesh (_cube, uvmaterial);
scene.addChild(_cubeMesh);
But...
What if you need a certain number of faces to have a specific material, and you need only certain faces to have a different UV scaling? You can’t. Not with a cube.
But you can sort of understand why this apparent limitation exists: An API can’t account for everything, it must simply give you the tools to help you apply your ideas, whatever they are. It would be cool if Away3D had a multi-material cube like ISGL3D has (and if you recall ISGL3D actually creates a 6 plane cube for its multiple material “Cube”.) But it is remarkably easy to build one.
So back to the Game Scene 3D. For the buildings, I created a cube, made out of six planes. Since each building may have a different height, I needed to be able to scale the UV map on the building based on its height so that the material tiles on the correct faces.
This is the result:
Refresh to see different buildings
The building uses two materials. One for its sides and one for its top. Of course the building used in the Game does not actually need 6 faces. Depending on what type of terrain you need for your game you could use only three faces.
Here’s the code to create 6 faces and tile the material accordingly:
package
{
import away3d.containers.ObjectContainer3D;
import away3d.entities.Mesh;
import away3d.materials.TextureMaterial;
import away3d.primitives.PlaneGeometry;
import away3d.utils.Cast;
public class Building3D extends GameSprite
{
[Embed(source="/../assets/building_blue.jpg")]
public static var SideTextureBlue:Class;
[Embed(source="/../assets/building_green.jpg")]
public static var SideTextureGreen:Class;
[Embed(source="/../assets/building_orange.jpg")]
public static var SideTextureOrange:Class;
[Embed(source="/../assets/building_pink.jpg")]
public static var SideTexturePink:Class;
[Embed(source="/../assets/texture_blank.png")]
public static var TopTexture:Class;
public var side:Number;
private var _heightRatio:int = 32;
public function Building3D() {
height = Math.floor(Math.random()*5 + 2) * 32;
drawCube(64);
}
public function drawCube (width:Number):void {
this.width = width;
this.height = height;
this.side = width;
var random:int = Math.floor(Math.random()*4);
var sideMaterial:TextureMaterial;
switch (random) {
case 0:
sideMaterial = new TextureMaterial (Cast.bitmapTexture(SideTextureBlue));
break;
case 1:
sideMaterial = new TextureMaterial (Cast.bitmapTexture(SideTextureGreen));
break;
case 2:
sideMaterial = new TextureMaterial (Cast.bitmapTexture(SideTexturePink));
break;
case 3:
sideMaterial = new TextureMaterial (Cast.bitmapTexture(SideTextureOrange));
break;
}
var topMaterial:TextureMaterial = new TextureMaterial (Cast.bitmapTexture(TopTexture));
sideMaterial.repeat = true;
var planeMesh:Mesh;
var sidePlane:PlaneGeometry = new PlaneGeometry (width, height);
sidePlane.scaleUV(1, height/_heightRatio);
var topPlane:PlaneGeometry = new PlaneGeometry (width, width);
this.skin = new ObjectContainer3D();
//front face
planeMesh = new Mesh(sidePlane, sideMaterial);
planeMesh.rotationX = -90;
planeMesh.z = -width * 0.5;
planeMesh.y = height*0.5;
this.skin.addChild(planeMesh);
//right face
planeMesh = new Mesh(sidePlane, sideMaterial);
planeMesh.rotationX = -90;
planeMesh.rotationY = -90;
planeMesh.x = width * 0.5;
planeMesh.y = height*0.5;
this.skin.addChild(planeMesh);
//left face
planeMesh = new Mesh(sidePlane, sideMaterial);
planeMesh.rotationX = -90;
planeMesh.rotationY = 90;
planeMesh.x = -width * 0.5;
planeMesh.y = height*0.5;
this.skin.addChild(planeMesh);
//back face face
planeMesh = new Mesh(sidePlane, sideMaterial);
planeMesh.rotationX = -90;
planeMesh.rotationY = 180;
planeMesh.z = width * 0.5;
planeMesh.y = height*0.5;
this.skin.addChild(planeMesh);
//top face
planeMesh = new Mesh(topPlane, topMaterial);
planeMesh.y = height;
this.skin.addChild(planeMesh);
}
public function checkCollision ():Building3D {
return null;
}
override public function update (dt:Number):void {
//stop moving buildings is speed is too low
if (Math.abs(vx) < 0.3) vx = 0;
nextX = x + vx;
//wrap buildings around screen
if (nextX < -side*0.5 && vx < 0) nextX += 600;
if (nextX > 480 + side*0.5 && vx > 0) nextX -= 600;
}
}
}
Chopper 3D: The Away3D Version
Here is the final version of the Chopper game scene done in Away3D. I ended up making a few changes to the 2D collision code because I decided that allowing the chopper to move backwards, away from the viewer, was too confusing. But I left the commented out lines that do that, in the source code.
NOTE: I updated the logic that recreates the building as it wraps around the screen. I optimized it a little bit so that I don’t end up with so many instantiations inside the main loop.
Use Arrow Keys to Move, Space bar to lift.
Chopper 3D: Endless Terrain
As I said, I wanted to show you a couple of examples on how to build an endless city terrain to be used in a more first-person-shooter type of game.
Here is Option 1
Here is Option 2
Use Arrow Keys to Move
Both options are included in the same source code.
The 2D Grid
As I did with the game scene, I created a 2D version of the grid first. For this example I decided that a 8×6 grid worked best. But when I moved it to 3D, it turned out that a 10×6 grid worked best.
Here is a zoomed out version of the grid. The white rectangle represents the screen.
Use Arrow Keys to Move
Chopper 3D: The iSGL3D Version
And now the final part, this time converting the code to Objective-C and using the iSGL3D engine. This is the second 3D game scene tutorial I do with iSGL but the first one converting from Away3D 4.0. This time it becomes even more noticeable how similar the two engines are and how easy it is to move from Flash to iOS using these engines.
The biggest challenge in my opinion is the scale. In other words, the translation of values from Away3D to iSGL3D. Just as in my previous tutorial, I used a 10:1 scale to all magnitudes related to position, and still I found enough difference in result to force me to do quite a lot of tweaking. I’ll try to hit upon an easier system for this in later tutorials.
But as far as this scene is concerned, I think UV mapping is the biggest topic, so here are the main differences.
UV Mapping in Away3D
var sidePlane:PlaneGeometry = new PlaneGeometry (width, height);
sidePlane.scaleUV(1, height/_heightRatio);
//front face
_frontMesh = new Mesh(sidePlane, _material_blue);
UV Mapping in ISGL3D
Isgl3dTextureMaterial * sideMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"building_blue.jpg" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:YES];
//this uses the texture once horizontally (uB)
//and as many times as needed vertically (vC)
_faceUVMap = [Isgl3dUVMap
uvMapWithUA:0
vA:0
uB:1.0
vB:0
uC:0
vC:_height/_heightRatio];
Isgl3dPlane * faceMesh = [Isgl3dPlane meshWithGeometryAndUVMap:_width*0.1
height:_height*0.1 nx:2 ny:2 uvMap:_faceUVMap];
//front face
_frontFace = [_skin createNodeWithMesh:faceMesh andMaterial:sideMaterial];
Then there is also the addition of a simple Accelerometer based control. And here how that’s done in iSGL3D:
//First: Initialise accelerometer
[[Isgl3dAccelerometer sharedInstance] setup:30];
[[Isgl3dAccelerometer sharedInstance] startTiltCalibration];
...
//Inside the loop I read raw accelerometer data
float * gravityVector = [Isgl3dAccelerometer sharedInstance].rawGravity;
if (gravityVector[1] > 0) {
_player.vx += dt;
} else if (gravityVector[1] < 0) {
_player.vx -= dt;
}
if (gravityVector[0] < 0.5) {
_player.lift = YES;
} else if (gravityVector[0] > 0.4) {
_player.lift = NO;
}
So in this version you can only move the helicopter left and right and up and down, so there is no Z movement at all.
Download all the sources of this tutorial
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.