How to use Vive's trackpad for menus using SteamVR 2.0 (2 of 2)

In part 1 of the article "How to use Vive's trackpad for menus using SteamVR 2.0" I showed how to create a custom action set and bind the trackpad controls to actions. In this second part, we will look at the implementation of button handling in Unity and some scripting.

Tip: If you get any errors or a blank screen on the binding page make sure you use the latest SteamVR version. They reportedly fixed some errors in the binding page lately. If you still get errors try the fix I posted on https://github.com/ValveSoftware/steamvr_unity_plugin/issues/154#issuecomment-438431516


Button Component

First, let's examine the buttons because they are pretty straightforward:


1. The panel has three buttons. We will only look at the NewGamebutton since each button works in the same way.

2. The button has the standard Button component with some transition settings, background image and so on.

3. I changed the Navigation direction to vertical because that's the way the buttons are arranged. 


The Panel

The parent GameObject MainMenuPanel is much more interesting. It is a standard Unity panel with two important components:


1. New with SteamVR 2.0 comes the script SteamVR_ActivateActionSetOnLoad. It "automatically activates an action set on Start() and deactivates the set on OnDestroy()." (Valve's comment).


As stated above, action sets will be explained elsewhere in detail. But basically all actions (a trigger click for example) are grouped into sets to allow different contexts for the same action. Maybe you want to use the trigger button in the game to pick up items but also to confirm a selection in the UI. So the trigger button has different meanings depending on whether the UI is visible or not.

We are telling Unity here that when the GameObject MenuPanel becomes enabled, the action set "/actions/helmetgui" should be activated. This action set has been generated within Unity and this process is described in detail in the SteamVR 2.0 documentation.

2. The script MainMenu is my custom script that will be explained next.

MainMenu Script

SteamVR provides classes that correspond to the action types you declared in the first part: SteamVR_Action_Vector2 and SteamVR_Action_Boolean. We use these classes to declare our variables:

public class MainMenu : MonoBehaviour
{
    private Selectable selectedObject;
    
    public SteamVR_Action_Vector2 actionNavigateMenu;
    public SteamVR_Action_Boolean actionTouchpadClick;
    public SteamVR_Action_Boolean actionConfirm;

    private void Start()
    {
        selectedObject = GetComponentInChildren();
        InputModule.instance.HoverBegin(selectedObject.gameObject);
    }

    private void Update()
    {
        Vector2 axis = actionNavigateMenu.GetAxis(SteamVR_Input_Sources.Any);
        Selectable nextSelectableObject = null;

        if (axis.y < -0.1)
        {
            if (actionTouchpadClick.GetStateDown(SteamVR_Input_Sources.Any))
            {
                nextSelectableObject = selectedObject.FindSelectableOnDown();
            }
        }
        else if (axis.y > 0.1)
        {
            if (actionTouchpadClick.GetStateDown(SteamVR_Input_Sources.Any))
            {
                nextSelectableObject = selectedObject.FindSelectableOnUp();
            }
        }

        if (nextSelectableObject)
        {
            InputModule.instance.HoverEnd(selectedObject.gameObject);
            InputModule.instance.HoverBegin(nextSelectableObject.gameObject);
            selectedObject = nextSelectableObject;           
        }

        if (actionConfirm.GetStateDown(SteamVR_Input_Sources.Any))
        {            
            InputModule.instance.Submit(selectedObject.gameObject);
        }
    }
}

These public variables are visible in the editor and they must be set to the action that you want these variables to represent:



Getting the trackpad values 

The trackpad's input will be mapped to the Vector2 class. When you look at the trackpad you can imagine a two-dimensional coordinate system with the origin being in the center of the trackpad: the x axis that runs from the center to left and right and the y axis that runs from the center to top and bottom. Or, as Valve's docs are describing it, a "combination of two analog values". Inputs are clamped to values between -1 to 1 for both axes where the lower half of the trackpad have negative values.

Variable actionNavigateMenu has been assigned the action /actions/helmetgui/in/Navigate which is mapped to the trackpad in the binding UI.

To get the current value we call GetAxis() with the argument SteamVR_Input_Sources_Any which means we are interested in inputs from any controller.

We then check whether the y "touch" value is positive or negative (up or down) with a small threshold of 0.1 to create a little deadzone in the middle of the trackpad.

In either case, we then check for trackpad clicks by calling GetStateDown() of actionTouchpadClick. This variable is mapped to "/actions/helmetgui/in/NavigateClick" in the editor which is bound to the trackpad click in the binding UI.

Select next/previous entry

The nice thing is that a Selectable knows about the next and previous Selectable in the panel so we can just call FindSelectableOnDown to get the next menu entry.

With the InputModule's HoverEnd and HoverBegin methods we can un-highlight the current menu item and hightlight the item that we selected in the previous lines.


Confirming the Selection

With GetStateDown() of actionConfirm we can finally check whether the trigger button has been pressed. Remember, actionConfirm is a SteamVR_Action_Boolean and was bound to the trigger button so we can just check if GetStateDown() returns true.

And that's it! If you found this tutorial helpful, subscribe to my newsletter to be the first to hear about new tutorials. You also get a free demo of Shopkeeper Simulator VR!