divillysausages.com

Colony tech update 1: Creating a game camera, part 3, controlling using the keyboard

In the second part of this series we saw the base CameraControls class. Now to actually put it to good use. First up: controlling the camera using the keyboard.

Handling input

Basically we want our camera to start moving when we press a key, and stop moving when we release it (ya duh). To help with this, I've made use of a class to encapsulate all of that messy event stuff. The Input class handles key and mouse input (which we'll use later for the mouse-controlled Control classes).

For keys, we keep a Vector of Booleans, large enough to handle all the keys that we want to track. Then, when a key is pressed, we set the right index to true and when it's released, reset it to false. Then, to check if a key is down, we simply check the Vector. The gist of the code is this:

// key being the keyCode of the Key that we want to check
public function isPressed( key:uint ):Boolean
{
	return ( key < this.m_keys.length ) ? this.m_keys[key] : false;
}

// called when a key is pressed
private function _onKeyDown( e:KeyboardEvent ):void
{
	if ( e.keyCode < this.m_keys.length )
		this.m_keys[e.keyCode] = true;
}

// called when a key is down
private function _onKeyUp( e:KeyboardEvent ):void
{
	if ( e.keyCode < this.m_keys.length )
		this.m_keys[e.keyCode] = false;
}

For mouse input, we simply keep track of the stage position of the mouse, and if the left-mouse button is currently pressed.

The CameraKeyControls class

We define four keys for movement: in this example, up, down, left, and right; and two for zooming in and out: PgUp and PgDown. Every update, we check if these keys are pressed. For the movement keys, it gives us a vector (i.e. dir = (right - left, down - up)). We have to normalise this so that it's length is 1.0, otherwise we'll end up going faster if we move the camera diagonally (e.g. up 1 pixel and right 1 pixel for a total distance of 1.414 pixels)

After that, depending on if we're moving using velocity or not, we call either the camera's setMoveVelocityBy() or moveCameraBy() method. We use the moveSpeed and other properties that we saw in the base CameraControls class.

Zooming works exactly the same, except that we work with a single value, or scalar, rather than a vector.

Using multiple controllers

While it's not much of a problem yet, you'll probably end up using multiple controllers for the same camera - e.g. you might want to be able to drag the camera with the mouse as well as use the keyboard. To get them all playing nicely with each other, we need to notify when one controller is active, and have a system of priorities, e.g. mouse > keyboard > following an object > wishing & hoping.

For the CameraKeyControls we simply dispatch a Signal when we're active. That way, we can deactivate any of the other controllers currently active. Remember, in the base CameraControls class, if active is false, then update() isn't called.

The code

Don't forget to add the class to your update loop. You can also download it below. Scroll on for an example.

package
{
	import flash.display.Stage;
	import flash.ui.Keyboard;
	import org.osflash.signals.Signal;
	
	/**
	 * Controls a Camera using key controls
	 * @author Damian Connolly
	 */
	public class CameraKeyControls extends CameraControls
	{
		
		/*******************************************************************************************/
		
		/**
		 * The signal dispatched when we start moving the camera with the keys. It should take no
		 * parameters. This is useful if you're using these controls with other ones, such as
		 * CameraFollowControls, where you can disable those while you're dragging
		 */
		public var signalOnStartedMoving:Signal = null;
		
		/*******************************************************************************************/
		
		private var m_hasFiredStartSignal:Boolean 	= false;// have we fired our signalOnStartedMoving signal?
		private var m_input:Input					= null; // the input object that keeps track of our keys
		
		/*******************************************************************************************/
		
		/**
		 * Creates a new controller for a camera
		 * @param camera The camera that we're going to control
		 * @param stage The main stage
		 * @param input The input object that keeps track of our keys
		 */
		public function CameraKeyControls( camera:Camera, stage:Stage, input:Input ) 
		{
			super( camera, stage );
			this.m_input				= input;
			this.signalOnStartedMoving 	= new Signal;
		}
		
		/**
		 * Destroys the CameraKeyControls and clears it for garbage collection
		 */
		override public function destroy():void 
		{
			super.destroy();
			this.signalOnStartedMoving.removeAll();
			this.signalOnStartedMoving 	= null;
			this.m_input				= null;
		}
		
		/**
		 * Called every frame the CameraKeyControls are active
		 * @param dt The delta time since the last update
		 */
		override public function update( dt:Number ):void
		{
			// get our xdir and ydir based on what keys are pressed
			if ( this.m_input.isPressed( Keyboard.LEFT  ) )
				this.m_moveDir.x = -1.0;
			else if ( this.m_input.isPressed( Keyboard.RIGHT ) )
				this.m_moveDir.x = 1.0;
			else
				this.m_moveDir.x = 0.0;
			if ( this.m_input.isPressed( Keyboard.UP ) )
				this.m_moveDir.y = -1.0;
			else if ( this.m_input.isPressed( Keyboard.DOWN ) )
				this.m_moveDir.y = 1.0;
			else
				this.m_moveDir.y = 0.0;
				
			// get the zoom
			var zoomDir:int = 0;
			if ( this.m_input.isPressed( Keyboard.PAGE_UP ) )
				zoomDir = 1;
			else if ( this.m_input.isPressed( Keyboard.PAGE_DOWN ) )
				zoomDir = -1;
				
			// move the camera is needed
			if ( this.m_moveDir.x != 0.0 || this.m_moveDir.y != 0.0 )
			{
				// normalise our dir if we're moving diagonally, otherwise we'll move quicker
				if ( this.m_moveDir.x != 0.0 && this.m_moveDir.y != 0.0 )
					this.m_moveDir.normalize( 1.0 );
					
				// check if we should compensate for zoom
				var zoomComp:Number	= ( this.shouldMoveCompenstateForZoom ) ? 1.0 / this.m_camera.zoom : 1.0;
				if( zoomComp != 1.0 )
				{
					this.m_moveDir.x *= zoomComp;
					this.m_moveDir.y *= zoomComp;
				}
					
				// either move directly, or with velocity
				if( this.shouldMoveWithVelocity )
					this.m_camera.setMoveVelocityBy( this.m_moveDir.x * this.moveSpeed * dt, this.m_moveDir.y * this.moveSpeed * dt );
				else
					this.m_camera.moveCameraBy( this.m_moveDir.x * this.moveSpeed * dt, this.m_moveDir.y * this.moveSpeed * dt );
				
				// dispatch our signal if we haven't already
				if ( !this.m_hasFiredStartSignal )
				{
					this.signalOnStartedMoving.dispatch();
					this.m_hasFiredStartSignal = true;
				}
			}
			else
				this.m_hasFiredStartSignal = false;
				
			// scale if needed
			if ( zoomDir != 0 )
			{
				if( this.shouldZoomWithVelocity )
					this.m_camera.setZoomVelocityBy( zoomDir * this.zoomSpeed * dt );
				else
					this.m_camera.zoomCameraLogarithmicallyBy( zoomDir * this.zoomSpeed * dt );
			}
		}
		
	}

}

Example

The CameraKeyControls class in action! Click on the stage to give it focus, and any time after that to reset the camera to the center. Up, down, left, and right control the movement, while PgUp and PgDown control the zoom.

An example of controlling the camera using the keyboard in action

Colony mailing list

This code is brought to you by Colony. Mailing list plug goes here.

* indicates required

Files

Comments

Submit a comment

* indicates required