Thursday, 16 March 2017

Xbox-One Analog Thumbstick mapping in Unity C#
Lately, I started working on a project where have to use Xbox-One controller that comes with Oculus Rift. I searched a bit to understand the key mapping of the controller in Unity and how to make it work properly especially the analog thumbstick and the angle calculation. Though most of the search results took me to Xbox 360 mapping. But all of those somehow gave me an idea to start.

After spending a few hours and many tries finally figured out how it can work properly in a manner needed. The good thing, I came across was this Controller Test package in Unity which helps you to see the exact mapping of a controller, also it adds up the settings in Editor--> Input Settings. Which apparently Unity also does as soon as you connect Xbox-One (Oculus_GearVR_***). 

I will not talk about how Controller Test package works, as its pretty simple to understand and can be found on this link: Controller Test Asset store

This post is just to provide you a virtual joystick in case you want to use it in your project, prior knowledge of Unity UI and C# is expected:

Step 1:
Check your Editor-->Input Settings
This step is important before you start writing your code for any device input.  Check if your attached Xbox One controller device has all the Inputs added into the list. If yes, then look for the ones that you want to map. 

In this article, I am focusing on only Xbox-One:
1.Oculus_GearVR_LThumbstickX
2.Oculus_GearVR_LThumbstickY
3.Oculus_GearVR_LIndexTrigger
4.Oculus_GearVR_RIndexTrigger

The proper settings in order to get desired results would be 
  •  Oculus_GearVR_LThumbstickX (X-axis )
  •  Oculus_GearVR_LThumbstickY (Y-axis)
  •  Gravity 0, Dead 0.5, Sensitivity 1
  •  Snap and Invert should be unchecked for both.   
Note: The implementation for Left Thumbstick can also work for Right Thumbstick, just set the right properties in the Input settings and use  Oculus_GearVR_RThumbstickX Oculus_GearVR_RThumbstickY

Step 2:
Make a pretty UI for your virtual joystick, by using any circular image for a base pad and a handler above it which can be rotated in Z-axis to show the angle. I have set up the UI in this way.
Screenshot for UI setup - Unity Editor

Pad is the base of the joystick on which the "Pointer" will be rotating to show the angle. But the "Pointer" should be only able to rotate in reference to a pivot point which is set on the center of the Pad. That's why the hierarchy and the position of the Pointer are important, as it can be seen from the image,  Pad--> Pivot--> Bar & Pointer. For further details of the setup, you can also check the Unity project linked.

Step 3:
Attach the "JoyStickController" script to the main JoyStick game object. And the set the properties accordingly.

Set enum states for which controller input to use.
Output:
Now, run the scene and you will get an output like this either you use arrow keys left and right, xbox Left thumb stick or Xbox trigger buttons.
Output of virtual joystick at different angles.

Source project:
https://github.com/asemahassan/XboxOneController.git



Complete Code:

public enum JSControllerState
{
 None = -1,
 Arrow_Keys,
 XboxOne_AnalogThumbstick,
 XboxOne_TriggerButtons
}

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class JoystickController : MonoBehaviour
{
 public JSControllerState _controllerState = JSControllerState.None;
 /// <summary>
 /// The user response active can be toggled from any script
 /// To control the input from user
 /// </summary>
 public static bool _userResponseActive = false;
 /// <summary>
 /// The user response angle stores the final angle 
 /// that user has pointed at using joystick
 /// </summary>
 public static float _userResponseAngle = 0f;

 private static RectTransform knobTransform = null;
 [SerializeField]
 private GameObject pivotHandle = null;
 [SerializeField]
 private Text angleValueText = null;
 [SerializeField]
 private float stepAmount = 1.0f;
 private static Vector3 rotationEuler;

 // Use this for initialization
 void Start ()
 {
  if (pivotHandle != null) {
   knobTransform = pivotHandle.GetComponent<RectTransform> ();
  }

  //For testing as its only one class you can set it here to true
  _userResponseActive = true;
   
 }

 // Update is called once per frame
 void Update ()
 {
  if (!_userResponseActive)
   return;

  switch (_controllerState) {
  case JSControllerState.None:
   {
    Debug.LogError ("Please select a state from JSControllerState");
    #if UNITY_EDITOR
    UnityEditor.EditorApplication.isPlaying = false;
    #endif
    break;
   }
  case JSControllerState.Arrow_Keys:
   {
    UseArrowKeys ();
    LockPlayerResponse ();
    break;
   }
  case JSControllerState.XboxOne_AnalogThumbstick:
   {
    UseAnalogStick ();
    LockPlayerResponse ();
    break;
   }

  case JSControllerState.XboxOne_TriggerButtons:
   {
    UseTriggerButtons ();
    LockPlayerResponse ();
    break;
   }
  default:
   break;
  }

  //If you want to ResetAngle at some point, can call ResetJoyStickAngle() method
  if (Input.GetKeyUp (KeyCode.R)) {
   ResetJoyStickAngle ();
  }
 
 }

 /// <summary>
 /// Uses the arrow keys.
 /// When testing in Editor without XboxOne controller can use this method.
 /// </summary>
 private void UseArrowKeys ()
 {
  if (Input.GetKey (KeyCode.LeftArrow)) {
   rotationEuler += Vector3.forward * stepAmount;
  }

  if (Input.GetKey (KeyCode.RightArrow)) {
   rotationEuler -= Vector3.forward * stepAmount;
  }

  knobTransform.transform.rotation = Quaternion.Euler (rotationEuler);
  float angle = 360 - knobTransform.rotation.eulerAngles.z;
  if (angleValueText != null) {
   angleValueText.text = angle.ToString ();
  }
  _userResponseAngle = angle;

 }

 /// <summary>
 /// Uses the trigger buttons to set angle in Z.
 /// Virtual joystick will be updated accordingly
 /// </summary>
 private void UseTriggerButtons ()
 {
  if (Input.GetAxis ("Oculus_GearVR_LIndexTrigger") > 0) {
   rotationEuler += Vector3.forward * stepAmount;
  }
   
  if (Input.GetAxis ("Oculus_GearVR_RIndexTrigger") > 0) {
   rotationEuler -= Vector3.forward * stepAmount;
  }

  knobTransform.transform.rotation = Quaternion.Euler (rotationEuler);
  float angle = 360 - knobTransform.rotation.eulerAngles.z;

  if (angleValueText != null)
   angleValueText.text = angle.ToString ();
  
  _userResponseAngle = angle;
   
 }

 /// <summary>
 /// Uses the left analoque stick to get the user response angle 
 /// Mostly effective when asking user for directional task
 /// </summary>
 private void UseAnalogStick ()
 {
  /*Input Settings:
         * Oculus_GearVR_LThumbstickX (X axis )&& Oculus_GearVR_LThumbstickY (Y axis)
         *  Gravity 0, Dead 0.5, Sensitivity 1
         * Snap/Invert all should be false
         */

  float x = Input.GetAxis ("Oculus_GearVR_LThumbstickX");
  float y = Input.GetAxis ("Oculus_GearVR_LThumbstickY");

  if (x != 0.0f || y != 0.0f) {
   float angle = Mathf.Atan2 (x, y) * Mathf.Rad2Deg;
   angle = (180.0f - angle);
   if (angleValueText != null)
    angleValueText.text = angle.ToString ();
   
   _userResponseAngle = angle;
   // multiplying  angle with -1 to show the correct orientation of knob on virtual joystick
   knobTransform.transform.rotation = Quaternion.Euler (new Vector3 (0, 0, -1 * angle));
  }
 }

 /// <summary>
 /// Locks the player response.
 /// </summary>
 private void LockPlayerResponse ()
 {
  #if UNITY_EDITOR
  if (Input.GetKeyUp (KeyCode.L)) {
   Debug.Log ("User response angle locked: " + _userResponseAngle);
   _userResponseActive = false;
   ResetJoyStickAngle ();
  }
  #else   
  if(Input.GetButton("Oculus_GearVR_AButton"))
  {
  Debug.Log("User response angle locked: " + _userResponseAngle);
  _userResponseActive = false;
  ResetJoyStickAngle();
  }    
  #endif
 }

 /// <summary>
 /// Resets the joy stick angle
 /// Resets the _knobTransform eulerAngles back to zero
 /// </summary>
 public static void ResetJoyStickAngle ()
 {
  rotationEuler = Vector3.zero;
  Quaternion tempQuat = knobTransform.transform.rotation;
  tempQuat.eulerAngles = Vector3.zero;
  knobTransform.transform.rotation = tempQuat;
 }
}

I am Asema Hassan, a Software Engineer and an AI researcher. I have been working in games industry since Feb 2012.

Contact
Asema Hassan
Magdeburg, Germany