using Lean.Pool; using System.Collections; using UnityEngine; namespace DemonBoss.Magic { /// /// AI component for crystal turret spawned by boss /// Rotates towards player and shoots fireballs for specified time /// public class CrystalShooterAI : MonoBehaviour { [Header("Shooting Configuration")] [Tooltip("Transform point from which projectiles are fired")] public Transform muzzle; [Tooltip("Fireball prefab")] public GameObject fireballPrefab; [Tooltip("Fireball speed in m/s")] public float fireballSpeed = 28f; [Tooltip("Time between shots in seconds")] public float fireRate = 0.7f; [Tooltip("Maximum number of shots before auto-despawn")] public int maxShots = 10; [Tooltip("Wait time after shooting before despawn")] public float despawnDelay = 3f; [Header("Rotation Configuration")] [Tooltip("Target rotation speed in degrees/s")] public float turnSpeed = 120f; [Tooltip("Idle spin speed when no target (degrees/s, 0 = disabled)")] public float idleSpinSpeed = 30f; [Tooltip("Aiming accuracy in degrees (smaller value = more accurate)")] public float aimTolerance = 5f; [Header("Targeting")] [Tooltip("Automatically find player on start")] public bool autoFindPlayer = true; [Tooltip("Player tag to search for")] public string playerTag = "Player"; [Tooltip("Maximum shooting range")] public float maxShootingRange = 50f; [Header("Effects")] [Tooltip("Particle effect at shot")] public GameObject muzzleFlashPrefab; [Tooltip("Shoot sound")] public AudioClip shootSound; [Header("Debug")] [Tooltip("Enable debug logging")] public bool enableDebug = false; [Tooltip("Show gizmos in Scene View")] public bool showGizmos = true; // Private variables private Transform target; private AudioSource audioSource; private Coroutine shootingCoroutine; private bool isActive = false; private int shotsFired = 0; private float lastShotTime = 0f; private Transform crystalTransform; /// /// Component initialization /// private void Awake() { crystalTransform = transform; audioSource = GetComponent(); if (audioSource == null && shootSound != null) { audioSource = gameObject.AddComponent(); audioSource.playOnAwake = false; audioSource.spatialBlend = 1f; } if (muzzle == null) { Transform muzzleChild = crystalTransform.Find("muzzle"); if (muzzleChild != null) { muzzle = muzzleChild; if (enableDebug) Debug.Log("[CrystalShooterAI] Found muzzle child"); } else { muzzle = crystalTransform; if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Using crystal center as muzzle"); } } } /// /// Start - begin crystal operation /// private void Start() { if (enableDebug) Debug.Log("[CrystalShooterAI] Crystal activated"); if (autoFindPlayer && target == null) { FindPlayer(); } StartShooting(); } /// /// Update - rotate crystal towards target /// private void Update() { if (!isActive) return; RotateTowardsTarget(); } /// /// Find player automatically /// private void FindPlayer() { GameObject player = GameObject.FindGameObjectWithTag(playerTag); if (player != null) { SetTarget(player.transform); if (enableDebug) Debug.Log("[CrystalShooterAI] Automatically found player"); } else { if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Cannot find player with tag: " + playerTag); } } /// /// Set target for crystal /// /// Target transform public void SetTarget(Transform newTarget) { target = newTarget; if (enableDebug && target != null) { Debug.Log($"[CrystalShooterAI] Set target: {target.name}"); } } /// /// Start shooting cycle /// public void StartShooting() { if (isActive) return; isActive = true; shotsFired = 0; shootingCoroutine = StartCoroutine(ShootingCoroutine()); if (enableDebug) Debug.Log("[CrystalShooterAI] Starting shooting"); } /// /// Stop shooting /// public void StopShooting() { isActive = false; if (shootingCoroutine != null) { StopCoroutine(shootingCoroutine); shootingCoroutine = null; } if (enableDebug) Debug.Log("[CrystalShooterAI] Stopped shooting"); } /// /// Main coroutine handling shooting cycle /// private IEnumerator ShootingCoroutine() { while (shotsFired < maxShots && isActive) { if (CanShoot()) { FireFireball(); shotsFired++; lastShotTime = Time.time; } yield return new WaitForSeconds(fireRate); } if (enableDebug) Debug.Log($"[CrystalShooterAI] Finished shooting ({shotsFired} shots)"); yield return new WaitForSeconds(despawnDelay); DespawnCrystal(); } /// /// Checks if crystal can shoot /// private bool CanShoot() { if (target == null) return false; float distanceToTarget = Vector3.Distance(crystalTransform.position, target.position); if (distanceToTarget > maxShootingRange) return false; Vector3 directionToTarget = (target.position - crystalTransform.position).normalized; float angleToTarget = Vector3.Angle(crystalTransform.forward, directionToTarget); return angleToTarget <= aimTolerance; } /// /// Fires fireball towards target /// private void FireFireball() { if (fireballPrefab == null || muzzle == null) { if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Missing fireball prefab or muzzle"); return; } Vector3 shootDirection = crystalTransform.forward; if (target != null) { Vector3 targetCenter = target.position + Vector3.up * 1f; shootDirection = (targetCenter - muzzle.position).normalized; } GameObject fireball = LeanPool.Spawn(fireballPrefab, muzzle.position, Quaternion.LookRotation(shootDirection)); Rigidbody fireballRb = fireball.GetComponent(); if (fireballRb != null) { fireballRb.linearVelocity = shootDirection * fireballSpeed; } PlayShootEffects(); if (enableDebug) { Debug.Log($"[CrystalShooterAI] Shot #{shotsFired + 1} in direction: {shootDirection}"); } } /// /// Plays shooting effects /// private void PlayShootEffects() { if (muzzleFlashPrefab != null && muzzle != null) { GameObject flash = LeanPool.Spawn(muzzleFlashPrefab, muzzle.position, muzzle.rotation); LeanPool.Despawn(flash, 2f); } if (audioSource != null && shootSound != null) { audioSource.PlayOneShot(shootSound); } } /// /// Rotates crystal towards target or performs idle spin /// private void RotateTowardsTarget() { if (target != null) { Vector3 directionToTarget = target.position - crystalTransform.position; directionToTarget.y = 0; if (directionToTarget != Vector3.zero) { Quaternion targetRotation = Quaternion.LookRotation(directionToTarget); crystalTransform.rotation = Quaternion.RotateTowards( crystalTransform.rotation, targetRotation, turnSpeed * Time.deltaTime ); } } else if (idleSpinSpeed > 0f) { crystalTransform.Rotate(Vector3.up, idleSpinSpeed * Time.deltaTime); } } /// /// Despawns crystal from map /// public void DespawnCrystal() { if (enableDebug) Debug.Log("[CrystalShooterAI] Despawning crystal"); StopShooting(); LeanPool.Despawn(gameObject); } /// /// Forces immediate despawn (e.g. on boss death) /// public void ForceDespawn() { if (enableDebug) Debug.Log("[CrystalShooterAI] Forced despawn"); DespawnCrystal(); } /// /// Returns crystal state information /// public bool IsActive() => isActive; public int GetShotsFired() => shotsFired; public int GetRemainingShots() => Mathf.Max(0, maxShots - shotsFired); public float GetTimeSinceLastShot() => Time.time - lastShotTime; /// /// Draws gizmos in Scene View /// private void OnDrawGizmosSelected() { if (!showGizmos) return; Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, maxShootingRange); if (target != null) { Gizmos.color = Color.yellow; Gizmos.DrawLine(transform.position, target.position); if (muzzle != null) { Gizmos.color = Color.green; Vector3 forward = transform.forward * 5f; Vector3 right = Quaternion.AngleAxis(aimTolerance, transform.up) * forward; Vector3 left = Quaternion.AngleAxis(-aimTolerance, transform.up) * forward; Gizmos.DrawLine(muzzle.position, muzzle.position + right); Gizmos.DrawLine(muzzle.position, muzzle.position + left); } } if (muzzle != null) { Gizmos.color = Color.blue; Gizmos.DrawWireSphere(muzzle.position, 0.2f); } } } }