Putting the IGameActionManager interface to work

Things have been pretty hectic for me lately. I have 3 week old twins and barely enough time to do much of anything for myself. My coding comes in bits and pieces here and there, but I have managed to make some strides on a game I have been working on. I am not ready to announce anything regarding it yet as it’s still just a prototype, but if it starts coming together to a point where I think it will be seen to fruition, I will definitely share.

To the topic at hand!

I posted some time ago prior to the Windows Phone 7 initial release about an idea I had for managing input between devices, and simplifying input management. Here is the post. I knew that the theory was sound, but I recently got to put it to the test. I have been developing a game prototype as a Windows project, knowing that XNA is cross platform. The plan has always been to release the game for the Windows Phone primarily, but I want to be able to run and debug on the PC for simplicity, so I have been doing so with the hope that some time I would port the thing to WP7. Recently I did just that, and that really gave my IGameActionManager abstraction a workout. I believe I have finally ironed out the best pattern to mimic the previous/current state of the KeyboardState and MouseState objects while keeping the game code focused, simple, and unencumbered by platform specific concerns.

I submit to you the results:

The interface becomes very simple:

public interface IGameActionManager
{
	GameActionState GetState();
}

Notice how it returns a new class GameActionState. That class is a read only data object that defines the state of input at any given frame. Here it is:

public class GameActionState
{
	public GameActionState(bool isJumping, 
	Vector2 motion, 
	Vector2? cameraVelocity,
	bool isIdle, 
	bool isFiring, 
	Vector2? screenSelectionType1, 
	Vector2? screenSelectionType2, 
	bool isQuitting, float zoomChange,
		Point? indicatorLocation)
	{
		_isJumping = isJumping;
		_motion = motion;
		_cameraVelocity = cameraVelocity;
		_isIdle = isIdle;
		_isFiring = isFiring;
		_screenSelectionType1 = screenSelectionType1;
		_screenSelectionType2 = screenSelectionType2;
		_isQuitting = isQuitting;
		_zoomChange = zoomChange;
		_indicatorLocation = indicatorLocation;
	}

	private Point? _indicatorLocation;
	public Point? IndicatorLocation
	{
		get
		{
			return _indicatorLocation;
		}
	}

	private float _zoomChange;
	public float ZoomChange
	{
		get
		{
			return _zoomChange;
		}
	}

	private bool _isJumping;
	public bool IsJumping
	{
		get
		{
			return _isJumping;
		}
	}

	private Vector2 _motion;
	public Vector2 Motion
	{
		get
		{
			return _motion;
		}
	}

	private bool _isIdle;
	public bool IsIdle
	{
		get
		{
			return _isIdle;
		}
	}

	private bool _isFiring;
	public bool IsFiring
	{
		get
		{
			return _isFiring;
		}
	}

	private Vector2? _screenSelectionType1;
	public Vector2? ScreenSelectionType1
	{
		get
		{
			return _screenSelectionType1;
		}
	}

	private Vector2? _screenSelectionType2;
	public Vector2? ScreenSelectionType2
	{
		get
		{
			return _screenSelectionType2;
		}
	}

	private bool _isQuitting;
	public bool IsQuitting
	{
		get
		{
			return _isQuitting;
		}
	}

	private Vector2? _cameraVelocity;
	public Vector2? CameraVelocity
	{
		get
		{
			return _cameraVelocity;
		}
	}
}

I made note of the fact that I used digital rather than analog values for some fields like IsMovingUp, IsJumping, etc, where it might be beneficial to use their analog equivalent. Here I have illustrated a partial change to that mindset.

Notice the Vector2 returned for the Motion property.. Basically, the thought here is that in a single Vector, I can return the direction and magnitude of motion instead of relying on some speed modifier and 1 dimensional digital booleans. In the concrete implementations (GameActionManagerWindows and GameActionManagerWindowsPhone), you can use anything to specify this value. For instance, as you will see in my implementations later in this post, I use the directional arrows on the keyboard for the motion vector, but in the phone implementaion I poll for the the FreeDrag GestureType. The important thing to note here is that the game code doesn’t care what you use. It just grabs the motion property off the GameActionState and uses it, oblivious to where that value came from (Side note: I always find it interesting how we talk about code as if it thinks or has a personality. I have seen this from so many developers, myself included and it’s somewhat fascinating to me.)

The other things I want to point out before moving on to pasting the implementations for windows and wp7 are these two properties:

private Vector2? _screenSelectionType1;
public Vector2? ScreenSelectionType1
{
	get
	{
		return _screenSelectionType1;
	}
}

private Vector2? _screenSelectionType2;
public Vector2? ScreenSelectionType2
{
	get
	{
		return _screenSelectionType2;
	}
}

Notice the Vector2? (aka Nullable) return type. That allows me to specify to the Game basically two different states for the game action: A selection was made (with coordinates) or no selection was made (null). In an otherwise non nullable value type (struct), Nullableis a great way to virtually turn that into a type which can have null assignments. It’s often useful in certain situations such as this where specifying some special value for the actual Vector2 wouldn’t help, because it’s also valid input (i.e. I can’t just use Vector.Zero as a way to specify that no selection was made, because Vector.Zero is a completely valid selection). Vector2(-1, -1) also just seemed strange to use since I’m not really sure what the coordinate system should look like, and there is the perfectly viable Nullable<> wrapper to use, so why not!

On to the implementations!

Windows/PC:

public class GameActionManagerWindows : IGameActionManager
{
	public GameActionState GetState()
	{
		KeyboardState keyboardState = Keyboard.GetState();
		MouseState mouseState = Mouse.GetState();
		Vector2? selectionType1 = null;
		Vector2? selectionType2 = null;

		if (mouseState.LeftButton == ButtonState.Pressed)
		{
			selectionType1 = new Vector2(mouseState.X, mouseState.Y);
		}

		if (mouseState.RightButton == ButtonState.Pressed)
		{
			selectionType2 = new Vector2(mouseState.X, mouseState.Y);
		}

		bool isJumping = keyboardState.IsKeyDown(Keys.Space);
		Vector2 motion = Vector2.Zero;

		if (keyboardState.IsKeyDown(Keys.Right))
		{
			motion.X += 1.0f;
		}
		if (keyboardState.IsKeyDown(Keys.Left))
		{
			motion.X -= 1.0f;
		}
		if (keyboardState.IsKeyDown(Keys.Up))
		{
			motion.Y -= 1.0f;
		}
		if (keyboardState.IsKeyDown(Keys.Down))
		{
			motion.Y += 1.0f;
		}

		bool isFiring = mouseState.RightButton == ButtonState.Pressed;

		bool isIdle = !keyboardState.IsKeyDown(Keys.Up) &&
			!keyboardState.IsKeyDown(Keys.Down) && !keyboardState.IsKeyDown(Keys.Left) &&
			!keyboardState.IsKeyDown(Keys.Right) && !keyboardState.IsKeyDown(Keys.Space);

		bool isQuitting = keyboardState.IsKeyDown(Keys.Escape);

		bool isZoomingIn = keyboardState.IsKeyDown(Keys.OemPlus);
		bool isZoomingOut = keyboardState.IsKeyDown(Keys.OemMinus);

		return new GameActionState(false, motion, Vector2.Zero,
			isIdle, isFiring, selectionType1, selectionType2, isQuitting,
			isZoomingIn ? 1.0f : isZoomingOut ? -1.0f : 0.0f,
			new Point(mouseState.X, mouseState.Y));
	}
}

I chose to use the keyboard plus/minus to zoom, but you could just as easily use the mouse scroll wheel here by polling the MouseState.

The WP7 Implementation:

#if WINDOWS_PHONE
using Microsoft.Xna.Framework.Input.Touch;
#endif

#if WINDOWS_PHONE
    public class GameActionManagerWindowsPhone : IGameActionManager
    {
        Vector2 MaxCameraVelocity = new Vector2(5f, 5f);
        public GameActionState GetState()
        {

            TouchPanel.EnabledGestures =
                GestureType.Hold |
                GestureType.Tap |
                GestureType.DoubleTap |
                GestureType.FreeDrag |
                GestureType.Flick |
                GestureType.Pinch;

            // we use raw touch points for selection, since they are more appropriate
            // for that use than gestures. so we need to get that raw touch data.
            TouchCollection touches = TouchPanel.GetState(); 

            Vector2? selectionType1 = null;
            Vector2? selectionType2 = null;

             // next we handle all of the gestures. since we may have multiple gestures available,
            // we use a loop to read in all of the gestures. this is important to make sure the
            // TouchPanel's queue doesn't get backed up with old data
            float zoomChange = 0.0f;
            Vector2 motion = Vector2.Zero;
            Vector2? cameraVelocity = null;

            while (TouchPanel.IsGestureAvailable)
            {
                // read the next gesture from the queue
                GestureSample gesture = TouchPanel.ReadGesture();

                switch (gesture.GestureType)
                {
                    case GestureType.Pinch:
                        // get the current and previous locations of the two fingers
                        Vector2 a = gesture.Position;
                        Vector2 aOld = gesture.Position - gesture.Delta;
                        Vector2 b = gesture.Position2;
                        Vector2 bOld = gesture.Position2 - gesture.Delta2;

                        // figure out the distance between the current and previous locations
                        float d = Vector2.Distance(a, b);
                        float dOld = Vector2.Distance(aOld, bOld);

                        // calculate the difference between the two and use that to alter the scale
                        zoomChange = (d - dOld) * .015f;

                        // Allow dragging while pinching by taking the average of the two touch points' deltas
                        motion = (gesture.Delta + gesture.Delta2) / 2;
                        break;
                    case GestureType.FreeDrag:
                        motion = gesture.Delta;
                        cameraVelocity = Vector2.Zero;
                        break;
                    case GestureType.Hold:
                        selectionType2 = gesture.Position;
                        break;
                    case GestureType.Tap:
                        selectionType1 = gesture.Position;
                        break;
                    case GestureType.Flick:
                        cameraVelocity = Vector2.Clamp(gesture.Delta, -MaxCameraVelocity, MaxCameraVelocity);
                        break;
                }
            }

            return new GameActionState(false, motion, cameraVelocity, false, false, selectionType1, selectionType2, false, zoomChange, Point.Zero);
        }
    }
#endif

I separated that into two #if blocks since it’s the using statement at the top and also the class itself that contains windows phone specific code. The #if blocks are only there to ensure that the windows project can still compile with the phone specific code in it, since the WINDOWS_PHONE directive will not be defined in that project, and therefore the compiler won’t use that code.

And the grand finale… the Game code in all its simplified glory!

To define the proper GameActionManager, in the Initialize() method of the Game class:

#if WINDOWS_PHONE
            gameActionManager = new GameActionManagerWindowsPhone();
// You could do the else if here for XBOX, but I am not targeting XBLIG yet
#else
            gameActionManager = new GameActionManagerWindows();
#endif

And the Update() method:

        protected override void Update(GameTime gameTime)
        {
            gameActionState = gameActionManager.GetState();

            if (gameActionState.IsQuitting)
            {
                this.Exit();
            }

            updateCamera();
            updateUnits();
            if (bullet.alive)
            {
                bullet.currentPosition += bullet.velocity;
            }
            handleSelections();
            handleFiring();

            previousGameActionState = gameActionState;

            base.Update(gameTime);
        }

private void updateCamera()
        {
            if (camera.Velocity.X != 0.0f || camera.Velocity.Y != 0.0f)
            {
                camera.Update();
            }

            if (gameActionState.CameraVelocity != null)
            {
                camera.Velocity = gameActionState.CameraVelocity.Value;
            }

            camera.Pos -= gameActionState.Motion;
            camera.Zoom += gameActionState.ZoomChange;
        }

Notice particularly the updateCamera() method… Camera is a Camera2D class I used for the 2D camera, which we don’t really need to talk about… maybe I’ll do another post on that, but regardless, the Pos and Zoom properties of the camera object cause transformations on the draw calls which give the appearance of a camera moving around. The gameActionState.Motion property is used, which was defined via polymorphism on the GameActionManager class’ GetState() function override as specified in the Initialize method’s declaration of the GameActionManager variable.

Good stuff… the best part about this whole idea is that because the platform specific bits are abstracted out, the Game code itself isn’t concerned with anything to do with the platform. The Game code is now completely portable across multiple devices, and is future proof!