3D-Collisions basics

13 02 2009

One thing that is absolutely vital in most games is the actual event when two objects somehow collides.

Be it bullets that collides with it’s target, wheels touching the floor, a paperMario reaching the end of the map or just a FPS-player that tries to walk through a wall, we just need to see whether something is touching something else (especially if inappropriate :) ) Now, there are several ways to detect this and as usual it’s all about processor vs quality. The absolute best and accurate way to do this is to let every single face check with every other single face out there if it has intersected and then dispatch that a collision has occured. Now, this (we call it face <-> face collision) is extremely demanding and even top notch 3D accelerated AAA-titles barely uses it so let’s just put that aside for the mathgeeks and dreamers and let’s look at some realistic aternatives.

Let us start in the other end. How fast can we go??? Well, I say (you can bash me as much as you want to, I have not studied this at all) that he fastest kind of collisiontest you can have is to just make a static numbercheck.

if (mesh.y < 0) { mesh.y = 0}

Yes, we all fid it extremely obvious but sometimes people make things WAY to difficult for them. Still in this game I have used this collisiontype many times where some people would have tried a more demanding approach just for the sake of being dynamic. Just because of the processorpower it takes to detect collisions I have purposely chosen to create a rectangular arena, opposite to the arena I used in my last game. It doesnt have any walls at the moment but when it get walls I don’t have to create some difficult collisioncheck, just a static numbercheck will make the job!

What about the ground?? Well I could have added bumbs in a sandy arena but now I choose a flat one for several reasons.

1. As soon as something like the grenade goes Z < 0 it will bounce up again.

2. When it’s flat and no units will be able to jump/have physics, I can remove all bottom faces of the unit. You should see them, they are all stripped and seethrough from underneath.

3. Putting obstacles on the arena (extra walls, barrels etc) is as easy as Z=0

Now you probably already knew this stupid collisioncheck but I still want you all to remember this simple solution when creating your design for the game. Maybe your 3D pong should go with square walls instead of your freakishly new plasmastyle.

Now, lets start with some “real” collisiontesting then. After the static one, I might be brave enough to say that the next least perfomancedemanding collisioncheck is a single distancecheck. “How far from this object is that object?? Is it to close? then BOOOM!” -style.

In Papervision we got the math already done with it’s  function: DisplayObject3D.distanceTo(otherObj:DisplayObject3D) .  (no it’s not rocketsciencemath either but it’s nice we don’t have to dig out mr.Pythagoras more than needed)

If the distance is lesser than your set value, then you consider it to be a collision. Now, this method is very rarely used but if we enhance it just a liiiiittle bit you will have one of the most common collisiontests in games…

Sphere-collision

When I was a kid, (yes, I promise i have been) I just couldn’t understand how spherecollision could be faster to use than ermm.. other that we will bring up in a short while. Spheres are complicated and as soon as round shapes are involved it means using PI and Sinus and maybe derivata. No, not this time. If you just think of it for a second: distanceTo() is really a sphere. It doesn’t have any direction but checks the distance in EVERY direction building a kind of a “sphere” around the object where the distance is actually the radius of the sphere. Look at this image:

spherecollision

Here I have set a distanceTo limit on the Defender and a distanceTo limit on the grenade so in code it is just:

if (defender.distanceTo(grenade)- (defender.collisionSphereRadius+grenade.collisionSphereRadius) < 0)  { trace(“defender is FUBAR!!!”)}

It’s all about the distance between the objects but we added some kind of radius to each object simulating it’s size. In my game I have set this radius manually and actually do have a “showCollisionSphere” function on each unit and bullet so I can see that it is somewhat correct with the size of the mesh. Still in Papervision3D DisplayObject3D you already have tis kind of collisiondetection in the function: hitTestObject( obj:DisplayObject3D, multiplier:Number=1 ):Boolean

This function takes the furthest pixels away and creates a radius out of that. This could create a collisionsphere that might not be the best for the whole object and therefor there is also a multiplier parameter if you would like to increase or decrease the size of your “sphere”. It works great (I just wanted to create my own flexible collisioncheck but I recommend using the inbuilt one).

Box Collision

What if my object is very flat? It might even be a plane! Creating a collisionsphere around that would just be stupic as it would, in worst cases, trigger collisions way beyond the actual planes location. The other single method that is used just as much as sphere collisions is the box collision. You have probably done it a thousands times already keeping something within a boundary or similar tasks.. does this looks familiar?

if (obj.x > MAX_WIDTH){obj.x = MAX_WIDTH;}

else if (obj.x < MIN_WIDTH){obj.x = MIN_WIDTH;}

if (obj.y > MAX_HEIGHT){obj.y = MAX_HEIGHT;}

else if (obj.y < MIN_HEIGHT){obj.y = MIN_HEIGHT;}

this is actually the technique to use with box collisions. Just check wether a box is within another box.

In every DisplayObject3D that contains a geometryobject you can somewhat easily achieve this as the geometryobject contains a variable with the freakedout name: aabb

This is actually the bounding box we need ( yes it is short for AxisAlignedBoundingBox). It holds all the MAX and MIN values of your mesh in all 3 dimensions so it’s just a matter of creating a lot of “If”‘ rows and you will have your own box collisiontest. Box collision is not as fast as spherecollision but is a very good alternativ for meshes with different proportions.

This was just some simple theory. I will try to stitch this up in some real code in the game to show you later on. Now already I can tell you that if you want to optimize collisioncheck you really should think about WHICH objects really do need to be checked with each other?? Can I remove some of the collisionchecks without harm? All geometry will probably not need to be checked against each other and even some units doesnt have to be checked with some bullets (eg the unit that shot the grenade doesn’t need to be collisionchecked with the nade). I am fond of lists in different ways so I will probably create one now again where objects subscribes to the list where it needs to check with other objects in that particular list.

59

The just-released StreetFighter IV went back from polygon collision to use classical box-collision just to speed up and recreate the classical feel of the game.

Also one small notice is that there are several other types of collisionchecks but thse are by far the most common ones. They are also very easy to mix together and it’s not unusual that game uses BOX <-> SPHERE collisions and POLYGON <->BOX etc.   Hope I could at least enlighten someone with this basic theoretical text. Have a great time creating things I can play!

Advertisements




Special Effects

8 02 2009

This is one of the last core parts of the game, at least when it comes to the Arenafighting (we got a lot of lobby, networking and boardgame to take care of). This is also one of the best parts as we will be able to handle explosions, lightnings, sparkles and other effects. I have decided to create an EffectEngine similar to the ShotEngine but to tweak it and keep it a little more open as effects will probably act and look very different depending on the effect we want to create.

First, as usual, I start with an Interface:

package interfaces
{
	public interface IEffect
	{
		function update():void
		function updateDying():void
		function remove():void
	}
}

As you can see, the effects will not have many common functions, but the ones they have, the EffectEngine will use. Let’s build an abstract class of the Effects, using the Interface we just made:

package effects
{
	import interfaces.IEffect;

	// abstract
	public class AEffect implements IEffect
	{
		public var life:int
		public var flag_Dying:Boolean
		public var flag_Dead:Boolean

		public function AEffect(life:int)
		{
			this.life = life
			flag_Dying = false
			flag_Dead = false
		}

		public function update():void
		{
			// here the effect will be updated each frametick
		}

		public function updateDying():void
		{
			// if flag_Dying==true , update with this function
			// instead of the normal update()
			// this is to be able to smoothly remove effects
			// when they are done
		}

		public function remove():void
		{
			// when updateDying is done (flag_Dead==true), remove effect
		}

	}
}

The only thing we add in this abstract class is 3 variables. time is the time the effect will be visible in millisecs. Now we are ready to create our EffectEngine.

package engine
{
	import effects.AEffect;
	import managers.Time;

	public class EffectEngine
	{

		static private var instance:EffectEngine
		private var aEffects:Array

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

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

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

		public function addEffect(effect:AEffect)
		{
			//Add a new effect into the effectlist
			aEffects.push(effect);

			// different from the other engines, the effects
			// has their own addChild procedure as they looke very
			// different to each other.
		}

		public function updateEffects():void
		{
			var tempEffect:AEffect
			var flagDelete:Boolean

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

				if (tempEffect.flag_Dying == false)
				{
					// if effect is alive and kicking!
					tempEffect.update()
					if (tempEffect.life < 0)
					{
						tempEffect.flag_Dying = true
					}
					tempEffect.life -= Time.getInstance().timeElapsed
				}
				else
				{
					// if effect is about to die out
					tempEffect.updateDying()
					if (tempEffect.flag_Dead == true)
					{
						tempEffect.remove()
						flagDelete = true
					}
				}

				if (flagDelete == true)
				{
					trace("Removing effect from list")
					aEffects.splice(i,1) // removing shot from the shot list.
				}
			}
		}
	}
}&#91;/sourcecode&#93;

As usual, it is a Singleton-class preventing several instances of EffectsEngine being created. Also, The normal 'add' function is there just like the shotEngine. Biggest difference is in the updateEffects() function, hopefully it is explaining itself. The procedure of an effect is this:
First it gets created and then immediatly added to the EffectEngine.
The effect updates itself using update() until 'life' has reached 0.
Then the effect update the updateDying() instead until , flag_Dead == true
Last the effect is removed from the list and is running the function remove().

Now I will create two effects that will need some kind of explanation. One Explosion (that is actually two effects in one, I'll explain later) and one BombDust that will dust up the arenaground where the explosion has been.
Let's start with the Explosion :) Here is my visual idea of creating it:

<img class="alignnone size-full wp-image-215" title="explosion" src="https://papergem.files.wordpress.com/2009/02/explosion.jpg" alt="explosion" width="510" height="250" />

I will use two planes. One that is aligned to the ground giving a lightEffect on the ground, and one that is a "billboard". A Billboard is a plane that is always facing the camera, no matter where the camera is placed. The Billboard will have an animated movieclip where I use an explosion I rendered in 3DSMax. The groundaligned plane will just have a BitmapMaterial with an image I created in Photoshop. I will scale this image back and forth to simulate an intensity increase and decrease of the explosion.

One important thing I want to stress right now before we get to coding is that png and alpha-channels doesn't match good with performance and optimization. In some projects it is very convinient to use alpha-channels and transparency but when creating a PV3D-game you will immediatly choke the system with more than a few alphatransparencies on the screen.

My solution to this is to use the blendmode ADD. ADD 'adds' it's RGB values to the normal RGB-values giving a "lighter" effect whereever the RGB is more than 0,0,0. If you didn't catch that just remember this: Whatever on the image you don't want to impact the screen, use 0,0,0. Hmm I maybe should show you the images instead:

<img class="alignnone size-full wp-image-216" title="explosionground1" src="https://papergem.files.wordpress.com/2009/02/explosionground1.jpg" alt="explosionground1" width="90" height="89" /><img class="alignnone size-full wp-image-217" title="010" src="https://papergem.files.wordpress.com/2009/02/010.jpg" alt="010" width="128" height="128" />

Wherever it is black, it will be invisible. The brighter the rgb is, the more it will lighten up the actual screen, AND most important, this is a faster way to create lighteffects with than using alphachannels, transparency and png's. Let's look at our implementation of the Explosion.

package effects
{
    import flash.display.BlendMode;
    import managers.Layers;
    import org.papervision3d.materials.BitmapMaterial;
    import org.papervision3d.materials.MovieMaterial;
    import org.papervision3d.objects.primitives.Plane;
    import org.papervision3d.view.layer.ViewportLayer;
    import se.xcom.math.Degrees;

    public class Explosion extends AEffect
    {
        private static var layerIndex:int = 1
        private var airExp:Plane
        private var groundExp:Plane
        private var groundCount:int
        private var layerAir:ViewportLayer
        private var layerGround:ViewportLayer

        public function Explosion(posX:Number,posY:Number, posZ:Number)
        {
            super(1100);
            init(posX,posY,posZ)
        }

        private function init(posX:Number,posY:Number,posZ:Number)
        {
        // create round sphere explosion in air
            var expMat:MovieMaterial = new MovieMaterial(new mcExplosion1(),false,true)
            airExp = new Plane(expMat,1000,1000)
            airExp.x = posX
            airExp.y = posY
            airExp.z = posZ
            airExp.lookAt(View3D.scope.camera)

            layerAir = new ViewportLayer(View3D.scope.viewport,airExp)
            layerAir.blendMode = BlendMode.ADD
            layerAir.layerIndex = layerIndex
            Layers.EFFECTAIR.addLayer(layerAir)

        // create the explosionlight on ground
            var groundMat:BitmapMaterial = new BitmapMaterial(new bmpGroundExplosion1(0,0))
            groundExp = new Plane (groundMat,600,600)
            groundExp.x = posX
            groundExp.y = 0
            groundExp.z = posZ
            groundExp.rotationX = 90

            layerGround = new ViewportLayer(View3D.scope.viewport,groundExp)
            layerGround.blendMode = BlendMode.ADD
            layerGround.layerIndex = layerIndex++
            Layers.EFFECTGROUND_EXP.addLayer(layerGround)

            View3D.scope.scene.addChild(airExp)
            View3D.scope.scene.addChild(groundExp)

        }

        override public function update():void
        {
            airExp.lookAt(View3D.scope.camera)
            airExp.pitch(180)

            groundExp.scaleX = 1+Degrees.dSin(groundCount)*1
            groundExp.scaleY = 1+Degrees.dSin(groundCount)*1
            groundCount += 10
        } 

        override public function updateDying():void
        {
            this.flag_Dead = true
        }

        override public function remove():void
        {
            View3D.scope.scene.removeChild(airExp)
            View3D.scope.scene.removeChild(groundExp)
            Layers.EFFECTAIR.removeLayer(layerAir)
            Layers.EFFECTGROUND_EXP.removeLayer(layerGround)

        }

    }
}

Some explanation here is needed. In my Layers.as class I have created a lot of different layers and put them in a, in my opinion, good indexed order. Now if I just put my objects directly in one of those layers I would get a very strange effect where two explosions would be drawn upon each other like transparent squares. I want the light of the effects to blend with each other if several explosions are on the screen and that is why I put every object in a single unique layer, put the correct blendmode on them and finally put those layers on the parent layer in Layers.as
Yes it could take some time to get your head around, but hey, it works. This is this typical thing that is hard to explain unless you’ve been there yourself.
Well! The explosion now works! This one doesn’t have any long fading when it’s time to “die” but is removed immediatly.

As we’re on it, let’s create a simpler effect, the BombDust.as

package effects
{
	import flash.display.BlendMode;

	import managers.Layers;

	import org.papervision3d.materials.BitmapMaterial;
	import org.papervision3d.objects.primitives.Plane;
	import org.papervision3d.view.layer.ViewportLayer;

	public class BombDust extends AEffect
	{
		private static var layerIndex:int = 1
		private var layer:ViewportLayer
		private var airExp:Plane
		private var dust:Plane
		private var groundCount:int

		public function BombDust(posX:Number,posZ:Number)
		{
			super(8000);
			init(posX,posZ)
		}

		private function init(posX:Number,posZ:Number)
		{
		// create dustPlane
			var dustMat:BitmapMaterial = new BitmapMaterial(new bmpBombDust(0,0))
			dust = new Plane (dustMat,600,600)
			dust.x = posX
			dust.y = 0
			dust.z = posZ
			dust.rotationX = 90
			layer = new ViewportLayer(View3D.scope.viewport,dust)
			layer.blendMode = BlendMode.MULTIPLY
			layer.layerIndex = layerIndex++
			Layers.EFFECTGROUND_DUST.addLayer(layer)
			View3D.scope.scene.addChild(dust)
		}

		override public function updateDying():void
		{
			this.flag_Dead = true
		}

		override public function remove():void
		{
			View3D.scope.scene.removeChild(dust)
			Layers.EFFECTGROUND_DUST.removeLayer(layer)
		}
	}
}

Instead of the ADD-blenmode, this one uses the MULTIPLY, darkening the area instead of brighten it up.
bombdust

Now its just a few lines of code that must be implanted in the GrenadeClass just to spawn these effects whenever the grenade explodes.

override public function killByAge():void
        {
            var exp:Explosion = new Explosion(this.x,this.y,this.z)
            var dust:BombDust = new BombDust(this.x,this.z)

            EffectEngine.getInstance().addEffect(exp)
            EffectEngine.getInstance().addEffect(dust)
        }

Try out the link at the new page above (yes in the menu, called Archon2160). If you’re lucky, there might be a build there where you can see how the effects are triggered. Have fun and hope you learn something. I always do!





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 :)





Delta Timing – Papervision3D Example

5 02 2009

I got several questions about Delta timing and as this is such a vital and exciting technique to use in EVERY aspect of Flashprogramming as soon as you want to withhold an exact speed of your flash no matter how bad or good the host computer is. Here comes a nice Papervision example.

First let’s create the document class so that is out of the way:

package {
	import flash.display.Sprite;

	public class DeltaTime extends Sprite
	{
		public function DeltaTime()
		{
			var tView:View3d = new View3d()
			this.addChild(tView)
		}
	}
}

and now to the real part:

package
{
	import flash.events.Event;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.materials.ColorMaterial;
	import org.papervision3d.materials.shadematerials.PhongMaterial;
	import org.papervision3d.objects.primitives.Plane;
	import org.papervision3d.objects.primitives.Sphere;
	import org.papervision3d.view.BasicView;
	import org.papervision3d.view.layer.util.ViewportLayerSortMode;

	public class View3d extends BasicView
	{
		private var light:PointLight3D
		private	var sphere1:Sphere
		private var sphere2:Sphere
		private var ground:Plane
		private var speed:Number
		private var expTimeInMillisecs:Number
		private var currentTime:int
		private var lastTime:int
		private var DELTA_SCALE:Number

		public function View3d()
		{
			super();
			setupScene()
			startRendering()
			initDeltaTiming()
		}

		private function setupScene():void
		{
			// set up camera so we an see the whole scene from a good perspective
			camera.y = 1100
			camera.z = -700

			// setup ground
			ground = new Plane(new ColorMaterial(0x224422),1600,1000,1,1)
			ground.rotationX = 90

			// create a light for the PhongMaterial to do it's shiny tricks)
			light = new PointLight3D()
			light.x = 400
			light.z = -400
			light.y = 600

			// creating a red sphere - this one will be the normal sphere
			sphere1 = new Sphere(new PhongMaterial(light,0xff1111,0x333333,1))
			sphere1.x = -800
			sphere1.y = 50
			sphere1.z = 200

			//creating a blue sphere - this will use Delta Timing.
			sphere2 = new Sphere(new PhongMaterial(light,0x3333ff,0x333333,1))
			sphere2.x = -800
			sphere2.y = 50
			sphere2.z = -200

			// add all objects to their own layer so no Z-fighting will appear.
			this.viewport.containerSprite.sortMode = ViewportLayerSortMode.INDEX_SORT
			this.viewport.getChildLayer(ground,true).layerIndex = 1
			this.viewport.getChildLayer(sphere1,true).layerIndex = 2
			this.viewport.getChildLayer(sphere2,true).layerIndex = 3

			// add all to the scene
			scene.addChild(ground)
			scene.addChild(sphere1)
			scene.addChild(sphere2)
		}

		private function initDeltaTiming():void
		{
			// we will make the spheres travel with the speed of 15.
			speed = 15
			// we also would like it to do that when the fps is 20.
			// so if the fps drops lower, the speed needs to be altered
			// to keep up our expected speed over screen.
			var expectedFPS:Number = 20

			// as FPS is "Frames Per Second" it's easy to calculate how
			// many millisecs it should take for one frame.
			expTimeInMillisecs = 1000/expectedFPS

			// just reseting the lastTime so DeltaTime doesn't
			// freak out at start
			lastTime = new Date().getTime()

		}

		private function calculateDeltaTime()
		{
			// first get the new current time (NOW that is)
			currentTime = new Date().getTime()

			// check how long time it took since last time
			// we were here (in Millisecs)
			var timeElapsed:int = currentTime-lastTime

			// calculate out the new DELTA_SCALE for the round
			// based on expected time it should have taken
			DELTA_SCALE = timeElapsed/expTimeInMillisecs

			// set lastTime to currentTime to be able to compare
			// next round we are in here.
			lastTime = currentTime
		}

		private function moveSpheres()
		{
			// sphere1 is moved with "normal" movement
			sphere1.x += speed
			if (sphere1.x > 800)
			{
				sphere1.x = -800
			}

			//sphere2 is moved with delta timing
			sphere2.x += speed * DELTA_SCALE
			if (sphere2.x > 800)
			{
				sphere2.x = -800
			}

		}

		override protected function onRenderTick(event:Event = null):void
		{
			// .. normally a lot more happens here to slow a game down but not today...

			calculateDeltaTime()
			moveSpheres()

			trace("DELTA_SCALE:"+DELTA_SCALE)

			super.onRenderTick(event)
		}
	}
}

Now set this up in your preferred Flashenvironment and try to run the app in different FPS-settings. You will find that the red sphere will move faster or slower depending on the current FPS. But the blue one will constantly move over the ground in the same speed regardless of the current fps.

Why then does the spheres not move equally fast if we set the Flash to run at 20 FPS ?? Well, as many of you flashdevelopers already are aware about is that Flash cannot assure the framrate to be exactly 20, so even then, the blue sphere is moving more correct than the red one due to small differences in the framerate.

Hope you got it all explained in more detail now. Have a nice evening, I will.





Why can’t we just get along??

31 01 2009

Weekend is here and I’m going away. Still I spent an hour or two tonight and move code around, which, if you’re into game programming, you know that you will do often. Suddenly you just realize that the code should be somewhere else, in another class, called another thing. Hopefully it will lead to a somewhat cleaner and more logical code. Sometimes it doesn’t :(

There are a some things with PV3D that I really don’t understand why they did like they did. Take size as an example. This is the first time ever (EVER) I stumbled apon a 3dobject that cannot tell me how big it is. It moves around in a 3dspace uses exact coordinates to travel. Still there is no sign of the object knowing what height it has or how wide it is??? There is this bounding box parameter inside the geometry param so it wouldn’t be that hard to implement a recursive check for size both before and after transform. Am I missing something? Why dont I extend the DO3D then and put the functions in there myself? Well, as the whole PV3D is built on DO3D it will not use my extended class so it’s no use.

“So why don’t you just hack the original class then. Just put the features in there?” Well I actually did, and removed it , and did it again and finally removed it  because I know that PV3D is always under development. I am a news freak and always want the latest build and it would be a drag just to update the library manually, putting in my beloved code, each time I update PV3D. Also I wouldn’t be able to write this kind of blog as I cannot expect every single reader out there to hack their basic library classes just to get my examples in here to work.

No, I’m satisfied with just cursing.  :)

So? What’s the deal with layers anyway?? I promised to show you and as I’m leaving in a few minutes, this is an excellent time to bring up that subject.

To be able to draw 3D objects correctly on the screen, the renderer always draws the thing that is furthest away from the camera first. That is logical because if then something appears in the way that is closer to the camera it should overwrite the object far away. But there are times that you actually want to force the renderer to draw the objects in another order thatn it’s default behaviour. This is where layers come in.

In the case of this game, I will use viewportlayers for 2 reasons. 1: the annoying Z-fighting problem and 2: Visual effects.

The Z-problem is a little thingie that has been haunting the 3D world since the beginning of time. As I mentioned earlier the renderer automatically renders the furthest objects first. But how does it know that it is furthest away? Well EVERY single triangle in the 3D-world has got a centerpoint (yes right in the center of the 3 vertices drawing the triangle) and the triangle itself then can calculate how far from that centrepoint it is to the camera. It’s a simple question of distance (and math).

But what happenes if the triangle is a big ass giant triangle? Well it is still the center that decides when it will be drawn. Our arena ground in the game is a perfect example!! It is built up with big ass triangles!! So if our car happens to be on a place on the ground where the centrepoint is behind it. The scene wil be drawn as normal BUT if the car (Defender) moves away at places itself in the ground far away it as a very big chance that the centrepoint is now closer to the camera that the Defendertriangles are. Then the renderer will draw the Defender first and then the ground, causin the defender to suddenly disappear (be drawn uderneath the ground).. hmmm damn.

So now layers is our saviour. We kind of “put” all the objects in different layers and then the renderer will render ALL objects in one layer and then continue to the next layer that has a larger layerindex.  Problem solved!

Effects, we’ll take when we get there. You just think about all cool effects you can do with viewportlayers… mmmm…   in front, in back, in front. Amazing huh? no? Tutorial coming up!





Paint the paper

28 01 2009

My guess is that PV3D chose the word “paper” because the way to treat 3D objects is very much like folding paper in different ways. Even if a 3D-object in the end could look like a very solid rock it is built up with simple planes and triangles that is carefully bent and rotated (actually the building blocks gets smaller than that but let’s rest here for a while). The small sides (triangles) that are building up the world doesn’t even have two sides. If you are looking on the other side of a triangle you will actually see… nothing. Nothing at all! So to even create the simpliest sheet of paper you will need to create two planes and flip one and stick it to the other to make it look like in real life (have both a front and a back side).

Now, technically, Papervision have a lot to wish for when it comes to performance and no matter how amazing you might be codewise you won’t just be able to create detailed lifelike models because it will take you thousands and thousands of these triangles to fold and model the details of a reallife complex object. Here is where “texturing comes in”.

Instead of using all these resourcedemanding triangles to showcase something you can actually paint stuff right on the triangles. Say you want to create an orange. Instead of creating every single small bump on an orange you could just paint those bumps on an image and then ‘wrap’ this image around a more simple sphere still creating the illusion of an orange.

Now, I’ve started up creating a few of the models I want to use in my game. Below you will see my unit: “DEFENDER” – a smaller buggy-like car which later on will have a big ass cannon on the roof.

textures

To create such a model, you will need some kind of a 3D-program. I’ve been working with 3D Studio MAX for the last 10 years so that is choice of app. First of all you create the actual “folding” of the object. Placing all triangles where they should be, bending rotating and modelling out all planes will give you a model something like the untextured one above. The texture in the picture is what I draw on a regular image in Photoshop. Wrapping that image on the planes causes the textured model which gives an impression of a much more detailed model than it really is. Good textures are very very important to create a good looking game. Spend a lot of time on that!

If you are interested in special tutorials in modelling, texturing, UVW-mapping etc.. just give me a call and I’ll see what I can do. At the moment I just want to get my “defender” to be a part of the game. To use these kind of custom models you will have to save them into some format that PV3D recognizes like *.3ds or collada.  In this case I will save my model into the *.3ds format and use the inbuilt parser Max3DS in PV3D.

package
{
	import controllers.*;
	import flash.events.*;
	import interfaces.*;
	import org.papervision3d.events.FileLoadEvent;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.objects.parsers.Max3DS;
	import org.papervision3d.view.BasicView;
	import transformers.*;
	import units.*;

	public class View3D extends BasicView
	{
		private var defender:AUnit
		private var defenderMesh:Max3DS
		private var light:PointLight3D

		public function View3D()
		{
			super();

			// set the camera in a nice position
			camera.y = 200
			camera.z = -1000
			camera.zoom = 90

			// place a light somewhere (just to get used to it, this one doesn't do much at the moment)
			light = new PointLight3D()
			light.x = 1000
			light.y = 5000
			light.z = -6000
			scene.addChild(light)

			startLoadMesh()

		}

		private function startLoadMesh()
		{
			// lets load our mesh
			defenderMesh = new Max3DS()
			defenderMesh.load("meshes/units/DefenderBody.3DS",null,"./meshes/units/")
			defenderMesh.addEventListener(FileLoadEvent.LOAD_COMPLETE,meshLoaded)
		}

		private function meshLoaded(e:Event)
		{
			// mesh has been loaded and is now put in as a parameter in this new object called Defender
			defender = new Defender(defenderMesh,new LocalController(),new CarTransform(7,0.85,0.3));
			scene.addChild(defender)

			// startRendering() must be called for the 3D-engine to actually start drawing each frame
			this.startRendering()

			// each time the 3Dengine redraws the screen, onFrameTick is called.
			this.addEventListener(Event.ENTER_FRAME,onFrameTick)
		}

		private function onFrameTick(e:Event)
		{
			// lets transform the unit due to the control-data it recieves.
			defender.transformUnit()
		}
	}
}

The code above actually loads my mesh and adds it on the scene in 3D. Not only that, the mesh is passed into my new class: Defender.as which is extended from AUnit so now FINALLY the chain is connected and I got a unit that do have a mesh, a control component and a transform component. I got my first primitive unit driving around on the scene!! Well.. almost, I just need to create that Defender class..

package units
{
	import interfaces.*;
	import org.papervision3d.objects.DisplayObject3D;

	public class Defender extends AUnit
	{
		public function Ball(mesh:DisplayObject3D,controller:IUnitController,transformer:ITransformer)
		{

			super(mesh,controller,transformer,0);
		}

	}
}

We need no more than that to create our unit. Remember now that we can create as many different components as we want to. Just combine them, reuse them and experiment with them to create new units.





Papervision3D – an introduction to the 3D world.

27 01 2009

Ok, lets take a small break from the game creating. I assume you already know about Flash and AS3 but if you haven’t worked with 3D programming before it would be good with some foundational theory.  Let’s start with the dimensions (no, kidding! a third??)

coordinatesystems

As you can see by the figures the 3D coordinate system differs in many aspects. First of all, there is a third axis showing the depth of the image. Imagine the screen as the 2D image a normal FlashMovie can project. Now think of the Z-axis as if you could put in your hand in the screen and push that movie further away or drag it closer to you. The movie would then move on the Z-axis. (I know this is not the whole truth but will you please pretend for a while, ok?)

Now, something is new with the other “normal” ones as well. It seems like the origo (0,0) has moved away from being in the topleft corner to the absolute middle of the screen. Ok, fair enough, so as soon as you want things to move to the left of the screen you will set the X-value negative. The last thing that might be easy to forget for you “Flashers” is that Y-axis is now pointing upwards meaning that negative values are below the 0,0,0 (origo). So whenever you want something to move upwards you increase the Y-axis.

There are many ways to use this 3D space but in my game I will look at the 3Dworld as if Y is the altitude of everything meaning that the flat arena we will create and all the units moving around will mostly be stretching out and move through the X and the Z-axis.

Today I will not dive deeper into 3D and how the models are built up rather then just present the core objects that are needed to even display something in 3D. Lets begin:

Viewport

There is no real 3D. You know that, right? I mean, we’re only simulating a 3D environment but in the end it will be projected on a 2D surface (the screen). The viewport is exactly that. It’s the 2D area where all this simulating will take place. For now, just think of it as a small rectangular MovieClip that you will just add to the displaylist just as normal. It’s what will be within this rectangle that is the cool stuff.

Camera

A camera is nothing else but your eyes in the 3D world. Think of it as an object in this 3D environment I just explained above that could move around in all axis and also rotate around. Wherever it is located, from that angle all objects in the scene will be projected.  The projected image will be visible in the Viewport explained above. (aaaah so the camera is the camera, and the viewport is like the TV showing whats catched be the camera?? … yes)

DisplayObjects3D

Now this is the objects that will be visible in the 3D-world. Damn, you know this already. It’s just like using DisplayObjects in flash. Actually, the PV3D team has worked hard for you to be as comfortable with this new technique giving you almost the same methods to work with 3D as with 2D. Want to add a DisplayObject3D in the scene?  use addChild(). Want to move it to the right?   Increase the X parameter. It’s that simple :)

Also, while we at it. DO3D is very much a container just like it’s cousins DisplayObjectContainers. This means that you can put other DO3D’s into other DO3D’s just like you would add a Sprite into a MovieClip etc. Works like a charm (because charms work, right?)

Light

Now this one is somehow new to some of you 2Dguys. Creating 3D projections is a lot about fooling the eye and mind with small tricks just to come closer to the “real thing” and what we expect to see in a 3D environment. One crucial thing that adds to the illusion of depth is light. If all sides of a green shape were litten equally, the viewer have a really hard time both getting the feeling of 3D-depth and to make out the correct form of that shape. See the image below:

lightsource1

We will work with PointLights (Pointlight3d) that as the word hints is a light placed in a point in the world. This means that you will be able to move that light just as you move objects and cameras around. With a good light you can enhance a 3D-environment to extreme standards!

Now that you are introduced to the basic components of a 3D-world, lets start the engines up and create our first bad ass scene!

First, lets create a FLA. Just create a new AS3 FLA in Flash and set the document class to ‘Main’

Now let’s create our Main.as   class and make it as simple as possible. I’ve always liked to keep the document class as clean as possible and as soon as it gets cluttery I move most of the code into a new class, cleaning Main up again.

package {

	public class Main extends Sprite
	{
		private var view:View3D

		public function Main()
		{
			view = new View3D()
			addChild(view)
		}
	}
}

View3D will be our class with all fancy 3Dstuff. To make it very (VERY) simple I will now extend a class called ‘BasicView’ and turn it into my View3D.as . Basicview’s only purpose is to take one viewport and one camera and put it all in one scene so that you are ready to go immediatly. Let’s have a look at it:

package
{

	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.view.BasicView;

	public class View3D extends BasicView
	{
		private var light:PointLight3D

		public function View3D()
		{
			super();
			createSomeObjectsOnTheScene()
			this.startRendering()
		}

		public function createSomeObjectsOnTheScene()
		{
			light = new PointLight3D()
			light.x = 1000
			light.y = 5000
			light.z = -6000
			scene.addChild(light)
			var tSphere:Sphere = new Sphere()
			scene.addChild(tSphere)
			}
		}
	}
}

Now let me take you through what just happened. First when View3D is created, I call super() meaning I call the BasicView constructor. BasicView class creates a ‘scene’ and puts a ‘camera’ on that scene. It also places the camera on z:-1000 so that it can view all objects from a fair distance (camera is by default “looking” towards 0,0,0). I then call my own function ‘createSomeObjectsOnTheScene’ where I first create a light and locates it somewhere fairly random. In the end I create the first really visible object and puts it in the displaylist using addChild. As i don’t locate it somewhere it will be placed on 0,0,0 (where camera is looking). taddaaa… but when running you will find the sphere looking kind of .. wireframed. Yes, and thats actually the correct term for that look as well.
It is as simple as if you havent specified HOW the surface will look on the sphere it will draw it in wireframe mode (as a sign of not having any material wrapped around itself. Tomorrow I’ll show you how to set some materials and then we will also head back to the game. Otherwise I will never be able to finish it!