Skip to content

Instantly share code, notes, and snippets.

@dpid
Created May 3, 2017 16:18
Show Gist options
  • Save dpid/ab4050dd98a423bc5f14594d71f60d03 to your computer and use it in GitHub Desktop.
Save dpid/ab4050dd98a423bc5f14594d71f60d03 to your computer and use it in GitHub Desktop.
Unity component that follows a target transform using physics.
using UnityEngine;
using System.Collections;
public class PhysicsFollower : MonoBehaviour {
public Transform targetTransform;
public Vector3 positionOffset = Vector3.zero;
public Quaternion rotationOffset = Quaternion.identity;
public bool isFollowing
{
get
{
return (rigidbody != null && targetTransform != null && targetTransform.gameObject.activeInHierarchy);
}
}
private new Rigidbody rigidbody;
private bool isClamping ;
private float velocityClamp;
private float angularVelocityClamp;
private float defaultVelocityClamp = 1.0f;
private float defaultAngularVelocityClamp = 10.0f;
private float fastVelocityClamp = 5.0f;
private float fastAngularVelocityClamp = 10.0f;
public void SetOffset(Vector3 position, Quaternion rotation)
{
positionOffset = position;
rotationOffset = rotation;
}
public void SetOffsetRelativeToTransform(Transform relativeTranform)
{
if (isFollowing == false) return;
positionOffset = targetTransform.InverseTransformPoint(relativeTranform.position);
rotationOffset = Quaternion.Inverse(relativeTranform.rotation) * targetTransform.rotation;
}
private void Start()
{
rigidbody = GetComponent<Rigidbody>();
rigidbody.maxAngularVelocity = Mathf.Infinity;
}
private void OnCollisionStay(Collision collision)
{
if (isFollowing == false) return;
Rigidbody otherRigidbody = collision.collider.GetComponent<Rigidbody>();
if (otherRigidbody == null || otherRigidbody.isKinematic)
{
StartClamping();
}
}
private void OnCollisionExit(Collision collision)
{
if (isFollowing == false) return;
StopClamping();
}
public void ClampToTarget()
{
if (isFollowing == false) return;
StartClamping();
StopClamping();
}
private void StartClamping()
{
if (isFollowing == false) return;
StopAllCoroutines();
isClamping = true;
velocityClamp = defaultVelocityClamp;
angularVelocityClamp = defaultAngularVelocityClamp;
}
private void StopClamping()
{
StopAllCoroutines();
StartCoroutine(StopClampingCoroutine());
}
private IEnumerator StopClampingCoroutine()
{
float loopCount = 0;
float fastClampLoop = 2;
float minDistance = 0.01f;
float distance = Vector3.Distance(transform.position, GetTargetPosition());
while (isFollowing && isClamping && distance > minDistance)
{
distance = Vector3.Distance(transform.position, GetTargetPosition());
loopCount += 1;
if (loopCount == fastClampLoop)
{
velocityClamp = fastVelocityClamp;
angularVelocityClamp = fastAngularVelocityClamp;
}
yield return null;
}
isClamping = false;
}
private void FixedUpdate() {
if (isFollowing == false) return;
Vector3 targetPosition = GetTargetPosition();
Quaternion targetRotation = GetTargetRotation();
Vector3 velocity = (targetPosition - (transform.position)) / Time.fixedDeltaTime;
Quaternion deltaRotation = targetRotation * Quaternion.Inverse(transform.rotation);
float angle = 0.0f;
Vector3 axis = Vector3.zero;
deltaRotation.ToAngleAxis(out angle, out axis);
if (angle >= 180.0f)
{
angle = 360.0f - angle;
axis = -axis;
}
Vector3 angularVelocity = rigidbody.angularVelocity;
if (angle != 0.0f)
{
angularVelocity = angle * axis;
}
if (isClamping)
{
velocity.x = Mathf.Clamp(velocity.x, -velocityClamp, velocityClamp);
velocity.y = Mathf.Clamp(velocity.y, -velocityClamp, velocityClamp);
velocity.z = Mathf.Clamp(velocity.z, -velocityClamp, velocityClamp);
angularVelocity.x = Mathf.Clamp(angularVelocity.x, -angularVelocityClamp, angularVelocityClamp);
angularVelocity.y = Mathf.Clamp(angularVelocity.y, -angularVelocityClamp, angularVelocityClamp);
angularVelocity.z = Mathf.Clamp(angularVelocity.z, -angularVelocityClamp, angularVelocityClamp);
}
rigidbody.velocity = velocity;
rigidbody.angularVelocity = angularVelocity;
}
private Vector3 GetTargetPosition()
{
Vector3 targetPosition = targetTransform.position;
targetPosition += targetTransform.right * positionOffset.x;
targetPosition += targetTransform.up * positionOffset.y;
targetPosition += targetTransform.forward * positionOffset.z;
return targetPosition;
}
private Quaternion GetTargetRotation()
{
Quaternion targetRotation = targetTransform.rotation * Quaternion.Inverse(rotationOffset);
return targetRotation;
}
}
@dpid
Copy link
Author

dpid commented May 3, 2017

A common use case in VR is having a hand pick up some item. To do this, add the PhysicsFollower component to the item and then set the hand as the target transform:

physicsFollower.targetTransform = handTransform;

If you want the picked up item to remain offset to the hand transform, you can set the offset with the SetOffsetRelativeToTransform method:

physicsFollower.SetOffsetRelativeToTransform(transform);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment