Figured it out, a little hacky. Added a reply to Olleus’s related question in their thread . Summarizing for posterity: the Event System + Standalone Input Module combination does do limited handling of keyboard input as long as
- the input names in the Input Manager are mapped correctly and match the Standalone Input Module’s names, and
- the Event System has a selectable object (like a button) to its “First Selected” field.
This has to work before you do anything else from this post.
To solve my issue I looked at these scripts: StandaloneInputModule, its parent class BaseInputModule, BaseInput, and Button.
In the Button script, OnSubmit() fires the “click” event (via Press()), then it uses a coroutine OnFinishSubmit() to transition back to its previous button state as soon as the button finishes its transition to the “pressed” state. This is what causes the behaviour I didn’t want described in my original post.
StandaloneInputModule gets various inputs in its Process() method. At the end of the method, SendSubmitEventToSelectedObject() is called. This is where the StandaloneInputModule uses a BaseInput instance to detect if the user presses the buttons mapped to “submit” or “cancel”, and if so, it fires those events. Unity’s source calls BaseInput an “interface” to its Input module. However, looking at BaseInput, one notices not all Input methods are present – for my purposes, there is a GetButtonDown method, but no GetButtonUp.
So, I created two subclasses: one derriving from BaseInput (which I’ll call CustomInput) and one derriving from StandaloneInputModule (which I’ll call CustomInputModule).
CustomInput just needed one method:
public virtual bool GetButtonUp(string buttonName) {
Input.GetButtonUp(buttonName);
}
Then, to make use of CustomInput, I had to do a few things in CustomInputModule:
- I changed the “input” variable from BaseInputModule to use my CustomInput.
- I rewrote StandaloneInputModule’s SendSubmitEventToSelectedObject() function to treat the submit key’s key down event as a pointer down event and its key up event as a pointer up event. Like this:
protected new virtual bool SendSubmitEventToSelectedObject() {
if (eventSystem.currentSelectedGameObject == null) return false;
GetPointerData(kMouseLeftId, out PointerEventData leftPointerClickData, true);
if (input.GetButtonDown(submitButton)) {
leftPointerClickData.eligibleForClick = true;
ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, leftPointerClickData, ExecuteEvents.pointerDownHandler);
clickedObject = eventSystem.currentSelectedGameObject;
}
if (input.GetButtonUp(submitButton)) {
clickedObject = clickedObject ? clickedObject : eventSystem.currentSelectedGameObject;
ExecuteEvents.Execute(clickedObject, leftPointerClickData, ExecuteEvents.pointerUpHandler);
if (leftPointerClickData.eligibleForClick) {
ExecuteEvents.Execute(clickedObject, leftPointerClickData, ExecuteEvents.pointerClickHandler);
clickedObject = null;
}
leftPointerClickData.eligibleForClick = false;
}
if (input.GetButtonDown(cancelButton)) {
ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, leftPointerClickData, ExecuteEvents.cancelHandler);
}
return leftPointerClickData.used;
}
For more insight into this, take a look at ProcessTouchPress() method of StandaloneInputModule – I tried to make it mimick a pointer event as much as I could.
The “clickedObject” referred to in the code is defined as a variable at the top of CustomInputModule. It keeps track of the button that was clicked on key “down”, just like “currentOverGo” from ProcessTouchPress(). This is so we still have a reference to the original pressed button so the Up event can transition it back to its original state, even if the Event System’s current selected game object has changed (i.e., the user holds the submit key down, then presses an arrow key to select a different button, then releases the submit key).
(One last code note, I did have to copy over several methods from StandaloneInputModule to make this work because SendSubmitEventToSelectedObject isn’t virtual and can’t be overridden, only hidden via “new”: Process(), ShouldIgnoreEventsOnNoFocus(), and ProcessTouchEvents().)
The last thing I did was swap the StandaloneInputModule component for my CustomInputModule on the EventSystem game object. Then all of my existing buttons worked as expected.
That’s the super broad overview of how I made it work – hopefully it isn’t complete gibberish.