Turret refactor
This commit is contained in:
@@ -1,59 +1,55 @@
|
||||
using Lean.Pool;
|
||||
using Lean.Pool;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DemonBoss.Magic
|
||||
{
|
||||
/// <summary>
|
||||
/// AI component for crystal turret spawned by boss
|
||||
/// Rotates towards player and shoots fireballs for specified time
|
||||
/// </summary>
|
||||
public class CrystalShooterAI : MonoBehaviour
|
||||
{
|
||||
[Header("Shooting Configuration")]
|
||||
[Tooltip("Transform point from which projectiles are fired")]
|
||||
public Transform muzzle;
|
||||
|
||||
[Tooltip("Fireball prefab")]
|
||||
[Tooltip("Fireball prefab (projectile with its own targeting logic)")]
|
||||
public GameObject fireballPrefab;
|
||||
|
||||
[Tooltip("Fireball speed in m/s")]
|
||||
public float fireballSpeed = 28f;
|
||||
|
||||
[Tooltip("Time between shots in seconds")]
|
||||
[Tooltip("Seconds between shots")]
|
||||
public float fireRate = 0.7f;
|
||||
|
||||
[Tooltip("Maximum number of shots before auto-despawn")]
|
||||
public int maxShots = 10;
|
||||
|
||||
[Tooltip("Wait time after shooting before despawn")]
|
||||
[Tooltip("Wait time after last shot before despawn")]
|
||||
public float despawnDelay = 3f;
|
||||
|
||||
[Header("Rotation Configuration")]
|
||||
[Tooltip("Target rotation speed in degrees/s")]
|
||||
[Tooltip("Yaw rotation speed in degrees per second")]
|
||||
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)")]
|
||||
[Tooltip("Aiming accuracy in degrees (smaller = stricter)")]
|
||||
public float aimTolerance = 5f;
|
||||
|
||||
[Header("Targeting")]
|
||||
[Tooltip("Automatically find player on start")]
|
||||
[Header("Targeting (Turret-Side Only)")]
|
||||
[Tooltip("Auto-find player on start by tag")]
|
||||
public bool autoFindPlayer = true;
|
||||
|
||||
[Tooltip("Player tag to search for")]
|
||||
public string playerTag = "Player";
|
||||
|
||||
[Tooltip("Maximum shooting range")]
|
||||
[Tooltip("Max range for allowing shots")]
|
||||
public float maxShootingRange = 50f;
|
||||
|
||||
[Header("Effects")]
|
||||
[Tooltip("Particle effect at shot")]
|
||||
[Tooltip("Enable or disable muzzle flash & sound effects when firing")]
|
||||
public bool useShootEffects = true;
|
||||
|
||||
[Tooltip("Particle effect at shot (pooled)")]
|
||||
public GameObject muzzleFlashPrefab;
|
||||
|
||||
[Tooltip("Shoot sound")]
|
||||
[Tooltip("Shoot sound (played on AudioSource)")]
|
||||
public AudioClip shootSound;
|
||||
|
||||
[Header("Debug")]
|
||||
@@ -63,20 +59,14 @@ namespace DemonBoss.Magic
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// Component initialization
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
crystalTransform = transform;
|
||||
@@ -91,6 +81,7 @@ namespace DemonBoss.Magic
|
||||
|
||||
if (muzzle == null)
|
||||
{
|
||||
// Try to find a child named "muzzle"; fallback to self
|
||||
Transform muzzleChild = crystalTransform.Find("muzzle");
|
||||
if (muzzleChild != null)
|
||||
{
|
||||
@@ -105,33 +96,27 @@ namespace DemonBoss.Magic
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start - begin crystal operation
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Crystal activated");
|
||||
|
||||
if (autoFindPlayer && target == null)
|
||||
{
|
||||
FindPlayer();
|
||||
}
|
||||
|
||||
StartShooting();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update - rotate crystal towards target
|
||||
/// Update tick: rotate towards target or idle spin.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (!isActive) return;
|
||||
|
||||
RotateTowardsTarget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find player automatically
|
||||
/// Attempts to find the player by tag (for turret-only aiming).
|
||||
/// </summary>
|
||||
private void FindPlayer()
|
||||
{
|
||||
@@ -141,42 +126,37 @@ namespace DemonBoss.Magic
|
||||
SetTarget(player.transform);
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Automatically found player");
|
||||
}
|
||||
else
|
||||
else if (enableDebug)
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Cannot find player with tag: " + playerTag);
|
||||
Debug.LogWarning("[CrystalShooterAI] Cannot find player with tag: " + playerTag);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set target for crystal
|
||||
/// Sets the turret's aiming target (does NOT propagate to projectiles).
|
||||
/// </summary>
|
||||
/// <param name="newTarget">Target transform</param>
|
||||
public void SetTarget(Transform newTarget)
|
||||
{
|
||||
target = newTarget;
|
||||
if (enableDebug && target != null)
|
||||
{
|
||||
Debug.Log($"[CrystalShooterAI] Set target: {target.name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start shooting cycle
|
||||
/// Starts the timed shooting routine (fires until maxShots, then despawns).
|
||||
/// </summary>
|
||||
public void StartShooting()
|
||||
{
|
||||
if (isActive) return;
|
||||
|
||||
isActive = true;
|
||||
shotsFired = 0;
|
||||
|
||||
shootingCoroutine = StartCoroutine(ShootingCoroutine());
|
||||
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Starting shooting");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop shooting
|
||||
/// Stops the shooting routine immediately.
|
||||
/// </summary>
|
||||
public void StopShooting()
|
||||
{
|
||||
@@ -192,7 +172,8 @@ namespace DemonBoss.Magic
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main coroutine handling shooting cycle
|
||||
/// Main shooting loop: checks aim/range → spawns fireball → waits fireRate.
|
||||
/// After finishing, waits a short delay and despawns the turret.
|
||||
/// </summary>
|
||||
private IEnumerator ShootingCoroutine()
|
||||
{
|
||||
@@ -211,12 +192,11 @@ namespace DemonBoss.Magic
|
||||
if (enableDebug) Debug.Log($"[CrystalShooterAI] Finished shooting ({shotsFired} shots)");
|
||||
|
||||
yield return new WaitForSeconds(despawnDelay);
|
||||
|
||||
DespawnCrystal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if crystal can shoot
|
||||
/// Aiming/range gate for firing.
|
||||
/// </summary>
|
||||
private bool CanShoot()
|
||||
{
|
||||
@@ -232,7 +212,7 @@ namespace DemonBoss.Magic
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fires fireball towards target
|
||||
/// Spawns a fireball oriented towards the turret's current aim direction.
|
||||
/// </summary>
|
||||
private void FireFireball()
|
||||
{
|
||||
@@ -242,39 +222,41 @@ namespace DemonBoss.Magic
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 shootDirection = crystalTransform.forward;
|
||||
Vector3 shootDirection;
|
||||
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<Rigidbody>();
|
||||
if (fireballRb != null)
|
||||
else
|
||||
{
|
||||
fireballRb.linearVelocity = shootDirection * fireballSpeed;
|
||||
shootDirection = crystalTransform.forward;
|
||||
}
|
||||
|
||||
Vector3 spawnPosition = muzzle.position;
|
||||
Quaternion spawnRotation = Quaternion.LookRotation(shootDirection);
|
||||
|
||||
LeanPool.Spawn(fireballPrefab, spawnPosition, spawnRotation);
|
||||
|
||||
PlayShootEffects();
|
||||
|
||||
if (enableDebug)
|
||||
{
|
||||
Debug.Log($"[CrystalShooterAI] Shot #{shotsFired + 1} in direction: {shootDirection}");
|
||||
Debug.Log($"[CrystalShooterAI] Shot #{shotsFired + 1} at {spawnPosition} dir: {shootDirection}");
|
||||
Debug.DrawRay(spawnPosition, shootDirection * 8f, Color.red, 2f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays shooting effects
|
||||
/// Plays muzzle VFX and shoot SFX (if enabled).
|
||||
/// </summary>
|
||||
private void PlayShootEffects()
|
||||
{
|
||||
if (!useShootEffects) return;
|
||||
|
||||
if (muzzleFlashPrefab != null && muzzle != null)
|
||||
{
|
||||
GameObject flash = LeanPool.Spawn(muzzleFlashPrefab, muzzle.position, muzzle.rotation);
|
||||
|
||||
LeanPool.Despawn(flash, 2f);
|
||||
}
|
||||
|
||||
@@ -285,14 +267,14 @@ namespace DemonBoss.Magic
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates crystal towards target or performs idle spin
|
||||
/// Smooth yaw rotation towards target; idles by spinning when no target.
|
||||
/// </summary>
|
||||
private void RotateTowardsTarget()
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
Vector3 directionToTarget = target.position - crystalTransform.position;
|
||||
directionToTarget.y = 0;
|
||||
directionToTarget.y = 0f;
|
||||
|
||||
if (directionToTarget != Vector3.zero)
|
||||
{
|
||||
@@ -311,19 +293,17 @@ namespace DemonBoss.Magic
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns crystal from map
|
||||
/// Despawns the turret via Lean Pool.
|
||||
/// </summary>
|
||||
public void DespawnCrystal()
|
||||
{
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Despawning crystal");
|
||||
|
||||
StopShooting();
|
||||
|
||||
LeanPool.Despawn(gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces immediate despawn (e.g. on boss death)
|
||||
/// Forces immediate despawn (e.g., boss death).
|
||||
/// </summary>
|
||||
public void ForceDespawn()
|
||||
{
|
||||
@@ -331,9 +311,7 @@ namespace DemonBoss.Magic
|
||||
DespawnCrystal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns crystal state information
|
||||
/// </summary>
|
||||
/// <summary> Returns crystal state information. </summary>
|
||||
public bool IsActive() => isActive;
|
||||
|
||||
public int GetShotsFired() => shotsFired;
|
||||
@@ -343,7 +321,7 @@ namespace DemonBoss.Magic
|
||||
public float GetTimeSinceLastShot() => Time.time - lastShotTime;
|
||||
|
||||
/// <summary>
|
||||
/// Draws gizmos in Scene View
|
||||
/// Gizmos for range and aim visualization.
|
||||
/// </summary>
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
@@ -366,6 +344,7 @@ namespace DemonBoss.Magic
|
||||
|
||||
Gizmos.DrawLine(muzzle.position, muzzle.position + right);
|
||||
Gizmos.DrawLine(muzzle.position, muzzle.position + left);
|
||||
Gizmos.DrawLine(muzzle.position, muzzle.position + forward);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user