I’m new to MonoGame and I think I’m having a little difficulty understanding input. To me, it looks like I’m expected to get input by querying the current and previous states of the mouse/keyboard/gamepad/etc. This seems flawed unless I’m misunderstanding something.
What keeps input from being missed if the player/user presses and releases a button on the same iteration of the update loop? What if the order of button presses matters and two or more buttons are triggered in the same iteration of the update loop?
Shouldn’t input be a list or array the framework pushes to that the developer reads/pops from?
I assume the update method is usually called far more frequently than draw. If that’s the case, I’m sure for most purposes what I’m describing isn’t an issue, but if what I’m describing is true… it makes me hesitate to use a framework that I know would be dropping input given the right combination of performance problems when there’s no reason for the implementation to be this way (or for this to be the ONLY implementation on retrieving input).
If you want to record for replays, push/pop is a good idea, remember input is scanned many times per second [ depending on; usually, your refresh rate/update rate ]… stream inputs is one way to go…
I believe each platform has jitters regarding Input, so, which platform[s] are you aiming for, so these can be addressed respectively…
What I have right now is only a concept, but my intent was to target Windows. Assuming I make it far enough to do so, I’d like to eventually include Mac as well as Linux.
Regarding the user tapping a key/button rapidly, how do you (on Windows) avoid missing that input if it happens during the same update iteration?
If update is called and you check the key state and it is unpressed, but then the user taps the key and releases it before the next update method is called… there’s no input buffer to read from, from what I see in MonoGame.
I don’t know a lot about hardware, but I would expect the OS receives interrupts at a speed faster than how frequently the update method is going to be called… so isn’t there a bit too much room there for issues? Or am I missing something?
It’s true, there is a potential for missing keystrokes. In practice this rarely becomes a problem, because humans just aren’t fast enough to beat the update loop. At very low update rates you will be missing keystrokes though.
In my experience it starts to get dangerous once you get below 20 updates per second. A quick search on human keypress timings also confirms that number. The average keydown time seems to be around 100 ms, where 50 ms seems to be the lower end.
To give you an idea what those update rates mean for video games. The target framerate (at least on PC) is usually 60+ fps. 30 fps is usually considered the minimum. At 20 or less fps most people would probably consider a game as unplayable. Of course it also depends on the type of game.
While not being a big problem in practice, I think there is a bit of a weakness here. Having an event-based alternative to the polling system would be nice. MonoGame does have an event-based alternative for keyboard input, but that seems to be mostly geared towards text input.
Not really, the most common scenario is to have one update for every draw.
Yes, Windows uses an event-based input system under the hood.
Interestingly enough if you are a fast typer words that end in er and other letter combos, have the distinction that a user can often press the e before r but not release it until the r is already being pressed.
This means more often you are more concerned with a delaying mechanic to registering events at 1/60th of a second.
Their is a underlying array you can access though.
That’d be my exact question as well. I’m having some difficulty finding anything related to what I’m interested in getting in the public API outside of keyboard input specifically related to text input.
Also Markus, thank you for your above reply as well. You described my own thoughts and concerns quite accurately.
//!_______________________________________________________________
//!
//! NAMESPACE GROUP
//!_______________________________________________________________
//!
namespace ZGDK.IO
{
//!___________________________________________________________
//!
//! KEYBOARD DEVICE CLASS
//!___________________________________________________________
//!
/// <summary>
/// Keyboard table
/// </summary>
public class Keyboard
{
//!___________________________________________________________
//!
//! F I E L D S
//!___________________________________________________________
internal XFI.KeyboardState __KeyBoardState;
//!
internal bool __KeyEvent;
internal bool __KeyDownEvent;
internal bool __KeyUpEvent;
internal bool __KeyHoldEvent;
//!
internal SCG.List<XFI.Keys> __KeysPressed;
internal SCG.List<XFI.Keys> __KeysHeld;
internal SCG.List<XFI.Keys> __KeysReleased;
//!
internal XFI.Keys[] __NewKeyPressed;
internal XFI.Keys[] __LastKeys;
internal XFI.Keys __LastSingleKey;
//!
internal bool __CtrlHold;
internal bool __ShiftHold;
internal bool __AltHold;
//!
internal bool __KeyboardInit;
internal bool __Disposed;
//!
private int __ThisTime;
private bool __Keyfound;
private int __HeldCnt;
//!___________________________________________________________
//!
//! C O N S T R U C T O R
//!___________________________________________________________
/// <summary>
///
/// </summary>
internal Keyboard()
{
__KeyboardInit = false;
__Disposed = false;
ZGLOBAL._Keyboard = this;
}
//!___________________________________________________________
//!
//! D E S T R U C T O R
//!___________________________________________________________
/// <summary>
/// Dispose by destructor
/// </summary>
~Keyboard()
{
_M_CleanUp();
}
/// <summary>
/// This table should not base on IDisposable, dispose this internally
/// </summary>
internal void _M_CleanUp()
{
if ( !__Disposed )
{
if ( __KeysPressed != null ) { __KeysPressed = null; }
if ( __KeysHeld != null ) { __KeysHeld = null; }
if ( __KeysReleased != null ) { __KeysReleased = null; }
if ( __NewKeyPressed != null ) { __NewKeyPressed = null; }
if ( __LastKeys != null ) { __LastKeys = null; }
ZGLOBAL._Keyboard = null;
__KeyboardInit = false;
__Disposed = true;
}
}
//!___________________________________________________________
//!
//! I N I T I A L I Z E
//!___________________________________________________________
internal void _M_Initialize()
{
__KeysPressed = new SCG.List<XFI.Keys>();
__KeysHeld = new SCG.List<XFI.Keys>();
__KeysReleased = new SCG.List<XFI.Keys>();
__NewKeyPressed = new XFI.Keys[0];
__LastKeys = new XFI.Keys[0];
__LastKeys = XFI.Keyboard.GetState().GetPressedKeys();
__LastSingleKey = XFI.Keys.None;
__KeyboardInit = true;
}
//!___________________________________________________________
//!
//! U P D A T E
//!___________________________________________________________
internal void _M_Update()
{
//! If keyboard not yet initialize exit from here
if (!__KeyboardInit || __Disposed ) return;
//------------------------------------------------------//
//! Local memvars needed
__ThisTime = UPS_FPS.__TICKS_NOW;
__Keyfound = false;
//! Reset keyboard events
__KeyEvent = false;
__KeyDownEvent = false;
__KeyUpEvent = false;
__KeyHoldEvent = false;
//! Reset special holding keys
__AltHold = false;
__CtrlHold = false;
__ShiftHold = false;
//------------------------------------------------------//
//! Clear previous frame key pressed and keys released
if ( __KeysPressed.Count > 0 ) { __KeysPressed.Clear(); }
if ( __KeysReleased.Count> 0 ) { __KeysReleased.Clear(); }
//------------------------------------------------------//
//! Control keys should work only games is the active app
if ( ! ZGLOBAL._GameFramework.IsActive )
{
return;
}
//------------------------------------------------------//
//! Saving new keybaord state for this frame
__KeyBoardState = XFI.Keyboard.GetState();
__NewKeyPressed = __KeyBoardState.GetPressedKeys();
//------------------------------------------------------//
//! Update special holding keys
if( __KeyBoardState.IsKeyDown(XFI.Keys.LeftShift) ||
__KeyBoardState.IsKeyDown(XFI.Keys.RightShift) )
{
__ShiftHold = true;
}
if( __KeyBoardState.IsKeyDown(XFI.Keys.LeftControl) ||
__KeyBoardState.IsKeyDown(XFI.Keys.RightControl) )
{
__CtrlHold = true;
}
if( __KeyBoardState.IsKeyDown(XFI.Keys.LeftAlt) ||
__KeyBoardState.IsKeyDown(XFI.Keys.RightAlt) )
{
__AltHold = true;
}
//------------------------------------------------------//
// Update newly pressed keys and held keys for this frame
for ( int N = 0; N < __NewKeyPressed.Length; N++ )
{
//! Keyboard events occur | A key was pressed down or still holding some old keys
__KeyEvent = true;
__Keyfound = false;
// Find if new keys already been held
for ( int H = 0; H < __KeysHeld.Count ; H++ )
{
if ( __NewKeyPressed[N] == __KeysHeld[H] )
{
__Keyfound = true;
break;
}
}
//! If not been held
if ( ! __Keyfound )
{
//! Add as newly pressed key
__KeysPressed.Add( __NewKeyPressed[N] );
//! Add also as being held
__KeysHeld.Add( __NewKeyPressed[N] );
//! A key down events
__KeyDownEvent = true;
}
}
//------------------------------------------------------//
//! Remove all keys from being held if released
__HeldCnt = __KeysHeld.Count - 1;
//!
while ( __HeldCnt >= 0 )
{
if ( __KeyBoardState.IsKeyUp( __KeysHeld[ __HeldCnt ] ) )
{
//! Add it to released keys for this frame
__KeysReleased.Add( __KeysHeld[ __HeldCnt ] );
//! Remove from keys from being held
__KeysHeld.RemoveAt( __HeldCnt );
//! Keyboard events occur | a keys was released
__KeyEvent = true;
__KeyUpEvent = true;
}
__HeldCnt--;
}
//!
if( __KeysHeld.Count>0 ) __KeyHoldEvent = true;
//------------------------------------------------------//
//! Update last keys and last single keys
__LastKeys = __NewKeyPressed;
//!
if( __NewKeyPressed.Length >0 )
{
//! Get the last single key being pressed down
__LastSingleKey = __NewKeyPressed[ __NewKeyPressed.Length-1 ];
}
else
{
__LastSingleKey = XFI.Keys.None;
}
}
//!___________________________________________________________
//!
//! P U B L I C M E T H O D S
//!___________________________________________________________
/// <summary>
/// Inquire if any keyboard events currently occurring from this frame
/// wheather a key down, key up or key holding events.
/// </summary>
public bool IsHasEvents()
{
return __KeyEvent;
}
/// <summary>
/// Inquire if key down event occur from this frame.
/// </summary>
public bool IsEventsKeyDown()
{
return __KeyDownEvent;
}
/// <summary>
/// Inquire if key up event occur from this frame.
/// </summary>
public bool IsEventsKeyUp()
{
return __KeyUpEvent;
}
/// <summary>
/// Inquire if keys holding still occurring from this frame.
/// </summary>
/// <returns></returns>
public bool IsEventsKeyHold()
{
return __KeyHoldEvent;
}
/// <summary>
/// Get all last keys currently being pressed down or being held from this frame
/// </summary>
/// <returns>Returns Keys</returns>
public XFI.Keys[] GetLastKeys()
{
return __LastKeys;
}
/// <summary>
/// Get the last single key currently pressed down or being held from this frame
/// </summary>
/// <returns>Returns Key, otherwise return Keys.None if there is no keyboard event</returns>
public XFI.Keys GetLastKey()
{
return __LastSingleKey;
}
/// <summary>
/// Inquire if left or right CTRL key is currently on hold
/// </summary>
public bool IsCtrlHold()
{
return __CtrlHold;
}
/// <summary>
/// Inquire if left or right SHIFT key is currently on hold
/// </summary>
public bool IsShiftHold()
{
return __ShiftHold;
}
/// <summary>
/// Inquire if left or right ALT key is currently on hold
/// </summary>
public bool IsAltHold()
{
return __AltHold;
}
/// <summary>
/// Inquire if Key has been pressed, keys on hold not accounted
/// </summary>
/// <param name="pKey">Key</param>
/// <returns>Returns true if key was pressed, otherwise false</returns>
public bool IsKeyPressed(XFI.Keys pKey)
{
if (__Disposed) return false;
if (__KeysPressed.Contains(pKey))
{
return true;
}
return false;
}
/// <summary>
/// Inquire if Key is currently on hold
/// </summary>
/// <param name="pKey">Key</param>
/// <returns>Return true if on hold, otherwise false</returns>
public bool IsKeyHoldDown(XFI.Keys pKey)
{
if (__Disposed) return false;
for (int i = 0; i < __KeysHeld.Count; i++)
{
if (__KeysHeld[i] == pKey) return true;
}
return false;
}
/// <summary>
/// Inquire if key was released from this frame
/// </summary>
/// <param name="pKey">Key</param>
/// <returns>Returns true if key was released, otherwise returns false</returns>
public bool IsKeyReleased(XFI.Keys pKey)
{
if (__Disposed) return false;
for (int i = 0; i < __KeysReleased.Count; i++)
{
if (__KeysReleased[i] == pKey) return true;
}
return false;
}
}//EndClass Keyboard
Ty for sharing your code (very nice of you), but all I really see is you’re comparing key states from frame to frame and adding changes to a list. You even have a comment saying as much here:
//! Saving new keybaord state for this frame
__KeyBoardState = XFI.Keyboard.GetState();
That is where you poll for the current state and then your next bit of code compares to the previous state. If the user presses a key after you call XFI.Keyboard.GetState() and releases it before you call XFI.Keyboard.GetState() again, your code will see the key up in one frame and up again in the next and therefore won’t catch the input.
It’s was my Javascript/TypeScript keyboard handling class I ported to Monogame, so far I have no problem using it and may answer your original inquiry “Handling ALL input without current/previous state checks?”
If I want to know if Key.UP button was released I just issue the code below, without needing to know whether if it was been presssed down the first time or if it still on hold.
if( myKeyboard.IsKeyRelease( XFI.Key.Up ) ) { }
// EDIT : This you can also inquire multiple keys just ask and forget
I don’t know that this question was really ever resolved. I’ve had this annoyance with MonoGame for a long time. Sometimes keystrokes just don’t appear. Sometimes a lot, sometimes rarely. My project is a psudo-terminal using MonoGame for rendering. I can notice it while typing into my app. I suspect it must be just typing too fast for the 60fps system.
It’s weird and annoying. Does anyone know of any other cross-platform .NET library we could use for better keyboard handling?