From a2b524ee04772d483750047c997172cc62090a9a Mon Sep 17 00:00:00 2001 From: Perry Date: Mon, 3 Nov 2025 09:37:46 +0100 Subject: [PATCH] Added input management, added lables to input handler entries, added filtering by label --- MonoGameLibrary.sln.DotSettings.user | 2 + MonoGameLibrary/Core.cs | 6 + MonoGameLibrary/Input/InputListenerHook.cs | 6 + MonoGameLibrary/Input/InputManager.cs | 159 +++++++++++++++++++++ MonoGameLibrary/Input/InputTiming.cs | 7 + 5 files changed, 180 insertions(+) create mode 100644 MonoGameLibrary.sln.DotSettings.user create mode 100644 MonoGameLibrary/Input/InputListenerHook.cs create mode 100644 MonoGameLibrary/Input/InputManager.cs create mode 100644 MonoGameLibrary/Input/InputTiming.cs diff --git a/MonoGameLibrary.sln.DotSettings.user b/MonoGameLibrary.sln.DotSettings.user new file mode 100644 index 0000000..75bd3ff --- /dev/null +++ b/MonoGameLibrary.sln.DotSettings.user @@ -0,0 +1,2 @@ + + ForceIncluded \ No newline at end of file diff --git a/MonoGameLibrary/Core.cs b/MonoGameLibrary/Core.cs index 0e031c4..bb54015 100644 --- a/MonoGameLibrary/Core.cs +++ b/MonoGameLibrary/Core.cs @@ -2,6 +2,7 @@ using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using MonoGameLibrary.Input; namespace MonoGameLibrary; @@ -88,4 +89,9 @@ public class Core : Game // Create the sprite batch instance. spriteBatch = new SpriteBatch(graphicsDevice); } + + protected override void Update(GameTime gameTime) { + InputManager.NextInputCycle(); + base.Update(gameTime); + } } \ No newline at end of file diff --git a/MonoGameLibrary/Input/InputListenerHook.cs b/MonoGameLibrary/Input/InputListenerHook.cs new file mode 100644 index 0000000..eb63870 --- /dev/null +++ b/MonoGameLibrary/Input/InputListenerHook.cs @@ -0,0 +1,6 @@ +namespace MonoGameLibrary.Input; + +public class InputListenerHook(bool enabled, bool oneTimeOnly = false) { + public bool Enabled { get; set; } = enabled; + public bool RemoveOnNextTrigger { get; set; } = oneTimeOnly; +} \ No newline at end of file diff --git a/MonoGameLibrary/Input/InputManager.cs b/MonoGameLibrary/Input/InputManager.cs new file mode 100644 index 0000000..533fa06 --- /dev/null +++ b/MonoGameLibrary/Input/InputManager.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework.Input; + +namespace MonoGameLibrary.Input; + + +public static class InputManager { + private static Dictionary> keyPressListeners = new(); + private static Dictionary> keyHoldListeners = new(); // The bool is for enabling/disabling that method + private static Dictionary> keyReleaseListeners = new(); + + private static Dictionary labelDict = new(); + + private static KeyboardState oldState = Keyboard.GetState(); + private static KeyboardState newState; + + // public static ConcurrentQueue inputEventQueue = new(); + + public delegate void KeypressHandler(KeyboardState state); + + private static int nextUnnamedId = 0; + + + + + // public static void StartListening() { + // processInputThread.Start(); + // Console.WriteLine("Started listening for input"); + // } + + public static void NextInputCycle() { + Queue inputEventQueue = new(); + + // Read + + newState = Keyboard.GetState(); + Keys[] pressed = newState.GetPressedKeys(); + Keys[] pressedLastFrame = oldState.GetPressedKeys(); + + Array.ForEach(pressed, key => { + if (oldState.IsKeyUp(key)){ + if (keyPressListeners.TryGetValue(key, out var pressHandlers)){ + pressHandlers.ForEach(t => { + if (t.Enabled){ + inputEventQueue.Enqueue(t.Action); + } + if (t.Hook.RemoveOnNextTrigger){ + pressHandlers.Remove(t); + } + }); + } + } + + if (keyHoldListeners.TryGetValue(key, out var holdHandlers)){ + holdHandlers.ForEach(t => { + if (t.Enabled){ + inputEventQueue.Enqueue(t.Action); + } + if (t.Hook.RemoveOnNextTrigger){ + holdHandlers.Remove(t); + } + }); + } + }); + + Array.ForEach(pressedLastFrame, key => { + if (!keyReleaseListeners.ContainsKey(key)){ + return; + } + + if (newState.IsKeyUp(key)){ + if (keyReleaseListeners.TryGetValue(key, out var releaseHandlers)) + releaseHandlers.ForEach(t => { + if (t.Enabled){ + inputEventQueue.Enqueue(t.Action); + } + if (t.Hook.RemoveOnNextTrigger){ + releaseHandlers.Remove(t); + } + }); + } + }); + oldState = newState; + + // Execute + + foreach (KeypressHandler handler in inputEventQueue){ + handler(newState); + } +} + + // private static Thread processInputThread = new(() => { + // + // }); + + public static void AddListener(string label, Keys key, KeypressHandler action, InputTiming timing, InputListenerHook hook) { + if (label.StartsWith("!")){ + throw new ArgumentException("Label cannot start with !"); + } + InputEntry entry = new (label, key, action, timing, hook); + _addListener(entry); + } + + public static void AddListener(Keys key, KeypressHandler action, InputTiming timing, InputListenerHook hook) { + InputEntry entry = new ($"!{Enum.GetName(typeof(Keys), key)}Handler{nextUnnamedId++}", key, action, timing, hook); + _addListener(entry); + } + + private static void _addListener(InputEntry entry) { + Dictionary> workingDict; + + switch (entry.Timing){ + case InputTiming.PRESS: + workingDict = keyPressListeners; + break; + case InputTiming.HOLD: + workingDict = keyHoldListeners; + break; + case InputTiming.RELEASE: + workingDict = keyReleaseListeners; + break; + default: + throw new ArgumentOutOfRangeException(nameof(entry.Timing), entry.Timing, null); + } + + if (!workingDict.ContainsKey(entry.Key)){ + workingDict.Add(entry.Key, new()); + } + + workingDict[entry.Key].Add(entry); + + labelDict.Add(entry.Label, entry); + } + + public static InputEntry GetEntry(string label) { + return labelDict[label]; + } + + public class InputEntry { + + public string Label; + public Keys Key; + public KeypressHandler Action; + public InputTiming Timing; + public InputListenerHook Hook; + + public bool Enabled => Hook.Enabled; + + public InputEntry(string label, Keys key, KeypressHandler action, InputTiming timing, InputListenerHook hook) { + Key = key; + Action = action; + Timing = timing; + Hook = hook; + Label = label; + } + } + // private static List inputQueue = new(); +} \ No newline at end of file diff --git a/MonoGameLibrary/Input/InputTiming.cs b/MonoGameLibrary/Input/InputTiming.cs new file mode 100644 index 0000000..1948d7e --- /dev/null +++ b/MonoGameLibrary/Input/InputTiming.cs @@ -0,0 +1,7 @@ +namespace MonoGameLibrary.Input; + +public enum InputTiming { + PRESS, + HOLD, + RELEASE +} \ No newline at end of file