MiniGames/Assets/Scripts/ChaseOn/ChasePlayerController.cs
2025-08-19 16:59:43 +05:00

367 lines
11 KiB
C#

using System.Collections;
using DG.Tweening;
using UnityEngine;
public class ChasePlayerController : MonoBehaviour
{
[Header("Movement")]
public float moveSpeed = 5f;
public float laneDistance = 2.5f; // 0=Left,1=Mid,2=Right
public float jumpPower = 0.6f;
public float jumpDuration = 1.2f;
public float laneSwitchSpeed = 10f; // used as units per second
[Header("Animator Driving")]
public bool useSpeedBlendTree = true;
public string speedParamName = "Speed";
public float runSpeedParamValue = 2f;
public string runStateName = "Locomotion";
public int baseLayer = 0;
[Header("Fall / GameOver Logic")]
public string fallStateName = "Fall";
public string fallingStateName = "Falling";
public float secondHitWindow = 10f;
public float stateWaitTimeout = 3f;
public static System.Action<float> OnMoveSpeedChanged;
int currentLane = 1;
Rigidbody rb;
Animator animator;
bool isGrounded = true;
bool isJumping = false;
Vector2 startTouchPosition;
bool swipeDetected = false;
float minSwipeDistance = 50f;
float lastObstacleHitTime = -999f;
public bool waitingForGameOver = false;
int runShortHash, fallShortHash;
public int fallingShortHash;
[SerializeField] bool validateStatesOnStart = true;
[SerializeField] string runTag = "Run";
[SerializeField] string fallTag = "Fall";
[SerializeField] string fallingTag = "Falling";
float originalMoveSpeed;
bool unableToMove = false;
ChaseScoreManager scoreManager;
void Start()
{
rb = GetComponent<Rigidbody>();
animator = GetComponent<Animator>();
scoreManager = FindObjectOfType<ChaseScoreManager>();
runShortHash = Animator.StringToHash(runStateName);
fallShortHash = Animator.StringToHash(fallStateName);
fallingShortHash = Animator.StringToHash(fallingStateName);
if (validateStatesOnStart)
{
if (!animator.HasState(baseLayer, runShortHash))
Debug.LogError($"Run state '{runStateName}' not found");
if (!animator.HasState(baseLayer, fallShortHash))
Debug.LogError($"Fall state '{fallStateName}' not found");
if (!animator.HasState(baseLayer, fallingShortHash))
Debug.LogError($"Falling state '{fallingStateName}' not found");
}
originalMoveSpeed = moveSpeed;
ForceRunStart();
}
public void SetMoveSpeed(float newSpeed)
{
moveSpeed = newSpeed;
OnMoveSpeedChanged?.Invoke(newSpeed);
}
void Update()
{
DriveRunAnimation();
if (!unableToMove)
{
HandleInput();
HandleSwipe();
}
}
void FixedUpdate()
{
MoveForward();
}
void HandleInput()
{
if (isJumping) return;
if (Input.GetKeyDown(KeyCode.LeftArrow) && currentLane > 0)
{
currentLane--;
TweenToLaneX((currentLane - 1) * laneDistance);
}
if (Input.GetKeyDown(KeyCode.RightArrow) && currentLane < 2)
{
currentLane++;
TweenToLaneX((currentLane - 1) * laneDistance);
}
if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
Jump();
}
void HandleSwipe()
{
if (Input.touchCount != 1) return;
var touch = Input.GetTouch(0);
switch (touch.phase)
{
case TouchPhase.Began:
startTouchPosition = touch.position;
swipeDetected = true;
break;
case TouchPhase.Ended:
if (!swipeDetected) return;
Vector2 swipe = touch.position - startTouchPosition;
if (swipe.magnitude >= minSwipeDistance && !isJumping)
{
if (Mathf.Abs(swipe.x) > Mathf.Abs(swipe.y))
{
if (swipe.x > 0 && currentLane < 2)
{
currentLane++;
TweenToLaneX((currentLane - 1) * laneDistance);
}
else if (swipe.x < 0 && currentLane > 0)
{
currentLane--;
TweenToLaneX((currentLane - 1) * laneDistance);
}
}
else if (swipe.y > 0 && isGrounded)
{
Jump();
}
}
swipeDetected = false;
break;
}
}
void TweenToLaneX(float targetX)
{
float distance = Mathf.Abs(rb.position.x - targetX);
float duration = distance / laneSwitchSpeed;
rb.DOMoveX(targetX, duration).SetEase(Ease.OutQuad);
}
void MoveForward()
{
rb.MovePosition(rb.position + Vector3.back * moveSpeed * Time.fixedDeltaTime);
}
void Jump()
{
if (!isGrounded) return;
isGrounded = false;
isJumping = true;
rb.velocity = Vector3.zero;
rb.useGravity = false;
float forwardDisplacement = moveSpeed * jumpDuration;
Vector3 jumpTarget = rb.position + Vector3.back * forwardDisplacement;
rb.DOJump(jumpTarget, jumpPower, 1, jumpDuration)
.SetEase(Ease.Linear)
.OnStart(() =>
{
SafeSetTrigger("Jump");
SafeSetBool("IsGrounded", false);
})
.OnComplete(() =>
{
rb.useGravity = true;
isGrounded = true;
isJumping = false;
SafeSetTrigger("Land");
SafeSetBool("IsGrounded", true);
ForceRunStart();
});
}
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
isGrounded = true;
SafeSetBool("IsGrounded", true);
ForceRunStart();
}
}
void ForceRunStart(bool ignoreGuards = false)
{
if (!ignoreGuards && (IsInOrGoingTo(fallShortHash) || IsInOrGoingTo(fallingShortHash) || waitingForGameOver))
return;
if (useSpeedBlendTree && HasParameter(speedParamName, AnimatorControllerParameterType.Float))
{
animator.SetFloat(speedParamName, runSpeedParamValue);
}
else if (!string.IsNullOrEmpty(runStateName))
{
animator.CrossFadeInFixedTime(runStateName, 0.1f, baseLayer, 0f);
}
}
void DriveRunAnimation()
{
if (IsInOrGoingTo(fallShortHash) || IsInOrGoingTo(fallingShortHash) || isJumping) return;
if (useSpeedBlendTree && HasParameter(speedParamName, AnimatorControllerParameterType.Float))
{
animator.SetFloat(speedParamName, runSpeedParamValue, 0.1f, Time.deltaTime);
}
else
{
var st = animator.GetCurrentAnimatorStateInfo(baseLayer);
var nxt = animator.GetNextAnimatorStateInfo(baseLayer);
bool inRunNow = st.shortNameHash == runShortHash || st.IsTag(runTag);
bool goingToRun = animator.IsInTransition(baseLayer) && (nxt.shortNameHash == runShortHash || nxt.IsTag(runTag));
if (!isJumping && !inRunNow && !goingToRun)
{
animator.CrossFadeInFixedTime(runStateName, 0.1f, baseLayer, 0f);
}
}
}
bool HasParameter(string name, AnimatorControllerParameterType type)
{
foreach (var p in animator.parameters)
if (p.type == type && p.name == name) return true;
return false;
}
void SafeSetTrigger(string trig)
{
if (HasParameter(trig, AnimatorControllerParameterType.Trigger))
animator.SetTrigger(trig);
}
void SafeSetBool(string param, bool v)
{
if (HasParameter(param, AnimatorControllerParameterType.Bool))
animator.SetBool(param, v);
}
bool IsInOrGoingTo(int shortHash)
{
var st = animator.GetCurrentAnimatorStateInfo(baseLayer);
if (st.shortNameHash == shortHash) return true;
if (animator.IsInTransition(baseLayer))
{
var nxt = animator.GetNextAnimatorStateInfo(baseLayer);
if (nxt.shortNameHash == shortHash) return true;
}
return false;
}
// ----------------- Obstacle/Fall flow -----------------
public void OnObstacleHit()
{
if (waitingForGameOver) return;
// Second hit within window → Falling then GameOver
if (Time.time - lastObstacleHitTime <= secondHitWindow)
{
waitingForGameOver = true;
//SetMoveSpeed(0);
moveSpeed = 0;
StartCoroutine(PlayStateAndGameOver(fallingStateName, fallingShortHash));
}
else
{
// First hit → Fall only (no GameOver), then resume run when Fall finishes
lastObstacleHitTime = Time.time;
PlayStateOnce(fallStateName);
originalMoveSpeed = moveSpeed; // remember whatever it was right now
//SetMoveSpeed(0f); // optional: pause forward motion during stumble
moveSpeed = 0;
StartCoroutine(ResumeRunAfter(fallStateName, fallShortHash));
}
}
void PlayStateOnce(string stateName, float xfade = 0.08f)
{
if (string.IsNullOrEmpty(stateName) || animator == null) return;
animator.CrossFadeInFixedTime(stateName, xfade, baseLayer, 0f);
}
IEnumerator ResumeRunAfter(string stateName, int shortHash, float xfade = 0.1f)
{
float t0 = Time.time;
while (!IsInOrGoingTo(shortHash) && !TimedOut(t0)) yield return null;
while (animator.IsInTransition(baseLayer)) yield return null;
// Wait for completion OR force resume after timeout
t0 = Time.time;
while (!StateFinished(shortHash) && !TimedOut(t0))
yield return null;
if (!waitingForGameOver)
{
ForceRunStart(ignoreGuards: true);
SetMoveSpeed(originalMoveSpeed);
animator.CrossFadeInFixedTime(runStateName, 0.1f, baseLayer, 0f);
}
}
public IEnumerator PlayStateAndGameOver(string stateName, int shortHash, float xfade = 0.08f)
{
unableToMove = true;
if (string.IsNullOrEmpty(stateName) || animator == null)
{
if (scoreManager) scoreManager.GameOver();
yield break;
}
animator.CrossFadeInFixedTime(stateName, xfade, baseLayer, 0f);
// Wait to enter target state
float t0 = Time.time;
while (!IsInOrGoingTo(shortHash) && !TimedOut(t0)) yield return null;
while (animator.IsInTransition(baseLayer)) yield return null;
// Wait for it to finish (non-looping recommended)
t0 = Time.time;
while (!StateFinished(shortHash) && !TimedOut(t0)) yield return null;
if (scoreManager) scoreManager.GameOver();
}
bool StateFinished(int shortHash)
{
var st = animator.GetCurrentAnimatorStateInfo(baseLayer);
return st.shortNameHash == shortHash && !animator.IsInTransition(baseLayer) && st.normalizedTime >= 1f;
}
bool TimedOut(float startTime)
{
return stateWaitTimeout > 0f && (Time.time - startTime) > stateWaitTimeout;
}
}