Demon Fixes
This commit is contained in:
@@ -13,7 +13,7 @@ namespace DemonBoss.Magic
|
||||
[Tooltip("Fireball prefab (projectile with its own targeting logic)")]
|
||||
public GameObject fireballPrefab;
|
||||
|
||||
[Tooltip("Seconds between shots")]
|
||||
[Tooltip("Base seconds between shots")]
|
||||
public float fireRate = 0.7f;
|
||||
|
||||
[Tooltip("Maximum number of shots before auto-despawn")]
|
||||
@@ -22,6 +22,16 @@ namespace DemonBoss.Magic
|
||||
[Tooltip("Wait time after last shot before despawn")]
|
||||
public float despawnDelay = 3f;
|
||||
|
||||
[Header("Randomization (Desync)")]
|
||||
[Tooltip("Random initial delay after spawn to desync turrets (seconds)")]
|
||||
public Vector2 initialStaggerRange = new Vector2(0.0f, 0.6f);
|
||||
|
||||
[Tooltip("Per-shot random jitter added to fireRate (seconds). Range x means +/- x.")]
|
||||
public float fireRateJitter = 0.2f;
|
||||
|
||||
[Tooltip("Aim wobble in degrees (0 = disabled). Small value adds natural dispersion.")]
|
||||
public float aimJitterDegrees = 0f;
|
||||
|
||||
[Header("Rotation Configuration")]
|
||||
[Tooltip("Yaw rotation speed in degrees per second")]
|
||||
public float turnSpeed = 120f;
|
||||
@@ -81,102 +91,69 @@ namespace DemonBoss.Magic
|
||||
|
||||
if (muzzle == null)
|
||||
{
|
||||
// Try to find a child named "muzzle"; fallback to self
|
||||
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");
|
||||
}
|
||||
muzzle = muzzleChild != null ? muzzleChild : crystalTransform;
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Crystal activated");
|
||||
|
||||
if (autoFindPlayer && target == null)
|
||||
FindPlayer();
|
||||
|
||||
StartShooting();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update tick: rotate towards target or idle spin.
|
||||
/// </summary>
|
||||
/// <summary> Update tick: rotate towards target or idle spin. </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (!isActive) return;
|
||||
RotateTowardsTarget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to find the player by tag (for turret-only aiming).
|
||||
/// </summary>
|
||||
/// <summary> Attempts to find the player by tag (for turret-only aiming). </summary>
|
||||
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);
|
||||
}
|
||||
if (player != null) SetTarget(player.transform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the turret's aiming target (does NOT propagate to projectiles).
|
||||
/// </summary>
|
||||
public void SetTarget(Transform newTarget)
|
||||
{
|
||||
target = newTarget;
|
||||
if (enableDebug && target != null)
|
||||
Debug.Log($"[CrystalShooterAI] Set target: {target.name}");
|
||||
}
|
||||
/// <summary> Sets the turret's aiming target (does NOT propagate to projectiles). </summary>
|
||||
public void SetTarget(Transform newTarget) => target = newTarget;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the timed shooting routine (fires until maxShots, then despawns).
|
||||
/// </summary>
|
||||
/// <summary> 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>
|
||||
/// Stops the shooting routine immediately.
|
||||
/// </summary>
|
||||
/// <summary> Stops the shooting routine immediately. </summary>
|
||||
public void StopShooting()
|
||||
{
|
||||
isActive = false;
|
||||
|
||||
if (shootingCoroutine != null)
|
||||
{
|
||||
StopCoroutine(shootingCoroutine);
|
||||
shootingCoroutine = null;
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Stopped shooting");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main shooting loop: checks aim/range → spawns fireball → waits fireRate.
|
||||
/// After finishing, waits a short delay and despawns the turret.
|
||||
/// Main shooting loop with initial spawn stagger and per-shot jitter.
|
||||
/// </summary>
|
||||
private IEnumerator ShootingCoroutine()
|
||||
{
|
||||
// 1) Initial stagger so multiple crystals don't start at the same frame
|
||||
if (initialStaggerRange.y > 0f)
|
||||
{
|
||||
float stagger = Random.Range(initialStaggerRange.x, initialStaggerRange.y);
|
||||
if (stagger > 0f) yield return new WaitForSeconds(stagger);
|
||||
}
|
||||
|
||||
// 2) Normal loop with CanShoot gate and per-shot jittered waits
|
||||
while (shotsFired < maxShots && isActive)
|
||||
{
|
||||
if (CanShoot())
|
||||
@@ -186,18 +163,17 @@ namespace DemonBoss.Magic
|
||||
lastShotTime = Time.time;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(fireRate);
|
||||
float wait = fireRate + (fireRateJitter > 0f ? Random.Range(-fireRateJitter, fireRateJitter) : 0f);
|
||||
// Clamp wait to something safe so it never becomes non-positive
|
||||
if (wait < 0.05f) wait = 0.05f;
|
||||
yield return new WaitForSeconds(wait);
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log($"[CrystalShooterAI] Finished shooting ({shotsFired} shots)");
|
||||
|
||||
yield return new WaitForSeconds(despawnDelay);
|
||||
DespawnCrystal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aiming/range gate for firing.
|
||||
/// </summary>
|
||||
/// <summary> Aiming/range gate for firing. </summary>
|
||||
private bool CanShoot()
|
||||
{
|
||||
if (target == null) return false;
|
||||
@@ -211,16 +187,10 @@ namespace DemonBoss.Magic
|
||||
return angleToTarget <= aimTolerance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a fireball oriented towards the turret's current aim direction.
|
||||
/// </summary>
|
||||
/// <summary> Spawns a fireball oriented towards the turret's current aim direction with optional dispersion. </summary>
|
||||
private void FireFireball()
|
||||
{
|
||||
if (fireballPrefab == null || muzzle == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Missing fireball prefab or muzzle");
|
||||
return;
|
||||
}
|
||||
if (fireballPrefab == null || muzzle == null) return;
|
||||
|
||||
Vector3 shootDirection;
|
||||
if (target != null)
|
||||
@@ -228,28 +198,24 @@ namespace DemonBoss.Magic
|
||||
Vector3 targetCenter = target.position + Vector3.up * 1f;
|
||||
shootDirection = (targetCenter - muzzle.position).normalized;
|
||||
}
|
||||
else
|
||||
else shootDirection = crystalTransform.forward;
|
||||
|
||||
// Apply small aim jitter (random yaw/pitch) to avoid perfect sync volleys
|
||||
if (aimJitterDegrees > 0f)
|
||||
{
|
||||
shootDirection = crystalTransform.forward;
|
||||
float yaw = Random.Range(-aimJitterDegrees, aimJitterDegrees);
|
||||
float pitch = Random.Range(-aimJitterDegrees * 0.5f, aimJitterDegrees * 0.5f); // usually less pitch dispersion
|
||||
shootDirection = Quaternion.Euler(pitch, yaw, 0f) * shootDirection;
|
||||
}
|
||||
|
||||
Vector3 spawnPosition = muzzle.position;
|
||||
Quaternion spawnRotation = Quaternion.LookRotation(shootDirection);
|
||||
|
||||
LeanPool.Spawn(fireballPrefab, spawnPosition, spawnRotation);
|
||||
|
||||
PlayShootEffects();
|
||||
|
||||
if (enableDebug)
|
||||
{
|
||||
Debug.Log($"[CrystalShooterAI] Shot #{shotsFired + 1} at {spawnPosition} dir: {shootDirection}");
|
||||
Debug.DrawRay(spawnPosition, shootDirection * 8f, Color.red, 2f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays muzzle VFX and shoot SFX (if enabled).
|
||||
/// </summary>
|
||||
/// <summary> Plays muzzle VFX and shoot SFX (if enabled). </summary>
|
||||
private void PlayShootEffects()
|
||||
{
|
||||
if (!useShootEffects) return;
|
||||
@@ -261,14 +227,10 @@ namespace DemonBoss.Magic
|
||||
}
|
||||
|
||||
if (audioSource != null && shootSound != null)
|
||||
{
|
||||
audioSource.PlayOneShot(shootSound);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smooth yaw rotation towards target; idles by spinning when no target.
|
||||
/// </summary>
|
||||
/// <summary> Smooth yaw rotation towards target; idles by spinning when no target. </summary>
|
||||
private void RotateTowardsTarget()
|
||||
{
|
||||
if (target != null)
|
||||
@@ -292,24 +254,15 @@ namespace DemonBoss.Magic
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns the turret via Lean Pool.
|
||||
/// </summary>
|
||||
/// <summary> 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., boss death).
|
||||
/// </summary>
|
||||
public void ForceDespawn()
|
||||
{
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Forced despawn");
|
||||
DespawnCrystal();
|
||||
}
|
||||
/// <summary> Forces immediate despawn (e.g., boss death). </summary>
|
||||
public void ForceDespawn() => DespawnCrystal();
|
||||
|
||||
/// <summary> Returns crystal state information. </summary>
|
||||
public bool IsActive() => isActive;
|
||||
@@ -320,9 +273,6 @@ namespace DemonBoss.Magic
|
||||
|
||||
public float GetTimeSinceLastShot() => Time.time - lastShotTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gizmos for range and aim visualization.
|
||||
/// </summary>
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!showGizmos) return;
|
||||
@@ -330,24 +280,6 @@ namespace DemonBoss.Magic
|
||||
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);
|
||||
Gizmos.DrawLine(muzzle.position, muzzle.position + forward);
|
||||
}
|
||||
}
|
||||
|
||||
if (muzzle != null)
|
||||
{
|
||||
Gizmos.color = Color.blue;
|
||||
|
||||
Reference in New Issue
Block a user