Jun 25 2010

Using as3isolib, Part 2

So, we have a scene and a box. Not very exciting just yet, but we’ll get there soon enough. Now with most games not everything will be in view so it’s very common to allow a user to navigate the view as needed. The two most common are pan and zoom, both of which, I’m about to show you. If you’re following along from part 1, we’ll be adding to the class we already created. Below is the full code.

package
{

	import as3isolib.display.IsoView;
	import as3isolib.display.primitive.IsoBox;
	import as3isolib.display.scene.IsoGrid;
	import as3isolib.display.scene.IsoScene;
	import caurina.transitions.Tweener;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;

	public class IsoViewExample extends Sprite
	{

		private var _view:IsoView;
		private var _scene:IsoScene;
		private var _box:IsoBox;
		private var _grid:IsoGrid;

		private var _panPt:Point;
		private var _zoom:Number = 1;

		public function IsoViewExample():void
		{
			_view = new IsoView();
			_view.setSize( stage.stageWidth, stage.stageHeight );
			addChild( _view );

			_scene = new IsoScene();
			_scene.hostContainer = this;
			_view.addScene( _scene );

			_box = new IsoBox();
			_box.setSize( 50, 50, 50 );
			_scene.addChild( _box );

			_grid = new IsoGrid();
			_grid.cellSize = 50;
			_scene.addChild( _grid );

			stage.addEventListener( MouseEvent.MOUSE_DOWN, onStartPan, false, 0, true );
			stage.addEventListener( MouseEvent.MOUSE_WHEEL, onZoom, false, 0, true );
			addEventListener( Event.ENTER_FRAME, onRender, false, 0, true );
		}

		private function onStartPan( e:MouseEvent ):void
		{
			_panPt = new Point( stage.mouseX, stage.mouseY );

			removeEventListener( MouseEvent.MOUSE_DOWN, onStartPan );

			stage.addEventListener( MouseEvent.MOUSE_MOVE, onPan, false, 0, true );
			stage.addEventListener( MouseEvent.MOUSE_UP, onStopPan, false, 0, true );
		}

		private function onPan( e:MouseEvent ):void
		{
			_view.pan( _panPt.x - stage.mouseX, _panPt.y - stage.mouseY );

			_panPt.x = stage.mouseX;
			_panPt.y = stage.mouseY;
		}

		private function onStopPan( e:MouseEvent ):void
		{
			stage.removeEventListener( MouseEvent.MOUSE_MOVE, onPan );
			stage.removeEventListener( MouseEvent.MOUSE_UP, onStopPan );

			stage.addEventListener( MouseEvent.MOUSE_DOWN, onStartPan, false, 0, true );
		}

		private function onZoom( e:MouseEvent ):void
		{
			e.delta > 0 ? _zoom += 0.5 : _zoom -= 0.5;

			Tweener.addTween( _view, { currentZoom:_zoom, time:0.5 } );
		}

		private function onRender( e:Event ):void
		{
			_scene.render();
		}

	}

}

Little bit more code to get through so lets just jump in. First thing I did here is I moved the scene render call off into its own function and added an ENTER_FRAME event to call it every frame. This is so the scene will update automatically for us.

So lets cover panning first. The logical steps we take here are as follows:
1. On MOUSE_DOWN, trap the location of the mouse and add an event listener for MOUSE_MOVE, to update the location of the scene, and MOUSE_UP to stop panning. Line 43 is where we add the listener and line 48 is the handler function. The important thing to note here is the variable _panPt. This variable holds the point for the location of the mouse at every frame.
2. On MOUSE_MOVE, grab the newest location of the mouse and calculate the difference in position from the last frame and pan the scene that much. Line 58 shows the handler function for onPan. Here, we call “pan” on the view and tell it to move by the difference between the _panPt and the new mouse location. Next, we set the x,y of the _panPt as the x,y of the current mouse position.
3. On MOUSE_UP, stop panning. Line 66 is the handler function for pan stop. Here, we just remove the MOUSE_MOVE and MOUSE_UP listeners and add the original MOUSE_DOWN listener.

Panning now works as expected. Lets move on to zooming. The steps here are a bit simpler. We just add a listener for MOUSE_WHEEL at line 44 and line 74 is where the magic happens. In the onZoom function we do a quick check to see if they scrolled up or down by checking the value of the “delta” property of the mouse event. We store the zoom value in a “_zoom” variable we declared previously. Finally, using Tweener, we create a tween to zoom the view to the new value. The important thing to note here is that we CAN use tweening libraries like Tweener with as3isolib. We just tell Tweener to change the “currentZoom” property of the view. Theoretically, this means that you should be able to incorporate any tweening library or even physics library with as3isolib. We won’t actually need physics, so Tweener is enough for us.

That’s it! We can now zoom and pan our view. Keep in mind that, for this example, I did not include an kind of limitations. This means that you could zoom and pan the view entirely off the stage. To limit those you would simply set boundaries for zoom and pan values and check those before you zoom or pan. I’ll leave that part up to you. Next, we’ll talk about creating objects that we can drag around.

Cheers!


Jun 25 2010

Using as3isolib, Part 1

So, it seems like I was unable to overcome certain technical hurdles that I encountered with Away3d so Battle Lines is on hold indefinitely. The Away3d team recently released a newer version of the library which, I believe, includes a major overhaul, but I haven’t had the time to really look into.

In the meantime I have started a new project which I have called Absolute Warfare. This game will also be a turned based strategy game very much akin to Heroes of Might Magic (the originals) and the Total War series. It’s a mix of turn based tactical movement and light city management. For this project I have decided to move to isometric display as it tends to be better for performance and visual quality. I searched around the web for a few isometric libraries and ultimately settled on as3isolib. So far as I can tell, it’s a very competent library with a straightforward API that is easy to work with.

In this first part, I want to show you how to setup a simple scene with a grid and a box. So, lets get started. I will layout code in full and explain line by line.

package
{

	import as3isolib.display.IsoView;
	import as3isolib.display.primitive.IsoBox;
	import as3isolib.display.scene.IsoGrid;
	import as3isolib.display.scene.IsoScene;
	import flash.display.Sprite;

	public class IsoSceneExample extends Sprite
	{

		private var _view:IsoView;
		private var _scene:IsoScene;
		private var _box:IsoBox;
		private var _grid:IsoGrid;

		public function IsoSceneExample():void
		{
			_view = new IsoView();
			_view.setSize( stage.stageWidth, stage.stageHeight );
			addChild( _view );

			_scene = new IsoScene();
			_scene.hostContainer = this;
			_view.addScene( _scene );

			_box = new IsoBox();
			_box.setSize( 50, 50, 50 );
			_scene.addChild( _box );

			_grid = new IsoGrid();
			_grid.cellSize = 50;
			_scene.addChild( _grid );

			_scene.render();
		}

	}

}

In lines 20-22 we create an instance of the IsoView. The IsoView is simply a window or view that displays the various scenes and objects. Here, we set the size to be the same as our stage and we add it to the display list.

Next, we need to create an IsoScene. The IsoScene is where all the objecst live. Depending on your needs it is also possible to have multiple scenes in a single view. In this case we only need the one. IsoScene has a property “hostContainer” which is a reference to the DisplayObjectContainer that the scene will be added to. Finally, we add it to the view.

Next, we create an instance of IsoBox. IsoBox is a vector primitive provided with as3isolib. It is very handy for demonstrations, quick prototypes, etc. Eventually, we will use actual sprites, but for now this will suffice. Here, we set the size to 50x50x50 and add it to our scene.

Next, we create an instance of IsoGrid. IsoGrid is just a vector grid used to trap mouse input among other things. In this case we simply use it as a frame of reference. In later tutorials I will actually build my own grid using IsoRectangle primitives which will be useful when pathfinding time comes. Here, we create our grid and set the size of the cells to be the same size as our box.

Finally, we just tell the scene to render. That’s it! We now have an isometric scene with a box. More to come.

Cheers!


Mar 10 2010

Loading an OBJ into Away3D

Sorry for the delay, things have been crazy. I’ve spent the last few months organizing my relocation for a new job. Anyhow, I promised to show how to load an OBJ file into Away3d and that’s why I’m here. So, let’s just jump right in shall we?

I will just display the sample class in its entirety and go through it line by line.

package
{
	import away3d.cameras.Camera3D;
	import away3d.containers.Scene3D;
	import away3d.containers.View3D;
	import away3d.core.base.Object3D;
	import away3d.core.math.Number3D;
	import away3d.core.render.Renderer;
	import away3d.events.Loader3DEvent;
	import away3d.loaders.Loader3D;
	import away3d.loaders.Obj;
	import flash.display.Sprite;
	import flash.events.Event;

	public class Main extends Sprite
	{

		protected var _scene:Scene3D;
		protected var _view:View3D;
		protected var _camera:Camera3D;
		protected var _loader:Loader3D;
		protected var _mesh:Object3D;

		public function Main()
		{
			initAway();
		}

		private function initAway():void
		{
			_camera = new Camera3D( { x: -50, y:50, z: -50, lookat:new Number3D( 0, 0, 0 ) } );

			_scene = new Scene3D();

			_view = new View3D( { x:stage.stageWidth / 2, y:stage.stageHeight / 2, scene:_scene, camera:_camera, renderer:Renderer.CORRECT_Z_ORDER } );
			addChild( _view );

			_loader = Obj.load( "m4 sherman.obj", { scaling:10, bothsides:true } );
			_loader.addOnSuccess( onObjLoad );
		}

		private function onObjLoad( e:Loader3DEvent ):void
		{
			_mesh = e.loader.handle;
			_mesh.scaleX = -1;

			_scene.addChild( _mesh );

			addEventListener( Event.ENTER_FRAME, onRender );
		}

		private function onRender( e:Event ):void
		{
			_mesh.rotationY += 1;

			_view.render();
		}

	}

}

Ok, so first we declare all of the variables we will use for this scene at the top of the class declaration which includes our scene, view, camera, loader and mesh. Next, we initialize the Away3d basics by creating our scene, camera and view. Line 38 is where the fun begins, it’s where we actually load the OBJ file. The load function takes 2 parameters, the first is the location of the OBJ file and the second is an optional parameter for any additional properties. You’ll notice that I have used two optional parameters, scaling and bothsides. Scaling is just the amount that Away will scale the object when loaded. This amount is specific to my case because of my unit scale in my 3d software. The property bothsides tells Away to render the material on both sides of every face. The reason I used this property is because I have to leverage certain tricks to keep poly counts low. This means that sometimes I can use a single face to represent a solid object, but it needs to be textured from both sides. Next, I added an listener for load success.

Now that the OBJ is loaded, we have to handle it to get it on the stage. Line 42 is where our handler function starts and it accepts a single parameter of type Loader3DEvent. We need to gain access to our newly loaded object so we use the handle property from the loader which we get from the function parameter. Line 44 illustrates how this is done. Finally, just add the object to the scene and render the view. You’ll notice on line 45 I set the x scale of the object to -1. Again, this is something I had to do in my specific case because my 3d software has an x axis that is inverted compared to Away. You may or may not have to do depending on what 3d software you are using.

That’s it. The object is loaded and displayed. The reason I like this approach is because the OBJ loader handles texture loading and applying. However, you’ll notice that the shape of the object is a little hard to make out and it looks very flat. In order for the object to look like solid, we need to add lights to our scene. This will create shadows and highlights, which will greatly increase the realism of the object. This means, though, that we have to tell Away to use a different material because the standard bitmap material used for the OBJ loader does not react to lights. But, that’s for next time.

Stay tuned!

Unfortunately, I can’t display the example SWF because Shadowbox ( image plugin ) stops the OBJ from loading thereby rendering the SWF useless. Instead I have zipped up the example files and made them available for download. Looks like you will get one of my game models absolutely free. :-) Thanks for playing!