Created
May 3, 2017 16:18
-
-
Save dpid/ab4050dd98a423bc5f14594d71f60d03 to your computer and use it in GitHub Desktop.
Unity component that follows a target transform using physics.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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:
If you want the picked up item to remain offset to the hand transform, you can set the offset with the SetOffsetRelativeToTransform method: