using UnityEngine; using System.Collections; using GogoGaga.OptimizedRopesAndCables; /// /// Component that allows breaking a rope in half. /// Attach this to the same GameObject that has a Rope and LineRenderer component. /// public class RopeBreaker : MonoBehaviour { [Header("Break Settings")] [Tooltip("Position along rope where break occurs (0-1)")] [Range(0f, 1f)] [SerializeField] private float breakPosition = 0.5f; [Tooltip("Effect to spawn at break point (optional)")] [SerializeField] private GameObject breakEffect; [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("Oscillation amplitude for the rope physics simulation")] [SerializeField] private float ropeOscillationAmplitude = 0.15f; [Tooltip("Oscillation frequency for the rope physics simulation")] [SerializeField] private float ropeOscillationFrequency = 2f; // Private references private Rope originalRope; private LineRenderer originalLineRenderer; private GameObject firstHalfRope; private GameObject secondHalfRope; private Rope firstHalfRopeComponent; private Rope secondHalfRopeComponent; private Transform breakPointTransform; private Transform secondBreakTransform; private void Awake() { // Get references to the required components originalRope = GetComponent(); originalLineRenderer = GetComponent(); if (originalRope == null || originalLineRenderer == null) { Debug.LogError("RopeBreaker requires both Rope and LineRenderer components on the same GameObject"); enabled = false; } } /// /// Breaks the rope at the specified position. /// /// Optional override for break position (0-1) /// True if rope was broken successfully, false otherwise public bool BreakRope(float? breakPositionOverride = null) { if (originalRope == null || !originalRope.StartPoint || !originalRope.EndPoint) { Debug.LogError("Cannot break rope: Missing rope component or endpoints"); return false; } // Use override position if provided float breakPos = breakPositionOverride ?? breakPosition; breakPos = Mathf.Clamp01(breakPos); // Get the world position at the break point Vector3 breakPointPosition = originalRope.GetPointAt(breakPos); // Create a transform at the break point to use as an anchor CreateBreakPointTransform(breakPointPosition); // Create two new rope GameObjects CreateRopeSegments(); // Hide the original rope originalLineRenderer.enabled = false; // Play effects PlayBreakEffects(breakPointPosition); // Start the animation coroutine StartCoroutine(AnimateRopeBreak()); return true; } /// /// Creates a transform at the break point to use as an anchor /// private void CreateBreakPointTransform(Vector3 breakPointPosition) { // Create a new GameObject for the break point GameObject breakPointObj = new GameObject("RopeBreakPoint"); breakPointTransform = breakPointObj.transform; breakPointTransform.position = breakPointPosition; breakPointTransform.SetParent(transform.parent); // Parent to the same parent as the rope // Add the physics follower component to the break point RopeEndPhysicsFollower follower = breakPointObj.AddComponent(); follower.targetTag = "Player"; follower.followSpeed = ropeFollowSpeed; follower.trailing = ropeTrailing; follower.oscillationAmplitude = ropeOscillationAmplitude; follower.oscillationFrequency = ropeOscillationFrequency; follower.disableOscillation = true; // Disable oscillation for player-attached rope } /// /// Creates two new rope GameObjects for the broken segments /// private void CreateRopeSegments() { // Create the first half rope (from start to break point) firstHalfRope = new GameObject("Rope_FirstHalf"); firstHalfRope.transform.position = transform.position; firstHalfRope.transform.rotation = transform.rotation; firstHalfRope.transform.SetParent(transform.parent); // Add Rope component which automatically adds LineRenderer due to RequireComponent firstHalfRopeComponent = firstHalfRope.AddComponent(); // Get the LineRenderer that was automatically added LineRenderer firstLineRenderer = firstHalfRope.GetComponent(); if (firstLineRenderer == null) { // Only add if somehow not created (shouldn't happen, but safety check) firstLineRenderer = firstHalfRope.AddComponent(); } CopyLineRendererProperties(originalLineRenderer, firstLineRenderer); // Create the second half rope (from break point to end) secondHalfRope = new GameObject("Rope_SecondHalf"); secondHalfRope.transform.position = transform.position; secondHalfRope.transform.rotation = transform.rotation; secondHalfRope.transform.SetParent(transform.parent); // Add Rope component which automatically adds LineRenderer due to RequireComponent secondHalfRopeComponent = secondHalfRope.AddComponent(); // Get the LineRenderer that was automatically added LineRenderer secondLineRenderer = secondHalfRope.GetComponent(); if (secondLineRenderer == null) { // Only add if somehow not created (shouldn't happen, but safety check) secondLineRenderer = secondHalfRope.AddComponent(); } CopyLineRendererProperties(originalLineRenderer, secondLineRenderer); // Configure the first half rope firstHalfRopeComponent.SetStartPoint(originalRope.StartPoint); firstHalfRopeComponent.SetEndPoint(breakPointTransform, false); // Don't recalculate yet // Copy properties from original rope CopyRopeProperties(originalRope, firstHalfRopeComponent); // Explicitly initialize the rope firstHalfRopeComponent.Initialize(); // Now force recalculation after initialization firstHalfRopeComponent.RecalculateRope(); // 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 // Copy properties from original rope CopyRopeProperties(originalRope, secondHalfRopeComponent); // Explicitly initialize the rope secondHalfRopeComponent.Initialize(); // Now force recalculation after initialization secondHalfRopeComponent.RecalculateRope(); } /// /// Copies properties from one LineRenderer to another /// private void CopyLineRendererProperties(LineRenderer source, LineRenderer destination) { // Copy material destination.material = source.material; // Copy colors destination.startColor = source.startColor; destination.endColor = source.endColor; // Copy width destination.startWidth = source.startWidth; destination.endWidth = source.endWidth; // Copy other properties destination.numCornerVertices = source.numCornerVertices; destination.numCapVertices = source.numCapVertices; destination.alignment = source.alignment; destination.textureMode = source.textureMode; destination.generateLightingData = source.generateLightingData; destination.useWorldSpace = source.useWorldSpace; destination.loop = source.loop; destination.sortingLayerID = source.sortingLayerID; destination.sortingOrder = source.sortingOrder; } /// /// Copies properties from one Rope to another /// private void CopyRopeProperties(Rope source, Rope destination) { destination.linePoints = source.linePoints; destination.stiffness = source.stiffness; destination.damping = source.damping; destination.ropeLength = source.ropeLength / 2f; // Halve the rope length for each segment destination.ropeWidth = source.ropeWidth; destination.midPointWeight = source.midPointWeight; destination.midPointPosition = source.midPointPosition; // Recalculate the rope to update its appearance destination.RecalculateRope(); } /// /// Plays visual and audio effects at the break point /// private void PlayBreakEffects(Vector3 breakPointPosition) { // Spawn break effect if assigned if (breakEffect != null) { Instantiate(breakEffect, breakPointPosition, Quaternion.identity); } // Play break sound if assigned if (breakSound != null) { AudioSource.PlayClipAtPoint(breakSound, breakPointPosition); } } /// /// 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 /// public void RestoreRope() { // Re-enable the original rope if (originalLineRenderer != null) { originalLineRenderer.enabled = true; } // Clean up the broken rope pieces if (firstHalfRope != null) { Destroy(firstHalfRope); } if (secondHalfRope != null) { Destroy(secondHalfRope); } // Clean up both break points if (breakPointTransform != null) { Destroy(breakPointTransform.gameObject); } // Find and destroy the second break point if it exists Transform secondBreakPoint = transform.parent?.Find("RopeBreakPoint_Second"); if (secondBreakPoint != null) { Destroy(secondBreakPoint.gameObject); } } }