AS3Isolib Game : Veer

I’ve been working on a facebook game called Veer (launched on January, 4th, 2012) for my client, Handson Entertainment . In this game, you play as a reporter, like Lois Lane, working for a news station, Veer. You begin your journey in a remote island called “Tuto Island” where you learn the basics of the game and then, as you level up, you will be taken to cities/places such as Paris, Monaco, Zanzibar, Beijing, etc. to hunt for interesting news. Based on your locations and levels/experience, you will have different missions/tasks from finding a missing colleague, locating a run-away celebrity, and so on. Also, you get to visit famous landmarks like The Eiffel and Louvre Museum in Paris.

Continue reading AS3Isolib Game : Veer

Making Rotatable IsoGroup

In this article, I will show you how to make a rotatable IsoGroup in As3Isolib. Well, I don’t actually rotate the group but instead, I reposition the group’s children so that the group appears to be rotated. :-)

If you’re new to As3Isolib, you can learn a bit about the basics here. If you know the library already, you might want to skip step 1.

Basic steps:

  • Subclass the IsoGroup
  • Map the group’s children’s positions to a 2d array
  • Add rotation logic
  • Rotate the array
  • Use the array to reposition the children
  • Render

What I need:

  1. as3ds Array2 class
  2. an algorithm to rotate a 2d rectangular array. Just google it. It’s everywhere :-)

Continue reading Making Rotatable IsoGroup

SpriteSheet class for AS3Isolib

One of the requirements of my latest as3isolib project was to have “realistic” looks for all objects. The only way to achieve this is use bitmap assets so I paid a friend who’s really good at making animated 3d models to create animated assets for me and render them as PNG sprite sheets. Another thing I had to do was blit the sheets but there’s no built-in bitmap blitter class in as3isolib that I could use so I wrote the SpriteSheet class below.

package iso
{
	import de.polygonal.ds.Array2;
	import iso.Directions;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.getTimer;

	/**
	 * Bitmap-blitter. The source must be a bitmap or sprite with 8 rows representing 8 directions.
	 * Each row contains an animation cycle where column 0 is for idle state.
	 * 
	 * @author Anggie Bratadinata
	 */
	public class SpriteSheet extends Sprite
	{
		//rendering interval
		public static const RENDER_INTERVAL:Number = 64;

		//bitmap frames
		protected var _bitmapArray:Array2;
		//sheet source
		protected var _source:DisplayObject;
		//frame size
		protected var _frameWidth:Number;
		protected var _frameHeight:Number;
		//the rendered bitmap
		protected var _frameBitmap:Bitmap;
		protected var _frameRect:Rectangle;

		//current facing direction
		protected var _direction:String;
		//current frame row
		protected var _currentRow:Array;
		protected var _currentColNum:Number;
		//render timer
		protected var _oldTime:Number = 0;
		protected var _renderInterval:Number;
		
		//is the south-facing sequence on the first row (true) or not (false)?
		protected var _southFirst:Boolean;
		
		//the visual state 
		protected var _isIdle:Boolean;
		
		//if true, a new bitmap will be generated
		protected var _isDirty:Boolean;

		public function SpriteSheet()
		{

		}

		/**
		 * Build the sprite sheet. The source must contain 8 rows of animation frames arranged in clockwise order.
		 * The first row can be the south face or the south west. A tool like SpriteForge renders tilesheets 
		 * with the south-facing sequence on the first row. 
		 * 
		 * Acceptable row arrangements : S,SW,W,NW,N,NE,E,SE or SW,W,NW,N,NE,E,SE,S
		 * 
		 * @param	source			The source Display object. 
		 * @param	frameWidth		animation frame width
		 * @param	frameHeight		animation frame height
		 * @param	renderInterval	delay between renders. This defines the animation blitting speed in milliseconds
		 * @param	southFirst		the south-facing sequence is on the first row (true) or the last (false). 
		 *                          
		 */
		public function build(source:DisplayObject, frameWidth:Number, frameHeight:Number, renderInterval:Number = SpriteSheet.RENDER_INTERVAL, southFirst:Boolean = true):void
		{
			
			_source = source;
			_frameWidth = frameWidth;
			_frameHeight = frameHeight;
			_renderInterval = renderInterval;
			_southFirst = southFirst;

			var sourceBd:BitmapData = new BitmapData(source.width, source.height, true, 0x00000000);
			sourceBd.draw(source, null, null, null, null, true);

			var numCols:Number = Math.floor(source.width / frameWidth);
			var numRows:Number = Math.floor(source.height / frameHeight);

			_frameRect = new Rectangle(0, 0, frameWidth, frameHeight);

			_bitmapArray = new Array2(numCols, numRows);

			for (var i:int = 0; i < numRows; i++)
			{
				for (var j:int = 0; j < numCols; j++)
				{
					var bd:BitmapData = new BitmapData(frameWidth, frameHeight, true, 0x000000);
					_frameRect.x = j * frameWidth;
					_frameRect.y = i * frameHeight;

					bd.copyPixels(sourceBd, _frameRect, new Point(0, 0));

					_bitmapArray.set(j, i, bd);

				}

			}
			
			_frameRect.x = 0;
			_frameRect.y = 0;
			
			//if the south faces is on the last row, move it to the first
			if (!_southFirst)
			{
				_bitmapArray.shiftDown();
			}

			setDirection(Directions.S);
			idle();

			_oldTime = getTimer();
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			
		}
		
		/**
		 * Set the state to idle
		 */
		public function idle():void
		{
			
			_isIdle = true;
			_isDirty = true;
		};
		
		/**
		 * Set the state to walk/animated
		 * 
		 */
		public function action():void
		{
			_isIdle = false;
			_isDirty = true;
		};

		protected function onEnterFrame(e:Event = null):void
		{

			var elapsed:Number = getTimer() - _oldTime;

			if (elapsed >= _renderInterval && _isDirty)
			{
				render();
				_oldTime = getTimer();
			}

		}

		protected function render():void
		{
			if (_frameBitmap == null)
			{
				_frameBitmap = new Bitmap(new BitmapData(_frameWidth, _frameHeight, true, 0x00000000));
				addChild(_frameBitmap);
			}

			_frameBitmap.bitmapData.lock();
			
			if (_isIdle)
			{
				_currentColNum = 0;
				_frameBitmap.bitmapData.copyPixels(_currentRow[_currentColNum], _frameRect, new Point(0, 0), null, null, false);
				//trace(this + "render idle");
				_isDirty = false;
			}
			else
			{
				
				if (_currentColNum < _currentRow.length-1)
				{
					try {
						_frameBitmap.bitmapData.copyPixels(_currentRow[_currentColNum], _frameRect, new Point(0, 0), null, null, false);
					}catch (error:Error) {
						//trace("ERROR RENDERING : " + _currentColNum);
						return;
					}
					_currentColNum++;
				}
				else
				{
					_currentColNum = 1;
				}
				
				_isDirty = true;
			}
			
			_frameBitmap.bitmapData.unlock();
		}

		/**
		 * Set the active direction and make _currentRow point to a specific row in _bitmapArray
		 * @param	direction
		 */
		public function setDirection(direction:String):void
		{
			//trace(this +"set direction : " +direction);
			_direction = direction;
			var rowNum:Number;
			switch (direction)
			{
				case Directions.S:
					rowNum = 0;
					break;
				case Directions.SW:
					rowNum = 1;
					break;
				case Directions.W:
					rowNum = 2;
					break;
				case Directions.NW:
					rowNum = 3;
					break;
				case Directions.N:
					rowNum = 4;
					break;
				case Directions.NE:
					rowNum = 5;
					break;
				case Directions.E:
					rowNum = 6;
					break;
				case Directions.SE:
					rowNum = 7;
					break;
				default:
					rowNum = 0;
			}
			//trace(this + "setDirection, row " + rowNum);
			_currentRow = _bitmapArray.getRow(rowNum);
			
			_isDirty = true;
			render();
			
		}
		
		/**
		 * Get currently rendered frame
		 * 
		 * @return Bitmap
		 */
		public function getFrameBitmap():Bitmap
		{
			return _frameBitmap;
		}
		
		/**
		 * Get current direction
		 * @return
		 */
		public function getDirection():String
		{
			return _direction;
		};

	}

}

Next, I'll explain how to create your sheets.

Continue reading SpriteSheet class for AS3Isolib

Creating Drag-and-Drop for AS3 Isolib

Some folks had asked about drag-and-drop mechanism in as3isolib discussion group. While there’s no such feature built-in in the library, it’s not hard to write it yourself.

So here’s a super simple class that provides that mechanism, called DragManager. I borrowed the idea from Flex’s DragManager. Note that this class is really a simple one and it doesn’t respect “grid” so you’ll have to modify it if you want to use it in a grid-based isometric space. Trust me, it’s not too hard 😉

Click to see the demo

Continue reading Creating Drag-and-Drop for AS3 Isolib