2025-10-14 15:53:58 +02:00
using Core ;
using GogoGaga.OptimizedRopesAndCables ;
2025-09-22 12:16:32 +00:00
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 ;
2025-10-10 14:31:51 +02:00
private bool debugLog = false ;
2025-09-22 12:16:32 +00:00
// 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 ;
2025-10-14 15:53:58 +02:00
if ( debugLog ) Logging . Debug ( $"[RopeEndPhysicsFollower] Using assigned target transform: {target.name}" ) ;
2025-09-22 12:16:32 +00:00
}
else if ( ! string . IsNullOrEmpty ( targetTag ) )
{
GameObject found = GameObject . FindGameObjectWithTag ( targetTag ) ;
if ( found )
{
target = found . transform ;
2025-10-14 15:53:58 +02:00
if ( debugLog ) Logging . Debug ( $"[RopeEndPhysicsFollower] Found target by tag '{targetTag}': {target.name}" ) ;
2025-09-22 12:16:32 +00:00
}
}
// 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 ) ;
2025-10-14 15:53:58 +02:00
if ( debugLog ) Logging . Debug ( $"[RopeEndPhysicsFollower] Initialized with target: {target.name}, initial Y velocity: {physicsVelocity.y}" ) ;
2025-09-22 12:16:32 +00:00
}
}
else
{
offset = Vector2 . zero ;
lastTargetPosition = transform . position ;
2025-10-14 15:53:58 +02:00
if ( debugLog ) Logging . Debug ( $"[RopeEndPhysicsFollower] No target found" ) ;
2025-09-22 12:16:32 +00:00
}
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 )
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[RopeEndPhysicsFollower] Vertical hanging: target X={target.position.x}, my X={transform.position.x}, offset={xOffset}" ) ;
2025-09-22 12:16:32 +00:00
}
}
// 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 )
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[RopeEndPhysicsFollower] Exceeding max distance: {exceededDistance}, applying constraint" ) ;
2025-09-22 12:16:32 +00:00
}
}
// Apply damping to physics velocity
physicsVelocity * = ( 1f - damping * deltaTime ) ;
// Log physics state periodically for debugging
if ( debugLog & & Time . frameCount % 60 = = 0 )
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[RopeEndPhysicsFollower] Y position: {transform.position.y}, Y velocity: {physicsVelocity.y}, Distance: {currentDistance}/{maxDistance}" ) ;
2025-09-22 12:16:32 +00:00
}
// 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 < Rope > ( ) ;
if ( attachedRope = = null )
{
attachedRope = GetComponentInParent < Rope > ( ) ;
}
// If we still couldn't find it, look for it on a child GameObject
if ( attachedRope = = null )
{
attachedRope = GetComponentInChildren < Rope > ( ) ;
}
// Look for a rope attached to this endpoint
if ( attachedRope = = null )
{
// Find any rope that has this transform as an endpoint
2025-09-22 14:50:48 +02:00
Rope [ ] allRopes = FindObjectsByType < Rope > ( FindObjectsSortMode . None ) ;
2025-09-22 12:16:32 +00:00
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 ;
2025-10-14 15:53:58 +02:00
if ( debugLog ) Logging . Debug ( $"[RopeEndPhysicsFollower] Found attached rope with length: {maxDistance}" ) ;
2025-09-22 12:16:32 +00:00
}
else
{
// Default fallback value if no rope is found
maxDistance = 2f ;
2025-10-14 15:53:58 +02:00
if ( debugLog ) Logging . Debug ( "[RopeEndPhysicsFollower] No attached rope found, using default max distance" ) ;
2025-09-22 12:16:32 +00:00
}
}
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 ) ;
2025-10-14 15:53:58 +02:00
if ( debugLog ) Logging . Debug ( $"[RopeEndPhysicsFollower] Reset Y velocity to {physicsVelocity.y} after target change to {target.name}" ) ;
2025-09-22 12:16:32 +00:00
}
}
}
// 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 ;
2025-10-14 15:53:58 +02:00
if ( debugLog ) Logging . Debug ( $"[RopeEndPhysicsFollower] Physics forcibly reset, new Y velocity: {physicsVelocity.y}" ) ;
2025-09-22 12:16:32 +00:00
}
}
// Method to manually set the maximum distance
public void SetMaxDistance ( float distance )
{
maxDistance = distance ;
2025-10-14 15:53:58 +02:00
if ( debugLog ) Logging . Debug ( $"[RopeEndPhysicsFollower] Max distance manually set to: {maxDistance}" ) ;
2025-09-22 12:16:32 +00:00
}
}