using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// GAME 1303 Tier 2 — Notices the player, runs at them, attacks when close, gives up
/// when they get away.
///
/// SETUP
/// 1. Add to an enemy that already has Rigidbody2D + Health. Works with OR without
/// EnemyPatrol (if present, patrol resumes when the chase ends).
/// 2. Detect Radius: how close the player must be to be noticed.
/// Lose Radius: bigger — how far they must get before the enemy gives up.
/// 3. Require Line Of Sight: raycasts against Ground Layer so walls hide the player.
/// 4. Movement:
/// Grounded – moves on X only, gravity applies (thugs, roaches).
/// Free – moves on X and Y, ignores gravity (ghosts, flying roaches,
/// top-down monsters in Last Delivery). Set Rigidbody2D gravity to 0.
/// 5. Attack: when within Attack Range, calls OnAttack every Attack Cooldown seconds.
/// Wire it to MeleeAttack.Attack or ProjectileShooter.Fire. Leave empty for
/// "just touch to hurt" enemies.
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class EnemyChase : MonoBehaviour
{
public enum Movement { Grounded, Free }
[Header("Detection")]
public float detectRadius = 6f;
public float loseRadius = 9f;
public bool requireLineOfSight = true;
public LayerMask groundLayer;
[Header("Movement")]
public Movement movement = Movement.Grounded;
public float chaseSpeed = 3.5f;
[Tooltip("Free mode only: how snappy direction changes are.")]
public float acceleration = 10f;
[Tooltip("Flip sprite to face the player.")]
public bool faceTarget = true;
public bool artFacesRight = true;
[Header("Attack")]
public float attackRange = 1.2f;
public float attackCooldown = 1f;
[Tooltip("Stop moving while within attack range.")]
public bool stopInRange = true;
public UnityEvent OnAttack;
[Header("Events")]
public UnityEvent OnNoticedPlayer;
public UnityEvent OnLostPlayer;
public bool IsChasing { get; private set; }
private Rigidbody2D rb;
private Transform player;
private EnemyPatrol patrol;
private Knockback knockback;
private Health health;
private PlayerAnimatorBridge animBridge;
private float attackTimer;
private bool facingRight;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
patrol = GetComponent<EnemyPatrol>();
knockback = GetComponent<Knockback>();
health = GetComponent<Health>();
animBridge = GetComponent<PlayerAnimatorBridge>();
facingRight = artFacesRight ? transform.localScale.x > 0 : transform.localScale.x < 0;
}
void Start()
{
var p = GameObject.FindGameObjectWithTag("Player");
if (p != null) player = p.transform;
}
void FixedUpdate()
{
if (player == null || (health != null && health.IsDead)) return;
attackTimer -= Time.fixedDeltaTime;
float dist = Vector2.Distance(transform.position, player.position);
// --- State changes ---
if (!IsChasing && dist <= detectRadius && CanSee())
{
IsChasing = true;
if (patrol != null) patrol.Active = false;
OnNoticedPlayer?.Invoke();
}
else if (IsChasing && dist > loseRadius)
{
IsChasing = false;
if (patrol != null) patrol.Active = true;
if (movement == Movement.Free) rb.linearVelocity = Vector2.zero;
OnLostPlayer?.Invoke();
}
if (!IsChasing) return;
if (knockback != null && knockback.IsStunned) return;
// --- Face ---
if (faceTarget)
{
bool shouldFaceRight = player.position.x > transform.position.x;
if (shouldFaceRight != facingRight) Flip();
}
// --- Attack ---
if (dist <= attackRange)
{
if (stopInRange)
{
rb.linearVelocity = movement == Movement.Grounded
? new Vector2(0f, rb.linearVelocity.y) : Vector2.zero;
animBridge?.SetSpeed(0f);
}
if (attackTimer <= 0f)
{
attackTimer = attackCooldown;
OnAttack?.Invoke();
}
if (stopInRange) return;
}
// --- Move ---
if (movement == Movement.Grounded)
{
float dir = Mathf.Sign(player.position.x - transform.position.x);
rb.linearVelocity = new Vector2(dir * chaseSpeed, rb.linearVelocity.y);
animBridge?.SetSpeed(rb.linearVelocity.x);
}
else
{
Vector2 desired = ((Vector2)player.position - rb.position).normalized * chaseSpeed;
rb.linearVelocity = Vector2.Lerp(rb.linearVelocity, desired, acceleration * Time.fixedDeltaTime);
animBridge?.SetSpeed(rb.linearVelocity.magnitude);
}
}
bool CanSee()
{
if (!requireLineOfSight) return true;
Vector2 dir = player.position - transform.position;
RaycastHit2D hit = Physics2D.Raycast(transform.position, dir.normalized, dir.magnitude, groundLayer);
return hit.collider == null;
}
void Flip()
{
facingRight = !facingRight;
Vector3 s = transform.localScale;
s.x *= -1f;
transform.localScale = s;
}
void OnDrawGizmosSelected()
{
Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, detectRadius);
Gizmos.color = Color.gray; Gizmos.DrawWireSphere(transform.position, loseRadius);
Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, attackRange);
}
}