#region File Description
//-----------------------------------------------------------------------------
// InputManager.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
#endregion
namespace InputSequenceSample
{
class InputManager
{
public PlayerIndex PlayerIndex { get; private set; }
public GamePadState GamePadState { get; private set; }
public KeyboardState KeyboardState { get; private set; }
///
/// The last "real time" that new input was received. Slightly late button
/// presses will not update this time; they are merged with previous input.
///
public TimeSpan LastInputTime { get; private set; }
///
/// The current sequence of pressed buttons.
///
public List Buffer;
///
/// This is how long to wait for input before all input data is expired.
/// This prevents the player from performing half of a move, waiting, then
/// performing the rest of the move after they forgot about the first half.
///
public readonly TimeSpan BufferTimeOut = TimeSpan.FromMilliseconds(500);
///
/// This is the size of the "merge window" for combining button presses that
/// occur at almsot the same time.
/// If it is too small, players will find it difficult to perform moves which
/// require pressing several buttons simultaneously.
/// If it is too large, players will find it difficult to perform moves which
/// require pressing several buttons in sequence.
///
public readonly TimeSpan MergeInputTime = TimeSpan.FromMilliseconds(100);
///
/// Provides the map of non-direction game pad buttons to keyboard keys.
///
internal static readonly Dictionary NonDirectionButtons =
new Dictionary
{
{ Buttons.A, Keys.A },
{ Buttons.B, Keys.B },
{ Buttons.X, Keys.X },
{ Buttons.Y, Keys.Y },
// Other available non-direction buttons:
// Start, Back, LeftShoulder, LeftTrigger, LeftStick,
// RightShoulder, RightTrigger, and RightStick.
};
public InputManager(PlayerIndex playerIndex, int bufferSize)
{
PlayerIndex = playerIndex;
Buffer = new List(bufferSize);
}
///
/// Gets the latest input and uses it to update the input history buffer.
///
public void Update(GameTime gameTime)
{
// Get latest input state.
GamePadState lastGamePadState = GamePadState;
KeyboardState lastKeyboardState = KeyboardState;
GamePadState = GamePad.GetState(PlayerIndex);
#if WINDOWS
if (PlayerIndex == PlayerIndex.One)
{
KeyboardState = Keyboard.GetState(PlayerIndex);
}
#endif
// Expire old input.
TimeSpan time = gameTime.TotalGameTime;
TimeSpan timeSinceLast = time - LastInputTime;
if (timeSinceLast > BufferTimeOut)
{
Buffer.Clear();
}
// Get all of the non-direction buttons pressed.
Buttons buttons = 0;
foreach (var buttonAndKey in NonDirectionButtons)
{
Buttons button = buttonAndKey.Key;
Keys key = buttonAndKey.Value;
// Check the game pad and keyboard for presses.
if ((lastGamePadState.IsButtonUp(button) &&
GamePadState.IsButtonDown(button)) ||
(lastKeyboardState.IsKeyUp(key) &&
KeyboardState.IsKeyDown(key)))
{
// Use a bitwise-or to accumulate button presses.
buttons |= button;
}
}
// It is very hard to press two buttons on exactly the same frame.
// If they are close enough, consider them pressed at the same time.
bool mergeInput = (Buffer.Count > 0 && timeSinceLast < MergeInputTime);
// If there is a new direction,
var direction = Direction.FromInput(GamePadState, KeyboardState);
if (Direction.FromInput(lastGamePadState, lastKeyboardState) != direction)
{
// combine the direction with the buttons.
buttons |= direction;
// Don't merge two different directions. This avoids having impossible
// directions such as Left+Up+Right. This also has the side effect that
// the direction needs to be pressed at the same time or slightly before
// the buttons for merging to work.
mergeInput = false;
}
// If there was any new input on this update, add it to the buffer.
if (buttons != 0)
{
if (mergeInput)
{
// Use a bitwise-or to merge with the previous input.
// LastInputTime isn't updated to prevent extending the merge window.
Buffer[Buffer.Count - 1] = Buffer[Buffer.Count - 1] | buttons;
}
else
{
// Append this input to the buffer, expiring old input if necessary.
if (Buffer.Count == Buffer.Capacity)
{
Buffer.RemoveAt(0);
}
Buffer.Add(buttons);
// Record this the time of this input to begin the merge window.
LastInputTime = time;
}
}
}
///
/// Determines if a move matches the current input history. Unless the move is
/// a sub-move, the history is "consumed" to prevent it from matching twice.
///
/// True if the move matches the input history.
public bool Matches(Move move)
{
// If the move is longer than the buffer, it can't possibly match.
if (Buffer.Count < move.Sequence.Length)
return false;
// Loop backwards to match against the most recent input.
for (int i = 1; i <= move.Sequence.Length; ++i)
{
if (Buffer[Buffer.Count - i] != move.Sequence[move.Sequence.Length - i])
{
return false;
}
}
// Rnless this move is a component of a larger sequence,
if (!move.IsSubMove)
{
// consume the used inputs.
Buffer.Clear();
}
return true;
}
}
}