VR Controller Input in Godot
Preface
For more information about C# in Godot:
https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_basics.html
Getting up to speed with C# in Godot: our wiki page.
Getting Started
Assuming you have followed the previous tutorial and confirmed VR is working with controller visuals (even cubes) and OpenXR, you can get started.
By default, after installing OpenXR within Godot enables a default OpenXR Action Map. Typically, I leave these defaults alone. There is a good article in the documentation that reviews how to create your own custom inputs, but these work great.
The event structure on how you process this input is up to you! Below is an EXAMPLE but should be structured!
1. Create subscription events for ButtonPressed and ButtonReleased.
Create a script on your XRController3D node (the VR controller) and the script should inherit from XRController3D.
Inside the script, create two Godot methods: EnterTree() and Exit Tree(). Add ButtonPressed and ButtonReleased and subscribe to a new method. Here is the layout:
C#
public override void _EnterTree()
{
ButtonPressed += OnButtonPressed;
ButtonReleased += OnButtonReleased;
}
GDScript
func _enter_tree() -> void:
button_pressed.connect(on_button_pressed)
button_released.connect(on_button_released)
Now, implement the two methods, OnButtonPressed and OnButtonReleased:
C#
void OnButtonPressed(string name)
{
}
void OnButtonReleased(string name)
{
}
GDScript
func on_button_pressed(btn_name: String) -> void:
pass
func on_button_released(btn_name: String) -> void:
pass
2. Process events
You will notice on the methods created above that there is a parameter called string name. This string is what is received from your OpenXR Action Map (or anything else with a button). For me, I have added a switch statement to process these inputs and invoke methods:
C#
void OnButtonPressed(string name)
{
switch (name)
{
case "grip_click":
OnGripPressed();
break;
case "trigger_click":
OnTriggerPressed();
break;
case "ax_button":
OnAXPressed();
break;
case "by_button":
OnBYPressed();
break;
}
}
void OnButtonReleased(string name)
{
switch (name)
{
case "grip_click":
OnGripReleased();
break;
case "trigger_click":
OnTriggerReleased();
break;
case "ax_button":
OnAXReleased();
break;
case "by_button":
OnBYReleased();
break;
}
}
GDScript
func on_button_pressed(btn_name: String) -> void:
match btn_name:
"grip_click":
on_grip_pressed()
"trigger_click":
on_trigger_pressed()
"ax_button":
on_ax_pressed()
"by_button":
on_by_pressed()
func on_button_released(btn_name: String) -> void:
match btn_name:
"grip_click":
on_grip_released()
"trigger_click":
on_trigger_released()
"ax_button":
on_ax_released()
"by_button":
on_by_released()
I'm not including every single method, but you can see that I now have a system to process input!
3. Expand (with some Object Oriented Programming)!!
Obviously, this script would be a pain to copy for both hands and alter the names of the methods slightly. You can do this if you wish to keep it simple.
Object Oriented Programming (OOP):
For me, I created a parent script called something like XRHand. Then, I created two scripts called LeftHand and RightHandthat are children of XRHand. Using the methods above, I made them virtual so in my LeftHand script for example, I can configure special implementation.
Here is the final script for XRHand:
C#
using Godot;
using System;
public partial class XRHand : XRController3D
{
public override void _EnterTree()
{
ButtonPressed += OnButtonPressed;
ButtonReleased += OnButtonReleased;
InputFloatChanged += OnInputFloatChanged;
InputVector2Changed += OnInputVector2Changed;
}
public override void _ExitTree()
{
ButtonPressed -= OnButtonPressed;
ButtonReleased -= OnButtonReleased;
InputFloatChanged -= OnInputFloatChanged;
InputVector2Changed -= OnInputVector2Changed;
}
void OnButtonPressed(string name)
{
switch (name)
{
case "grip_click":
OnGripPressed();
break;
case "trigger_click":
OnTriggerPressed();
break;
case "ax_button":
OnAXPressed();
break;
case "by_button":
OnBYPressed();
break;
}
}
void OnButtonReleased(string name)
{
switch (name)
{
case "grip_click":
OnGripReleased();
break;
case "trigger_click":
OnTriggerReleased();
break;
case "ax_button":
OnAXReleased();
break;
case "by_button":
OnBYReleased();
break;
}
}
void OnInputFloatChanged(string name, double value)
{
}
void OnInputVector2Changed(string name, Vector2 value)
{
switch (name)
{
case "primary":
OnThumbstickMoved(value);
break;
}
}
// Digital Input
public virtual void OnGripPressed() { }
public virtual void OnGripReleased() { }
public virtual void OnTriggerPressed() { }
public virtual void OnTriggerReleased() { }
public virtual void OnAXPressed() { }
public virtual void OnAXReleased() { }
public virtual void OnBYPressed() { }
public virtual void OnBYReleased() { }
// Axial-1D Input
// Axial-2D Input
public virtual void OnThumbstickMoved(Vector2 value) { }
// Signals
}
GDScript
class_name XRHand
extends XRController3D
func _enter_tree() -> void:
button_pressed.connect(on_button_pressed)
button_released.connect(on_button_released)
func on_button_pressed(btn_name: String) -> void:
match btn_name:
"grip_click":
on_grip_pressed()
"trigger_click":
on_trigger_pressed()
"ax_button":
on_ax_pressed()
"by_button":
on_by_pressed()
func on_button_released(btn_name: String) -> void:
match btn_name:
"grip_click":
on_grip_released()
"trigger_click":
on_trigger_released()
"ax_button":
on_ax_released()
"by_button":
on_by_released()
func on_grip_pressed():
pass
func on_trigger_pressed():
pass
func on_ax_pressed():
pass
func on_by_pressed():
pass
func on_grip_released():
pass
func on_trigger_released():
pass
func on_ax_released():
pass
func on_by_released():
pass
And as for LeftHand or RightHand, I would just override a button that I would need:
C#
using Godot;
using System;
public partial class LeftHand : XRHand
{
public override void OnGripPressed()
{
GD.Print("Left grip released!");
}
public override void OnGripReleased()
{
GD.Print("Left grip released!");
}
}
GDScript
extends XRHand
func on_grip_pressed():
print("Left grip pressed!")
func on_grip_released():
print("Left grip released")
The LeftHand and RightHand would now go on their respective XRController3D nodes, in the scene.