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 🙂

Step 1 – Set up my world

I’m going to make a world ( IsoView ) that contains 2 scenes. One at the bottom to contain an instance of IsoGrid and on top of that, another scene to contain isometric objects. So I’ll start a document class with just a few methods as follows:

package
{
	import as3isolib.display.IsoView;
	import as3isolib.display.scene.IsoGrid;
	import as3isolib.display.scene.IsoScene;
	import flash.display.Sprite;
	import flash.events.Event;

	public class Main extends Sprite
	{

		private var _world:IsoView;
		private var _grid:IsoGrid;
		private var _groupScene:IsoScene;

		public function Main():void
		{
			if (stage)
				init();
			else
				addEventListener(Event.ADDED_TO_STAGE, init);
		}

		private function init(e:Event = null):void
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			createWorld();
			
		}

		private function createWorld():void
		{
			_world = new IsoView();
			_world.setSize(400, 400);
			addChild(_world);

			///////// GRID
			_grid = new IsoGrid();
			_grid.setGridSize(10, 10);
			_grid.cellSize = 20;

			var gridScene:IsoScene = new IsoScene();
			gridScene.addChild(_grid);

			_world.addScene(gridScene);

			///////// GROUP SCENE
			_groupScene = new IsoScene();
			_world.addScene(_groupScene);

			_world.addEventListener(Event.ENTER_FRAME, render);
		}

		private function render(e:Event):void
		{
			_world.render(true);
			
		}
	}
}

Empty World

Line 33 – 35 : Creates an instance of IsoView.

Line 38 – 45: Creates an IsoGrid and an IsoScene to contain it. And then adds the scene to the IsoView.

Line 48 – 49: Creates a scene to contain the IsoGroup and adds it to the IsoView.

The last line sets ENTER_FRAME listener to render the world.

If I compile the class now, I’ll get something like the picture on the left.

Step 2 – Write a custom IsoGroup

Now I’m going to extend IsoGroup and add some cool stuff. First, some basic stuff just to set things up.

package
{
	import as3isolib.display.IsoGroup;
	import as3isolib.display.scene.IsoGrid;
	
	public class Group extends IsoGroup
	{
		protected var _grid:IsoGrid;

		public function Group(grid:IsoGrid, descriptor:Object = null)
		{
			super(descriptor);
			_grid = grid;
		}
	}
}

Then I add a method through which I can send a 2d array to the Group and let it build a bunch of IsoBox.

public function setMap(map:Array):void
{

	if (children.length > 0)
	{
		removeAllChildren();
	}
	
	//make NxN array that represents the group's layout 
	//this is the array we'll rotate
	var w:Number = Math.max(map[0].length, map.length);
	_layout = new Array2(w,w);
	_layout.fill(0);

	for (var row:int = 0; row < map.length; row++)
	{
		var r:Array = map[row];

		for (var col:int = 0; col < r.length; col++)
		{
			if (map[row][col] != 0)
			{
				var box:IsoBox = new IsoBox();
				box.fills = [new SolidColorFill(0x00FFFF, 1), new SolidColorFill(0x8080C0, 1), new SolidColorFill(0xFF8040, 1)];
				box.setSize(_grid.cellSize, _grid.cellSize, 40);
				box.moveTo(_grid.cellSize * col, _grid.cellSize * row, 0);
				box.name = map[row][col];
				addChild(box);

				_layout.set(col, row, box);
			}
		}
	}

}

Line 4 - 7: Removes all previous children in the group if there's any.

Line 11 - 13: make a square Array2 based on the number of columns or rows in the passed array -- whichever the largest to make sure that when I move its elements, they will not fall out of bounds. I use the array as a reference to the positions of the group's children (IsoBox) and I call it _layout.

Line 15 - 33: Loop through the Array2 and create IsoBox instances. Note that I'll use 0 in the array to mark places that I want to be empty. So in the loop, I check if an element is a 0 or not. If it is not a 0, I create an IsoBox there. And store a reference to it in the _layout array.

Back to the Main class. I add a new method to create my group and I also define the group's map.

private var _map:Array = [
	[0, 'A', 'B', 'C'], 
	['D', 'E', 'F', 'G']
];

private function init(e:Event = null):void
{
	removeEventListener(Event.ADDED_TO_STAGE, init);
	createWorld();
	createGroup();
}

private function createGroup():void
{
	_group = new Group(_grid);
	_group.setMap(_map);
	_group.moveTo(60, 60, 0);
	_groupScene.addChild(_group);

}

So far I get this when I compile the codes :

Basic Group

Pretty, eh? 🙂

Step 3 - Make the group rotatable

Now that I have the basic group in place it's time to make it rotatable. So I add a new method rotate() that takes a boolean flag to indicates if the rotation is clockwise (true) or counter-clockwise (false).

public function rotate(clockWise:Boolean = true):void
{
	
	//make a new NxN array for the new layout ( after rotation )
	var w:Number =_layout.width;
	var newLayout:Array2 = new Array2(w, w);
	newLayout.fill(0);

	for (var r:int = 0; r < w; r++)
	{
		for (var c:int = 0; c < w; c++)
		{
			//found this array-rotation algo somewhere on the web
			var c0:int = (clockWise == true) ? w - 1 - r : r;
			var r0:int = (clockWise == true) ? c : w - 1 - c;

			newLayout.set(c0, r0, _layout.get(c, r));
		}
	}

	_layout = newLayout;
	updateChildren();

}

protected function updateChildren():void
{
	for (var r:int = 0; r < _layout.height; r++)
	{
		for (var c:int = 0; c < _layout.width; c++)
		{
			if (_layout.get(c, r) is IsoBox)
			{
				_layout.get(c, r).moveTo(c * _grid.cellSize, r * _grid.cellSize, 0);
			}
		}

	}
}

Line 5 - 7: Makes an empty array for the new layout

Line 13 -14: Using the algorithm, I figure out where each box should end up

Line 15: Set the box to a corresponding position in the new layout

Line 21: replace the old layout with the new one

Line 26 - 39: move the boxes to their new positions using the new layout as a reference

Back to the Main class again, I add some buttons to set the group's rotation and I have something like this (click the black buttons):

So that's basically all the steps to make a rotatable IsoGroup. Pretty simple, right?

Extra - A group that can be rotated over a pivot

As an extra, here's a Group subclass that allows you to rotate it around a selected box (pivot). What different in this new class from the Group is the way it handles the rotation. Look at rotate() and updateChildren(). Basically, what this class does is move itself so that the pivot box stays at the same position as it was before rotation.

Here's the demo. Click a box to select a pivot. Click it again to deselect. When there's no box selected, the group can rotate just like the one above.

And here's the class:

package
{
	import as3isolib.display.primitive.IsoBox;
	import as3isolib.display.scene.IsoGrid;
	import as3isolib.graphics.SolidColorFill;
	import eDpLib.events.ProxyEvent;
	import flash.events.MouseEvent;

	/**
	 * ...
	 * @author Anggie Bratadinata | www.masputih.com
	 */
	public class GroupWithPivot extends Group
	{

		public var selectedBox:IsoBox;

		private var _selectedBox_oldX:Number;
		private var _selectedBox_oldY:Number;

		public function GroupWithPivot(grid:IsoGrid, descriptor:Object = null)
		{
			super(grid, descriptor);

		}

		override public function setMap(map:Array):void
		{
			super.setMap(map);

			for each (var isoBox:IsoBox in children)
			{
				isoBox.addEventListener(MouseEvent.CLICK, onBoxClick);
			}
		}

		private function onBoxClick(e:ProxyEvent):void
		{
			var newBox:IsoBox = e.target as IsoBox;

			if (selectedBox != null)
			{
				//reset color
				selectedBox.fills = [new SolidColorFill(0x00FFFF, 1), new SolidColorFill(0x8080C0, 1), new SolidColorFill(0xFF8040, 1)];
			}
			if (newBox != selectedBox)
			{
				//set new color
				newBox.fills = [new SolidColorFill(0x804040, 1), new SolidColorFill(0x400000, 1), new SolidColorFill(0x804040, 1)];
				selectedBox = newBox;
			}
			else
			{
				selectedBox = null;
			}

		}

		override public function rotate(clockWise:Boolean = true):void
		{
			if (selectedBox != null)
			{
				_selectedBox_oldX = selectedBox.x;
				_selectedBox_oldY = selectedBox.y;
			}

			super.rotate(clockWise);

		}

		override protected function updateChildren():void
		{
			super.updateChildren();

			if (selectedBox != null)
			{
				var dx:int = selectedBox.x - _selectedBox_oldX;
				var dy:int = selectedBox.y - _selectedBox_oldY;
				moveTo(x - dx, y - dy, 0);
				
			}
		}

	}

}

Download

Here's a zip containing all of those classes : → Rotatable Group Project

Also in this category ...


2 Comments

  1. Hi there,
    just want to thumb up for your work around as3isolib.
    Really hope to know something more on the new as3isolib v2 version. In the meanwhile i really enjoy integrating v1 with your useful class!

Comments are closed.