MiniGames/Assets/Scripts/ChaseOn/ChaseRunEnemy.cs
2025-09-15 21:14:32 +05:00

284 lines
9.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using System.Collections;
public class ChaseRunEnemy : MonoBehaviour
{
[Header("Refs")]
public Animator m_animator;
public Rigidbody rb;
[Header("Movement")]
public float moveSpeed = 4f;
[Tooltip("Enter stomp when within this distance to player.")]
public float stopDistance = 1.5f;
[Tooltip("Resume run if player is at least this far (hysteresis). Must be > stopDistance.")]
public float resumeDistance = 2.2f;
[Header("Stomp Resume Control")]
[Tooltip("If player escapes by at least this many meters beyond closest point during stomp, resume.")]
public float escapeDelta = 0.75f;
[Tooltip("Minimum time to wait in stomp before allowing resume (prevents instant pop-back).")]
public float stompTimeout = 0.6f;
[Tooltip("Absolute max time to hold stomp before forcing resume if no squish happens.")]
public float maxStompHold = 2.0f;
[Header("Animator Parameters")]
[Tooltip("Optional idle/stop bool (can block Stomp). Leave empty to ignore.")]
public string isStopBoolName = "isStop";
[Tooltip("Bool that transitions Run -> Stomp/Squish in your Animator.")]
public string stompBoolName = "Stomp";
[Header("Animator State Fallback (optional)")]
[Tooltip("Name of your stomp/squish state for forced crossfade if the bool doesnt enter.")]
public string stompStateName = ""; // e.g. "Stomp" or "Squish"
[Tooltip("Animator base layer index.")]
public int baseLayer = 0;
[Header("Speed Source")]
public bool usePlayerSpeed = false; // keep false so enemy doesn't inherit wolf boost
[Header("VFX (optional)")]
[SerializeField] private ParticleSystem LeftFootDustPoof, RightFootDustPoof;
[Header("Squish Fallback (optional)")]
[Tooltip("If true, also call KillBySquish after a short delay in case the foot trigger misses.")]
public bool ensureSquishFallback = false;
[Tooltip("Delay that matches the stomp impact frame in your animation.")]
public float stompImpactDelay = 0.25f;
[Header("Debug")]
public bool debugLogs = false;
private Transform player;
private ChasePlayerController playerController;
private float stopDistanceSqr, resumeDistanceSqr, escapeDeltaSqr;
private bool stompTriggered = false;
private float stompStartTime = -999f;
private float minDistanceSqrDuringStomp = float.PositiveInfinity;
private int stompStateHash = 0;
private void Awake()
{
if (!rb) rb = GetComponent<Rigidbody>();
var playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj)
{
player = playerObj.transform;
playerController = player.GetComponentInParent<ChasePlayerController>();
}
else
{
Debug.LogError("[ChaseRunEnemy] Player not found in scene.");
}
stopDistanceSqr = stopDistance * stopDistance;
resumeDistanceSqr = resumeDistance * resumeDistance;
escapeDeltaSqr = escapeDelta * escapeDelta;
if (!string.IsNullOrEmpty(stompStateName))
stompStateHash = Animator.StringToHash(stompStateName);
if (resumeDistance <= stopDistance)
Debug.LogWarning("[ChaseRunEnemy] resumeDistance should be > stopDistance to avoid flip-flop.");
}
private void OnEnable()
{
if (usePlayerSpeed)
ChasePlayerController.OnMoveSpeedChanged += UpdateMoveSpeed;
}
private void OnDisable()
{
if (usePlayerSpeed)
ChasePlayerController.OnMoveSpeedChanged -= UpdateMoveSpeed; // correct unsubscribe
}
private void UpdateMoveSpeed(float speed) => moveSpeed = speed;
private void FixedUpdate()
{
if (!player) return;
Vector3 toPlayer = player.position - transform.position;
float distanceSqr = toPlayer.sqrMagnitude;
if (stompTriggered)
{
// Track closest approach while in stomp
if (distanceSqr < minDistanceSqrDuringStomp)
minDistanceSqrDuringStomp = distanceSqr;
// If player actually died, do nothing—let animation/foot trigger finish
if (playerController && playerController.waitingForGameOver)
return;
// Resume conditions:
bool waitedEnough = (Time.time - stompStartTime) >= stompTimeout;
bool farEnoughByResume = distanceSqr > resumeDistanceSqr;
bool farEnoughByEscape = distanceSqr > (minDistanceSqrDuringStomp + escapeDeltaSqr);
bool forceTimeout = (Time.time - stompStartTime) >= maxStompHold;
if ((waitedEnough && (farEnoughByResume || farEnoughByEscape)) || forceTimeout)
{
if (debugLogs) Debug.Log("[ChaseRunEnemy] Resuming run (no squish).");
ClearStompAndResume();
return;
}
// Stay stopped (keep zero velocity)
if (rb)
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
return;
}
// Not stomping yet: check entry condition
if (distanceSqr <= stopDistanceSqr)
{
TriggerStomp(distanceSqr);
return;
}
// Otherwise, keep running forward
MoveForward();
}
private void MoveForward()
{
rb.MovePosition(rb.position + Vector3.back * moveSpeed * Time.fixedDeltaTime);
}
private void TriggerStomp(float currentDistanceSqr)
{
if (stompTriggered) return;
stompTriggered = true;
stompStartTime = Time.time;
minDistanceSqrDuringStomp = currentDistanceSqr;
// Stop instantly via physics — DO NOT set isStop yet (it may block your stomp transition)
if (rb)
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
if (m_animator)
{
// 1) Request stomp first
if (!string.IsNullOrEmpty(stompBoolName))
m_animator.SetBool(stompBoolName, true);
// 2) Optionally mark stop AFTER stomp gets a frame to take effect (handled in coroutine)
if (!string.IsNullOrEmpty(isStopBoolName))
m_animator.SetBool(isStopBoolName, false); // make sure it isn't blocking
// Verify we actually enter stomp; if not, fix animator conditions or force crossfade.
StartCoroutine(EnsureEnteredStomp());
}
if (debugLogs) Debug.Log("[ChaseRunEnemy] Enter Stomp request sent.");
// Optional safety: squish fallback if foot trigger misses
if (ensureSquishFallback)
Invoke(nameof(FallbackSquishPlayer), stompImpactDelay);
}
private IEnumerator EnsureEnteredStomp()
{
// Wait one frame so Animator can process the bool
yield return null;
if (IsInOrGoingTo(stompStateHash))
{
// Now that stomp transition started, you MAY set isStop=true if you still need it for other graphs
if (!string.IsNullOrEmpty(isStopBoolName))
m_animator.SetBool(isStopBoolName, true);
if (debugLogs) Debug.Log("[ChaseRunEnemy] Stomp detected on Animator.");
yield break;
}
// If stomp not entered, try unblocking by ensuring isStop is false
if (!string.IsNullOrEmpty(isStopBoolName))
m_animator.SetBool(isStopBoolName, false);
// Give it another frame
yield return null;
if (IsInOrGoingTo(stompStateHash))
{
if (!string.IsNullOrEmpty(isStopBoolName))
m_animator.SetBool(isStopBoolName, true);
if (debugLogs) Debug.Log("[ChaseRunEnemy] Stomp detected after clearing isStop.");
yield break;
}
// Still not in stomp — force crossfade if we have a state name
if (stompStateHash != 0)
{
m_animator.CrossFadeInFixedTime(stompStateHash, 0.08f, baseLayer, 0f);
if (debugLogs) Debug.Log("[ChaseRunEnemy] Forced crossfade to stomp state.");
// Optional: set isStop after forcing
if (!string.IsNullOrEmpty(isStopBoolName))
m_animator.SetBool(isStopBoolName, true);
}
else
{
if (debugLogs) Debug.LogWarning("[ChaseRunEnemy] Stomp not entered and no stompStateName set for fallback.");
}
}
private void ClearStompAndResume()
{
stompTriggered = false;
if (m_animator)
{
if (!string.IsNullOrEmpty(isStopBoolName))
m_animator.SetBool(isStopBoolName, false);
if (!string.IsNullOrEmpty(stompBoolName))
m_animator.SetBool(stompBoolName, false);
}
}
private void FallbackSquishPlayer()
{
if (!playerController /*|| playerController.waitingForGameOver*/) return;
if (debugLogs) Debug.Log("[ChaseRunEnemy] Fallback KillBySquish.");
playerController.KillBySquish(null, 0.25f, 0.35f);
}
// ===== Helpers =====
private bool IsInOrGoingTo(int shortHash)
{
if (m_animator == null || shortHash == 0) return false;
var st = m_animator.GetCurrentAnimatorStateInfo(baseLayer);
if (st.shortNameHash == shortHash) return true;
if (m_animator.IsInTransition(baseLayer))
{
var nxt = m_animator.GetNextAnimatorStateInfo(baseLayer);
if (nxt.shortNameHash == shortHash) return true;
}
return false;
}
// Animation Events (optional)
public void LeftFootOnFloor()
{
if (LeftFootDustPoof) { LeftFootDustPoof.Stop(); LeftFootDustPoof.Play(); }
}
public void RightFootOnFloor()
{
if (RightFootDustPoof) { RightFootDustPoof.Stop(); RightFootDustPoof.Play(); }
}
}