using System.Collections; using System.Collections.Generic; using UnityEngine; using Autohand.Demo; using System; using NaughtyAttributes; using UnityEngine.Serialization; #if UNITY_EDITOR using UnityEditor; #endif namespace Autohand { public enum RotationType { snap, smooth } public delegate void AutoHandPlayerEvent(AutoHandPlayer player); [RequireComponent(typeof(Rigidbody)), RequireComponent(typeof(CapsuleCollider)), DefaultExecutionOrder(1)] [HelpURL("https://app.gitbook.com/s/5zKO0EvOjzUDeT2aiFk3/auto-hand-3.1/auto-hand-player")] public class AutoHandPlayer : MonoBehaviour { static bool notFound = false; public static AutoHandPlayer _Instance; public static AutoHandPlayer Instance { get { if(_Instance == null && !notFound) _Instance = AutoHandExtensions.CanFindObjectOfType(); if(_Instance == null) notFound = true; return _Instance; } } [AutoHeader("Auto Hand Player")] public bool ignoreMe; [Tooltip("The tracked headCamera object")] public Camera headCamera; [Tooltip("The object that represents the forward direction movement, usually should be set as the camera or a tracked controller")] public Transform forwardFollow; [Tooltip("This should NOT be a child of this body. This should be a GameObject that contains all the tracked objects (head/controllers)")] public Transform trackingContainer; public Hand handRight; public Hand handLeft; [AutoToggleHeader("Movement")] public bool useMovement = true; [EnableIf("useMovement"), FormerlySerializedAs("moveSpeed")] [Tooltip("Movement speed when isGrounded")] public float maxMoveSpeed = 1.5f; [EnableIf("useMovement")] [Tooltip("Movement acceleration when isGrounded")] public float moveAcceleration = 10f; [EnableIf("useMovement")] [Tooltip("Whether or not to use snap turning or smooth turning"), Min(0)] public RotationType rotationType = RotationType.snap; [Tooltip("turn speed when not using snap turning - if snap turning, represents angle per snap")] public float snapTurnAngle = 30f; public float smoothTurnSpeed = 10f; [AutoToggleHeader("Height")] public bool showHeight = true; [ShowIf("showHeight"), Tooltip("Smooths camera upward movement when stepping up")] public float heightSmoothSpeed = 20f; [ShowIf("showHeight")] public float heightOffset = 0f; [ShowIf("showHeight")] public bool crouching = false; [ShowIf("showHeight")] public float crouchHeight = 0.6f; [ShowIf("showHeight")] [Tooltip("Whether or not the capsule height should be adjusted to match the headCamera height")] public bool autoAdjustColliderHeight = true; [ShowIf("showHeight")] [Tooltip("Minimum and maximum auto adjusted height, to adjust height without auto adjustment change capsule collider height instead")] public Vector2 minMaxHeight = new Vector2(0.5f, 2.5f); [ShowIf("showHeight")] public bool useHeadCollision = true; [ShowIf("showHeight")] public float headRadius = 0.15f; [AutoToggleHeader("Use Grounding")] public bool useGrounding = true; [EnableIf("useGrounding"), Tooltip("Maximum height that the body can step up onto"), Min(0)] public float maxStepHeight = 0.3f; [EnableIf("useGrounding"), Tooltip("Maximum angle the player can walk on"), Min(0)] public float maxStepAngle = 30f; [EnableIf("useGrounding"), Tooltip("The layers that count as ground")] public LayerMask groundLayerMask; [EnableIf("useGrounding"), Tooltip("Movement acceleration when isGrounded")] public float groundedDrag = 4f; [Tooltip("Movement acceleration when grounding is disabled")] public float flyingDrag = 4f; [AutoToggleHeader("Enable Climbing")] [Tooltip("Whether or not the player can use Climbable objects (Objects with the Climbable component)")] public bool allowClimbing = true; [Tooltip("Whether or not the player move while climbing")] [ShowIf("allowClimbing")] public bool allowClimbingMovement = true; [Tooltip("How quickly the player can climb")] [ShowIf("allowClimbing")] public Vector3 climbingStrength = new Vector3(20f, 20f, 20f); public float climbingAcceleration = 30f; public float climbingDrag = 5f; [Tooltip("Inscreases the step height while climbing up to make it easier to step up onto a surface")] public float climbUpStepHeightMultiplier = 1; [AutoToggleHeader("Enable Pushing")] [Tooltip("Whether or not the player can use Pushable objects (Objects with the Pushable component)")] public bool allowBodyPushing = true; [Tooltip("How quickly the player can climb")] [EnableIf("allowBodyPushing")] public Vector3 pushingStrength = new Vector3(10f, 10f, 10f); public float pushingAcceleration = 10f; public float pushingDrag = 3f; [Tooltip("Inscreases the step height while pushing up to make it easier to step up onto a surface")] public float pushUpStepHeightMultiplier = 1; [AutoToggleHeader("Enable Platforming")] [Tooltip("Platforms will move the player with them. A platform is an object with the Transform component on it")] public bool allowPlatforms = true; [EnableIf("useGrounding"), Tooltip("The layers that platforming will be enabled on, will not work with layers that the HandPlayer can't collide with")] public LayerMask platformingLayerMask = ~0; public AutoHandPlayerEvent OnSnapTurn; public AutoHandPlayerEvent OnTeleported; [HideInInspector] public float movementDeadzone = 0.1f; [HideInInspector] public float turnDeadzone = 0.4f; public const string HandPlayerLayer = "HandPlayer"; public CapsuleCollider bodyCollider { get { return bodyCapsule; } } public Rigidbody body { get; protected set; } protected float turnResetzone = 0.3f; protected float groundedOffset = 0.1f; protected HeadPhysicsFollower headPhysicsFollower; protected Vector3 moveDirection; protected float turningAxis; protected Vector3 climbAxis; protected Dictionary climbing = new Dictionary(); protected Dictionary pushRight = new Dictionary(); protected Dictionary pushRightCount = new Dictionary(); protected Dictionary pushLeft = new Dictionary(); protected Dictionary pushLeftCount = new Dictionary(); protected Vector3 pushAxis; protected CapsuleCollider bodyCapsule; protected Hand lastRightHand; protected Hand lastLeftHand; protected Collider[] colliderNonAlloc = new Collider[50]; bool isGrounded = false; bool axisReset = true; float playerHeight = 0; bool lastCrouching; float lastCrouchingHeight; Vector3 targetTrackedPos; Vector3 lastUpdatePosition; bool tempDisableGrounding = false; bool editorSelected; Vector3 lastPlatformPosition; Quaternion lastPlatformRotation; RaycastHit closestHit; Vector3 targetPosOffset; Vector3 offset; RaycastHit newClosestHit; float highestPoint; int handPlayerMask; public virtual void Awake() { if(_Instance == null) { _Instance = this; notFound = false; } lastUpdatePosition = transform.position; gameObject.layer = LayerMask.NameToLayer(HandPlayerLayer); bodyCapsule = GetComponent(); bodyCapsule.material = Resources.Load("NoFriction"); body = GetComponent(); body.interpolation = RigidbodyInterpolation.None; body.freezeRotation = true; if(body.collisionDetectionMode == CollisionDetectionMode.Discrete) body.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic; if(forwardFollow == null) forwardFollow = headCamera.transform; targetTrackedPos = trackingContainer.position; if(useHeadCollision) CreateHeadFollower(); } public virtual void Start() { StartCoroutine(CheckForTrackingStart()); handPlayerMask = AutoHandExtensions.GetPhysicsLayerMask(gameObject.layer); #if UNITY_EDITOR if (Selection.activeGameObject == gameObject) { Selection.activeGameObject = null; Debug.Log("Auto Hand: highlighting hand component in the inspector can cause lag and quality reduction at runtime in VR. (Automatically deselecting at runtime) Remove this code at any time.", this); editorSelected = true; } Application.quitting += () => { if (editorSelected && Selection.activeGameObject == null) Selection.activeGameObject = gameObject; }; #endif } protected virtual void OnEnable() { EnableHand(handRight); EnableHand(handLeft); } protected virtual void OnDisable() { DisableHand(handRight); DisableHand(handLeft); } bool trackingStarted = false; Vector3 lastHeadPos; IEnumerator CheckForTrackingStart() { yield return new WaitForEndOfFrame(); yield return new WaitForFixedUpdate(); lastHeadPos = headCamera.transform.position; while(!trackingStarted) { if(headCamera.transform.position != lastHeadPos) { //OnHeadTrackingStarted(); trackingStarted = true; } lastHeadPos = headCamera.transform.position; yield return new WaitForEndOfFrame(); } } protected virtual void OnHeadTrackingStarted() { SetPosition(transform.position); } void CreateHeadFollower() { if(headPhysicsFollower == null) { var headFollower = new GameObject().transform; headFollower.transform.position = headCamera.transform.position; headFollower.name = "Head Follower"; headFollower.parent = transform.parent; var col = headFollower.gameObject.AddComponent(); col.material = bodyCapsule.material; col.radius = bodyCapsule.radius; var headBody = headFollower.gameObject.AddComponent(); headBody.drag = 5; headBody.angularDrag = 5; headBody.freezeRotation = false; headBody.mass = body.mass / 3f; headPhysicsFollower = headFollower.gameObject.AddComponent(); headPhysicsFollower.headCamera = headCamera; headPhysicsFollower.followBody = transform; headPhysicsFollower.trackingContainer = trackingContainer; headPhysicsFollower.Init(); } } void CheckHands() { if(lastLeftHand != handLeft) { EnableHand(handLeft); lastLeftHand = handLeft; } if(lastRightHand != handRight) { EnableHand(handRight); lastRightHand = handRight; } } void EnableHand(Hand hand) { hand.OnGrabbed += OnHandGrab; hand.OnReleased += OnHandRelease; if(allowClimbing) { hand.OnGrabbed += StartClimb; hand.OnReleased += EndClimb; } if(allowBodyPushing) { hand.OnGrabbed += StartGrabPush; hand.OnReleased += EndGrabPush; hand.OnHandCollisionStart += StartPush; hand.OnHandCollisionStop += StopPush; } } void DisableHand(Hand hand) { hand.OnGrabbed -= OnHandGrab; hand.OnReleased -= OnHandRelease; if(allowClimbing) { hand.OnGrabbed -= StartClimb; hand.OnReleased -= EndClimb; if(climbing.ContainsKey(hand)) climbing.Remove(hand); } if(allowBodyPushing) { hand.OnGrabbed -= StartGrabPush; hand.OnReleased -= EndGrabPush; hand.OnHandCollisionStart -= StartPush; hand.OnHandCollisionStop -= StopPush; if(hand.left) { pushLeft.Clear(); pushLeftCount.Clear(); } else { pushRight.Clear(); pushRightCount.Clear(); } } } void OnHandGrab(Hand hand, Grabbable grab) { grab.IgnoreColliders(bodyCapsule); if(headPhysicsFollower != null) grab?.IgnoreColliders(headPhysicsFollower.headCollider); } void OnHandRelease(Hand hand, Grabbable grab) { if(grab != null && grab.HeldCount() == 0) { grab?.IgnoreColliders(bodyCapsule, false); if(headPhysicsFollower != null) grab?.IgnoreColliders(headPhysicsFollower.headCollider, false); if(grab && grab.parentOnGrab && grab.body != null && !grab.body.isKinematic) grab.body.velocity += body.velocity / 2f; } } public void IgnoreCollider(Collider col, bool ignore) { Physics.IgnoreCollision(bodyCapsule, col, ignore); Physics.IgnoreCollision(headPhysicsFollower.headCollider, col, ignore); } /// Sets move direction for this fixedupdate public virtual void Move(Vector2 axis, bool useDeadzone = true, bool useRelativeDirection = false) { moveDirection.x = (!useDeadzone || Mathf.Abs(axis.x) > movementDeadzone) ? axis.x : 0; moveDirection.z = (!useDeadzone || Mathf.Abs(axis.y) > movementDeadzone) ? axis.y : 0; if(useRelativeDirection) moveDirection = transform.rotation * moveDirection; } public virtual void Turn(float turnAxis) { turnAxis = (Mathf.Abs(turnAxis) > turnDeadzone) ? turnAxis : 0; turningAxis = turnAxis; } protected virtual void LateUpdate() { if(useMovement) { UpdateTrackedObjects(); UpdateTurn(Time.deltaTime); } } protected virtual void FixedUpdate() { CheckHands(); UpdatePlayerHeight(); if(useMovement) { ApplyPushingForce(); ApplyClimbingForce(); UpdateRigidbody(); UpdatePlatform(); Ground(); } } protected virtual void UpdateRigidbody() { var move = AlterDirection(moveDirection); var yVel = body.velocity.y; //1. Moves velocity towards desired push direction if (pushAxis != Vector3.zero) { body.velocity = Vector3.MoveTowards(body.velocity, pushAxis, pushingAcceleration * Time.fixedDeltaTime); body.velocity *= Mathf.Clamp01(1 - pushingDrag * Time.fixedDeltaTime); } //2. Moves velocity towards desired climb direction if(climbAxis != Vector3.zero) { body.velocity = Vector3.MoveTowards(body.velocity, climbAxis, climbingAcceleration * Time.fixedDeltaTime); body.velocity *= Mathf.Clamp01(1 - climbingDrag * Time.fixedDeltaTime); } //3. Moves velocity towards desired movement direction if(move != Vector3.zero && CanInputMove()) { var newVel = Vector3.MoveTowards(body.velocity, move * maxMoveSpeed, moveAcceleration * Time.fixedDeltaTime); if(newVel.magnitude > maxMoveSpeed) newVel = newVel.normalized * maxMoveSpeed; body.velocity = newVel; } //5. Checks if gravity should be turned off if (IsClimbing() || pushAxis.y > 0) body.useGravity = false; //4. This creates extra drag when grounded to simulate foot strength, or if flying greats drag in every direction when not moving if (move.magnitude <= movementDeadzone && isGrounded) body.velocity *= (Mathf.Clamp01(1 - groundedDrag * Time.fixedDeltaTime)); else if(!useGrounding) body.velocity *= (Mathf.Clamp01(1 - flyingDrag * Time.fixedDeltaTime)); //6. This will keep velocity if consistent when moving while falling if(body.useGravity) body.velocity = new Vector3(body.velocity.x, yVel, body.velocity.z); SyncBodyHead(); } protected virtual void UpdateTrackedObjects() { var startRightHandPos = handRight.transform.position; var startLeftHandPos = handLeft.transform.position; //Moves the tracked objects based on the physics bodys delta movement targetTrackedPos += (transform.position - lastUpdatePosition); var flatPos = new Vector3(targetTrackedPos.x, trackingContainer.position.y, targetTrackedPos.z); trackingContainer.position = flatPos; //This slow moves the head + controllers on the Y-axis so it doesn't jump when stepping up if(isGrounded) trackingContainer.position = Vector3.MoveTowards(trackingContainer.position, targetTrackedPos + Vector3.up * heightOffset, (Mathf.Abs(trackingContainer.position.y - targetTrackedPos.y) + 0.1f) * Time.deltaTime * heightSmoothSpeed); else trackingContainer.position = targetTrackedPos + Vector3.up * heightOffset; //This code will move the tracking objects to match the body collider position when moving var targetPos = transform.position - headCamera.transform.position; targetPos.y = 0; targetPosOffset = Vector3.MoveTowards(targetPosOffset, targetPos, body.velocity.magnitude * Time.deltaTime); trackingContainer.position += targetPosOffset; if(headPhysicsFollower != null && isGrounded) { //Keeps the head down when colliding something above it and manages bouncing back up when not if(Vector3.Distance(headCamera.transform.position, headPhysicsFollower.transform.position) > headPhysicsFollower.headCollider.radius / 1.5f) { var idealPos = headPhysicsFollower.transform.position + (headCamera.transform.position - headPhysicsFollower.transform.position).normalized * headPhysicsFollower.headCollider.radius / 1.5f; var offsetPos = headCamera.transform.position - idealPos; trackingContainer.position -= offsetPos; } } //This helps prevent the hands from clipping var deltaHandPos = handRight.transform.position - startRightHandPos; if(pushRight.Count > 0) handRight.transform.position -= deltaHandPos; else PreventHandClipping(handRight, startRightHandPos); deltaHandPos = handLeft.transform.position - startLeftHandPos; if(pushLeft.Count > 0) handLeft.transform.position -= deltaHandPos; else PreventHandClipping(handLeft, startLeftHandPos); lastUpdatePosition = transform.position; } void PreventHandClipping(Hand hand, Vector3 startPosition) { var deltaHandPos = hand.transform.position - startPosition; if (deltaHandPos.magnitude < Physics.defaultContactOffset) return; var center = hand.handEncapsulationBox.transform.TransformPoint(hand.handEncapsulationBox.center) - deltaHandPos; var halfExtents = hand.handEncapsulationBox.transform.TransformVector(hand.handEncapsulationBox.size) / 2f; var hits = Physics.BoxCastAll(center, halfExtents, deltaHandPos, hand.handEncapsulationBox.transform.rotation, deltaHandPos.magnitude*1.5f, handPlayerMask); for(int i = 0; i < hits.Length; i++) { var hit = hits[i]; if(hit.collider.isTrigger) continue; if(hand.holdingObj == null || hit.collider.attachedRigidbody == null || (hit.collider.attachedRigidbody != hand.holdingObj.body && !hand.holdingObj.jointedBodies.Contains(hit.collider.attachedRigidbody))) { var deltaHitPos = hit.point - hand.transform.position; hand.transform.position = Vector3.MoveTowards(hand.transform.position, startPosition, deltaHitPos.magnitude); break; } } } void SyncBodyHead() { var delta = 50f * Time.fixedDeltaTime; float scale = transform.lossyScale.x > transform.lossyScale.z ? transform.lossyScale.x : transform.lossyScale.z; var flatHeadPos = headCamera.transform.position; flatHeadPos.y = 0; var flatBodyPos = transform.position; flatBodyPos.y = 0; if(Vector3.Distance(flatHeadPos, flatBodyPos) > 0.05f* delta) { var direction = headCamera.transform.position - transform.position; direction.y = 0; if(!Physics.CheckCapsule( direction * 0.1f * delta + scale * transform.position + Vector3.up * scale * bodyCapsule.radius, direction * 0.1f * delta + transform.position - scale * Vector3.up * bodyCapsule.radius + scale * Vector3.up * bodyCapsule.height + Vector3.up * maxStepHeight/2f, scale * bodyCapsule.radius, handPlayerMask, QueryTriggerInteraction.Ignore)) { offset = direction * 0.1f * delta; transform.position += offset; body.position += offset; targetTrackedPos -= offset; } else { for(int y = -80; y <= 80; y += 40) { var newDirection = Quaternion.Euler(0, y, 0) * direction; if(!Physics.CheckCapsule( newDirection * 0.1f * delta + scale * transform.position + Vector3.up * scale * bodyCapsule.radius, newDirection * 0.1f * delta + transform.position - scale * Vector3.up * bodyCapsule.radius + scale * Vector3.up * bodyCapsule.height, scale * bodyCapsule.radius, handPlayerMask, QueryTriggerInteraction.Ignore)) { offset = newDirection * 0.1f * delta; transform.position += offset; body.position = transform.position; targetTrackedPos -= offset; break; } } } } } protected virtual bool CanInputMove() { return (allowClimbingMovement || !IsClimbing()); } protected virtual void UpdateTurn(float deltaTime) { //Snap turning if(rotationType == RotationType.snap) { if(Mathf.Abs(turningAxis) > turnDeadzone && axisReset) { var angle = turningAxis > turnDeadzone ? snapTurnAngle : -snapTurnAngle; var targetPos = transform.position - headCamera.transform.position; targetPos.y = 0; trackingContainer.position += targetPos; if(headPhysicsFollower != null) { headPhysicsFollower.transform.position += targetPos; headPhysicsFollower.body.position = headPhysicsFollower.transform.position; } lastUpdatePosition = new Vector3(transform.position.x, lastUpdatePosition.y, transform.position.z); var handRightStartPos = handRight.transform.position; var handLeftStartPos = handLeft.transform.position; trackingContainer.RotateAround(transform.position, Vector3.up, angle); targetPosOffset = Vector3.zero; targetTrackedPos = new Vector3(trackingContainer.position.x, targetTrackedPos.y, trackingContainer.position.z); handRight.SetMoveTo(); handRight.SetHandLocation(handRight.moveTo.position, handRight.moveTo.rotation); handLeft.SetMoveTo(); handLeft.SetHandLocation(handLeft.moveTo.position, handLeft.moveTo.rotation); PreventHandClipping(handRight, handRightStartPos); PreventHandClipping(handLeft, handLeftStartPos); OnSnapTurn?.Invoke(this); axisReset = false; } } else if(Mathf.Abs(turningAxis) > turnDeadzone) { var targetPos = transform.position - headCamera.transform.position; targetPos.y = 0; trackingContainer.position += targetPos; if(headPhysicsFollower != null) { headPhysicsFollower.transform.position += targetPos; headPhysicsFollower.body.position = headPhysicsFollower.transform.position; } lastUpdatePosition = new Vector3(transform.position.x, lastUpdatePosition.y, transform.position.z); trackingContainer.RotateAround(transform.position, Vector3.up, smoothTurnSpeed * (Mathf.MoveTowards(turningAxis, 0, turnDeadzone)) * deltaTime); targetPosOffset = Vector3.zero; targetTrackedPos = new Vector3(trackingContainer.position.x, targetTrackedPos.y, trackingContainer.position.z); axisReset = false; } if(Mathf.Abs(turningAxis) < turnResetzone) axisReset = true; } protected virtual void Ground() { isGrounded = false; newClosestHit = new RaycastHit(); if(!tempDisableGrounding && useGrounding && !IsClimbing() && !(pushAxis.y > 0)) { highestPoint = -1; float stepAngle; float dist; float scale = transform.lossyScale.x > transform.lossyScale.z ? transform.lossyScale.x : transform.lossyScale.z; var maxStepHeight = this.maxStepHeight; maxStepHeight *= climbAxis.y > 0 ? climbUpStepHeightMultiplier : 1; maxStepHeight *= pushAxis.y > 0 ? pushUpStepHeightMultiplier : 1; maxStepHeight *= scale; var point1 = scale * bodyCapsule.center + transform.position + scale * bodyCapsule.height / 2 * -Vector3.up + (maxStepHeight + scale * bodyCapsule.radius * 2) * Vector3.up; var point2 = scale * bodyCapsule.center + transform.position + (scale * bodyCapsule.height / 2f + groundedOffset) * -Vector3.up; var radius = scale*bodyCapsule.radius*2 + Physics.defaultContactOffset*2; var groundHits = Physics.SphereCastAll(point1, radius, -Vector3.up, Vector3.Distance(point1, point2) + scale * bodyCapsule.radius*4, groundLayerMask, QueryTriggerInteraction.Ignore); for(int i = 0; i < groundHits.Length; i++) { var hit = groundHits[i]; if(hit.collider != bodyCapsule) { if(hit.point.y >= point2.y && hit.point.y <= point2.y + maxStepHeight) { stepAngle = Vector3.Angle(hit.normal, Vector3.up); dist = hit.point.y - transform.position.y; if(stepAngle < maxStepAngle && dist > highestPoint) { isGrounded = true; highestPoint = dist; newClosestHit = hit; } } } } if(isGrounded) { body.velocity = new Vector3(body.velocity.x, 0, body.velocity.z); body.position = new Vector3(body.position.x, newClosestHit.point.y, body.position.z); transform.position = body.position; } body.useGravity = !isGrounded; } } public bool IsGrounded() { return isGrounded; } public void ToggleFlying() { useGrounding = !useGrounding; body.useGravity = useGrounding; } protected virtual void UpdatePlayerHeight() { if(crouching != lastCrouching) { if(lastCrouching) heightOffset += lastCrouchingHeight; if(!lastCrouching) heightOffset -= crouchHeight; lastCrouching = crouching; lastCrouchingHeight = crouchHeight; } if(autoAdjustColliderHeight) { playerHeight = Mathf.Clamp(headCamera.transform.position.y - transform.position.y, minMaxHeight.x, minMaxHeight.y); bodyCapsule.height = playerHeight; var centerHeight = playerHeight / 2f > bodyCapsule.radius ? playerHeight / 2f : bodyCapsule.radius; bodyCapsule.center = new Vector3(0, centerHeight, 0); } } protected void UpdatePlatform() { if (isGrounded && newClosestHit.transform != null && (platformingLayerMask == (platformingLayerMask | (1 << newClosestHit.collider.gameObject.layer)))) { if (!newClosestHit.transform.Equals(closestHit.transform)) { closestHit = newClosestHit; lastPlatformPosition = closestHit.transform.position; lastPlatformRotation = closestHit.transform.rotation; } else if(newClosestHit.transform.Equals(closestHit.transform)) { if (closestHit.transform.position != lastPlatformPosition || closestHit.transform.rotation.eulerAngles != lastPlatformRotation.eulerAngles) { closestHit = newClosestHit; Transform ruler = AutoHandExtensions.transformRuler; ruler.position = transform.position; ruler.rotation = transform.rotation; ruler.position += closestHit.transform.position - lastPlatformPosition; var deltaPos = ruler.transform.position - transform.position; var deltaRot = (closestHit.transform.rotation * Quaternion.Inverse(lastPlatformRotation)); ruler.transform.RotateAround(closestHit.transform.position, Vector3.up, deltaRot.eulerAngles.y); trackingContainer.RotateAround(headCamera.transform.position, Vector3.up, deltaRot.eulerAngles.y); transform.position += deltaPos; body.position = transform.position; trackingContainer.position += deltaPos; lastUpdatePosition = transform.position; targetPosOffset = Vector3.zero; targetTrackedPos = new Vector3(trackingContainer.position.x, targetTrackedPos.y + deltaPos.y, trackingContainer.position.z); lastPlatformPosition = closestHit.transform.position; lastPlatformRotation = closestHit.transform.rotation; } } } } public void Jump(float jumpPower = 1) { if(isGrounded) { DisableGrounding(0.1f); body.useGravity = true; body.AddForce(Vector3.up * jumpPower, ForceMode.VelocityChange); } } public void DisableGrounding(float seconds) { if(disableGroundingRoutine != null) StopCoroutine(disableGroundingRoutine); disableGroundingRoutine = StartCoroutine(DisableGroundingSecondsRoutine(seconds)); } Coroutine disableGroundingRoutine; IEnumerator DisableGroundingSecondsRoutine(float seconds) { tempDisableGrounding = true; isGrounded = false; yield return new WaitForSeconds(seconds); tempDisableGrounding = false; } /// Legacy function, use body.addfoce instead public void AddVelocity(Vector3 force, ForceMode mode = ForceMode.Acceleration) { body.AddForce(force, mode); } protected virtual void StartPush(Hand hand, GameObject other) { if(!allowBodyPushing || IsClimbing()) return; if(other.CanGetComponent(out Pushable push) && push.enabled) { if(hand.left) { if(!pushLeft.ContainsKey(push)) { pushLeft.Add(push, hand); pushLeftCount.Add(push, 1); } else { pushLeftCount[push]++; } } if(!hand.left && !pushRight.ContainsKey(push)) { if(!pushRight.ContainsKey(push)) { pushRight.Add(push, hand); pushRightCount.Add(push, 1); } else { pushRightCount[push]++; } } } } protected virtual void StopPush(Hand hand, GameObject other) { if(!allowBodyPushing) return; if(other.CanGetComponent(out Pushable push)) { if(hand.left && pushLeft.ContainsKey(push)) { var count = --pushLeftCount[push]; if(count == 0) { pushLeft.Remove(push); pushLeftCount.Remove(push); } } if(!hand.left && pushRight.ContainsKey(push)) { var count = --pushRightCount[push]; if(count == 0) { pushRight.Remove(push); pushRightCount.Remove(push); } } } } protected virtual void StartGrabPush(Hand hand, Grabbable grab) { if(!allowBodyPushing) return; if(grab.CanGetComponent(out Pushable push) && push.enabled) { if(hand.left && !pushLeft.ContainsKey(push)) { pushLeft.Add(push, hand); pushLeftCount.Add(push, 1); } if(!hand.left && !pushRight.ContainsKey(push)) { pushRight.Add(push, hand); pushRightCount.Add(push, 1); } } } protected virtual void EndGrabPush(Hand hand, Grabbable grab) { if(grab != null && grab.CanGetComponent(out Pushable push)) { if(hand.left && pushLeft.ContainsKey(push)) { pushLeft.Remove(push); pushLeftCount.Remove(push); } else if(!hand.left && pushRight.ContainsKey(push)) { pushRight.Remove(push); pushRightCount.Remove(push); } } } protected virtual void ApplyPushingForce() { pushAxis = Vector3.zero; if(allowBodyPushing) { foreach(var push in pushRight) { if(push.Key.enabled && !push.Value.IsGrabbing()) { Vector3 offset = Vector3.zero; var distance = Vector3.Distance(push.Value.body.position, push.Value.moveTo.position); if(distance > 0) offset = Vector3.Scale((push.Value.body.position - push.Value.moveTo.position), push.Key.strengthScale); offset = Vector3.Scale(offset, pushingStrength); pushAxis += offset / 2f; } } foreach(var push in pushLeft) { if(push.Key.enabled && !push.Value.IsGrabbing()) { Vector3 offset = Vector3.zero; var distance = Vector3.Distance(push.Value.body.position, push.Value.moveTo.position); if(distance > 0) offset = Vector3.Scale((push.Value.body.position - push.Value.moveTo.position), push.Key.strengthScale); offset = Vector3.Scale(offset, pushingStrength); pushAxis += offset / 2f; } } } } public bool IsPushing() { foreach(var push in pushRight) if(push.Key.enabled) return true; foreach(var push in pushLeft) if(push.Key.enabled) return true; return false; } protected virtual void StartClimb(Hand hand, Grabbable grab) { if(!allowClimbing) return; if(!climbing.ContainsKey(hand) && grab != null && grab.CanGetComponent(out Climbable climbbable) && climbbable.enabled) { if(climbing.Count == 0) { pushRight.Clear(); pushRightCount.Clear(); pushLeft.Clear(); pushLeftCount.Clear(); } if(climbing.Count == 0) body.velocity /= 4f; climbing.Add(hand, climbbable); } } protected virtual void EndClimb(Hand hand, Grabbable grab) { if(!allowClimbing) return; if(climbing.ContainsKey(hand)) climbing.Remove(hand); foreach(var climb in climbing) climb.Key.ResetGrabOffset(); } protected virtual void ApplyClimbingForce() { climbAxis = Vector3.zero; if(allowClimbing && climbing.Count > 0) { foreach(var hand in climbing) { if(hand.Value.enabled) { var offset = Vector3.Scale(hand.Key.body.position - hand.Key.moveTo.position, hand.Value.axis); offset = Vector3.Scale(offset, climbingStrength); climbAxis += offset / climbing.Count; } } } } public bool IsClimbing() { foreach(var climb in climbing) if(climb.Value.enabled) return true; return false; } public virtual void SetPosition(Vector3 position) { SetPosition(position, headCamera.transform.rotation); } public virtual void SetPosition(Vector3 position, Quaternion rotation) { Vector3 deltaPos = position - transform.position; transform.position += deltaPos; //This code will move the tracking objects to match the body collider position when moving var targetPos = transform.position - headCamera.transform.position; targetPos.y = deltaPos.y; trackingContainer.position += targetPos; lastUpdatePosition = transform.position; targetTrackedPos = new Vector3(trackingContainer.position.x, targetTrackedPos.y + deltaPos.y, trackingContainer.position.z); targetPosOffset = Vector3.zero; body.position = transform.position; if(headPhysicsFollower != null) { headPhysicsFollower.transform.position += targetPos; headPhysicsFollower.body.position = headPhysicsFollower.transform.position; } handRight.body.position = handRight.transform.position; handLeft.body.position = handLeft.transform.position; handRight.SetHandLocation(handRight.transform.position); handLeft.SetHandLocation(handLeft.transform.position); var deltaRot = rotation * Quaternion.Inverse(headCamera.transform.rotation); trackingContainer.RotateAround(headCamera.transform.position, Vector3.up, deltaRot.eulerAngles.y); if(deltaRot.eulerAngles.magnitude > 10f || deltaPos.magnitude > 0.5f) OnTeleported?.Invoke(this); } public virtual void SetRotation(Quaternion rotation) { var targetPos = transform.position - headCamera.transform.position; targetPos.y = 0; trackingContainer.position += targetPos; if(headPhysicsFollower != null) { headPhysicsFollower.transform.position += targetPos; headPhysicsFollower.body.position = headPhysicsFollower.transform.position; } lastUpdatePosition = transform.position; var deltaRot = rotation * Quaternion.Inverse(headCamera.transform.rotation); trackingContainer.RotateAround(headCamera.transform.position, Vector3.up, deltaRot.eulerAngles.y); targetPosOffset = Vector3.zero; targetTrackedPos = new Vector3(trackingContainer.position.x, targetTrackedPos.y, trackingContainer.position.z); if(deltaRot.eulerAngles.magnitude > 10f) OnTeleported?.Invoke(this); } public virtual void AddRotation(Quaternion addRotation) { var targetPos = transform.position - headCamera.transform.position; targetPos.y = 0; trackingContainer.position += targetPos; if(headPhysicsFollower != null) { headPhysicsFollower.transform.position += targetPos; headPhysicsFollower.body.position = headPhysicsFollower.transform.position; } lastUpdatePosition = transform.position; trackingContainer.RotateAround(headCamera.transform.position, Vector3.up, addRotation.eulerAngles.y); targetPosOffset = Vector3.zero; targetTrackedPos = new Vector3(trackingContainer.position.x, targetTrackedPos.y, trackingContainer.position.z); if(addRotation.eulerAngles.magnitude > 10f) OnTeleported?.Invoke(this); } public virtual void Recenter() { var targetPos = transform.position - headCamera.transform.position; targetPos.y = 0; trackingContainer.position += targetPos; if(headPhysicsFollower != null) { headPhysicsFollower.transform.position += targetPos; headPhysicsFollower.body.position = headPhysicsFollower.transform.position; } lastUpdatePosition = transform.position; targetPosOffset = Vector3.zero; targetTrackedPos = new Vector3(trackingContainer.position.x, targetTrackedPos.y, trackingContainer.position.z); } public bool IsHolding(Grabbable grab) { return handRight.GetHeld() == grab || handLeft.GetHeld() == grab; } Vector3 AlterDirection(Vector3 moveAxis) { if(useGrounding) return Quaternion.AngleAxis(forwardFollow.eulerAngles.y, Vector3.up) * (new Vector3(moveAxis.x, moveAxis.y, moveAxis.z)); else return forwardFollow.rotation * (new Vector3(moveAxis.x, moveAxis.y, moveAxis.z)); } } }