MiniGames/Assets/Scripts/ChaseOn/ChaseRunEnemy.cs

284 lines
9.7 KiB
C#
Raw Normal View History

2025-08-18 17:52:40 +05:00
using UnityEngine;
2025-09-11 18:46:20 +05:00
using System.Collections;
2025-08-18 17:52:40 +05:00
public class ChaseRunEnemy : MonoBehaviour
{
2025-09-11 18:46:20 +05:00
[Header("Refs")]
2025-08-18 17:52:40 +05:00
public Animator m_animator;
public Rigidbody rb;
2025-09-11 18:46:20 +05:00
[Header("Movement")]
public float moveSpeed = 4f;
[Tooltip("Enter stomp when within this distance to player.")]
2025-08-18 17:52:40 +05:00
public float stopDistance = 1.5f;
2025-09-11 18:46:20 +05:00
[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 doesn<73>t enter.")]
public string stompStateName = ""; // e.g. "Stomp" or "Squish"
[Tooltip("Animator base layer index.")]
public int baseLayer = 0;
2025-08-18 17:52:40 +05:00
2025-09-04 17:01:33 +05:00
[Header("Speed Source")]
2025-09-11 18:46:20 +05:00
public bool usePlayerSpeed = false; // keep false so enemy doesn't inherit wolf boost
2025-08-18 17:52:40 +05:00
2025-09-11 18:46:20 +05:00
[Header("VFX (optional)")]
[SerializeField] private ParticleSystem LeftFootDustPoof, RightFootDustPoof;
2025-09-11 18:46:20 +05:00
[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;
2025-08-18 17:52:40 +05:00
private void Awake()
{
2025-09-11 18:46:20 +05:00
if (!rb) rb = GetComponent<Rigidbody>();
2025-08-18 17:52:40 +05:00
2025-09-11 18:46:20 +05:00
var playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj)
2025-08-18 17:52:40 +05:00
{
player = playerObj.transform;
2025-09-11 18:46:20 +05:00
playerController = player.GetComponentInParent<ChasePlayerController>();
2025-08-18 17:52:40 +05:00
}
else
{
Debug.LogError("[ChaseRunEnemy] Player not found in scene.");
}
2025-09-11 18:46:20 +05:00
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.");
2025-08-18 17:52:40 +05:00
}
private void OnEnable()
{
2025-09-04 17:01:33 +05:00
if (usePlayerSpeed)
ChasePlayerController.OnMoveSpeedChanged += UpdateMoveSpeed;
2025-08-18 17:52:40 +05:00
}
private void OnDisable()
{
2025-09-04 17:01:33 +05:00
if (usePlayerSpeed)
2025-09-11 18:46:20 +05:00
ChasePlayerController.OnMoveSpeedChanged -= UpdateMoveSpeed; // correct unsubscribe
2025-08-18 17:52:40 +05:00
}
2025-09-11 18:46:20 +05:00
private void UpdateMoveSpeed(float speed) => moveSpeed = speed;
2025-08-18 17:52:40 +05:00
private void FixedUpdate()
{
2025-09-11 18:46:20 +05:00
if (!player) return;
2025-08-18 17:52:40 +05:00
Vector3 toPlayer = player.position - transform.position;
float distanceSqr = toPlayer.sqrMagnitude;
2025-09-11 18:46:20 +05:00
if (stompTriggered)
2025-08-18 17:52:40 +05:00
{
2025-09-11 18:46:20 +05:00
// Track closest approach while in stomp
if (distanceSqr < minDistanceSqrDuringStomp)
minDistanceSqrDuringStomp = distanceSqr;
// If player actually died, do nothing<6E>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)
2025-08-18 17:52:40 +05:00
{
2025-09-11 18:46:20 +05:00
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;
2025-08-18 17:52:40 +05:00
}
return;
}
2025-09-11 18:46:20 +05:00
// Not stomping yet: check entry condition
if (distanceSqr <= stopDistanceSqr)
2025-08-18 17:52:40 +05:00
{
2025-09-11 18:46:20 +05:00
TriggerStomp(distanceSqr);
return;
2025-08-18 17:52:40 +05:00
}
2025-09-11 18:46:20 +05:00
// Otherwise, keep running forward
2025-08-18 17:52:40 +05:00
MoveForward();
}
private void MoveForward()
{
rb.MovePosition(rb.position + Vector3.back * moveSpeed * Time.fixedDeltaTime);
}
2025-09-11 18:46:20 +05:00
private void TriggerStomp(float currentDistanceSqr)
{
if (stompTriggered) return;
stompTriggered = true;
stompStartTime = Time.time;
minDistanceSqrDuringStomp = currentDistanceSqr;
// Stop instantly via physics <20> 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 <20> 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()
{
2025-09-11 18:46:20 +05:00
if (LeftFootDustPoof) { LeftFootDustPoof.Stop(); LeftFootDustPoof.Play(); }
}
2025-09-11 18:46:20 +05:00
public void RightFootOnFloor()
{
2025-09-11 18:46:20 +05:00
if (RightFootDustPoof) { RightFootDustPoof.Stop(); RightFootDustPoof.Play(); }
}
2025-08-18 17:52:40 +05:00
}