Grabbable rigidbodies
In this guide, I’ll walk you through creating grabbable rigidbody objects with Normcore.
Before we get started, you’ll want to download this project we put together for this guide. It includes a basic environment with some grabbable cubes, and single player VR support.
Go ahead and open the project - at the time of writing this guide, we’re using 2019.1.14, so you’ll need that version or later.
A quick overview of the project
Go ahead and open the main scene under Main.unity in your project inspector. The scene contain a basic environment with a floor and skybox, as well as some colored cubes resting on a table. The cubes may not look like much, but they are our grabbable rigidbodies!
If you look at the inspector for any of the coloured cubes by selecting one in the Hierarchy under Grabbable Cubes > Cube, you’ll notice each one contains Cube component–the first half of our logic.
Our scene also contains two Cube Grabber objects - one nested under each player hand. You can see them for yourself by selecting one under Player > Left/Right Hand > Cube Grabber in the Hierarchy. You’ll see each one has a matching Cube Grabber component added to it – this is where the second half of our logic lives. This Cube Grabber component interacts with the Cube component living on each Cube, and vice-versa.
Now that we’ve taken a look at the project hierarchy, let’s dive into how the Cube Grabber and Cube components interact with each other.
CubeGrabber
Below is the script you’ll have in your project under CubeGrabber.cs
. Feel free to skim through it, and we’ll break it down step-by-step after the excerpt. If you’re following along in the editor, open the file in Visual Studio to reference.
// CubeGrabber.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
public class CubeGrabber : MonoBehaviour {
//// Class ////
// A list of all grabbable objects.
private static HashSet<Cube> _cubes = new HashSet<Cube>();
public static void RegisterCube(Cube cube) {
_cubes.Add(cube);
}
public static void UnregisterCube(Cube cube) {
_cubes.Remove(cube);
}
//// Instance ////
// Keep track of which hand we want to watch.
private enum Hand { LeftHand, RightHand };
[SerializeField] private Hand _hand;
// Whether or not we're grabbing.
private bool _triggerPressed;
// Information on the current cube we're holding & controller velocity
private Cube _activeCube;
private Vector3 _grabPositionOffset;
private Quaternion _grabRotationOffset;
// Current controller pose data. Updated in UpdatePose().
private Vector3 _lastValidControllerVelocity;
private Vector3 _lastValidControllerAngularVelocity;
void Update() {
XRNode handNode = _hand == Hand.LeftHand ? XRNode.LeftHand : XRNode.RightHand;
string trigger = _hand == Hand.LeftHand ? "Left Trigger" : "Right Trigger";
// Get velocity & angular velocity of the hand.
bool handIsTracking = UpdatePose(handNode, ref _lastValidControllerVelocity, ref _lastValidControllerAngularVelocity);
// Determine if we're pressing the trigger far enough to activate grabbing.
bool triggerPressed = Input.GetAxisRaw(trigger) > 0.8f;
bool triggerBegan = triggerPressed && !_triggerPressed;
bool triggerActive = triggerPressed;
bool triggerEnded = !triggerPressed && _triggerPressed;
// If we start holding the trigger and aren't already holding a cube, start grabbing it.
if (triggerBegan && _activeCube == null) {
Cube cubeToGrab = GetClosestGrabbableCube();
// Found a valid cube, let's grab it!
if (cubeToGrab != null) {
_activeCube = cubeToGrab;
_activeCube.BeginGrabbing();
// Calculate grab offset.
_grabPositionOffset = transform.InverseTransformPoint(_activeCube.transform.position);
_grabRotationOffset = Quaternion.Inverse(transform.rotation) * _activeCube.transform.rotation;
}
}
// If we're currently holding a cube, position it.
if (triggerActive && _activeCube != null) {
// Position the cube
PositionActiveCube();
}
// If we let go of the trigger with an active cube, drop it.
if (triggerEnded && _activeCube != null) {
// Calculate cube throw velocity. Take into account the offset from the controller.
Vector3 cubeVelocity = _lastValidControllerVelocity + Vector3.Cross(_lastValidControllerAngularVelocity, transform.rotation * _grabPositionOffset);
Vector3 cubeAngularVelocity = _lastValidControllerAngularVelocity;
// Throw cube.
_activeCube.EndGrabbing(cubeVelocity, cubeAngularVelocity);
_activeCube = null;
}
// Update cached trigger value.
_triggerPressed = triggerPressed;
}
private Cube GetClosestGrabbableCube() {
Cube cubeToGrab = null;
float cubeToGrabDistance = float.MaxValue;
foreach (Cube cube in _cubes) {
// Skip cubes that have already been grabbed.
if (cube.grabbed) continue;
float distance = cube.DistanceToCubeSurface(transform.position);
// Skip if we're not colliding with the cube.
if (distance > 0.0f) continue;
// Replace cubeToGrab if it's closer than any previously seen cube.
if (distance < cubeToGrabDistance) {
cubeToGrab = cube;
cubeToGrabDistance = distance;
}
}
return cubeToGrab;
}
// Position the active cube.
private void PositionActiveCube() {
// Calculate the cube position, including delta grab offset.
Vector3 cubePosition = transform.TransformPoint(_grabPositionOffset);
Quaternion cubeRotation = transform.rotation * _grabRotationOffset;
// Position the cube.
_activeCube.PositionCube(cubePosition, cubeRotation);
}
private static bool UpdatePose(XRNode node, ref Vector3 velocity, ref Vector3 angularVelocity) {
// Update velocity & angular velocity with the given XRNode via XRNodeState
List<XRNodeState> nodeStates = new List<XRNodeState>();
InputTracking.GetNodeStates(nodeStates);
foreach (XRNodeState nodeState in nodeStates) {
if (nodeState.nodeType == node) {
// Matches the hand we're looking for - let's grab its velocity.
Vector3 nodeVelocity;
Vector3 nodeAngularVelocity;
bool gotVelocity = nodeState.TryGetVelocity(out nodeVelocity);
bool gotAngularVelocity = nodeState.TryGetAngularVelocity(out nodeAngularVelocity);
if (gotVelocity)
velocity = nodeVelocity;
if (gotAngularVelocity)
angularVelocity = nodeAngularVelocity;
return gotVelocity;
}
}
return false;
}
}
So firstly, our CubeGrabber
implementation defines some static members, used to create a single registry of grabbable cubes across both grabbers. These members are:
_cubes
, aHashSet<Cube>
property for holding the references to each registered cubeRegisterCube(Cube cube)
andUnregisterCube(Cube cube)
, functions called by each Cube to register or unregister itself from the grabbers
After our class members are the instance members, for holding state related to each independent grabber.
- an internal
Hand
enum with typesLeftHand
andRightHand
for representing either the left or right hand _hand
for setting whichHand
the grabber belongs to_triggerPressed
for denoting whether the appropriate trigger is currently being pressed_activeCube
for storing the currently grabbedCube
, if one is being held_grabPositionOffset
and_grabRotationOffset
for storing the position and rotation offset of the_activeCube
if there is one
Finally, our instance methods make up the rest of the file, defining the logic for checking to see if we need to start grabbing cubes, as well as position the currently grabbed Cube each frame (if there is one).
- An
UpdatePose
method: used to grab the current velocity & angular velocity of the appropriate controller with the XRNodeState APIs GetClosestGrabbableCube()
: returns the closest valid grabbable cube relative to the respective grabber positionPositionActiveCube()
: used to position the cube being actively grabbed, if there is one- Our
Update
loop: determine on each frame if we should start, continue, or stop grabbing a cube. Uses ourUpdatePose
,GetClosestGrabbableCube
, andPositionActiveCube
methods
That about sums up the CubeGrabber
implementation. Now, let’s take a quick look at the Cube implementation.
Cube.cs
Below is our Cube script - like before, feel free to follow along in Visual Studio for reference when reading the member descriptions.
// Cube.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Cube : MonoBehaviour {
[SerializeField] private float _grabRadius = 0.25f;
public bool grabbed { get; private set; }
private Rigidbody _rigidbody;
// Unity events
private void Awake() {
_rigidbody = GetComponent<Rigidbody>();
}
private void OnEnable() {
CubeGrabber.RegisterCube(this);
}
private void OnDisable() {
CubeGrabber.UnregisterCube(this);
}
// Used to verify we're actually colliding with the cube at the given grabPosition.
public float DistanceToCubeSurface(Vector3 grabPosition) {
return Vector3.Distance(grabPosition, transform.position) - _grabRadius;
}
// Grab events
public void BeginGrabbing() {
// Mark as grabbed.
grabbed = true;
// Mark the rigidbody as kinematic.
_rigidbody.isKinematic = true;
}
public void PositionCube(Vector3 position, Quaternion rotation) {
// Move rigidbody
_rigidbody.MovePosition(position);
_rigidbody.MoveRotation(rotation);
}
public void EndGrabbing(Vector3 velocity, Vector3 angularVelocity) {
// Mark the rigidbody as non-kinematic
_rigidbody.isKinematic = false;
_rigidbody.velocity = velocity;
_rigidbody.angularVelocity = angularVelocity;
// Mark as free to grab again.
grabbed = false;
}
}
Our Cube class has a few key pieces of functionality. Firstly, you’ll see there’s a _grabRadius
that you can configure for determining an appropriate radius for your grabbable objects. Feel free to adjust this if you’d like, but we’ll keep it at 0.25
.
On Awake()
and OnEnable()
, each Cube
registers with the CubeGrabber
s in our scene, by calling the static method CubeGrabber.RegisterCube
and passing itself. It also caches a reference to its own Rigidbody as _rigidbody
, so it can disable or enable gravity when grabbing begins or ends.
That sums up the Cube implementation - it’s much more straightforward than the Cube Grabber, but equally as important! Feel free to hit Play and put on your VR headset to pick up some cubes and try it out.
Check out that sweet cube-grabbing action. Nice.
Multiplayer integration
To get this working with Normcore, we’ll only need a few additions. First, of course, be sure to download the latest version of Normcore from the Dashboard, and go ahead and import it into your project.
Import the latest version of Normcore into your project.
With Normcore imported, head to the Normal > Examples > VR Player folder in your project Hierarchy and drag a Realtime + VR Player prefab into your scene. Paste in an app key from the Dashboard, and set a room name. While we’re here, we can hook up our local player avatar to Realtime Avatar Manager, by dragging in the root Player, Head, Left Hand, and Right Hand objects into the appropriate fields.
Add a Realtime + VR Player prefab into your scene, and hook up your local Player avatar to the Realtime Avatar Manager.
With Normcore set up with our player, we’ll need to do one last thing and network our cubes. We can do this by simply adding a Realtime Transform component to each Cube
. This will add both the Realtime Transform and Realtime View components to the Cube, and Normcore will automatically synchronize the transform of the cubes for all players.
Add a Realtime Transform component to each of your grabbable cubes.
Now, we need to make one last modification before we can call the project complete. With a Realtime Transform component on each Cube
, we need to be sure to request ownership of the transform when grabbing it. Let’s do so now with a few additions to Cube.cs
: caching a reference of our RealtimeTransform
, as well as calling RequestOwnership()
in BeginGrabbing
.
// Cube.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Normal.Realtime;
public Class Cube : MonoBehaviour {
// ...
private Rigidbody _rigidbody;
private RealtimeTransform _realtimeTransform;
// Unity Events
private void Awake() {
_rigidbody = GetComponent<Rigidbody>();
_realtimeTransform = GetComponent<RealtimeTransform>();
}
// ...
public void BeginGrabbing() {
// ...
// Request ownership from the transform.
_realtimeTransform.RequestOwnership();
}
}
Awesome. With that settled, our cubes should be properly networked. Our implementation works the same as it does without Normcore: when we grab a cube, our CubeGrabber
will set the Cube
rigidbody to kinematic, allowing our user to position the cube freely. Then, when the user lets go of the Cube
, the rigidbody is marked as non-kinematic again, allowing the Cube
to fall and come to rest. The only Normcore changes necessary were adding RealtimeTransform
to each Cube
, and requesting ownership of the appropriate RealtimeTransform
on grab.
Feel free to build & run the project and send it to a friend - you should both be able to grab the cubes in VR and toss them around to one another. It’s pretty basic in its current form, but does a great job at illustrating how to handle user interaction with networked objects in Normcore!