using GogoGaga.OptimizedRopesAndCables; using UnityEngine; public class RopeEndPhysicsFollower : MonoBehaviour { [Header("Target Settings")] [Tooltip("Tag of the object this endpoint should follow")] public string targetTag; [Tooltip("How quickly the endpoint follows the target")] public float followSpeed = 5f; [Tooltip("How much trailing (0 = instant, 1 = very slow)")] public float trailing = 0.2f; [Header("Physics Simulation")] [Tooltip("Enable/disable gravity effect")] public bool useGravity = true; [Tooltip("Gravity strength")] public float gravityStrength = 9.8f; [Tooltip("How strongly the rope attempts to hang vertically")] public float verticalHangStrength = 2f; [Tooltip("Damping for physics movement (higher = less bouncy)")] public float damping = 0.3f; [Tooltip("Initial downward impulse when gravity is enabled")] public float initialFallImpulse = 2.0f; [Tooltip("Force a Y position reset on start to ensure falling occurs")] public bool forceYReset = true; // Private variables private Transform target; private Vector3 velocity; private Vector2 physicsVelocity; private Vector2 offset; private Vector3 lastTargetPosition; private bool initialized = false; private bool debugLog = true; // Rope reference to get the actual rope length private Rope attachedRope; private float maxDistance; void Start() { // Find the Rope component on the same GameObject or parent attachedRope = GetComponent(); if (attachedRope == null) { attachedRope = GetComponentInParent(); } // If we still couldn't find it, look for it on a child GameObject if (attachedRope == null) { attachedRope = GetComponentInChildren(); } // Look for a rope attached to this endpoint if (attachedRope == null) { // Find any rope that has this transform as an endpoint Rope[] allRopes = FindObjectsOfType(); foreach (var rope in allRopes) { if (rope.EndPoint == transform || rope.StartPoint == transform) { attachedRope = rope; break; } } } // Set max distance based on rope length if we found a rope if (attachedRope != null) { maxDistance = attachedRope.ropeLength; if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Found attached rope with length: {maxDistance}"); } else { // Default fallback value if no rope is found maxDistance = 2f; if (debugLog) Debug.Log("[RopeEndPhysicsFollower] No attached rope found, using default max distance"); } if (!string.IsNullOrEmpty(targetTag)) { GameObject found = GameObject.FindGameObjectWithTag(targetTag); if (found) target = found.transform; } // Initialize offset and velocities if (target) { // Only store horizontal offset, not vertical Vector2 offsetVec = transform.position - target.position; offset.x = offsetVec.x; offset.y = 0; // Don't preserve vertical offset for gravity simulation lastTargetPosition = target.position; // Apply initial falling impulse if using gravity if (useGravity) { physicsVelocity = new Vector2(0, -initialFallImpulse); // Force an initial position change to ensure things start moving if (forceYReset) { Vector3 pos = transform.position; pos.y = target.position.y; // Reset to target's Y position transform.position = pos; } if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Initialized with tag {targetTag}, gravity enabled, initial Y velocity: {physicsVelocity.y}"); } } else { offset = Vector2.zero; lastTargetPosition = transform.position; if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] No target found with tag {targetTag}"); } velocity = Vector3.zero; initialized = true; } void Update() { if (!target) return; // Calculate deltaTime for physics stability float deltaTime = Time.deltaTime; // Get target velocity Vector3 targetVelocity = (target.position - lastTargetPosition) / deltaTime; lastTargetPosition = target.position; // Basic position the endpoint should be at based on target position and horizontal offset Vector3 basePosition = target.position + new Vector3(offset.x, 0, 0); // Apply physics simulation if enabled if (useGravity) { // Get the straight-line distance between target and this transform float currentDistance = Vector2.Distance( new Vector2(transform.position.x, transform.position.y), new Vector2(target.position.x, target.position.y) ); // Current position relative to target Vector2 relativePos = new Vector2( transform.position.x - target.position.x, transform.position.y - target.position.y ); // Normalized direction from target to this transform Vector2 directionToTarget = relativePos.normalized; // Apply forces: // 1. Gravity - always pulls down physicsVelocity.y -= gravityStrength * deltaTime; // 2. Vertical hanging force - try to align X with target when stationary if (Mathf.Abs(targetVelocity.x) < 0.1f) { float xOffset = transform.position.x - target.position.x; physicsVelocity.x -= xOffset * verticalHangStrength * deltaTime; } // 3. Rope length constraint - apply a force toward the target if we're exceeding the rope length if (currentDistance > maxDistance) { // Calculate constraint force proportional to how much we're exceeding the rope length float exceededDistance = currentDistance - maxDistance; // Apply a stronger constraint force the more we exceed the max distance Vector2 constraintForce = -directionToTarget * exceededDistance * 10f; // Apply to velocity physicsVelocity += constraintForce * deltaTime; if (debugLog && Time.frameCount % 60 == 0) { Debug.Log($"[RopeEndPhysicsFollower] Exceeding max distance: {exceededDistance}, applying constraint"); } } // Apply damping to physics velocity physicsVelocity *= (1f - damping * deltaTime); // Log physics state periodically for debugging if (debugLog && Time.frameCount % 60 == 0) { Debug.Log($"[RopeEndPhysicsFollower] Y position: {transform.position.y}, Y velocity: {physicsVelocity.y}, Distance: {currentDistance}/{maxDistance}"); } // Apply physics velocity to position Vector3 newPos = transform.position; newPos.x += physicsVelocity.x * deltaTime; newPos.y += physicsVelocity.y * deltaTime; transform.position = newPos; // Final distance check - hard constraint to ensure we never exceed the rope length // This prevents numerical instability from causing the rope to stretch float finalDistance = Vector2.Distance( new Vector2(transform.position.x, transform.position.y), new Vector2(target.position.x, target.position.y) ); if (finalDistance > maxDistance) { // Calculate the direction from target to this transform Vector2 direction = new Vector2( transform.position.x - target.position.x, transform.position.y - target.position.y ).normalized; // Set position to be exactly at the maximum distance Vector3 constrainedPos = new Vector3( target.position.x + direction.x * maxDistance, target.position.y + direction.y * maxDistance, transform.position.z ); transform.position = constrainedPos; } } else { // Original smooth follow behavior without physics transform.position = Vector3.SmoothDamp(transform.position, basePosition, ref velocity, trailing, followSpeed); } } public void SetTargetTag(string tag) { targetTag = tag; GameObject found = GameObject.FindGameObjectWithTag(targetTag); if (found) { target = found.transform; lastTargetPosition = target.position; // Only update horizontal offset to maintain current vertical position if (initialized) { Vector2 newOffset = transform.position - target.position; offset.x = newOffset.x; // Don't update offset.y to allow gravity to work } else { Vector2 newOffset = transform.position - target.position; offset.x = newOffset.x; offset.y = 0; // Don't preserve vertical offset for gravity simulation } // Apply initial falling impulse if using gravity if (useGravity) { physicsVelocity = new Vector2(physicsVelocity.x, -initialFallImpulse); if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Reset Y velocity to {physicsVelocity.y} after target change"); } } } // Debug method to force reset the physics public void ForceResetPhysics() { if (target) { // Reset velocity with a strong downward impulse physicsVelocity = new Vector2(0, -initialFallImpulse * 2f); // Reset position to be at the same level as the target Vector3 pos = transform.position; pos.y = target.position.y; transform.position = pos; if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Physics forcibly reset, new Y velocity: {physicsVelocity.y}"); } } // Method to manually set the maximum distance public void SetMaxDistance(float distance) { maxDistance = distance; if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Max distance manually set to: {maxDistance}"); } }