Meteor changes
This commit is contained in:
@@ -7,7 +7,7 @@ using UnityEngine.Playables;
|
||||
namespace DemonBoss.Magic
|
||||
{
|
||||
/// <summary>
|
||||
/// Spawns a meteor behind the BOSS and launches it toward the player's position.
|
||||
/// Spawns multiple meteors behind the BOSS and launches them toward the player's position.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Call Meteor")]
|
||||
public class SA_CallMeteor : vStateAction
|
||||
@@ -25,8 +25,22 @@ namespace DemonBoss.Magic
|
||||
[Tooltip("Height above the BOSS to spawn meteor (meters)")]
|
||||
public float aboveBossHeight = 8f;
|
||||
|
||||
[Tooltip("Delay before meteor spawns (wind-up)")]
|
||||
public float castDelay = 0.4f;
|
||||
[Header("Multi-Meteor Configuration")]
|
||||
[Tooltip("Number of meteors to spawn in sequence")]
|
||||
public int meteorCount = 5;
|
||||
|
||||
[Tooltip("Delay before first meteor spawns (wind-up)")]
|
||||
public float initialCastDelay = 0.4f;
|
||||
|
||||
[Tooltip("Time between each meteor spawn")]
|
||||
public float meteorSpawnInterval = 0.6f;
|
||||
|
||||
[Header("Muzzle Flash Effect")]
|
||||
[Tooltip("Particle effect prefab for muzzle flash at spawn position")]
|
||||
public GameObject muzzleFlashPrefab;
|
||||
|
||||
[Tooltip("Duration to keep muzzle flash alive (seconds)")]
|
||||
public float muzzleFlashDuration = 1.5f;
|
||||
|
||||
[Header("Targeting")]
|
||||
[Tooltip("Tag used to find the target (usually Player)")]
|
||||
@@ -45,6 +59,11 @@ namespace DemonBoss.Magic
|
||||
private Transform _boss;
|
||||
private Transform _target;
|
||||
|
||||
// --- Multi-meteor state ---
|
||||
private int _meteorsSpawned = 0;
|
||||
|
||||
private bool _spawningActive = false;
|
||||
|
||||
// --- Playables runtime ---
|
||||
private PlayableGraph _overlayGraph;
|
||||
|
||||
@@ -61,7 +80,27 @@ namespace DemonBoss.Magic
|
||||
}
|
||||
else if (execType == vFSMComponentExecutionType.OnStateUpdate)
|
||||
{
|
||||
if (_overlayPlaying && Time.time >= _overlayStopAtTime) StopOverlayWithFade();
|
||||
// Keep the state active until all meteors are spawned
|
||||
if (_spawningActive && _meteorsSpawned < meteorCount)
|
||||
{
|
||||
// Don't allow the state to exit while spawning
|
||||
return;
|
||||
}
|
||||
|
||||
if (_overlayPlaying && Time.time >= _overlayStopAtTime)
|
||||
{
|
||||
StopOverlayWithFade();
|
||||
}
|
||||
|
||||
// Only signal completion when all meteors are done AND overlay is finished
|
||||
if (!_spawningActive && !_overlayPlaying && _meteorsSpawned >= meteorCount)
|
||||
{
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Sequence complete, ready to exit state");
|
||||
}
|
||||
}
|
||||
else if (execType == vFSMComponentExecutionType.OnStateExit)
|
||||
{
|
||||
OnExit(fsm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,42 +111,111 @@ namespace DemonBoss.Magic
|
||||
|
||||
if (_target == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[SA_CallMeteor] No target found – abort");
|
||||
if (enableDebug) Debug.LogWarning("[SA_CallMeteor] No target found – abort");
|
||||
return;
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Boss: {_boss.name}, Target: {_target.name}");
|
||||
// Reset multi-meteor state
|
||||
_meteorsSpawned = 0;
|
||||
_spawningActive = true;
|
||||
|
||||
// SET COOLDOWN IMMEDIATELY when meteor ability is used
|
||||
DEC_CheckCooldown.SetCooldownStatic(fsm, "Meteor", 80f);
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Boss: {_boss.name}, Target: {_target.name}, Count: {meteorCount}");
|
||||
|
||||
// Fire overlay clip (no Animator params)
|
||||
PlayOverlayOnce(_boss);
|
||||
|
||||
// Optional: wait for castDelay before spawning (simple timer via Invoke)
|
||||
if (castDelay > 0f)
|
||||
_boss.gameObject.AddComponent<DelayedInvoker>().Init(castDelay, SpawnMeteor);
|
||||
// Start the meteor sequence
|
||||
if (initialCastDelay > 0f)
|
||||
_boss.gameObject.AddComponent<DelayedInvoker>().Init(initialCastDelay, StartMeteorSequence);
|
||||
else
|
||||
SpawnMeteor();
|
||||
StartMeteorSequence();
|
||||
}
|
||||
|
||||
private void SpawnMeteor()
|
||||
private void OnExit(vIFSMBehaviourController fsm)
|
||||
{
|
||||
if (meteorPrefab == null)
|
||||
// Stop any active spawning
|
||||
_spawningActive = false;
|
||||
StopOverlayImmediate();
|
||||
|
||||
// Clean up any DelayedInvokers attached to the boss
|
||||
var invokers = _boss?.GetComponents<DelayedInvoker>();
|
||||
if (invokers != null)
|
||||
{
|
||||
if (enableDebug) Debug.LogError("[SA_CallMeteor] Missing meteorPrefab");
|
||||
return;
|
||||
foreach (var invoker in invokers)
|
||||
{
|
||||
if (invoker != null) Object.Destroy(invoker);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] State exited. Spawned {_meteorsSpawned}/{meteorCount} meteors");
|
||||
}
|
||||
|
||||
private void StartMeteorSequence()
|
||||
{
|
||||
if (!_spawningActive) return;
|
||||
SpawnNextMeteor();
|
||||
}
|
||||
|
||||
private void SpawnNextMeteor()
|
||||
{
|
||||
if (!_spawningActive || _meteorsSpawned >= meteorCount) return;
|
||||
|
||||
if (_boss == null || _target == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogError("[SA_CallMeteor] Missing boss or target reference");
|
||||
return;
|
||||
}
|
||||
|
||||
SpawnSingleMeteor();
|
||||
_meteorsSpawned++;
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Spawned meteor {_meteorsSpawned}/{meteorCount}");
|
||||
|
||||
// Schedule next meteor if needed
|
||||
if (_meteorsSpawned < meteorCount && _spawningActive)
|
||||
{
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Scheduling next meteor in {meteorSpawnInterval}s");
|
||||
_boss.gameObject.AddComponent<DelayedInvoker>().Init(meteorSpawnInterval, SpawnNextMeteor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// All meteors spawned
|
||||
_spawningActive = false;
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] All meteors spawned, sequence complete");
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnSingleMeteor()
|
||||
{
|
||||
if (meteorPrefab == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogError("[SA_CallMeteor] Missing meteorPrefab");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate spawn position: behind the BOSS + height
|
||||
Vector3 bossForward = _boss.forward.normalized;
|
||||
Vector3 behindBoss = _boss.position - (bossForward * behindBossDistance);
|
||||
Vector3 spawnPos = behindBoss + Vector3.up * aboveBossHeight;
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Spawning meteor at: {spawnPos}");
|
||||
// Add slight randomization to spawn position for multiple meteors
|
||||
if (_meteorsSpawned > 0)
|
||||
{
|
||||
Vector3 randomOffset = new Vector3(
|
||||
Random.Range(-1f, 1f),
|
||||
Random.Range(-0.5f, 0.5f),
|
||||
Random.Range(-1f, 1f)
|
||||
);
|
||||
spawnPos += randomOffset;
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Spawning meteor #{_meteorsSpawned + 1} at: {spawnPos}");
|
||||
|
||||
// Spawn muzzle flash effect first
|
||||
SpawnMuzzleFlash(spawnPos);
|
||||
|
||||
// Spawn the meteor
|
||||
var meteorGO = LeanPool.Spawn(meteorPrefab, spawnPos, Quaternion.identity);
|
||||
@@ -116,12 +224,23 @@ namespace DemonBoss.Magic
|
||||
var meteorScript = meteorGO.GetComponent<MeteorProjectile>();
|
||||
if (meteorScript != null)
|
||||
{
|
||||
// Set it to target the player's current position
|
||||
// Update target position for each meteor (player might be moving)
|
||||
Vector3 targetPos = _target.position;
|
||||
|
||||
// Add slight prediction/leading for moving targets
|
||||
var playerRigidbody = _target.GetComponent<Rigidbody>();
|
||||
if (playerRigidbody != null)
|
||||
{
|
||||
Vector3 playerVelocity = playerRigidbody.linearVelocity;
|
||||
float estimatedFlightTime = 2f; // rough estimate
|
||||
targetPos += playerVelocity * estimatedFlightTime * 0.5f; // partial leading
|
||||
}
|
||||
|
||||
meteorScript.useOverrideImpactPoint = true;
|
||||
meteorScript.overrideImpactPoint = _target.position;
|
||||
meteorScript.overrideImpactPoint = targetPos;
|
||||
meteorScript.snapImpactToGround = true;
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor configured to target: {_target.position}");
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor #{_meteorsSpawned + 1} configured to target: {targetPos}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -129,6 +248,27 @@ namespace DemonBoss.Magic
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnMuzzleFlash(Vector3 position)
|
||||
{
|
||||
if (muzzleFlashPrefab == null) return;
|
||||
|
||||
var muzzleFlash = LeanPool.Spawn(muzzleFlashPrefab, position, Quaternion.identity);
|
||||
|
||||
if (muzzleFlashDuration > 0f)
|
||||
{
|
||||
// Auto-despawn muzzle flash after duration
|
||||
_boss.gameObject.AddComponent<DelayedInvoker>().Init(muzzleFlashDuration, () =>
|
||||
{
|
||||
if (muzzleFlash != null)
|
||||
{
|
||||
LeanPool.Despawn(muzzleFlash);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Muzzle flash spawned at: {position}");
|
||||
}
|
||||
|
||||
private void PlayOverlayOnce(Transform owner)
|
||||
{
|
||||
if (overlayClip == null) return;
|
||||
@@ -153,10 +293,15 @@ namespace DemonBoss.Magic
|
||||
_overlayGraph.Play();
|
||||
_overlayPlaying = true;
|
||||
|
||||
float len = overlayClip.length / Mathf.Max(0.0001f, overlaySpeed);
|
||||
_overlayStopAtTime = Time.time + len;
|
||||
// Calculate total sequence duration for overlay
|
||||
float totalSequenceDuration = initialCastDelay + (meteorCount * meteorSpawnInterval) + 2f; // +2s buffer
|
||||
float overlayDuration = overlayClip.length / Mathf.Max(0.0001f, overlaySpeed);
|
||||
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] Overlay clip started via Playables");
|
||||
// Use the longer of the two durations, ensuring overlay covers entire sequence
|
||||
float finalDuration = Mathf.Max(overlayDuration, totalSequenceDuration);
|
||||
_overlayStopAtTime = Time.time + finalDuration;
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Overlay clip started via Playables, duration: {finalDuration:F1}s (sequence: {totalSequenceDuration:F1}s, clip: {overlayDuration:F1}s)");
|
||||
}
|
||||
|
||||
private void StopOverlayImmediate()
|
||||
@@ -177,6 +322,15 @@ namespace DemonBoss.Magic
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] Overlay clip stopped");
|
||||
}
|
||||
|
||||
// Public methods for external monitoring
|
||||
public bool IsSequenceActive() => _spawningActive;
|
||||
|
||||
public int GetMeteorsSpawned() => _meteorsSpawned;
|
||||
|
||||
public int GetTotalMeteorCount() => meteorCount;
|
||||
|
||||
public float GetSequenceProgress() => meteorCount > 0 ? (float)_meteorsSpawned / meteorCount : 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Tiny helper MonoBehaviour to delay a callback without coroutines here.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user