using System; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using DG.Tweening.Plugins.Core.PathCore; using UnityEngine.SceneManagement; public class PlayerPathRunner : MonoBehaviour { [Header("Speed Settings")] public float baseSpeed = 2f; public float maxSpeed = 10f; public float acceleration = 5f; public float constantSpeed = 5f; // Speed to use when not in tap mode [Header("Tap Speed Mode")] public bool useTapToMove = true; public float tapWindowSeconds = 0.5f; [Header("Jump Settings")] public float jumpPower = 2f; public float jumpDuration = 0.5f; public float jumpDistance = 3f; // Distance in world units [Header("Path Setup")] public DOTweenPath pathSource; public bool faceMovementDirection = true; public float rotationSpeed = 10f; [Header("Hurdle Setup")] public List hurdles; // Assign all hurdles here private List tapTimestamps = new(); private float currentSpeed = 0f; private float pathPosition = 0f; private Vector3[] drawPoints; private Path bakedPath; private bool isJumping = false; private System.Action onJumpComplete; private void Awake() { Application.targetFrameRate = 120; } void Start() { if (pathSource == null) { Debug.LogError("Path Source not assigned!"); return; } // Bake DOTween path (we pause immediately) transform.DOPath(pathSource.wps.ToArray(), 1f, pathSource.pathType, pathSource.pathMode) .SetOptions(pathSource.isClosedPath) .SetEase(Ease.Linear) .SetLookAt(0) .Pause(); drawPoints = pathSource.GetDrawPoints(); if (drawPoints == null || drawPoints.Length < 2) { Debug.LogError("Draw points not generated properly."); } // Initialize speed based on mode if (!useTapToMove) { currentSpeed = constantSpeed; } } void Update() { // Don't process normal movement if jumping if (isJumping) return; if (useTapToMove) { HandleInput(); UpdateSpeed(); } else { // In constant mode, set speed directly without acceleration currentSpeed = constantSpeed; } MoveOnPath(); } void HandleInput() { if (Input.GetMouseButtonDown(0) || Input.touchCount > 0) { tapTimestamps.Add(Time.time); } } void UpdateSpeed() { float cutoff = Time.time - tapWindowSeconds; tapTimestamps.RemoveAll(t => t < cutoff); float tps = tapTimestamps.Count / tapWindowSeconds; float targetSpeed = Mathf.Clamp(baseSpeed + tps, baseSpeed, maxSpeed); currentSpeed = Mathf.MoveTowards(currentSpeed, targetSpeed, acceleration * Time.deltaTime); } void MoveOnPath() { if (drawPoints == null || drawPoints.Length < 2) return; float speed = currentSpeed / 50f; pathPosition += speed * Time.deltaTime; pathPosition %= 1f; UpdatePositionOnPath(pathPosition); } void UpdatePositionOnPath(float normalizedPosition) { float floatIndex = normalizedPosition * (drawPoints.Length - 1); int iA = Mathf.FloorToInt(floatIndex); int iB = Mathf.Min(iA + 1, drawPoints.Length - 1); float t = floatIndex - iA; Vector3 pos = Vector3.Lerp(drawPoints[iA], drawPoints[iB], t); transform.position = pos; // Handle rotation to face movement direction if (faceMovementDirection && iB < drawPoints.Length) { Vector3 direction = drawPoints[iB] - drawPoints[iA]; direction.y = 0; // Keep rotation only on Y axis (no tilting) if (direction != Vector3.zero) { Quaternion targetRotation = Quaternion.LookRotation(direction); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime); } } } public void TriggerJump(System.Action onComplete = null) { if (isJumping) return; Debug.Log("🦘 Jump triggered in PlayerPathRunner!"); isJumping = true; onJumpComplete = onComplete; // Calculate how much to advance on the path based on world distance float pathLength = CalculateTotalPathLength(); float jumpPercentage = jumpDistance / pathLength; // Calculate end position on path float targetPathPosition = pathPosition + jumpPercentage; targetPathPosition %= 1f; // Wrap around if needed // Get world positions for start and end Vector3 startPos = transform.position; Vector3 endPos = GetWorldPositionAtPathPosition(targetPathPosition); // Do the jump transform.DOJump(endPos, jumpPower, 1, jumpDuration) .SetEase(Ease.OutQuad) .OnUpdate(() => { // Update rotation during jump to face the landing direction if (faceMovementDirection) { Vector3 jumpDirection = GetDirectionAtPathPosition(targetPathPosition); if (jumpDirection != Vector3.zero) { Quaternion targetRot = Quaternion.LookRotation(jumpDirection); transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, rotationSpeed * Time.deltaTime); } } }) .OnComplete(() => { // Update our path position to match where we jumped to pathPosition = targetPathPosition; isJumping = false; Debug.Log("Jump completed!"); onJumpComplete?.Invoke(); onJumpComplete = null; }); } float CalculateTotalPathLength() { float totalLength = 0f; for (int i = 0; i < drawPoints.Length - 1; i++) { totalLength += Vector3.Distance(drawPoints[i], drawPoints[i + 1]); } return totalLength; } Vector3 GetWorldPositionAtPathPosition(float normalizedPos) { float floatIndex = normalizedPos * (drawPoints.Length - 1); int iA = Mathf.FloorToInt(floatIndex); int iB = Mathf.Min(iA + 1, drawPoints.Length - 1); float t = floatIndex - iA; return Vector3.Lerp(drawPoints[iA], drawPoints[iB], t); } Vector3 GetDirectionAtPathPosition(float normalizedPos) { float floatIndex = normalizedPos * (drawPoints.Length - 1); int iA = Mathf.FloorToInt(floatIndex); int iB = Mathf.Min(iA + 1, drawPoints.Length - 1); Vector3 direction = drawPoints[iB] - drawPoints[iA]; direction.y = 0; // Keep only horizontal direction return direction.normalized; } public float GetCurrentSpeed() { return currentSpeed; } public bool IsJumping() { return isJumping; } public void GoToMainMenu() { SceneManager.LoadScene(0); } }