With the recent release of our first looks at half-life Alyx I thought it would be a good time to ride those search terms and give you a tutorial on how to do physics-based hands. Now, this is different from my previous tutorial on physics interactions as that code didn’t let the hand collide with stuff. Basically it couldn’t do this:
But it can now:
In all these tests there were no scripts used except for the hand physics, so that climbing was completely physics-based. Imagine how good it will be once we add in a grabbing system in a later tutorial!
Now, what would you say if I told you that this only takes less than 100 lines of code? And you can expand it to a full-body setup or use it for anything else? It’s called calling it the ghost system. (Plz give credit)
The ghost system can be used for any ragdoll that you want to simulate physics but also try to move to a pose. I’ve used it for hands as you see above but I’ve also used it for body tracking like in Boneworks. The basic idea is to have three objects, a target (or ghost), the physics ragdoll, and a common physics root. You first put a Rigidbody on the common root and then connect it to all of the rigidbodies in your ragdoll with a configurable joint. Then you set the target position and rotation of the configurable joint with a script so that they try to move to the positions in your target object. So lets set it up:
Setup
First, as always, get your steamVR [cameraRig] prefab and add a movement script to it. You can use any method you want as long as it uses a rigidbody, not a character controller. Next add some SteamVR skeleton enabled hands as children to your controllers as so:
You want to make sure they have update pose turned off ant that their transforms are zeroed out.
Now to add our physics hands
You want to create a steamVR skeleton enabled hand outside of the camera rig and add a bunch of colliders so that the collision is realistic. It should not look like this when you are done:
You can see where I changed collider type on the fingers for whatever reason, you should probably use consistent colliders on them.
Now add a rigidbody to each hand and a configurable joint set up as so:
Make sure you set the configurable joint to world space, it’s important for the script.
Now we can add in our scripts:
The code
The first set of code we need is something to get rid of the runtime wrist offset animations so our hand will pivot from it. Attach this script to the slim_l bone or equivalent:
using UnityEngine;
public class cancelOffset : MonoBehaviour
{
public Transform root;
public Transform transformToOffset;
// Update is called once per frame
void Update()
{
transform.localRotation = Quaternion.Inverse(transformToOffset.rotation) * transform.rotation;
transform.localPosition = Quaternion.Inverse(root.rotation) * -(transformToOffset.position - transform.position);
}
}
Set the root variable to the root of the hand game object and transformToOffset to the wrist bone.
Next, we have the main script:
using UnityEngine;
public class JointPositioning : MonoBehaviour
{
public ConfigurableJoint joint;
public Transform target;
private Quaternion startRotation;
private void Start()
{
startRotation = transform.rotation;
}
void Update()
{
//joint.connectedAnchor = target.position - GameController.instance.player.CameraRig.position;
//joint.targetRotation = Quaternion.Inverse(target.rotation);
joint.configuredInWorldSpace = true;
SetRotation(joint, target.rotation, startRotation);
joint.connectedAnchor = target.position - joint.connectedBody.position;
}
void SetRotation(ConfigurableJoint joint, Quaternion targetRotation, Quaternion startRotation)
{
var right = joint.axis;
var forward = Vector3.Cross(joint.axis, joint.secondaryAxis).normalized;
var up = Vector3.Cross(forward, right).normalized;
Quaternion worldToJointSpace = Quaternion.LookRotation(forward, up);
Quaternion resultRotation = Quaternion.Inverse(worldToJointSpace);
resultRotation *= startRotation * Quaternion.Inverse(targetRotation);
resultRotation *= worldToJointSpace;
joint.targetRotation = resultRotation;
}
Add this to the physics hand, set joint to the configurable joint we just set up and target to the corresponding wrist bone on the controller’s hand model.
It should all work now if you test it out. You will see two hands on each hand one is the target hand, the other is the physics hand. Just disable that mesh renderer for the non-physics hand and you should be good to go. If it seems jittery try adding a collider to the root object of your hands (the one with the rigidbody) I found that it greatly increased the stability. You can also try lowering your fixed time stamp to 0.111111, by doing this you are changing physics from 60 to 90 times a second.
And that’s it, that’s all you need to have physics-based hands in your game. Hopefully, I’ll get around to making a second tutorial soon that explains how to add in grabbing using this system. (Hint for the road, just use fixed joints for grabbing)
Now THIS is exactly what I was looking for. You sir are a legend and I’ll be bookmarking your blog and subscribing to your youtube channel for this! Keep up the good work 🙂
At the start of the post, you say you will be adding a grabbing system. Any chance you can go in-depth on that one once it’s released? I’m trying to make a physics-based grabbing system but for the Oculus Quest Hand Tracking SDK but my newbie uni student skills are dragging me back on that since I’m trying to take a “check if you got a grip firm” to pick up the objects instead of just checking if the fingers are touching it and lifting it up, although this one has helped already quite a lot! I’ll definitely subscribe to your Patreon page to help out 🙂
I can’t get the code to work, because in the main script the variable ‘offset’ is not defined. Can you help?
Apart that this is bugged because there is an “offset” undeclared variable
those super exagerated values make this system go super slow because is hard for the physics system to get rid of it