using UnityEngine; using System.Collections.Generic; public class EnemyAI : MonoBehaviour { public enum EnemyState { Patrol, Chase, Returning } public EnemyState currentState = EnemyState.Patrol; [Header("Movement Settings")] public float patrolSpeed = 3f; public float chaseSpeed = 6f; public float detectionRadius = 12f; public float maxChaseDistance = 30f; public float damagePerSecond = 20f; [Header("Hover Settings")] public float hoverHeight = 2.0f; // Distance above the ground public float hoverSmoothness = 5f; // Lower = smoother/floatier private Vector3 spawnPosition; public List patrolPoints = new List(); private int currentPointIndex = 0; private Transform player; private LayerMask terrainLayer; void Start() { player = GameObject.FindGameObjectWithTag("Player").transform; terrainLayer = LayerMask.GetMask("Terrain"); spawnPosition = transform.position; GeneratePatrolPoints(); } void GeneratePatrolPoints() { patrolPoints.Clear(); for (int i = 0; i < 3; i++) { // Raycast from high up to find the ground Vector3 randomPos = spawnPosition + new Vector3(Random.Range(-15f, 15f), 20f, Random.Range(-15f, 15f)); if (Physics.Raycast(randomPos, Vector3.down, out RaycastHit hit, 50f, terrainLayer)) { // We add the hoverHeight to the patrol point so they don't dive toward the grass patrolPoints.Add(hit.point + Vector3.up * hoverHeight); } } } void Update() { float distToPlayer = Vector3.Distance(transform.position, player.position); float distFromHome = Vector3.Distance(transform.position, spawnPosition); if (distToPlayer < detectionRadius && distFromHome < maxChaseDistance) { currentState = EnemyState.Chase; } else if (distFromHome > maxChaseDistance || (currentState == EnemyState.Chase && distToPlayer > detectionRadius)) { currentState = EnemyState.Returning; } if (currentState == EnemyState.Returning && distFromHome < 2f) { currentState = EnemyState.Patrol; } ExecuteState(); if (currentState == EnemyState.Chase && distToPlayer < 2.0f) // Slightly increased range for floating enemies { PlayerStats stats = player.GetComponent(); if (stats != null) stats.TakeDamage(damagePerSecond * Time.deltaTime); } } void ExecuteState() { switch (currentState) { case EnemyState.Patrol: Patrol(); break; case EnemyState.Chase: MoveTowards(player.position, chaseSpeed); break; case EnemyState.Returning: MoveTowards(spawnPosition, patrolSpeed); break; } } void Patrol() { if (patrolPoints.Count == 0) { GeneratePatrolPoints(); return; } Vector3 target = patrolPoints[currentPointIndex]; MoveTowards(target, patrolSpeed); // Check distance in 3D space now that we hover if (Vector3.Distance(transform.position, target) < 2.0f) currentPointIndex = (currentPointIndex + 1) % patrolPoints.Count; } void MoveTowards(Vector3 target, float speed) { // 1. Horizontal Direction Vector3 dir = (target - transform.position).normalized; dir.y = 0; // Obstacle avoidance (nudge direction if hitting a steep hill) RaycastHit wallHit; if (Physics.Raycast(transform.position + Vector3.up * 0.5f, dir, out wallHit, 1.5f, terrainLayer)) { dir += wallHit.normal * 0.5f; } // 2. Apply Move transform.position += dir * speed * Time.deltaTime; // 3. Floating / Height Adjustment // Fire ray from above the enemy to find the ground directly beneath it if (Physics.Raycast(transform.position + Vector3.up * 10f, Vector3.down, out RaycastHit groundHit, 30f, terrainLayer)) { float targetHeight = groundHit.point.y + hoverHeight; // Use Lerp for a floaty, smooth height change float newY = Mathf.Lerp(transform.position.y, targetHeight, Time.deltaTime * hoverSmoothness); transform.position = new Vector3(transform.position.x, newY, transform.position.z); } // 4. Rotation if (dir != Vector3.zero) { Quaternion targetRotation = Quaternion.LookRotation(dir); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 5f); } } }