diff --git a/CHANGELOG.md b/CHANGELOG.md index bd1fd97..46cf4d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Additions - Added RandomPitchModifier and GetRandomPitch to SoundEffectInfo - Added TextInput class, which is an isolated version of MLEM.Ui's TextField logic - Added MLEM.FNA, which is fully compatible with FNA +- Added TryGetUpTime, GetUpTime, TryGetTimeSincePress and GetTimeSincePress to InputHandler Improvements - Allow comparing Keybind and Combination based on the amount of modifiers they have diff --git a/MLEM/Input/InputHandler.cs b/MLEM/Input/InputHandler.cs index 90064a6..6135eda 100644 --- a/MLEM/Input/InputHandler.cs +++ b/MLEM/Input/InputHandler.cs @@ -145,13 +145,15 @@ namespace MLEM.Input { /// public KeyboardState KeyboardState { get; private set; } - private readonly GamePadState[] lastGamepads = new GamePadState[MaximumGamePadCount]; - private readonly GamePadState[] gamepads = new GamePadState[MaximumGamePadCount]; - private readonly DateTime[] lastGamepadButtonRepeats = new DateTime[MaximumGamePadCount]; - private readonly bool[] triggerGamepadButtonRepeat = new bool[MaximumGamePadCount]; - private readonly Buttons?[] heldGamepadButtons = new Buttons?[MaximumGamePadCount]; + private readonly GamePadState[] lastGamepads = new GamePadState[InputHandler.MaximumGamePadCount]; + private readonly GamePadState[] gamepads = new GamePadState[InputHandler.MaximumGamePadCount]; + private readonly DateTime[] lastGamepadButtonRepeats = new DateTime[InputHandler.MaximumGamePadCount]; + private readonly bool[] triggerGamepadButtonRepeat = new bool[InputHandler.MaximumGamePadCount]; + private readonly Buttons?[] heldGamepadButtons = new Buttons?[InputHandler.MaximumGamePadCount]; private readonly List gestures = new List(); private readonly HashSet<(GenericInput, int)> consumedPresses = new HashSet<(GenericInput, int)>(); + private readonly Dictionary<(GenericInput, int), DateTime> inputUpTimes = new Dictionary<(GenericInput, int), DateTime>(); + private readonly Dictionary<(GenericInput, int), DateTime> inputPressedTimes = new Dictionary<(GenericInput, int), DateTime>(); private Point ViewportOffset => new Point(-this.Game.GraphicsDevice.Viewport.X, -this.Game.GraphicsDevice.Viewport.Y); private Dictionary<(GenericInput, int), DateTime> inputsDownAccum = new Dictionary<(GenericInput, int), DateTime>(); @@ -232,8 +234,8 @@ namespace MLEM.Input { } if (this.HandleGamepads) { - this.ConnectedGamepads = MaximumGamePadCount; - for (var i = 0; i < MaximumGamePadCount; i++) { + this.ConnectedGamepads = InputHandler.MaximumGamePadCount; + for (var i = 0; i < InputHandler.MaximumGamePadCount; i++) { this.lastGamepads[i] = this.gamepads[i]; this.gamepads[i] = default; if (GamePad.GetCapabilities((PlayerIndex) i).IsConnected) { @@ -293,12 +295,28 @@ namespace MLEM.Input { if (this.inputsDownAccum.Count <= 0 && this.inputsDown.Count <= 0) { this.InputsPressed = Array.Empty(); this.InputsDown = Array.Empty(); - this.inputsDown.Clear(); } else { + // handle pressed inputs + var pressed = new List(); // if we're inverting press behavior, we need to check the press state for the inputs that were down in the last frame - var down = this.InvertPressBehavior ? this.inputsDown : this.inputsDownAccum; - this.InputsPressed = down.Keys.Where(kv => this.IsPressed(kv.Item1, kv.Item2)).Select(kv => kv.Item1).ToArray(); - this.InputsDown = this.inputsDownAccum.Keys.Select(kv => kv.Item1).ToArray(); + foreach (var key in (this.InvertPressBehavior ? this.inputsDown : this.inputsDownAccum).Keys) { + if (this.IsPressed(key.Item1, key.Item2)) { + this.inputPressedTimes[key] = DateTime.UtcNow; + pressed.Add(key.Item1); + } + } + this.InputsPressed = pressed.ToArray(); + + // handle inputs that changed to up + foreach (var key in this.inputsDownAccum.Keys) + this.inputUpTimes.Remove(key); + foreach (var key in this.inputsDown.Keys) { + if (!this.inputsDownAccum.ContainsKey(key)) + this.inputUpTimes[key] = DateTime.UtcNow; + } + + // handle inputs that are currently down + this.InputsDown = this.inputsDownAccum.Keys.Select(key => key.Item1).ToArray(); // swapping these collections means that we don't have to keep moving entries between them (this.inputsDown, this.inputsDownAccum) = (this.inputsDownAccum, this.inputsDown); this.inputsDownAccum.Clear(); @@ -788,7 +806,7 @@ namespace MLEM.Input { /// /// Returns the amount of time that a given has been held down for. - /// If this input isn't currently own, this method returns . + /// If this input isn't currently down, this method returns . /// /// The input whose down time to query. /// The index of the gamepad to query (if applicable), or -1 for any gamepad. @@ -798,6 +816,62 @@ namespace MLEM.Input { return time; } + /// + /// Tries to retrieve the amount of time that a given has been up for since the last time it was down. + /// If the input is currently up, this method returns true and the amount of time that it has been up for is stored in . + /// + /// The input whose up time to query. + /// The resulting up time, or if the input is being held. + /// The index of the gamepad to query (if applicable), or -1 for any gamepad. + /// Whether the input is currently up. + public bool TryGetUpTime(GenericInput input, out TimeSpan upTime, int index = -1) { + if (this.inputUpTimes.TryGetValue((input, index), out var start)) { + upTime = DateTime.UtcNow - start; + return true; + } + return false; + } + + /// + /// Returns the amount of time that a given has been up for since the last time it was down. + /// If this input isn't currently up, this method returns . + /// + /// The input whose up time to query. + /// The index of the gamepad to query (if applicable), or -1 for any gamepad. + /// The resulting up time, or if the input is being held. + public TimeSpan GetUpTime(GenericInput input, int index = -1) { + this.TryGetUpTime(input, out var time, index); + return time; + } + + /// + /// Tries to retrieve the amount of time that has passed since a given last counted as pressed. + /// If the input has previously been pressed, or is currently pressed, this method returns true and the amount of time that has passed since it was last pressed is stored in . + /// + /// The input whose last press time to query. + /// The resulting up time, or if the input was never pressed or is currently pressed. + /// The index of the gamepad to query (if applicable), or -1 for any gamepad. + /// if the input has previously been pressed or is currently pressed, otherwise. + public bool TryGetTimeSincePress(GenericInput input, out TimeSpan lastPressTime, int index = -1) { + if (this.inputPressedTimes.TryGetValue((input, index), out var start)) { + lastPressTime = DateTime.UtcNow - start; + return true; + } + return false; + } + + /// + /// Returns the amount of time that has passed since a given last counted as pressed. + /// If this input hasn't been pressed previously, or is currently pressed, this method returns . + /// + /// The input whose up time to query. + /// The index of the gamepad to query (if applicable), or -1 for any gamepad. + /// The resulting up time, or if the input has never been pressed, or is currently pressed. + public TimeSpan GetTimeSincePress(GenericInput input, int index = -1) { + this.TryGetTimeSincePress(input, out var time, index); + return time; + } + /// /// Returns whether the given 's press state has been consumed using . /// If an input has been consumed, and will always return false for that input. diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index bc82ecf..b356196 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -365,6 +365,10 @@ namespace Sandbox { Console.WriteLine("Down: " + string.Join(", ", Input.InputsDown));*/ if (MlemGame.Input.InputsPressed.Length > 0) Console.WriteLine("Pressed: " + string.Join(", ", MlemGame.Input.InputsPressed)); + MlemGame.Input.HandleKeyboardRepeats = false; + Console.WriteLine("Down time: " + MlemGame.Input.GetDownTime(Keys.A)); + Console.WriteLine("Time since press: " + MlemGame.Input.GetTimeSincePress(Keys.A)); + Console.WriteLine("Up time: " + MlemGame.Input.GetUpTime(Keys.A)); } protected override void DoDraw(GameTime gameTime) {