Adding Guns and Explosives

7 02 2009

It’s been a while since I wrote about the game in here and as I’ve previously mentioned, I will not cover every single line of code in this blog.

The simple reason is that I would have no time left for developing Archon2160 if I were to comment every adjustment of the previous classes and additions of small elementary things. Instead I will try to focus of the problems I face and how I solve them, trying to be as tutorial-based and educative as possible.

Now, I promised guns and bullets long ago and what would an action game be without that? Previously I created that ShotEngine.as class taking care of every single lethal object, but as usual it has been slightly modified. Today it looks like this:

package engine
{
	import interfaces.IShot;

	import units.AUnit;

	public class ShotEngine
	{

		static private var instance:ShotEngine
		private var aShots:Array

		// ************ SINGLETON CLASS *************
		public function ShotEngine()
		{
			init()
		}

		public static function getInstance():ShotEngine
		{
			if (ShotEngine.instance == null)
			{
				ShotEngine.instance = new ShotEngine();
			}
			return ShotEngine.instance;
		}

		private function init()
		{
			aShots = new Array()
		}

		public function addShot(shot:IShot)
		{
			//Add a new shot into the shotlist
			aShots.push(shot);

			// code to put shot in the 3D scene will be implanted here
			// this is just temporary ugly code
			View3D.scope.scene.addChild(shot)

		}

		public function updateShots():void
		{
			var tempShot:IShot
			var tempUnit:AUnit
			var flagDelete:Boolean

			for (var i:int = aShots.length-1 ; i>-1 ; i--)
			{
				flagDelete = false	// reseting deleteflag.
				tempShot = aShots[i]

				// first make the shot move/rotate/animate/beam...
				tempShot.transformMesh()

				// is it hitting anyone or anything?
				tempUnit = tempShot.checkUnitCollision()
				if (tempUnit != null)
				{
					// returning 'TRUE' if that specific shot wants to remove itself on impact
					flagDelete = tempShot.onUnitCollision(tempUnit);
				}
				if (tempShot.checkStaticCollision()==true)
				{
					// returning 'TRUE' if that specific shot wants to remove itself on impact
					flagDelete = tempShot.onStaticCollision();
				}

				// checks if the bullet/shot has been on the scene for too long.
				// good for exploding shots or bullets that's been flying out of view.
				// has the life run out?
				if (tempShot.isOld())
				{
					// call the new IShot function
					tempShot.killByAge()
					flagDelete = true
				}

				if (flagDelete == true)
				{
					trace("removed!")
					// code will be implanted here to remove the visual shot from the 3D scene
					// this is temporary ugly code
					View3D.scope.scene.removeChild(tempShot)
					aShots.splice(i,1) // removing shot from the shot list.
				}
			}
		}
	}
}

Newest additions is that AShot now recieved a “lifeTime” that decreases for each frame. When it’s less than zero (0) the shot will be taken out of the scene and the ShotList. I will use this function both for deleting shots that has gone out of view for a while and for timeTriggered explosives (like the grenade I am about to show you). When time run out… BOOOM!
So, let’s look at our first real shot then! It’s a grenade :)

package shots
{
	import engine.UnitList;

	import managers.MeshConstants;
	import managers.Time;

	import org.papervision3d.objects.DisplayObject3D;

	import se.xcom.framework.managers.MeshManager;
	import se.xcom.math.Degrees;

	import units.AUnit;

	public class Grenade extends AShot
	{
		private const speed:Number = 90
		private const grav:Number = 8
		private var speedX:Number
		private var speedY:Number
		private var speedZ:Number
		private var rotX:Number
		private var rotY:Number

		public function Grenade(pitch:Number, yaw:Number, id:uint)
		{
			var tMesh:DisplayObject3D = initMesh()

			// number 1300 is the lifeTime of the grenade.
			super(tMesh,1300,40,id,false)

			setSpeed(pitch,yaw)	

		}

		private function setSpeed(pitch:Number, yaw:Number)
		{
			// here all calculations take place to see at which direction the
			// grenade should go and how high the angle should be.
			// also sets a random rotation to the grenade.

			// pitch = rotating along X-axis
			// yaw = rotation along Y-axis
			speedY = Degrees.dSin(pitch)*speed
			var tempSpeed:Number = Degrees.dCos(pitch)*speed
			speedX = Degrees.dCos(yaw)*tempSpeed
			speedZ = -Degrees.dSin(yaw)*tempSpeed
			rotX = Math.random()*40-20
			rotY = Math.random()*40-20 

		}

		override public function checkUnitCollision():AUnit
		{
			var aUnits:Array = UnitList.getInstance().units
			for each (var tUnit:AUnit in aUnits)
			{
				if (tUnit.mesh.distanceTo(this) < this.csRadius+tUnit.csRadius && tUnit.unitId != this.unitId)
				{
					trace("BOOOM!!")
					return UnitList.getInstance().units&#91;1&#93;
				}
				return null
			}

			return null;
		}

		override public function checkStaticCollision():void
		{
		}

		override public function onUnitCollision(unit:AUnit):Boolean
		{
			return true;
		}

		override public function onStaticCollision():Boolean
		{
			return false;
		}

		override public function transformMesh():void
		{
			/*
			here is our first real use of the DELTA-TIMING I wrote
			about in my previous article. This grenade will move in
			exactly the same speed regardless of the framerate of the
			computer.
			*/

			var delta:Number = Time.DELTA

			// move the Grenade
			this.x +=speedX*delta
			this.y +=speedY*delta
			this.z +=speedZ*delta

			// rotate it
			this.yaw(rotX*delta)
			this.pitch(rotY*delta)

			// check Y boundary
			// if grenade hits the floor. Bounce upwards
			if (this.y < 0)
			{
				this.y = 0
				speedY = -speedY
			}

			// set fake gravity
			speedY -= grav*delta

		}

		private function initMesh():DisplayObject3D
		{
			/* 	here I am putting the grenade into another DisplayObject3D
				and slightly adjusting it's x-position. Later when I rotate
				the wholeMesh, the grenade will "wobble" as it is not placed
				exactly in the center. Not a big thing but it's a nice little
				detail giving some character to the grenade.
			*/

			var wholeMesh:DisplayObject3D = new DisplayObject3D()
			var tMan:MeshManager = MeshManager.getInstance()
			var mBody:DisplayObject3D = tMan.getMesh(MeshConstants.NADE)
			mBody.scale = 70
			mBody.x = -40
			wholeMesh.addChild(mBody)

			return wholeMesh;
		}
	}
}
&#91;/sourcecode&#93;

I have now also put a mouseButton parameter into the controls so now I can actually drive the Defender, move the cannon up and down and fire a grenade that bounces on the ground that after a while EXPLODES!!  ... well no not yet. It just traces "boom" and gets removed from the screen and from the ShotList.
<h4>Several instances of the same object</h4>
During the implementation and testing of the grenade I stumbled over the problem of using several meshes using the same original model. Firing one grenade worked just fine but as soon as I pressed fire again I either got an errormessage because I tried to add the mesh on the scene when it was already added or I got the effect of the first grenade disappearing and spawning by the gun again. All this just because I was using the same DO3D for all grenades.

So how do we avoid this? Well, if we know for sure that we will not alter the geometrydata of the object in any way, we can use the same geometrydata to create several "instances" of the DO3D. Having a quick look, not at the documentation but checking the class as of DisplayObject3D you will find a function called <strong>COPY()</strong>. It actually copys the geometrydata into a new DO3D so we can instead of taking our originalmesh to the scene, we copy the geometry to a DO3D and use that instead. There are two problems to solve though...

<strong>Problem 1: </strong>Using parsed meshes just like *.3ds or *.dae creates a DO3D and puts all models in there that also got their own DO3D's. So the first actual DO3D that you control as your mesh is just a container for another DO3D inside. Now this has worked so far but now that we need to recieve the geometrydata fo the 3DS we need to get our hands of that DO3D-child. Somehow for some hightech reason beyond me, you cannot just take it. You will need to know it's name<strong> (!)</strong>

So even if you know that the child is in there there is only one function to get it and that is DO3D.getChildByName() ... damn. One quick solution to this is to trace out the DO3D.childrenList() string and then see the name and after that code: DO3D.getChildByName(nameTraced) this does somehow seem pretty unconvinient because we don't at this stage have a clue how many meshes we will need to do this for and it somehow is considered ugly code with this hardcoded names if you later on would like to change the model of your meshes.

<strong>Here's my solution:</strong>


			var mesh:DisplayObject3D = parsedMesh
			// getting first child name
			var nameOfChild:String = String(mesh.childrenList()).substring(0,String(mesh.childrenList()).indexOf(":"))
			// extracting child
			var newMesh:DisplayObject3D = mesh.getChildByName(nameOfChild)

Without knowing the name of the child you can now retrieve it. Remember that this only works if the containerObject only contains one single mesh.

If not, the above code will only grab the first object.
Problem 2: When importing different meshes using different parsers the models comes from very different 3D-environments. One typical thing is that there is a difference in what actually is up/down, left/right and in/out. Some uses the idea of letting Y becoming the in/out depth meanwhile Z s going up and down. This is often handled by the parser turing it so it looks “correct” in PV3D. That transformationdata could get lost when copying geometrydata.

Solution: I “ugly”-solved it this time, just rotating every single mesh of mine -90 in x-axis so you wont notice a difference in the end.

I’ve decided to create a new page in here with just the latest compiled version of the game. Make sure to come in here from time to time to check the progress of the game. Sometimes it might look ugly as I’m testing stuff but most of the time I hope you will see the progress of it.

Now there is only one core object I need to create before I an start putting all these pieces together to something looking like a game. We need special effects (!) explosions, lightbeams, dust on the ground, flares… things that makes all the difference that is :)

Advertisements




ShotEngine – all bullets in one place

29 01 2009

In the same way all units will be controlled in the same engine, the bullets fired by the units will be moved into their own engine and controlled from there. First let’s create a “blueprint” for the shots as well (yes an Interface).

package interfaces
{
	import units.AUnit;

	public interface IShot
	{
		// How will we check the collision? Is it a big radius or just a point check?
		function checkUnitCollision():IUnit;
		function checkStaticCollision():void;

		// What happens if the shot hits something (damage? explosions?)
		// returns TRUE if shot will be removed.
		function onUnitCollision(unit:AUnit):Boolean; 

		// What happens if the shot hits something static in the world (like a wall or other obstacle)
		// returns TRUE if shot will be removed.
		function onStaticCollision():Boolean;

		// update movement, rotation, scale, animation etc
		function transform():void;
	}
}

Now we’re getting used to the above view so with a little time looking at the code I believe that in the end you could tell me that the shots flying around (or behaving in other ways) will have a few basic functions that will be called apon. checkUnitCollision() checkStaticCollision() onUnitCollision()  onStaticCollision() and transform().  Good. I don’t even care about creating a real implemented class of this interface, instead I want to start with the whole shotEngine that will control all shots.

I think we will take this class in parts as it might contain a few new things to the new actionscript coder. First you can start with creating a new folder besides the rest of the folders and name it “engine“. And then create a new class starting like this:

package engine
{
	import interfaces.IShot;

	import units.AUnit;

	public class ShotEngine
	{

		static private var instance:ShotEngine
		private var aShots:Array

		// ************ SINGLETON CLASS *************
		public function ShotEngine()
		{
			init()
		}

		public static function getInstance():ShotEngine
		{
			if (ShotEngine.instance == null)
			{
				ShotEngine.instance = new ShotEngine();
			}
			return ShotEngine.instance;
		}

For you who are familiar with these kinds of Singleton-classes, skip this section. For you who is not:   “WHOOOOAA!!” WTF was that??

Above is my favourite code experience of the year (yes I’m new to this as well). I’ve always had problems with the whole capsulating thing when it comes to OOP as I just don’t get how people work with global variables. I always found myself coding in a small component class deep down in a hierachy tree and suddenly need an instance of a class that’s in a toal other place, unreachable for me. The quick, dirty solution back then was to make more and more variables public and static so I could reach them from wherever I wanted to in the project. Not that OOP’ish… right.

The wonderful solution is a class called a Singleton-class. With this type of class, you can create one and only one single instance. Whenever you try to create another one you will only recieve the same instance you created from the start and it will contain the same variables and data as that first instance has. If you look at the code above you will see that it has a function called getInstance()

To use a singleton class, ALWAYS call the getInstance() function to get the instance of the class. Never ever create a new instance using the constructorname. Always call getInstance(). This way you assure yourself that you will have only one single instance of the class and in this way you can have access to the same data and methods wherever you need in a project.

Another very important thing is that a lot of classes in a gameproject can only have one single instance! You maybe want to have only one gameengines running at the same time.Noramlly you want to use only one keyboard. Only one HUD? One highscorelist, one onlineLobby etc etc.  There are ways to control that the constructor cannot be directly called apon but I won’t bring that up here. Lets focus on our class instead. Continue….

        private function init()
        {
            aShots = new Array()
        }

        public function addShot(shot:IShot)
        {
            //Add a new shot into the shotlist
            aShots.push(shot);

            // code to put shot in the 3D scene will be implanted here

        }

        public function updateShots():void
        {
            var tempShot:IShot
            var tempUnit:AUnit
            var flagDelete:Boolean

            for (var i:int = aShots.length-1 ; i>-1 ; i--)
            {
                flagDelete = false    // reseting deleteflag.
                tempShot = aShots[i]

                // first make the shot move/rotate/animate/beam...
                tempShot.transform()

                // is it hitting anyone or anything?
                tempUnit = tempShot.checkUnitCollision()
                if (tempUnit != null)
                {
                    // returning 'TRUE' if that specific shot wants to remove itself on impact
                    flagDelete = tempShot.onUnitCollision(tempUnit);       
                }
                if (tempShot.checkStaticCollision()==true)
                {
                    // returning 'TRUE' if that specific shot wants to remove itself on impact
                    flagDelete = tempShot.onStaticCollision();
                }

                if (flagDelete == true)
                {
                    // code will be implanted here to remove the visual shot from the 3D scene
                    aShots.splice(i,1) // removing shot from the shot list.
                }
            }
        }
    }
}

This “engine” will handle all shots that are flying around on stage, update them, check if they hit things and then remove them on collision if they are coded that way( some shots, a lightingcloud for example, may not want to be removed on impact but draw damage over time as the unit stays in the cloud).
So, here you go. Another post without anything happening on the scene ;) remember, the more boring code we get done underneath, the faster the progress will get later on. Next post I promise we must create something visual. Like.. hmm, a gun?