using GogoGaga.OptimizedRopesAndCables; using UnityEngine; public class RopeEndPhysicsFollower : MonoBehaviour { [Header("Target Settings")] [Tooltip("Transform this endpoint should follow")] public Transform targetTransform; [Tooltip("Tag of the object this endpoint should follow (only used if targetTransform is not set)")] public string targetTag; [Tooltip("How quickly the endpoint follows the target when not using physics")] public float followSpeed = 5f; [Tooltip("How much trailing (0 = instant, 1 = very slow)")] public float trailing = 0.2f; [Header("Physics Simulation")] [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 enabled")] public float initialFallImpulse = 2.0f; [Tooltip("Whether this end can fall with gravity (false for player-attached ends)")] public bool canFall = true; // Private variables private Transform target; 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 to determine the maximum distance FindAttachedRope(); // Use targetTransform if set, otherwise try to find by tag if (targetTransform != null) { target = targetTransform; if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Using assigned target transform: {target.name}"); } else if (!string.IsNullOrEmpty(targetTag)) { GameObject found = GameObject.FindGameObjectWithTag(targetTag); if (found) { target = found.transform; if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Found target by tag '{targetTag}': {target.name}"); } } // Initialize offset and velocities if (target) { // Only store horizontal offset, not vertical for physics simulation 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 this end can fall if (canFall) { physicsVelocity = new Vector2(0, -initialFallImpulse); if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Initialized with target: {target.name}, initial Y velocity: {physicsVelocity.y}"); } } else { offset = Vector2.zero; lastTargetPosition = transform.position; if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] No target found"); } 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; // Current position relative to target Vector2 relativePos = new Vector2( transform.position.x - target.position.x, transform.position.y - target.position.y ); // Get the straight-line distance between target and this transform float currentDistance = relativePos.magnitude; // Normalized direction from target to this transform Vector2 directionToTarget = relativePos.normalized; // Apply forces based on whether this end can fall if (canFall) { // 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) { // Use the actual X position of the target as the desired X position float xOffset = transform.position.x - target.position.x; physicsVelocity.x -= xOffset * verticalHangStrength * deltaTime; // Debug log to track vertical hanging behavior if (debugLog && Time.frameCount % 120 == 0) { Debug.Log($"[RopeEndPhysicsFollower] Vertical hanging: target X={target.position.x}, my X={transform.position.x}, offset={xOffset}"); } } // 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; // Only apply vertical movement if this end can fall if (canFall) { 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; } } private void FindAttachedRope() { // 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 = FindObjectsByType(FindObjectsSortMode.None); 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"); } } public void SetTargetTransform(Transform newTarget) { targetTransform = newTarget; target = newTarget; if (target != null) { 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 this end can fall if (canFall) { physicsVelocity = new Vector2(physicsVelocity.x, -initialFallImpulse); if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Reset Y velocity to {physicsVelocity.y} after target change to {target.name}"); } } } // Original tag-based method kept for backward compatibility public void SetTargetTag(string tag) { targetTag = tag; GameObject found = GameObject.FindGameObjectWithTag(targetTag); if (found) { SetTargetTransform(found.transform); } } // 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}"); } }