Jun 28 2010

Using as3isolib, Part 3

Next up in my as3isolib tutorials is drag and drop. I won’t be using drag and drop in my game, but it is such a common feature in isometric games that I’ve gone ahead and created an example for those who may need it. Lets get started. Below is the full class code.

package
{

	import as3isolib.core.ClassFactory;
	import as3isolib.display.IsoView;
	import as3isolib.display.primitive.IsoBox;
	import as3isolib.display.renderers.DefaultShadowRenderer;
	import as3isolib.display.scene.IsoGrid;
	import as3isolib.display.scene.IsoScene;
	import as3isolib.geom.Pt;
	import caurina.transitions.Tweener;
	import eDpLib.events.ProxyEvent;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.filters.GlowFilter;
	import flash.geom.Point;

	public class IsoDragExample extends Sprite
	{

		private var _view:IsoView;
		private var _scene:IsoScene;
		private var _box:IsoBox;
		private var _grid:IsoGrid;
		private var _dragObject:IsoBox;
		private var _dragPt:Pt;
		private var _glow:GlowFilter;

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

			_scene = new IsoScene();
			_scene.hostContainer = this;
			_scene.styleRenderers = [ new ClassFactory( DefaultShadowRenderer ) ];
			_view.addScene( _scene );

			_glow = new GlowFilter( 0xFF8000, 1, 6, 6, 64 );

			_box = new IsoBox
			_box.setSize( 50, 50, 50 );
			_box.addEventListener( MouseEvent.ROLL_OVER, onRollOverHandler, false, 0, true );
			_box.addEventListener( MouseEvent.ROLL_OUT, onRollOutHandler, false, 0, true );
			_box.addEventListener( MouseEvent.MOUSE_DOWN, onPickup, false, 0, true );
			_scene.addChild( _box );

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

			addEventListener( Event.ENTER_FRAME, onRender, false, 0, true );
		}

		private function onPickup( e:ProxyEvent ):void
		{
			_dragObject = e.target as IsoBox;
			_dragPt = _view.localToIso( new Point( stage.mouseX, stage.mouseY ) );
			_dragPt.x -= _dragObject.x;
			_dragPt.y -= _dragObject.y;

			_dragObject.removeEventListener( MouseEvent.MOUSE_DOWN, onPickup );

			_dragObject.addEventListener( MouseEvent.MOUSE_UP, onDrop, false, 0, true );
			stage.addEventListener( MouseEvent.MOUSE_UP, onDrop, false, 0, true );
			stage.addEventListener( MouseEvent.MOUSE_MOVE, onMoveObject, false, 0, true );

			Tweener.addTween( _dragObject, { z:25, time:0.5 } );
		}

		private function onDrop( e:Event ):void
		{
			_dragObject.removeEventListener( MouseEvent.MOUSE_UP, onDrop );
			stage.removeEventListener( MouseEvent.MOUSE_UP, onDrop );
			stage.removeEventListener( MouseEvent.MOUSE_MOVE, onMoveObject );

			_dragObject.addEventListener( MouseEvent.MOUSE_DOWN, onPickup, false, 0, true );

			Tweener.addTween( _dragObject, { z:0, time:0.5 } );
		}

		private function onMoveObject( e:MouseEvent ):void
		{
			var pt:Pt = _view.localToIso( new Point( stage.mouseX, stage.mouseY ) );

			_dragObject.moveTo( pt.x - _dragPt.x, pt.y - _dragPt.y, _dragObject.z );
		}

		private function onRollOverHandler( e:ProxyEvent ):void
		{
			( e.target as IsoBox ).container.filters = [ _glow ];
		}

		private function onRollOutHandler( e:ProxyEvent ):void
		{
			( e.target as IsoBox ).container.filters = [];
		}

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

	}

}

In the constructor we do our normal setup for our view and scene. First thing you might notice is on line 38 we set a property called “styleRenderers” of the IsoScene. This is a built in feature of as3isolib that allows for certain styling elements to be drawn during render. In this case, as3isolib comes with a shadow renderer. This just draws squares at grid level for each object. I have this set because when we pick up objects, this allows you to see exactly where they are positioned in isometric space.

Next, at line 43, we create our box and we add some mouse event listeners. I have added ROLL_OVER and ROLL_OUT that triggers a glow on the box just so you can see the mouse interaction. Also, there is a listener for MOUSE_DOWN. This is the start of the dragging process which calls the method onPickup.

Line 57 is the onPickup method. Here we store a reference to the box object and on line 60 we track the current isometric point of the mouse. I use this value to calculate the space between the object and the mouse position. This is so when I drag the object around it maintains this distance instead of snapping directly to the mouse. Next I add some more listeners to track MOUSE_UP on both the object and the stage because it is entirely possible that the mouse will not be over the object when released. Finally, using Tweener, I animate the object up by 25 pixels. This is just to simulate physically pickup it up.

Jumping ahead to line 84, this is where the calculations happen to actually move the object around. First thing I do is grab the mouse’s new isometric point. Then, I move the object to that position, but I also remember to include the original distance between mouse and object.

Line 73, is where the object gets dropped. Here, I just remove old event listeners and add the previous ones. I also use Tweener to animate the box back to grid level.

NOTE: It is always good practice to remove event listeners when they are no longer needed and only add them when needed. You’ll notice that when an object is picked up I remove the MOUSE_DOWN event listener and add the MOUSE_UP and MOUSE_MOVE listeners. There are two reasons for this. First, I only care about the MOUSE_UP and MOUSE_MOVE events once the object is picked. There is no need to have those listeners there anytime before then. Second, having multiple listeners active when not needed can cause unexpected behavior.

That’s it. Now we have a box and we can drop it anywhere in the view. You’ll also notice that the box has a neat little shadow beneath it so you know when it is picked up. The current example lets you drag the box anywhere without restrictions. A tile based game might constrain the object to grid squares. In this case, you would need to do additional calculation so ensure the object is in the right place. That part is up to you though.

Cheers!


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!