Input & Movement > Custom Input Handling |
While the VRSimulator handles most basic movement for you out of the box, there are interactions specific to your VR experience that (obviously) we can't foresee. There might be many situations in which you want your VR experience to react to your user's actions (inputs) beyond simple movement:
You can do all of this and more using Custom Input Handling, which lets your code listen for our Input Action Events and then respond accordingly.
The way movement works in any VR experience (or computer game, etc.) is really straightforward:
The software needs to receive (capture) the user's input from whatever device was used. Based on the input received (what button was pressed, what thumbstick was moved, etc.) the system needs to decide what that input means ("Move the player forward", "Fire the blaster cannon", etc.). And once that decision is made, the system needs to respond and do whatever it should do ("Move the player one step forward", "Start the process of firing the blaster cannon").
Where this can get complicated is that most input devices are built and designed differently, have different hardware features, different drivers, etc. And different experiences (and even different users within the same experience!) may want to have different button layouts, controller configurations, etc. for their style of play. Trying to accommodate all of the variability that's out there can get very complicated, very fast.
Which is why we have broken this process into four key concepts:
Be Aware | |||
Not all input devices supported by the VRSimulator are reflected in the Unity Input Manager. In particular, VR-specific devices like the HTC/Vive Wands, the Oculus Remote, and the Oculus Touch are only accessible via their corresponding Unity plugin (SteamVR plugin and Oculus Utilities for Unity). The VRSimulator supports these devices through special device-specific logic which adds an easier-to-understand abstraction layer to the platform-specific APIs.
|
Be Careful! | |
If your custom input handling logic directly affects the player's or camera's position, movement, or rotation it might lead to some unexpected results. Basically, both our default movement / look logic and your custom input handling logic will execute - and we can't guarantee what the final effect will look like. |
The key to custom input handling is for your methods to "listen" for an InputAction Event. A method that is listening for (subscribed to) an InputAction Event will always execute whenever that InputAction Event is fired. Below is a sample of a custom InputAction listener method:
public class myCustomClass : MonoBehaviour { void OnEnable() { // "Listen" for the Input Action. EventManager.OnInputActionStart += myCustomListener; } void OnDisable() { // "Unlisten" for the Input Action. EventManager.OnInputActionStart -= myCustomListener; } void myCustomListener(InputAction firedAction) { if (firedAction.name == "DesiredInputAction") { // Do something (or call another method) here. } } }
In the code above, the OnEnable() method is where you set myCustomListener to "listen" for the event EventManager.OnInputActionStart to occur. Essentially, what you're doing is adding the method myCustomListener to a list of methods that will be called whenever OnInputActionStart occurs.
Best Practice | |
To make our code easier to follow / keep straight, we always name our listener methods the same as the event they're listening to. Thus, we wouldn't actually use myCustomListener but instead OnInputActionStart. Of course, you can use whatever naming conventions you like, but this one works well for us. |
The OnDisable() method is where you then remove the myCustomListener method from the list of methods listening for OnInputActionStart. This is to prevent memory leaks - if you didn't "unlisten", then even when you closed Unity, shut down your game, and went off to get a cup of coffee there would still be a lonely little bit of software somewhere waiting in vain for the OnInputActionStart event to occur. That's a textbook definition of a nasty memory leak problem.
The sample code above contains a method called myCustomListener which is a good example of reacting to the Input Action. Here is that code again, just so we can go over its important parts:
void myCustomListener(InputAction firedAction) { if (firedAction.name == "DesiredInputAction") { // Do something (or call another method) here. } }
First, please notice the return type and parameters for the method. Every single method that listens for the OnInputActionStart event must have the same return type (void) and the same parameters (a single InputAction object).
Be Careful! | |
If your custom listener for the OnInputActionStart event does not have a return type of void and accept a single InputAction parameter, your code will throw an exception. |
Second, notice the if { ... } statement inside of the myCustomListener method. This myCustomListener method will execute whenever any InputAction is detected, regardless of what that InputAction actually represents. But presumably, you want your myCustomListener to only respond to a particular InputAction (a primary trigger pull, for example). This if { ... } statement makes sure your code is only executed for the specific InputAction(s) that you want - in the example above, an InputAction named "DesiredInputAction".
And that's it! There you have a custom input handler.
Best Practice | |||
If you're using our default InputActions but wish to create custom handlers for them, there is an easier way: Each of our default InputActions has its own InputAction-specific events which get fired whenever the InputAction is detected. This allows you to listen for a specific InputAction without checking its name property as shown above. For more information on the events fired by our default Input Actions, please see the Default Input Actions reference.
|
There are three ways to customize the VRSimulator's movement logic:
If you wish to retain our default Input Actions while customizing movement, you need to do the following:
The best (easiest to understand and most-performative) way to do this is to use one "switchboard" method as in the example below:
void OnEnable() { EventManager.OnInputActionStart += myCustomSwitchboard; } void OnDisable() { EventManager.OnInputActionStart -= myCustomSwitchboard; } void myCustomSwitchboard(InputAction firedAction) { switch (firedAction.name) { case "togglePauseMenu": // ... Custom Code Here break; case "toggleView": // ... Custom Code Here break; case "togglePrimaryTrigger": // ... Custom Code Here break; case "toggleSecondaryTrigger": // ... Custom Code Here break; case "toggleRightThumbstickClick": // ... Custom Code Here break; case "toggleLeftThumbstickClick": // ... Custom Code Here break; case "toggleRightBumper": // ... Custom Code Here break; case "toggleLeftBumper": // ... Custom Code Here break; case "toggleSelectionButton": // ... Custom Code Here break; case "toggleCancelButton": // ... Custom Code Here break; case "toggleSecondaryButton": // ... Custom Code Here break; case "toggleTertiaryButton": // ... Custom Code Here break; case "xAxisMovement": // ... Custom Code Here break; case "zAxisMovement": // ... Custom Code Here break; case "pitchRotation": // ... Custom Code Here break; case "yawRotation": // ... Custom Code Here break; } }
Given the code above, just replace the comments // ... Custom Code Here with your own method calls, and you'll have successfully rewired our movement system.
If you wish to use your own custom Input Actions but retain all of Immerseum's movement logic, you should:
And that's it! Now the VRSimulator movement system will move the user in response to your custom InputActions. For more information, please see Replacing Default InputActions.
If you wish to use your own custom Input Actions with your own custom movement logic, you should:
And that's it! Your InputActions will now result in whatever movement you've defined through your custom listeners.