Flash 9 and AS3 provide a lot of nice structure that Flash 8 and AS2 lacked. Instead of just MovieClip and TextField, we have a whole hierarchy of DisplayObject derivatives, including MovieClip and TextField. Most AS3 programmers know that Sprite is a more efficient class than MovieClip and have grown to use them when animation is not required. However, I find that very few AS3 programmers ever use some of the other, more obscure DisplayObject derivatives. Today I’ll talk a little about one of them: Shape.

I rarely see any AS3 programmer ever use a Shape. I think this is because Shapes are more limited than Sprites in many ways: they cannot have children, they make poor buttons, they cannot be a drop target, and so forth. They are rarely noticed too since they are not a superclass of the more common DisplayObjects like Sprite, MovieClip, TextField, or Bitmap. All in all they offer less of everything to the AS3 programmer. However, they exist for the reason pointed out in the Adobe documentation: a lightweight alternative to the heavyweight Sprite. Let’s see just how lightweight they are! Here’s a little app to see the difference in construction time:

const NUM_REPS:int = 100000;
var i:int;
var beforeTime:int;
 
beforeTime = getTimer();
var sprite:Sprite;
for (i = 0; i < NUM_REPS; ++i)
{
	sprite = new Sprite();
}
trace("Constructing Sprites: " + (getTimer()-beforeTime));
 
beforeTime = getTimer();
var shape:Shape;
for (i = 0; i < NUM_REPS; ++i)
{
	shape = new Shape();
}
trace("Constructing Shapes: " + (getTimer()-beforeTime));

And here are the results:

Environment Sprite Shape
2.2 Ghz Intel Core 2 Duo, 2GB RAM, Mac OS X 10.6 809 418
3.0 Ghz Intel Core 2 Duo, 4GB RAM, Windows XP 465 255

Right there we have a 89% speedup! Now how about real time performance?

package
{
	import flash.display.*;
	import flash.events.*;
	import flash.text.*;
	import flash.utils.*;
 
	[SWF(backgroundColor=0xEEEADB,frameRate="1000")]
 
	/**
	*   A simple app to display the difference in performance between Shape
	*   and Sprite
	*   @author Jackson Dunstan
	*/
	public class ShapesSpritesPerformanceTest extends Sprite
	{
		/** Last time we updated FPS */
		private var __lastFPSUpdateTime:uint;
 
		/** Last time we entered a frame */
		private var __lastFrameTime:uint;
 
		/** Count of frames since the last time we updated the FPS */
		private var __frameCount:uint;
 
		/** Framerate display */
		private var __framerate:TextField;
 
		/** Text field to show the type of object we're displaying */
		private var __mode:TextField;
 
		/** Time when we started timing */
		private var __beginTime:int;
 
		/** Container of the example Sprites */
		private var __sprites:Sprite;
 
		/** Container of the example Shapes */
		private var __shapes:Sprite;
 
		/** Current container */
		private var __curContainer:Sprite;
 
		/**
		*   Application entry point
		*/
		public function ShapesSpritesPerformanceTest()
		{
			// Setup stage
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			var format:TextFormat = new TextFormat();
			format.font = "_sans";
 
			// Setup framerate display
			__framerate = new TextField();
			__framerate.autoSize = TextFieldAutoSize.LEFT;
			__framerate.background = true;
			__framerate.backgroundColor = 0xEEEADB;
			__framerate.selectable = false;
			__framerate.text = "Gathering FPS data...";
			__framerate.setTextFormat(format);
			__framerate.defaultTextFormat = format;
			addChild(__framerate);
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
 
			__mode = new TextField();
			__mode.y = __framerate.height;
			__mode.autoSize = TextFieldAutoSize.LEFT;
			__mode.setTextFormat(format);
			__mode.defaultTextFormat = format;
			__mode.text = "Sprites";
			addChild(__mode);
 
			const NUM_TO_DISPLAY:int = 1000;
			var i:int;
 
			__sprites = new Sprite();
			var sprite:Sprite;
			__shapes = new Sprite();
			var shape:Shape;
			for (i = 0; i < NUM_TO_DISPLAY; ++i)
			{
				sprite = new Sprite();
				sprite.graphics.beginFill(0x261c13);
				sprite.graphics.drawCircle(0, 0, 10);
				sprite.graphics.endFill();
				sprite.x = Math.random()*500;
				sprite.y = Math.random()*(500-__mode.height)+__mode.y+__mode.height;
				__sprites.addChild(sprite);
 
				shape = new Shape();
				shape.graphics.beginFill(0x261c13);
				shape.graphics.drawCircle(0, 0, 10);
				shape.graphics.endFill();
				shape.x = sprite.x;
				shape.y = sprite.y;
				__shapes.addChild(shape);
			}
 
			__curContainer = __sprites;
			addChild(__sprites);
			stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
		}
 
		/**
		*   Callback for when a key is pressed
		*   @param ev KEY_DOWN event
		*/
		private function onKeyDown(ev:KeyboardEvent): void
		{
			if (__curContainer == __sprites)
			{
				removeChild(__sprites);
				addChild(__shapes);
				__mode.text = "Shapes";
				__curContainer = __shapes;
			}
			else
			{
				removeChild(__shapes);
				addChild(__sprites);
				__mode.text = "Sprites";
				__curContainer = __sprites;
			}
		}
 
		/**
		*   Callback for when a frame is entered
		*   @param ev ENTER_FRAME event
		*/
		private function onEnterFrame(ev:Event): void
		{
			__frameCount++;
			var now:int = getTimer();
			var dTime:int = now - __lastFrameTime;
			var elapsed:int = now - __lastFPSUpdateTime;
			if (elapsed > 1000)
			{
				var framerateValue:Number = 1000 / (elapsed / __frameCount);
				if (__framerate.visible)
				{
					__framerate.text = "FPS: " + framerateValue.toFixed(4);
				}
				__lastFPSUpdateTime = now;
				__frameCount = 0;
			}
			__lastFrameTime = now;
 
			for (var i:int = 0; i < __curContainer.numChildren; ++i)
			{
				var cur:DisplayObject = __curContainer.getChildAt(i);
				cur.x = Math.random()*500;
				cur.y = Math.random()*(500-__mode.height)+__mode.y+__mode.height;
			}
		}
	}
}

I get the following results:

Environment Sprite Shape
2.2 Ghz Intel Core 2 Duo, 2GB RAM, Mac OS X 10.6 39 FPS 39 FPS
3.0 Ghz Intel Core 2 Duo, 4GB RAM, Windows XP 63 FPS 63 FPS

It seems that there is no rendering difference between the two. Therefore if you go with a Shape, you won’t have to worry about the performance after you have them constructed. How about memory usage?

package
{
	import flash.system.*;
	import flash.display.*;
	import flash.events.*;
	import flash.text.*;
	import flash.utils.*;
 
	[SWF(backgroundColor=0xEEEADB)]
 
	/**
	*   A simple app to display the difference in memory between Shape
	*   and Sprite
	*   @author Jackson Dunstan
	*/
	public class ShapesSpritesMemoryTest extends Sprite
	{
		/** Number of objects to allocate */
		private static const NUM_REPS:int = 100000;
 
		/** Memory display */
		private var __memory:TextField;
 
		/** Objects allocated */
		private var __objects:Array = [];
 
 		/** Current mode */
 		private var __mode:String = "Sprites";
 
		/**
		*   Application entry point
		*/
		public function ShapesSpritesMemoryTest()
		{
			// Setup stage
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			var format:TextFormat = new TextFormat();
			format.font = "_sans";
 
			// Setup memory display
			__memory = new TextField();
			__memory.autoSize = TextFieldAutoSize.LEFT;
			__memory.background = true;
			__memory.backgroundColor = 0xEEEADB;
			__memory.selectable = false;
			__memory.text = "Gathering memory data...";
			__memory.setTextFormat(format);
			__memory.defaultTextFormat = format;
			addChild(__memory);
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
 
			allocateSprites();
			stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
		}
 
		/**
		*   Callback for when a key is pressed
		*   @param ev KEY_DOWN event
		*/
		private function onKeyDown(ev:KeyboardEvent): void
		{
			__objects.length = 0;
			if (__mode == "Sprites")
			{
				__mode = "Shapes";
				allocateShapes();
			}
			else
			{
				__mode = "Sprites";
				allocateSprites();
			}
		}
 
		/**
		*   Allocate sprites to the __objects list
		*/
		private function allocateSprites(): void
		{
			for (var i:int = 0; i < NUM_REPS; ++i)
			{
				__objects.push(new Sprite());
			}
		}
 
		/**
		*   Allocate shapes to the __objects list
		*/
		private function allocateShapes(): void
		{
			for (var i:int = 0; i < NUM_REPS; ++i)
			{
				__objects.push(new Shape());
			}
		}
 
		/**
		*   Callback for when a frame is entered
		*   @param ev ENTER_FRAME event
		*/
		private function onEnterFrame(ev:Event): void
		{
			__memory.text = "Memory (KB) for " + __mode + ": " + (System.totalMemory)/1024;
		}
	}
}

And the results are:

Environment Sprite Shape
2.2 Ghz Intel Core 2 Duo, 2GB RAM, Mac OS X 10.6 85172 36916
3.0 Ghz Intel Core 2 Duo, 4GB RAM, Windows XP 49508 32672

Here we see Sprites taking 36% more memory as Shapes, but 2.3x as much on Mac OS X. This is another big win on behalf of the Shape class. So in summary, we see that Shapes beat Sprites for construction time and memory usage, but make no difference in rendering performance. So if you’re in need of better performance and can live with the limited feature set of Shapes, it may be time to consider a switch from Sprites to Shapes!