using System; using System.ComponentModel; using System.Runtime.CompilerServices; using UnityEngine; namespace KitsuneCafe.Entities { public class Spring : MonoBehaviour, INotifyPropertyChanged { [SerializeField] private new Rigidbody rigidbody; [SerializeField] private float rayLength = 1.5f; [SerializeField] private float rideHeight = 1f; [SerializeField] private float rideSpringStrength; [SerializeField] private float rideSpringDamper; [SerializeField] private Vector3 rayOffset = Vector3.zero; [SerializeField] private Vector3 down = Vector3.down; [SerializeField] private LayerMask layerMask; private bool isColliding = false; public bool IsColliding { get => isColliding; private set { if (isColliding != value) { isColliding = value; Notify(); } } } private bool isAtRideHeight = false; public bool IsAtRideHeight { get => isAtRideHeight; private set { if (isAtRideHeight != value) { isAtRideHeight = value; Notify(); } } } private Vector3 relativeDown; private float relativeVelocity; private float targetDistance; private float springForce; public event PropertyChangedEventHandler PropertyChanged; private void OnValidate() { if (rigidbody == null) { rigidbody = GetComponent(); } } private void Reset() { layerMask = LayerMask.NameToLayer("Default"); } private void FixedUpdate() { relativeDown = transform.TransformDirection(down); IsColliding = Raycast(relativeDown, out var hit); if (isColliding) { relativeVelocity = RelativeVelocity(down, rigidbody, hit.rigidbody); targetDistance = hit.distance - rideHeight; springForce = (targetDistance * rideSpringStrength) - (relativeVelocity * rideSpringDamper); IsAtRideHeight = Approximately(springForce, Physics.gravity.y, 0.001f); rigidbody.AddForce(relativeDown * springForce); } } private static bool Approximately(float a, float b, float e = float.Epsilon) { return Math.Abs(a - b) < e; } private bool Raycast(Vector3 direction, out RaycastHit hit) { return Physics.Raycast( transform.position + rayOffset, direction, out hit, rayLength, layerMask ); } private static float RelativeVelocity(Vector3 direction, Rigidbody a, Rigidbody b) { var aVelocity = GetVelocity(a); var bVelocity = GetVelocity(b); var aDirVelocity = Vector3.Dot(direction, aVelocity); var bDirVelocity = Vector3.Dot(direction, bVelocity); return aDirVelocity - bDirVelocity; } private static Vector3 GetVelocity(Rigidbody rb) { return rb == null ? Vector3.zero : rb.linearVelocity; } private void Notify([CallerMemberName] string name = default) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } private void OnDrawGizmos() { var origin = transform.position + rayOffset; Gizmos.color = isColliding ? Color.yellow : Color.red; Gizmos.DrawRay(origin, relativeDown * rayLength); if (isColliding) { Gizmos.color = Color.green; Gizmos.DrawRay(origin, relativeDown * rideHeight); } } } }