Problem with writing a blog about such a creative thing as developing a game is that things happens very fast and sometimes very, very much.
It’s a good thing of course. I don’t have to bother about if I do have something to write about but already now understand that it is very dificult to find a balance between documenting it all carefully but pulling the brakes too much and getting all those creative ideas down on paper (yes digital ones) just to look back and see if they (you) ever will be able to understand and follow what just happened.
I still have decided that some parts of this game must be covered in shadow. not because I’m scared that you’ll exploits my darkest secrets but just the fact that they are not really interesting when it comes to the core subject: building a game with AS3 and PV3D. Remember this carefully now: IF you find yourself saying: “whoaa, how did he do that?” or “hey?? Where did THAT suspicious and geniously functional class come from?” it is just normal. I won’t cover every class and function anymore. Still remember THIS even carefullier: If you are very much interested in finding out exactly HOW I did it it or where you could find that seductive class of mine, just write me a comment and I’ll try to fit it in.
Why did I tell you this right now? Because I’ve been coding today :) I will from now on try to pack all my source into zip-files so you don’t have to write down every stupid line in here. Still, I believe that reading and writing yourself (and destroying the code with reckless experiments) is the best way to learn.
THIS IS WHERE WE ARE NOW
Let’s see what we got:
First of all. A lot of previous classes has been updated and changed due to.. well new thinking I guess.
The things a just want to sweep away at the moment is the MeshManager and AssetsLoader classes I created. AssetsLoader is called at the init start of the whole game and handles all loading of all assets (duh!). MeshManager is the Managerclass I have to retrieve the meshes when I need them just by sending in an id so you will see me call this class from time to time so you only need to know that I send in an ID and get a DisplayObject3D. Yes, it’s kind of a magic box ;)
Let’s then skip Main.as (where all assets are loaded). You can look at it in the zipfiles.
With loading out of the way the View3D is a lot cleaner now. Let’s take a look at that instead:
package
{
import flash.events.Event;
import managers.MeshConstants;
import org.papervision3d.lights.PointLight3D;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.view.BasicView;
import org.papervision3d.view.layer.util.ViewportLayerSortMode;
import se.xcom.framework.managers.MeshManager;
import units.*
public class View3D extends BasicView
{
private var defender:AUnit
private var light:PointLight3D
private var topDepth:uint = 100
public function View3D()
{
super();
initScene()
createGround()
createDefender()
this.startRendering() // never ever forget to start this one
}
private function initScene():void
{ // inits all basic stuff like camera, light and.. stuff
// sets ViewportLayers to sort using index-numbers. (just like the DisplayList)
viewport.containerSprite.sortMode = ViewportLayerSortMode.INDEX_SORT;
this.camera.y = 3060
this.camera.z = -4001
this.camera.zoom = 230 // now look here! you can zoom!
light = new PointLight3D()
light.x = 1000
light.y = 5000
light.z = -6000
scene.addChild(light)
}
private function createGround():void
{
var tGround:DisplayObject3D = MeshManager.getInstance().getMesh(MeshConstants.GROUND)
tGround.scale=25
scene.addChild(tGround)
// Putting the ground in it's own ViewportLayer. Also setting an index-number to it.
viewport.getChildLayer(tGround, true).layerIndex = ++topDepth;
}
private function createDefender():void
{
defender = new Defender(0) // creates our Defender. '0' is the ID and could be any number at this stage of development.
scene.addChild(defender.mesh) // News: I removed the DisplayObject3D extension on AUnit so the mesh needs to be added
viewport.getChildLayer(defender.mesh, true).layerIndex = ++topDepth; //The unit is rendered on its own viewPortLayer.
}
override protected function onRenderTick(event:Event = null):void
{
defender.transformUnit();
super.onRenderTick(event);
}
}
}
package interfaces
{
// this is the Interface for all controllers, be it local, ai or remote via multiplayer.
public interface IUnitController
{
/* each Controller gives away a ControlMatrix-object containing at least the following parameters:
* horizontal: think of this as left and right (-1 1)
* vertical: think of this as up and down (1,-1)
* newLastPosition: if true then these values are used: , posX:, posY:, rotation:
* mouseDistX: how far the mouse has been moved horizontally since last update
* mouseDistY: how far the mouse has been moved vertically since last update
*/
function getControl():Object // gets the control object
function updateControl():void // updates the calues and creates an object
}
}
package controllers
{
import flash.ui.Keyboard;
import interfaces.IUnitController;
import se.xcom.input.ArcadeKeyboard;
public class LocalController implements interfaces.IUnitController
{
private var controlObject:Object
private var key:ArcadeKeyboard
private var lastMouseX:Number
private var lastMouseY:Number
public function LocalController()
{
key = new ArcadeKeyboard(Main.scope)
lastMouseX = Main.scope.mouseX
lastMouseY = Main.scope.mouseY
}
public function getControl():Object
{
return controlObject;
}
public function updateControl():void
{
// getting up, down, left, right from keyboard
var tVert:Number=0
var tHoriz:Number=0
if (key.isDown(Keyboard.UP))
{
tVert++
}
if (key.isDown(Keyboard.DOWN))
{
tVert--
}
if (key.isDown(Keyboard.LEFT))
{
tHoriz--
}
if (key.isDown(Keyboard.RIGHT))
{
tHoriz++
}
// calculating movement of mouseX and mouseY
var mouseXdist:Number = lastMouseX-Main.scope.mouseX
var mouseYdist:Number = lastMouseY-Main.scope.mouseY
lastMouseX = Main.scope.mouseX
lastMouseY = Main.scope.mouseY
// creates new controlObject
controlObject = {vertical:tVert , horizontal:tHoriz , mouseDistX:mouseXdist , mouseDistY:mouseYdist , newLastPosition:false}
}
}
}
package units
{
import controllers.*;
import interfaces.*;
import managers.MeshConstants;
import org.papervision3d.materials.ColorMaterial;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Sphere;
import se.xcom.framework.managers.MeshManager;
import transformers.*;
import weapons.*;
public class Defender extends AUnit
{
private const CANNON_ANGLE_MAX:int = 75
private const CANNON_ANGLE_MIN:int = 10
private var mCannon:DisplayObject3D
private var mBody:DisplayObject3D
public function Defender(id:uint)
{
var tMesh:DisplayObject3D = initMesh()
super(tMesh,new LocalController(),new CarTransform(9,0.85,0.2),new MissileLauncher(),id);
}
private function initMesh():DisplayObject3D
{
// in this function I fetch my meshes with my newly built Meshmanager
// unfortunately the meshes are not centered nor scaled correctly
// so I need to tweak them a lot before they are correctly placed.
// There are 2 meshes. One body and one cannon. They are all a part of
// "wholeMesh" that is sent to AUnit construcor.
var wholeMesh:DisplayObject3D = new DisplayObject3D()
var tMan:MeshManager = MeshManager.getInstance()
mBody = tMan.getMesh(MeshConstants.DEFENDER)
mBody.rotationY = 180
mBody.z += 165
mBody.x += 180
mBody.scale = 50
wholeMesh.addChild(mBody)
// to be able to rotate my cannon at desired axis, I create a DisplayObject
// and puts the cannon inside it. Rotation pivot point is always 0,0,0 so I just
// move my cannon inside so itgets the desired pivot point.
mCannon = new DisplayObject3D()
var tMesh:DisplayObject3D = tMan.getMesh(MeshConstants.DEFENDER_TOWER)
tMesh.rotationY = 180
tMesh.z -= 30
tMesh.x -= 70
tMesh.y += 35
tMesh.scale = 50
tMesh.scaleZ = 60
mCannon.addChild(tMesh)
mCannon.y = 85
mCannon.x = -185
mCannon.rotationZ= 8
wholeMesh.addChild(mCannon)
return wholeMesh;
}
//
override public function transformUnit():void
{
// Here I call the same function at the parent class AUnit.
super.transformUnit()
// get already updated control object.
var tObj:Object = control.getControl()
// set cannon rotation by mouseY movements
mCannon.rotationZ += (tObj.mouseDistY/10)
// set cannon angles within boundaries
if (mCannon.rotationZ >this.CANNON_ANGLE_MAX)
{
mCannon.rotationZ = this.CANNON_ANGLE_MAX
} else if (mCannon.rotationZ < this.CANNON_ANGLE_MIN)
{
mCannon.rotationZ = this.CANNON_ANGLE_MIN
}
}
}
}
So here it is! The updated and enhanced Defender-class. Now a lot more code has been added, not only new features but a lot of the “building up a unit” will be inside the final unit-classes like this. Let’s go through it and see if we can learn something new. First of all you can see that I override the transformUnit function that is defined in AUnit. I actually tells Flash to go with this new function instead of AUnit’s old one. Now, there are important code in AUnit’s function that I need to be run so I just start with calling super.transformUnit meaning that I call and run the function transformUnit() in the parent class (super class). The rest of the function rotates the cannon angle due to mouse movements (try the compiled swf above!).
The other thing that I’d like to mention is the way I set the pivot angle of the cannon. If you are familiar to rotating with AS3 since before this is no news to you but everything is rotated using 0,0,0 (origo) as the pivotpoint. So now when I doesn’t want that as my pivot I need to put the cannon inside another DisplayObject3D and then move it around so that the point of rotation winds up on exactly the local 0,0,0 in that new DisplayObject3D. Then finally I rotate my whole DO3D and voila, my cannon is rotating just like it should. A few other small tweaks here and there are made in the classes but I guess you can look at them yourselves. I just post this post now (of 1803 words.. blog record?) and Post a new one when the compiled SWF and the source is ready for download.
(1844 now)
If I were you, I would remove the MeshManager.getInstance() line from the View3D class and pass the meshManager instance as a reference instead.
Why that? Because View3D shouldn’t know about MeshManager being a singleton or something else. Maybe one day, you’ll decide MeshManager is no more a singleton and you will have to change every MeshManager.getInstance calls. In my opinion, you should instanciate MeshManager in your Main class and pass that reference to every objects that need it from your Main class.
Misko Hevery has a serie of great articles about that subject on his blog, you may have a look at them :)
Thanks PeZ for this great advice. As you probably can see, (and as I’ve mentioned throughout this blog) I’m new to this whole OOP thing but I’m very eager to learn. Getting these tips and tricks is very helpful both for me and probably for a lot of people out there.
I will check Hevery out.