divillysausages.com

Colony tech update 1: Creating a game camera, part 1

For my current project, Colony, I found myself needing a game camera. I've never actually needed one for a personal project before, so it was a good opportunity to write up the system and release the code here. This is the first part in a eight-part series on making a game camera and the related controllers.

What's in a camera?

A camera is simply an object that exists in memory, and that defines a view rect that we use to position our different layers. This implementation is rendering-agnostic; meaning you can use it for a DisplayObject-, blitting-, or Starling-rendered game. So, the list of things that I wanted:

Camera.x vs. cameraX

The x, y, width and height of the camera define its screen space bounds. X and y, we use to "position" the camera on the stage - for the most part these are 0,0 as your camera will typically take up the entire screen, but sometimes you may wish to move it, if you have a GUI in the way, for example.

CameraX and cameraY on the other hand, specify the virtual position of your camera in the game world. In screen space, these are bang in the middle of your camera screen rect, and don't move, however, in world space, these dictate where your camera is currently looking. When we "move" the camera, these are the coordinates moved. Setting cameraX and cameraY to the x and y values of a DisplayObject in your layer will center that object on the screen.

The following image illustrates the point:

Illustrating the difference between camera.x and cameraX

A point on zooming

Zooming is pretty simple: we just take the camera zoom and set it as the scale of the layers that the camera is controlling. The problem comes in scaling smoothly.

Lets say we want our camera to be able to zoom in and out by a factor of 10. This would mean our normal zoom level is 1.0, while our min zoom is 0.1 and our max zoom is 10.0. Now lets say we want to do a linear zoom from our min to our max. See the problem yet? Zooming out is considered to be between 0.1 and 1.0, while zooming in is considered to be between 1.0 and 10.0. So for the same relative zoom, zooming out takes 1/10th of the time.

Linear camera zoom, showing the scale problem

Each dot represents a step in time. As you can see, going from min zoom to normal takes about 1/10th of the time as from normal to max zoom. If we were to give zooming in the same time as zooming out, we'd end up with a graph like this:

Camera zoom, giving the same amount of time for zooming in as zooming out

Hm, that looks suspiciously like a logarithmic graph :)

In order to get a smooth scale, from min to normal to max, we need to take this fact into account. Internally, the min and max zoom are turned into their Math.log() equivalents, -2.3025850929940455 and 2.302585092994046 for 0.1 and 10.0 respectively. Then, we do our linear scale between these two values, and use Math.exp() to get the actual zoom value, resulting in a scale like this:

Linear camera zoom, using a logarithmic scaling solution

So we have a nice linear scale, yet going from min zoom to normal takes the same amount of time as going from normal to max.

Layers

Our camera needs to actually control something (or multiple things), so this is where the ICameraLayer interface comes in. It's a pretty simple interface, just declaring properties and functions necessary for the camera to work properly. If your layer is a Sprite or Bitmap, you're already about 60% covered. This class is also available below.

package 
{
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	/**
	 * The interface that describes the layers that we add to our camera
	 * @author Damian Connolly
	 */
	public interface ICameraLayer 
	{
		
		/*******************************************************************************************/
		
		/**
		 * The x position of the layer
		 */
		function get x():Number;
		function set x( n:Number ):void;
		
		/**
		 * The y position of the layer
		 */
		function get y():Number;
		function set y( n:Number ):void;
		
		/**
		 * The x scale of the layer
		 */
		function get scaleX():Number;
		function set scaleX( n:Number ):void;
		
		/**
		 * The y scale of the layer
		 */
		function get scaleY():Number;
		function set scaleY( n:Number ):void;
		
		/**
		 * Should this layer be scaled when the camera is zoomed?
		 */
		function get isZoomEnabled():Boolean;
		
		/*******************************************************************************************/
		
		/**
		 * Converts the Point coordinates from stage space to local space
		 * @param point A Point with coordinates declared in stage space
		 * @return A Point, where our coordinates have been translated to local space
		 */
		function globalToLocal( point:Point ):Point
		
		/**
		 * Checks the visibility of the children based on the camera view rect
		 * @param viewRect The view rect of the camera, in layer space
		 */
		function checkChildrenVisibility( viewRect:Rectangle ):void;
		
		/**
		 * Sorts all the children
		 * @param compareFunction The function to use to sort the children; works similar to Vector or Array sort
		 */
		function sortChildren( compareFunction:Function ):void;
		
		/**
		 * Called when there's a click on the layer
		 * @param localX The x position of the click, in layer space
		 * @param localY The y position of the click, in layer space
		 */
		function onClick( localX:Number, localY:Number ):void;
		
	}

}

checkChildrenVisibility() and sortChildren() are called every frame by the camera. It's up to you to decide whether that's too much or too little. As Colony is a space game, there's generally not going to be hundreds of units on screen at once, so this is not a problem for me.

The code

For the camera to work properly, it'll need to be added to an update loop, or at the very least, have update() called every frame.

The main properties of interest are cameraX, cameraY, and zoom. You can set these directly or change them via a tween. They stop any current movement and update immediately (i.e. in the next call to update()). You can also use the moveCameraTo() and moveCameraBy() functions to move in one call, or by relative amounts.

For moving/zooming using velocity, which is where the fun is, you can use setMoveVelociyTo(), setMoveVelocityBy, setZoomVelocityTo, and setZoomVelocityBy. The different velocities will fade off depending on the deceleration and cutoff properties set.

To actually add a layer to control, you use the amazingly-named addLayerToControl():

public function addLayerToControl( layer:ICameraLayer, speedFactor:Number = 1.0, offset:Point = null ):void
{
	...
}

The speedFactor param is there to enable parallax scrolling. Yep, it really is that easy. Parallax is just different layers moving at different speeds. So if you had layerA with a speedFactor of 1.0 and layerB with a speedFactor of 0.5, then when the camera moved, layerB would move at half the speed of layerA, thus looking like it's far away. Fill it with distant graphics and set it behind everything else, and the illusion is complete. The offset is just to provide a fixed offset for the layer when the camera moves, in case all your layers aren't aligned.

setBounds() limits where the camera can move to, while onClick() will simply call onClick() in each of the layers, transforming the click coordinates into local space at the same time. You can use this to, say, select object, tiles, set destinations, or anything else where clicking comes in useful.

Below is the full code for the camera, or you can also download the file below. Note that it uses the internal class CameraLayerRef class; this is just to keep track of the layer specific parameters. The Camera class seems more complicated that it really is, but about half of it is support for moving/zooming using velocity :) (NOTE: keep scrolling for an actual example)

package
{
	import flash.display.DisplayObject;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	/**
	 * A camera that we can use to move around our game
	 * @author Damian Connolly
	 */
	public class Camera implements IUpdateObj
	{
		
		/*******************************************************************************************/
		
		/**
		 * The max move velocity that we can have
		 */
		public var maxMoveVel:Number = 50.0;
		
		/**
		 * The max zoom velocity that we can have
		 */
		public var maxZoomVel:Number = 0.5;
		
		/**
		 * Should we snap to the nearest pixel when moving our objects?
		 */
		public var shouldPixelSnap:Boolean = true;
		
		/**
		 * How much to decelerate the move velocity by, every frame
		 */
		public var moveVelDecel:Number = 0.9;
		
		/**
		 * How much to decelerate the zoom velocity by, every frame
		 */
		public var zoomVelDecel:Number = 0.8;
		
		/**
		 * The cutoff for the move velocity, before we set it to 0.0
		 */
		public var moveVelCutoff:Number	= 1.0;
		
		/**
		 * The cutoff for the zoom velocity, before we set it to 0.0
		 */
		private var zoomVelCutoff:Number = 0.05;
		
		/*******************************************************************************************/
		
		private var m_displayRect:Rectangle				= null; // our display rect; x, y, width and height
		private var m_viewRect:Rectangle				= null;	// our view rect, in layer coords (used for child visibility)
		private var m_viewPoint:Point					= null;	// a helper point, when calculating the view rect
		private var m_clickPoint:Point					= null; // a helper point, when clicking the layers
		private var m_layers:Vector.<CameraLayerRef>	= null;	// the references to the layers that we're rendering
		private var m_bounds:Rectangle					= null;	// the bounds for the camera
		private var m_cameraPos:Point					= null;	// the camera center position
		private var m_moveVel:Point						= null;	// the camera velocity
		private var m_isActive:Boolean					= false;// is the camera active?
		private var m_currZoom:Number					= 1.0;	// our current zoom
		private var m_currZoomLog:Number				= 0.0;	// our current zoom (in our log scale)
		private var m_zoomBoundsLog:Bounds				= null;	// our bounds for our zoom (in our log scale)
		private var m_zoomVel:Number					= 0.0;	// our zoom velocity
		
		/*******************************************************************************************/
		
		/**
		 * Returns true if the camera is active
		 */
		[Inline] public final function get active():Boolean { return true; }
		
		/**
		 * The camera's display x position
		 */
		[Inline] public final function get x():Number { return this.m_displayRect.x; }
		[Inline] public final function set x( n:Number ):void
		{
			this.m_displayRect.x 	= n;
			this.m_isActive			= true;
		}
		
		/**
		 * The camera's display y position
		 */
		[Inline] public final function get y():Number { return this.m_displayRect.y; }
		[Inline] public final function set y( n:Number ):void
		{
			this.m_displayRect.y 	= n;
			this.m_isActive			= true;
		}
		
		/**
		 * The camera's display width
		 */
		[Inline] public final function get width():Number { return this.m_displayRect.width; }
		[Inline] public final function set width( n:Number ):void
		{
			this.m_displayRect.width 	= ( n < 0.0 ) ? 0.0 : n;
			this.m_isActive				= true;
		}
		
		/**
		 * The camera's display height
		 */
		[Inline] public final function get height():Number { return this.m_displayRect.height; }
		[Inline] public final function set height( n:Number ):void
		{
			this.m_displayRect.height 	= ( n < 0.0 ) ? 0.0 : n;
			this.m_isActive				= true;
		}
		
		/**
		 * The camera's center x position
		 */
		[Inline] public final function get cameraX():Number { return this.m_cameraPos.x; }
		[Inline] public final function set cameraX( n:Number ):void
		{
			this.m_cameraPos.x	= n;
			this.m_isActive		= true;
			
			// stop our camera so we don't keep moving
			this.stop();
		}
		
		/**
		 * The camera's center y position
		 */
		[Inline] public final function get cameraY():Number { return this.m_cameraPos.y; }
		[Inline] public final function set cameraY( n:Number ):void
		{
			this.m_cameraPos.y 	= n;
			this.m_isActive		= true;
			
			// stop our camera so we don't keep moving
			this.stop();
		}
		
		/**
		 * The current zoom level of the camera
		 */
		[Inline] public final function get zoom():Number { return this.m_currZoom; }
		[Inline] public final function set zoom( n:Number ):void
		{
			var logN:Number		= Math.log( ( n < 0.1 ) ? 0.1 : n );
			this.m_currZoomLog	= ( logN < this.m_zoomBoundsLog.min ) ? this.m_zoomBoundsLog.min : ( logN > this.m_zoomBoundsLog.max ) ? this.m_zoomBoundsLog.max : logN;
			this.m_currZoom		= Math.exp( this.m_currZoomLog );
			this.m_isActive		= true;
			
			// kill our velocity so we don't keep zooming
			this.m_zoomVel = 0.0;
		}
		
		/**
		 * The min zoom level of the camera. When set, this will clamp to maxZoom if high enough
		 */
		[Inline] public final function get minZoom():Number { return Math.exp( this.m_zoomBoundsLog.min ); }
		[Inline] public final function set minZoom( n:Number ):void
		{
			this.m_zoomBoundsLog.min	= Math.log( ( n < 0.1 ) ? 0.1 : n );
			this.m_isActive				= true;
		}
		
		/**
		 * The max zoom level of the camera. When set, this will clamp to the minZoom if low enough
		 */
		[Inline] public final function get maxZoom():Number { return Math.exp( this.m_zoomBoundsLog.max ); }
		[Inline] public final function set maxZoom( n:Number ):void
		{
			this.m_zoomBoundsLog.max	= Math.log( n );
			this.m_isActive				= true;
		}
		
		/*******************************************************************************************/
		
		/**
		 * Creates a new Camera
		 * @param width The camera's display width
		 * @param height The camera's display height
		 */
		public function Camera( width:Number, height:Number ) 
		{
			// create our objects
			this.m_displayRect 		= new Rectangle;
			this.m_viewRect			= new Rectangle;
			this.m_viewPoint		= new Point;
			this.m_clickPoint		= new Point;
			this.m_bounds			= new Rectangle;
			this.m_cameraPos		= new Point;
			this.m_moveVel			= new Point;
			this.m_layers			= new Vector.<CameraLayerRef>;
			this.m_zoomBoundsLog	= new Bounds( Math.log( 0.1 ), Math.log( 10.0 ) );
			this.m_currZoomLog		= Math.log( this.m_currZoom );
			
			// set our display width and height
			this.width 	= width;
			this.height	= height;
		}
		
		/**
		 * Destroys the Camera and clears it for garbage collection
		 */
		public function destroy():void
		{
			// update - NOTE: it still needs to be removed from the Update class
			this.m_isActive = false;
			
			// clear our vector
			for each( var ref:CameraLayerRef in this.m_layers )
				ref.destroy();
			this.m_layers.length = 0;
			
			// null our properties
			this.m_bounds			= null;
			this.m_cameraPos		= null;
			this.m_moveVel			= null;
			this.m_displayRect		= null;
			this.m_viewRect			= null;
			this.m_viewPoint		= null;
			this.m_clickPoint		= null;
			this.m_layers			= null;
			this.m_zoomBoundsLog	= null;
		}
		
		/**
		 * Adds a layer to be controlled by the camera
		 * @param layer The ICameraLayer that we want the camera to control
		 * @param speedFactor The speed factor for this object (e.g. for implementing parallax)
		 * @param offset The offset for this ICameraLayer; used in the final positioning. If null, then
		 * no offset is used, and when the cameraX/camerY is 0, the (0,0) of the ICameraLayer will correspond
		 * with the center of the camera
		 */
		public function addLayerToControl( layer:ICameraLayer, speedFactor:Number = 1.0, offset:Point = null ):void
		{
			var ref:CameraLayerRef 	= new CameraLayerRef( layer );
			ref.speedFactor			= speedFactor;
			ref.origScale.x			= layer.scaleX;
			ref.origScale.y			= layer.scaleY;
			if ( offset != null )
				ref.offset = offset;
			this.m_layers.push( ref );
			this.m_isActive = true;
		}
		
		/**
		 * Removes a layer from the camera's control
		 * @param layer The ICameraLayer to remove from our camera
		 */
		public function removeLayerFromControl( layer:ICameraLayer ):void
		{
			for ( var i:int = this.m_layers.length - 1; i >= 0; i-- )
			{
				if ( this.m_layers[i].layer == layer )
				{
					this.m_layers[i].destroy();
					this.m_layers.splice( i, 1 );
					
					// if this is the last object, kill our velocity
					if ( this.m_layers.length == 0 )
						this.stop();
						
					// activate to update
					this.m_isActive = true;
					return;
				}
			}
			trace( "[Camera] Can't remove " + layer + " from the camera, as we're not controlling it" );
		}
		
		/**
		 * Sets the offset for one of the layers under the camera's control. This offset
		 * will be used when positioning the layer
		 * @param layer The ICameraLayer under the camera's control
		 * @param offset The offset for this layer
		 */
		public function setLayerOffset( layer:ICameraLayer, offset:Point ):void
		{
			// set the offset on the right objects
			for each( var ref:CameraLayerRef in this.m_layers )
			{
				if ( ref.layer == layer )
				{
					ref.offset 		= offset;
					this.m_isActive	= true;
					return;
				}
			}
			
			// we don't have it
			trace( "[Camera] Can't set the offset for " + layer + ", as we're not controlling it" );
		}
		
		/**
		 * Moves the camera center position to a specific position
		 * @param x The x position to move to
		 * @param y The y position to move to
		 */
		public function moveCameraTo( x:Number, y:Number ):void
		{
			this.cameraX = x;
			this.cameraY = y;
		}
		
		/**
		 * Moves the camera center position by a specific amount
		 * @param x The x amount to move by
		 * @param y The y amount to move by
		 */
		public function moveCameraBy( x:Number, y:Number ):void
		{
			this.cameraX += x;
			this.cameraY += y;
		}
		
		/**
		 * Sets the move velocity of the camera to a specific amount
		 * @param x The camera move x velocity
		 * @param y The camera move y velocity
		 */
		public function setMoveVelocityTo( x:Number, y:Number ):void
		{
			// set it and clamp if necessary
			this.m_moveVel.setTo( x, y );
			if ( this.m_moveVel.length > this.maxMoveVel )
				this.m_moveVel.normalize( this.maxMoveVel );
			this.m_isActive = true;
		}
		
		/**
		 * Sets the camera move velocity by a specific amount
		 * @param x The camera move x velocity difference
		 * @param y The camera move y velocity difference
		 */
		public function setMoveVelocityBy( x:Number, y:Number ):void
		{
			this.setMoveVelocityTo( this.m_moveVel.x + x, this.m_moveVel.y + y );
		}
		
		/**
		 * Sets the camera zoom using a logarithmic scale - this will give smoother results
		 * than just setting the zoom directly
		 * @param n The amount that we want to zoom the camera by
		 */
		public function zoomCameraLogarithmicallyBy( n:Number ):void
		{				
			// set our current log zoom, clear our zoom velocity and set that we're active
			this.m_currZoomLog += n;
			this.m_zoomVel		= 0.0; // no velocity
			this.m_isActive		= true;
		}
		
		/**
		 * Sets the camera zoom velocity to a specific amount
		 * @param n The camera zoom velocity
		 */
		public function setZoomVelocityTo( n:Number ):void
		{
			this.m_zoomVel 	= ( n < -this.maxZoomVel ) ? -this.maxZoomVel : ( n > this.maxZoomVel ) ? this.maxZoomVel : n;
			this.m_isActive	= true;
		}
		
		/**
		 * Set the camera zoom velocity by a specific amount
		 * @param n The camera zoom velocity difference
		 */
		public function setZoomVelocityBy( n:Number ):void
		{
			this.setZoomVelocityTo( this.m_zoomVel + n );
		}
		
		/**
		 * Stops the camera from moving and zooming
		 */
		public function stop():void
		{
			// NOTE: don't set active to false as we might have been stopped because we set the
			// camera position directly, and we still need it to update
			// NOTE: don't kill the zoomVel as stop() is called by the cameraX/cameraY setters,
			// which are used in some controllers (moveTo()), such as the follow controller. If
			// we zero out the zoomVel, then we won't be able to zoom while the camera is
			// tracking an object
			this.m_moveVel.setTo( 0.0, 0.0 );
		}
		
		/**
		 * Sets the bounds for the camera
		 * @param x The x bounds for the camera
		 * @param y The y bounds for the camera
		 * @param w The width for the bounds
		 * @param h The height for the bounds
		 */
		public function setBounds( x:Number, y:Number, w:Number, h:Number ):void
		{
			// make sure our width and height are good
			w = ( w < 0 ) ? 0 : w;
			h = ( h < 0 ) ? 0 : h;
			
			// set our bounds
			this.m_bounds.setTo( x, y, w, h );
			this.m_isActive = true;
		}
		
		/**
		 * Goes through and clicks each of the layers that we control
		 * @param stageX The x position of the click, in global space
		 * @param stageY The y position of the click, in global space
		 */
		public function onClick( stageX:Number, stageY:Number ):void
		{
			// stop the camera from moving
			this.stop();
			
			// notify our layers
			this.m_clickPoint.x	= stageX;
			this.m_clickPoint.y	= stageY;
			for each( var ref:CameraLayerRef in this.m_layers )
			{
				var localPos:Point = ref.layer.globalToLocal( this.m_clickPoint );
				ref.layer.onClick( localPos.x, localPos.y );
			}
		}
		
		/**
		 * Updates the Camera so that we render our view objects etc
		 * @param dt The delta time since the last update
		 */
		public function update( dt:Number ):void
		{
			// only update the camera position etc if we're active
			if ( this.m_isActive )
			{	
				// update our zoom based on our vel and clamp it.
				// NOTE: as scale is logarithmic, we're using Math.log() and Math.exp()
				// to get the final value
				this.m_currZoomLog += this.m_zoomVel;
				if ( this.m_currZoomLog < this.m_zoomBoundsLog.min )
				{
					this.m_currZoomLog 	= this.m_zoomBoundsLog.min;
					this.m_zoomVel		= 0.0;
				}
				else if ( this.m_currZoomLog > this.m_zoomBoundsLog.max )
				{
					this.m_currZoomLog	= this.m_zoomBoundsLog.max;
					this.m_zoomVel		= 0.0;
				}
				this.m_currZoom = Math.exp( this.m_currZoomLog );
				
				// update our position
				this.m_cameraPos.x += this.m_moveVel.x;
				this.m_cameraPos.y += this.m_moveVel.y;
				
				// clamp it to our bounds
				if ( this.m_bounds.width > 0 || this.m_bounds.height > 0 )
				{
					var invZoom:Number	= ( this.m_currZoom == 0.0 ) ? 1.0 : 1.0 / this.m_currZoom;
					var hw:Number 		= this.m_displayRect.width * 0.5 * invZoom;
					var hh:Number 		= this.m_displayRect.height * 0.5 * invZoom;
					
					// horizontal
					if ( this.m_cameraPos.x < this.m_bounds.x + hw )
					{
						this.m_cameraPos.x 	= this.m_bounds.x + hw;
						this.m_moveVel.x	= 0.0; // stop moving
					}
					else if ( this.m_cameraPos.x > this.m_bounds.x + this.m_bounds.width - hw )
					{
						this.m_cameraPos.x 	= this.m_bounds.x + this.m_bounds.width - hw;
						this.m_moveVel.x	= 0.0; // stop moving
					}
						
					// vertical
					if ( this.m_cameraPos.y < this.m_bounds.y + hh )
					{
						this.m_cameraPos.y 	= this.m_bounds.y + hh;
						this.m_moveVel.y	= 0.0; // stop moving
					}
					else if ( this.m_cameraPos.y > this.m_bounds.y + this.m_bounds.height - hh )
					{
						this.m_cameraPos.y 	= this.m_bounds.y + this.m_bounds.height - hh;
						this.m_moveVel.y	= 0.0; // stop moving
					}
				}
				
				// update all our controlled objects
				for each( var ref:CameraLayerRef in this.m_layers )
				{
					// update the zoom
					var zoom:Number		= ( ref.layer.isZoomEnabled ) ? this.m_currZoom : 1.0;
					var scaleX:Number 	= zoom * ref.origScale.x;
					var scaleY:Number 	= zoom * ref.origScale.y;
					if ( ref.layer.isZoomEnabled && ( ref.layer.scaleX != scaleX || ref.layer.scaleY != scaleY ) )
					{
						ref.layer.scaleX = scaleX;
						ref.layer.scaleY = scaleY;
					}
						
					// NOTE: the camera position is inversed, because, as the camera moves right, the object should move left
					var x:Number 	= this.m_displayRect.x + ( this.m_displayRect.width * 0.5 ) - ( this.m_cameraPos.x * ref.speedFactor * scaleX ) + ( ref.offset.x * scaleX );
					var y:Number 	= this.m_displayRect.y + ( this.m_displayRect.height * 0.5 ) - ( this.m_cameraPos.y * ref.speedFactor * scaleY ) + ( ref.offset.y * scaleY );
					ref.layer.x		= ( this.shouldPixelSnap ) ? Math.round( x ) : x;
					ref.layer.y		= ( this.shouldPixelSnap ) ? Math.round( y ) : y;
				}
				
				// slow down
				this.m_moveVel.x 	*= this.moveVelDecel;
				this.m_moveVel.y 	*= this.moveVelDecel;
				this.m_zoomVel		*= this.zoomVelDecel;
				if ( this.m_moveVel.x < this.moveVelCutoff && this.m_moveVel.x > -this.moveVelCutoff )
					this.m_moveVel.x = 0.0;
				if ( this.m_moveVel.y < this.moveVelCutoff && this.m_moveVel.y > -this.moveVelCutoff )
					this.m_moveVel.y = 0.0;
				if ( this.m_zoomVel < this.zoomVelCutoff && this.m_zoomVel >- this.zoomVelCutoff )
					this.m_zoomVel = 0.0;
			}
			
			// we need to update the children visibility and sort on our layers every frame, as even if
			// we're not moving, they could be
			this.m_viewPoint.x 	= this.m_displayRect.x;
			this.m_viewPoint.y 	= this.m_displayRect.y;
			for each( ref in this.m_layers )
			{
				// get our view rect in local space
				var localPos:Point	= ref.layer.globalToLocal( this.m_viewPoint );
				this.m_viewRect.x	= localPos.x;
				this.m_viewRect.y	= localPos.y;
				
				// get our view rect size
				var invScaleX:Number 	= ( this.m_currZoom == 0.0 || ref.origScale.x == 0.0 ) ? 1.0 : 1.0 / ( this.m_currZoom * ref.origScale.x );
				var invScaleY:Number	= ( this.m_currZoom == 0.0 || ref.origScale.y == 0.0 ) ? 1.0 : 1.0 / ( this.m_currZoom * ref.origScale.y );
				this.m_viewRect.width	= this.m_displayRect.width * invScaleX;
				this.m_viewRect.height	= this.m_displayRect.height * invScaleY;
				
				// update children visibility
				ref.layer.checkChildrenVisibility( this.m_viewRect );
				
				// sort them
				ref.layer.sortChildren( this._sortChildren );
			}
				
			// if our velocity is zero, stop update
			if ( this.m_moveVel.x == 0.0 && this.m_moveVel.y == 0.0 && this.m_zoomVel == 0.0 )
				this.m_isActive = false;
		}
		
		/*******************************************************************************************/
		
		// the function we use to sort all the children in a layer
		private function _sortChildren( a:DisplayObject, b:DisplayObject ):int
		{
			return ( ( a.x + a.y ) < ( b.x + b.y ) ) ? -1 : 1;
		}
		
	}

}

Example

To see what all this produces, this is an example of the classes in action. The stars and planets make up different layers, and the camera moves/zooms randomly. In the next parts in this series, we'll get around to controlling the camera ourselves. Click on the stage to reset the camera to the center.

An example of the camera in action

Colony mailing list

If you'd like to hear more about Colony, both game and tech updates, you can sign up for the mailing list below. I promise I won't do anything naughty with your email address.

* indicates required

Files

Comments

Submit a comment

* indicates required