using UnityEngine;
/// <summary>
/// GAME 1303 Tier 2 — Ghosts, bees, flying roaches. Ignores gravity, bobs gently,
/// drifts along a path.
///
/// SETUP
/// 1. Sprite + Rigidbody2D (Gravity Scale 0, Freeze Rotation Z) + Collider2D + Health
/// + this script. Hurtbox child (Team = Enemy). To hurt the player on touch add
/// DamageOnContact or an always-on Hitbox.
/// 2. Path:
/// Hover – stays put, just bobs (Commission Witch's ghost that "moves around
/// but don't touch it" works well as Hover with a big bob).
/// Waypoints – drifts through a list of Transforms (loop).
/// Wander – picks random points within Wander Radius of its start.
/// 3. Combine with EnemyChase (Movement = Free) for a ghost that hovers until it
/// notices you — this script yields while EnemyChase.IsChasing is true.
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class FloatingEnemy : MonoBehaviour
{
public enum Path { Hover, Waypoints, Wander }
public Path path = Path.Hover;
public float speed = 1.5f;
[Header("Bob")]
public float bobHeight = 0.25f;
public float bobSpeed = 2f;
[Header("Waypoints")]
public Transform[] waypoints;
[Header("Wander")]
public float wanderRadius = 3f;
public float repickEvery = 2f;
[Header("Facing")]
public bool flipToFaceDirection = true;
public bool artFacesRight = true;
private Rigidbody2D rb;
private EnemyChase chase;
private Knockback knockback;
private Vector2 startPos;
private Vector2 target;
private int wpIndex;
private float repickTimer;
private float bobPhase;
private bool facingRight;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
rb.gravityScale = 0f;
chase = GetComponent<EnemyChase>();
knockback = GetComponent<Knockback>();
facingRight = artFacesRight ? transform.localScale.x > 0 : transform.localScale.x < 0;
bobPhase = Random.value * Mathf.PI * 2f; // so groups don't bob in sync
}
void Start()
{
startPos = rb.position;
PickTarget();
}
void FixedUpdate()
{
if (chase != null && chase.IsChasing) return;
if (knockback != null && knockback.IsStunned) return;
bobPhase += bobSpeed * Time.fixedDeltaTime;
float bobVel = Mathf.Cos(bobPhase) * bobHeight * bobSpeed; // derivative of sin bob
Vector2 vel = Vector2.zero;
if (path != Path.Hover)
{
Vector2 toTarget = target - rb.position;
if (toTarget.magnitude < 0.15f) PickTarget();
else vel = toTarget.normalized * speed;
if (path == Path.Wander)
{
repickTimer -= Time.fixedDeltaTime;
if (repickTimer <= 0f) PickTarget();
}
}
rb.linearVelocity = new Vector2(vel.x, vel.y + bobVel);
if (flipToFaceDirection && Mathf.Abs(vel.x) > 0.05f)
{
bool right = vel.x > 0f;
if (right != facingRight)
{
facingRight = right;
Vector3 s = transform.localScale; s.x *= -1f; transform.localScale = s;
}
}
}
void PickTarget()
{
switch (path)
{
case Path.Waypoints:
if (waypoints == null || waypoints.Length == 0) { target = startPos; return; }
wpIndex = (wpIndex + 1) % waypoints.Length;
target = waypoints[wpIndex].position;
break;
case Path.Wander:
target = startPos + Random.insideUnitCircle * wanderRadius;
repickTimer = repickEvery;
break;
default:
target = startPos;
break;
}
}
void OnDrawGizmosSelected()
{
if (path == Path.Wander)
{
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(Application.isPlaying ? (Vector3)startPos : transform.position, wanderRadius);
}
}
}