diff --git a/Assets/Scenes/MiniGames/DivingForPictures.unity b/Assets/Scenes/MiniGames/DivingForPictures.unity index b0d9ba00..9f57b92b 100644 --- a/Assets/Scenes/MiniGames/DivingForPictures.unity +++ b/Assets/Scenes/MiniGames/DivingForPictures.unity @@ -433,12 +433,15 @@ MonoBehaviour: breakPosition: 0.5 breakEffect: {fileID: 0} breakSound: {fileID: 0} - fallSpeed: 2 - swingAmount: 0.5 ropeFollowSpeed: 5 ropeTrailing: 0.2 + ropeGravityStrength: 9.8 + ropeMaxHangDistance: 2 + ropeVerticalHangStrength: 2 ropeOscillationAmplitude: 0.15 ropeOscillationFrequency: 2 + ropeDamping: 0.3 + initialSeparationDistance: 0.1 --- !u!1 &224729330 GameObject: m_ObjectHideFlags: 0 @@ -1313,12 +1316,15 @@ MonoBehaviour: breakPosition: 0.5 breakEffect: {fileID: 0} breakSound: {fileID: 0} - fallSpeed: 2 - swingAmount: 0.5 ropeFollowSpeed: 5 ropeTrailing: 0.2 + ropeGravityStrength: 9.8 + ropeMaxHangDistance: 2 + ropeVerticalHangStrength: 2 ropeOscillationAmplitude: 0.15 ropeOscillationFrequency: 2 + ropeDamping: 0.3 + initialSeparationDistance: 0.1 --- !u!1 &1063641111 GameObject: m_ObjectHideFlags: 0 @@ -1807,12 +1813,15 @@ MonoBehaviour: breakPosition: 0.5 breakEffect: {fileID: 0} breakSound: {fileID: 0} - fallSpeed: 2 - swingAmount: 0.5 ropeFollowSpeed: 5 ropeTrailing: 0.2 + ropeGravityStrength: 9.8 + ropeMaxHangDistance: 2 + ropeVerticalHangStrength: 2 ropeOscillationAmplitude: 0.1 ropeOscillationFrequency: 3 + ropeDamping: 0.3 + initialSeparationDistance: 0.1 --- !u!1 &1679185997 GameObject: m_ObjectHideFlags: 0 @@ -2041,7 +2050,7 @@ GameObject: - component: {fileID: 2106431004} m_Layer: 0 m_Name: Rock - m_TagString: Untagged + m_TagString: Rock m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 diff --git a/Assets/Scripts/Minigames/DivingForPictures/RopeBreaker.cs b/Assets/Scripts/Minigames/DivingForPictures/RopeBreaker.cs index 8cc06605..0b8bf685 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/RopeBreaker.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/RopeBreaker.cs @@ -19,25 +19,30 @@ public class RopeBreaker : MonoBehaviour [Tooltip("Sound to play when rope breaks (optional)")] [SerializeField] private AudioClip breakSound; - [Header("Animation Settings")] - [Tooltip("How quickly the rope ends fall after breaking")] - [SerializeField] private float fallSpeed = 2f; - - [Tooltip("How much the rope ends swing after breaking")] - [SerializeField] private float swingAmount = 0.5f; - [Header("Physics Settings")] [Tooltip("Follow speed for the rope physics simulation")] [SerializeField] private float ropeFollowSpeed = 5f; [Tooltip("Trailing amount for the rope physics simulation")] [SerializeField] private float ropeTrailing = 0.2f; + + [Tooltip("Gravity strength applied to hanging rope end")] + [SerializeField] private float ropeGravityStrength = 9.8f; - [Tooltip("Oscillation amplitude for the rope physics simulation")] - [SerializeField] private float ropeOscillationAmplitude = 0.15f; + [Tooltip("How strongly the rope tries to hang vertically")] + [SerializeField] private float ropeVerticalHangStrength = 2f; - [Tooltip("Oscillation frequency for the rope physics simulation")] - [SerializeField] private float ropeOscillationFrequency = 2f; + [Tooltip("Damping for physics movement (higher = less bouncy)")] + [SerializeField] private float ropeDamping = 0.3f; + + [Tooltip("Initial separation distance between rope ends when broken")] + [SerializeField] private float initialSeparationDistance = 0.1f; + + [Tooltip("Initial downward impulse for falling rope end")] + [SerializeField] private float initialFallImpulse = 2.0f; + + [Tooltip("Force Y position reset on start to ensure proper falling")] + [SerializeField] private bool forceYReset = true; // Private references private Rope originalRope; @@ -86,7 +91,7 @@ public class RopeBreaker : MonoBehaviour CreateBreakPointTransform(breakPointPosition); // Create two new rope GameObjects - CreateRopeSegments(); + CreateRopeSegments(breakPointPosition); // Hide the original rope originalLineRenderer.enabled = false; @@ -94,9 +99,6 @@ public class RopeBreaker : MonoBehaviour // Play effects PlayBreakEffects(breakPointPosition); - // Start the animation coroutine - StartCoroutine(AnimateRopeBreak()); - return true; } @@ -116,15 +118,38 @@ public class RopeBreaker : MonoBehaviour follower.targetTag = "Player"; follower.followSpeed = ropeFollowSpeed; follower.trailing = ropeTrailing; - follower.oscillationAmplitude = ropeOscillationAmplitude; - follower.oscillationFrequency = ropeOscillationFrequency; - follower.disableOscillation = true; // Disable oscillation for player-attached rope + follower.useGravity = false; // Player rope end doesn't use gravity + + // Create second break point + GameObject secondBreakObj = new GameObject("RopeBreakPoint_Second"); + secondBreakTransform = secondBreakObj.transform; + secondBreakTransform.position = breakPointPosition; + secondBreakTransform.SetParent(transform.parent); + + // Add physics behavior to second break point + RopeEndPhysicsFollower secondFollower = secondBreakObj.AddComponent(); + secondFollower.targetTag = "Rock"; + secondFollower.followSpeed = ropeFollowSpeed; + secondFollower.trailing = ropeTrailing; + secondFollower.useGravity = true; // Enable gravity for hanging rope end + secondFollower.gravityStrength = ropeGravityStrength; + secondFollower.verticalHangStrength = ropeVerticalHangStrength; + secondFollower.damping = ropeDamping; + secondFollower.initialFallImpulse = initialFallImpulse; + secondFollower.forceYReset = forceYReset; + + // Create initial separation + Vector3 direction = (originalRope.EndPoint.position - breakPointPosition).normalized; + if (direction.magnitude < 0.01f) direction = Vector3.down; + + breakPointTransform.position -= direction * initialSeparationDistance * 0.5f; + secondBreakTransform.position += direction * initialSeparationDistance * 0.5f; } /// /// Creates two new rope GameObjects for the broken segments /// - private void CreateRopeSegments() + private void CreateRopeSegments(Vector3 breakPointPosition) { // Create the first half rope (from start to break point) firstHalfRope = new GameObject("Rope_FirstHalf"); @@ -177,7 +202,7 @@ public class RopeBreaker : MonoBehaviour // Configure the second half rope - REVERSED: Rock (End) is now Start, Break point is now End secondHalfRopeComponent.SetStartPoint(originalRope.EndPoint); - secondHalfRopeComponent.SetEndPoint(breakPointTransform, false); // Don't recalculate yet + secondHalfRopeComponent.SetEndPoint(secondBreakTransform, false); // Don't recalculate yet // Copy properties from original rope CopyRopeProperties(originalRope, secondHalfRopeComponent); @@ -187,6 +212,20 @@ public class RopeBreaker : MonoBehaviour // Now force recalculation after initialization secondHalfRopeComponent.RecalculateRope(); + + // Set explicit rope length constraints on the physics followers + // This needs to be done after the rope segments are created so we have the correct rope lengths + RopeEndPhysicsFollower playerFollower = breakPointTransform.GetComponent(); + if (playerFollower != null) + { + playerFollower.SetMaxDistance(firstHalfRopeComponent.ropeLength); + } + + RopeEndPhysicsFollower rockFollower = secondBreakTransform.GetComponent(); + if (rockFollower != null) + { + rockFollower.SetMaxDistance(secondHalfRopeComponent.ropeLength); + } } /// @@ -252,92 +291,6 @@ public class RopeBreaker : MonoBehaviour } } - /// - /// Animates the rope break with falling and swinging effects - /// - private IEnumerator AnimateRopeBreak() - { - float elapsedTime = 0f; - Vector3 originalBreakPosition = breakPointTransform.position; - - // Create a second break point that will move in the opposite direction - GameObject secondBreakPoint = new GameObject("RopeBreakPoint_Second"); - secondBreakTransform = secondBreakPoint.transform; - secondBreakTransform.position = originalBreakPosition; - secondBreakTransform.SetParent(transform.parent); - - // Add the physics follower component to the second break point - RopeEndPhysicsFollower secondFollower = secondBreakPoint.AddComponent(); - secondFollower.targetTag = "Rock"; - secondFollower.followSpeed = ropeFollowSpeed; - secondFollower.trailing = ropeTrailing; - secondFollower.oscillationAmplitude = ropeOscillationAmplitude * 1.5f; // Slightly more oscillation for falling piece - secondFollower.oscillationFrequency = ropeOscillationFrequency; - secondFollower.disableOscillation = true; // Enable oscillation for rock-attached end - - // Update the second rope to use this new break point as the end point - // (since we reversed the rope direction, the break point is now the end) - secondHalfRopeComponent.SetEndPoint(secondBreakTransform); - secondHalfRopeComponent.RecalculateRope(); - - // Get the directions for swinging - Vector3 direction1 = (originalRope.StartPoint.position - breakPointTransform.position).normalized; - Vector3 direction2 = (originalRope.EndPoint.position - breakPointTransform.position).normalized; - - // Make sure the directions have horizontal components - direction1.y = 0; - direction2.y = 0; - - // Normalize to prevent zero vectors - if (direction1.magnitude < 0.01f) direction1 = Vector3.right; - if (direction2.magnitude < 0.01f) direction2 = Vector3.left; - - direction1.Normalize(); - direction2.Normalize(); - - // Initial separation to create a visual gap - float separationDistance = originalLineRenderer.startWidth * 2f; // Base the gap on the rope width - breakPointTransform.position += -direction2 * separationDistance * 0.5f; - secondBreakTransform.position += direction2 * separationDistance * 0.5f; - - // Recalculate the ropes after initial separation - firstHalfRopeComponent.RecalculateRope(); - secondHalfRopeComponent.RecalculateRope(); - - while (elapsedTime < 0.75f) // Animate for 1 second - { - elapsedTime += Time.deltaTime; - - // Calculate swing motion using sine wave (with different phases for each end) - float swingFactor1 = Mathf.Sin(elapsedTime * 4f) * swingAmount * Mathf.Exp(-elapsedTime); - float swingFactor2 = Mathf.Sin(elapsedTime * 4f + Mathf.PI * 0.5f) * swingAmount * Mathf.Exp(-elapsedTime); - - // First break point (attached to player) - maintain Y position but allow swinging - Vector3 pos1 = originalBreakPosition; - pos1.y = originalBreakPosition.y; // Keep original Y position - no falling - pos1 += direction1 * swingFactor1; - pos1 += -direction2 * (separationDistance * 0.5f); // Maintain separation - breakPointTransform.position = pos1; - - // Second break point (hanging rope end) - allow small amount of falling - Vector3 pos2 = originalBreakPosition; - float fallDistance = fallSpeed * elapsedTime; - pos2.y = originalBreakPosition.y - fallDistance; // Limited falling - pos2 += direction2 * swingFactor2; - pos2 += direction2 * (separationDistance * 0.5f); // Maintain separation - secondBreakTransform.position = pos2; - - // Recalculate the ropes each frame - firstHalfRopeComponent.RecalculateRope(); - secondHalfRopeComponent.RecalculateRope(); - - yield return null; - } - - // After the initial animation, let the physics followers take over - // They will continue to move and animate the rope endpoints - } - /// /// Restores the original rope and cleans up the broken pieces /// @@ -366,11 +319,9 @@ public class RopeBreaker : MonoBehaviour Destroy(breakPointTransform.gameObject); } - // Find and destroy the second break point if it exists - Transform secondBreakPoint = transform.parent?.Find("RopeBreakPoint_Second"); - if (secondBreakPoint != null) + if (secondBreakTransform != null) { - Destroy(secondBreakPoint.gameObject); + Destroy(secondBreakTransform.gameObject); } } } diff --git a/Assets/Scripts/Minigames/DivingForPictures/RopeEndPhysicsFollower.cs b/Assets/Scripts/Minigames/DivingForPictures/RopeEndPhysicsFollower.cs index 0d299589..2eb88e90 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/RopeEndPhysicsFollower.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/RopeEndPhysicsFollower.cs @@ -1,45 +1,233 @@ -using UnityEngine; +using GogoGaga.OptimizedRopesAndCables; +using UnityEngine; public class RopeEndPhysicsFollower : MonoBehaviour { - [Tooltip("Tag of the object this endpoint should follow (e.g., 'player' or 'rock')")] + [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; - [Tooltip("Amplitude of fake gravity/flap")] public float oscillationAmplitude = 0.15f; - [Tooltip("Frequency of fake gravity/flap")] public float oscillationFrequency = 2f; - [Tooltip("If true, disable vertical oscillation (useful for player attachment)")] - public bool disableOscillation = true; + [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 Vector3 offset; - private float oscillationTime; + 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; } - offset = transform.position - (target ? target.position : Vector3.zero); - oscillationTime = Random.value * Mathf.PI * 2f; // randomize phase + + // 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; - // Smooth follow with trailing - Vector3 desired = target.position + offset; - transform.position = Vector3.SmoothDamp(transform.position, desired, ref velocity, trailing, followSpeed); + + // Calculate deltaTime for physics stability + float deltaTime = Time.deltaTime; - // Add fake gravity/flap (only if oscillation is enabled) - if (!disableOscillation) + // 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) { - oscillationTime += Time.deltaTime * oscillationFrequency; - float yOsc = Mathf.Sin(oscillationTime) * oscillationAmplitude; - transform.position += new Vector3(0, yOsc, 0); + // 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); } } @@ -47,6 +235,55 @@ public class RopeEndPhysicsFollower : MonoBehaviour { targetTag = tag; GameObject found = GameObject.FindGameObjectWithTag(targetTag); - if (found) target = found.transform; + 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}"); } }