InputManager.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // InputManager.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Linq;
  13. using Microsoft.Xna.Framework;
  14. using Microsoft.Xna.Framework.Input;
  15. #endregion
  16. namespace InputSequenceSample
  17. {
  18. class InputManager
  19. {
  20. public PlayerIndex PlayerIndex { get; private set; }
  21. public GamePadState GamePadState { get; private set; }
  22. public KeyboardState KeyboardState { get; private set; }
  23. /// <summary>
  24. /// The last "real time" that new input was received. Slightly late button
  25. /// presses will not update this time; they are merged with previous input.
  26. /// </summary>
  27. public TimeSpan LastInputTime { get; private set; }
  28. /// <summary>
  29. /// The current sequence of pressed buttons.
  30. /// </summary>
  31. public List<Buttons> Buffer;
  32. /// <summary>
  33. /// This is how long to wait for input before all input data is expired.
  34. /// This prevents the player from performing half of a move, waiting, then
  35. /// performing the rest of the move after they forgot about the first half.
  36. /// </summary>
  37. public readonly TimeSpan BufferTimeOut = TimeSpan.FromMilliseconds(500);
  38. /// <summary>
  39. /// This is the size of the "merge window" for combining button presses that
  40. /// occur at almsot the same time.
  41. /// If it is too small, players will find it difficult to perform moves which
  42. /// require pressing several buttons simultaneously.
  43. /// If it is too large, players will find it difficult to perform moves which
  44. /// require pressing several buttons in sequence.
  45. /// </summary>
  46. public readonly TimeSpan MergeInputTime = TimeSpan.FromMilliseconds(100);
  47. /// <summary>
  48. /// Provides the map of non-direction game pad buttons to keyboard keys.
  49. /// </summary>
  50. internal static readonly Dictionary<Buttons, Keys> NonDirectionButtons =
  51. new Dictionary<Buttons, Keys>
  52. {
  53. { Buttons.A, Keys.A },
  54. { Buttons.B, Keys.B },
  55. { Buttons.X, Keys.X },
  56. { Buttons.Y, Keys.Y },
  57. // Other available non-direction buttons:
  58. // Start, Back, LeftShoulder, LeftTrigger, LeftStick,
  59. // RightShoulder, RightTrigger, and RightStick.
  60. };
  61. public InputManager(PlayerIndex playerIndex, int bufferSize)
  62. {
  63. PlayerIndex = playerIndex;
  64. Buffer = new List<Buttons>(bufferSize);
  65. }
  66. /// <summary>
  67. /// Gets the latest input and uses it to update the input history buffer.
  68. /// </summary>
  69. public void Update(GameTime gameTime)
  70. {
  71. // Get latest input state.
  72. GamePadState lastGamePadState = GamePadState;
  73. KeyboardState lastKeyboardState = KeyboardState;
  74. GamePadState = GamePad.GetState(PlayerIndex);
  75. #if WINDOWS
  76. if (PlayerIndex == PlayerIndex.One)
  77. {
  78. KeyboardState = Keyboard.GetState(PlayerIndex);
  79. }
  80. #endif
  81. // Expire old input.
  82. TimeSpan time = gameTime.TotalGameTime;
  83. TimeSpan timeSinceLast = time - LastInputTime;
  84. if (timeSinceLast > BufferTimeOut)
  85. {
  86. Buffer.Clear();
  87. }
  88. // Get all of the non-direction buttons pressed.
  89. Buttons buttons = 0;
  90. foreach (var buttonAndKey in NonDirectionButtons)
  91. {
  92. Buttons button = buttonAndKey.Key;
  93. Keys key = buttonAndKey.Value;
  94. // Check the game pad and keyboard for presses.
  95. if ((lastGamePadState.IsButtonUp(button) &&
  96. GamePadState.IsButtonDown(button)) ||
  97. (lastKeyboardState.IsKeyUp(key) &&
  98. KeyboardState.IsKeyDown(key)))
  99. {
  100. // Use a bitwise-or to accumulate button presses.
  101. buttons |= button;
  102. }
  103. }
  104. // It is very hard to press two buttons on exactly the same frame.
  105. // If they are close enough, consider them pressed at the same time.
  106. bool mergeInput = (Buffer.Count > 0 && timeSinceLast < MergeInputTime);
  107. // If there is a new direction,
  108. var direction = Direction.FromInput(GamePadState, KeyboardState);
  109. if (Direction.FromInput(lastGamePadState, lastKeyboardState) != direction)
  110. {
  111. // combine the direction with the buttons.
  112. buttons |= direction;
  113. // Don't merge two different directions. This avoids having impossible
  114. // directions such as Left+Up+Right. This also has the side effect that
  115. // the direction needs to be pressed at the same time or slightly before
  116. // the buttons for merging to work.
  117. mergeInput = false;
  118. }
  119. // If there was any new input on this update, add it to the buffer.
  120. if (buttons != 0)
  121. {
  122. if (mergeInput)
  123. {
  124. // Use a bitwise-or to merge with the previous input.
  125. // LastInputTime isn't updated to prevent extending the merge window.
  126. Buffer[Buffer.Count - 1] = Buffer[Buffer.Count - 1] | buttons;
  127. }
  128. else
  129. {
  130. // Append this input to the buffer, expiring old input if necessary.
  131. if (Buffer.Count == Buffer.Capacity)
  132. {
  133. Buffer.RemoveAt(0);
  134. }
  135. Buffer.Add(buttons);
  136. // Record this the time of this input to begin the merge window.
  137. LastInputTime = time;
  138. }
  139. }
  140. }
  141. /// <summary>
  142. /// Determines if a move matches the current input history. Unless the move is
  143. /// a sub-move, the history is "consumed" to prevent it from matching twice.
  144. /// </summary>
  145. /// <returns>True if the move matches the input history.</returns>
  146. public bool Matches(Move move)
  147. {
  148. // If the move is longer than the buffer, it can't possibly match.
  149. if (Buffer.Count < move.Sequence.Length)
  150. return false;
  151. // Loop backwards to match against the most recent input.
  152. for (int i = 1; i <= move.Sequence.Length; ++i)
  153. {
  154. if (Buffer[Buffer.Count - i] != move.Sequence[move.Sequence.Length - i])
  155. {
  156. return false;
  157. }
  158. }
  159. // Rnless this move is a component of a larger sequence,
  160. if (!move.IsSubMove)
  161. {
  162. // consume the used inputs.
  163. Buffer.Clear();
  164. }
  165. return true;
  166. }
  167. }
  168. }