using UnityEngine; using System.Collections; using System.Collections.Generic; using System; using TMPro; using DG.Tweening; public class HurdleManager : MonoBehaviour { public PlayerPathRunner playerRunner; public List hurdles = new(); public TMP_Text distanceToHurdleText; public TMP_Text tapsRequiredText; public DOTweenPath pathSource; private bool challengeRunning = false; private int currentTapCount = 0; public Transform currentHurdle = null; private int requiredTaps; private Coroutine activeChallenge; private Vector3[] drawPoints; private float gameStartTime; private bool challengeCompleted = false; private bool gameOver = false; void Start() { if (pathSource != null) drawPoints = pathSource.GetDrawPoints(); gameStartTime = Time.time; } void Update() { // Stop everything if game is over if (gameOver) return; // Wait a bit before starting challenges to let speed stabilize if (Time.time - gameStartTime < 1f) return; if (!challengeRunning && hurdles.Count > 0) { StartNextChallenge(); } else if (challengeRunning) { CountTaps(); CheckForJump(); } } void StartNextChallenge() { if (playerRunner == null || drawPoints == null || hurdles.Count == 0) return; currentHurdle = hurdles[0]; requiredTaps = UnityEngine.Random.Range(2, 8); currentTapCount = 0; challengeCompleted = false; activeChallenge = StartCoroutine(StartTapChallenge()); challengeRunning = true; } IEnumerator StartTapChallenge() { Debug.Log($"🟢 Challenge started! Tap {requiredTaps} times before reaching the hurdle!"); while (currentHurdle != null && hurdles.Contains(currentHurdle)) { // Calculate distance in real-time float remainingDistance = GetRemainingDistance(playerRunner.transform.position, currentHurdle.position); // Update UI with distance if (distanceToHurdleText != null) distanceToHurdleText.text = $"{remainingDistance:F1}m"; if (tapsRequiredText != null) { if (challengeCompleted) tapsRequiredText.text = "✓ Complete!"; else { tapsRequiredText.text ="Tap " + (requiredTaps - currentTapCount) + " times!"; } } // Check if challenge is completed if (currentTapCount >= requiredTaps && !challengeCompleted) { Debug.Log("✅ Challenge Completed! Ready to jump!"); challengeCompleted = true; // Don't remove hurdle or end challenge yet - wait for jump } yield return null; } } void CountTaps() { if (Input.GetMouseButtonDown(0) || (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)) { currentTapCount++; Debug.Log($"Tap {currentTapCount}/{requiredTaps}"); } } void CheckForJump() { if (currentHurdle == null || playerRunner.IsJumping()) return; float remainingDistance = GetRemainingDistance(playerRunner.transform.position, currentHurdle.position); // When very close to hurdle and challenge is completed if (remainingDistance < 5f && challengeCompleted) { // Trigger jump in the PlayerPathRunner playerRunner.TriggerJump(() => { // This callback runs after jump completes if (currentHurdle != null) { hurdles.Remove(currentHurdle); if (activeChallenge != null) StopCoroutine(activeChallenge); EndChallengeUI(); } }); } } void EndChallengeUI() { challengeRunning = false; currentHurdle = null; if (distanceToHurdleText != null) distanceToHurdleText.text = ""; if (tapsRequiredText != null) tapsRequiredText.text = ""; activeChallenge = null; } void OnTriggerEnter(Collider other) { if (other.CompareTag("Hurdle")) { // Check if this is the current hurdle and challenge wasn't completed if (other.transform == currentHurdle && !challengeCompleted && !playerRunner.IsJumping()) { Debug.Log("❌ GAME OVER! Failed to complete tap challenge!"); GameOver(); } } } void GameOver() { gameOver = true; // Stop all coroutines if (activeChallenge != null) StopCoroutine(activeChallenge); // Clear UI EndChallengeUI(); // Stop player movement playerRunner.enabled = false; // Show game over UI if (tapsRequiredText != null) tapsRequiredText.text = "GAME OVER!"; Debug.Log("💀 GAME OVER - Player failed to clear hurdle!"); // You can add more game over logic here: // - Show game over screen // - Play game over sound // - Show restart button // - Save score, etc. } float GetRemainingDistance(Vector3 playerPos, Vector3 hurdlePos) { if (drawPoints == null || drawPoints.Length < 2) return 0f; // Find closest point indices for player and hurdle int playerIndex = GetClosestPathSegmentIndex(playerPos); int hurdleIndex = GetClosestPathSegmentIndex(hurdlePos); // If player is past the hurdle, return 0 if (playerIndex >= hurdleIndex) return 0f; float totalDistance = 0f; // Calculate distance from player to the end of its current segment Vector3 playerClosestPoint = GetClosestPointOnSegment(drawPoints[playerIndex], drawPoints[playerIndex + 1], playerPos); totalDistance += Vector3.Distance(playerClosestPoint, drawPoints[playerIndex + 1]); // Add distances of complete segments between player and hurdle for (int i = playerIndex + 1; i < hurdleIndex; i++) { totalDistance += Vector3.Distance(drawPoints[i], drawPoints[i + 1]); } // Add distance from start of hurdle's segment to hurdle position if (playerIndex < hurdleIndex) { Vector3 hurdleClosestPoint = GetClosestPointOnSegment(drawPoints[hurdleIndex], drawPoints[hurdleIndex + 1], hurdlePos); totalDistance += Vector3.Distance(drawPoints[hurdleIndex], hurdleClosestPoint); } return totalDistance; } int GetClosestPathSegmentIndex(Vector3 worldPos) { float minDist = float.MaxValue; int closestIndex = 0; for (int i = 0; i < drawPoints.Length - 1; i++) { Vector3 closestPoint = GetClosestPointOnSegment(drawPoints[i], drawPoints[i + 1], worldPos); float dist = Vector3.Distance(worldPos, closestPoint); if (dist < minDist) { minDist = dist; closestIndex = i; } } return closestIndex; } Vector3 GetClosestPointOnSegment(Vector3 a, Vector3 b, Vector3 point) { Vector3 ab = b - a; float t = Mathf.Clamp01(Vector3.Dot(point - a, ab) / Vector3.Dot(ab, ab)); return a + t * ab; } }