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,29 +111,57 @@ 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)
|
||||
{
|
||||
@@ -102,12 +169,53 @@ namespace DemonBoss.Magic
|
||||
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>
|
||||
|
||||
@@ -235,9 +235,13 @@ MonoBehaviour:
|
||||
editingName: 0
|
||||
meteorPrefab: {fileID: 1947871717301538, guid: f99aa3faf46a5f94985344f44aaf21aa,
|
||||
type: 3}
|
||||
behindBossDistance: 6
|
||||
behindBossDistance: 8
|
||||
aboveBossHeight: 6
|
||||
castDelay: 1.5
|
||||
meteorCount: 5
|
||||
initialCastDelay: 0.4
|
||||
meteorSpawnInterval: 1
|
||||
muzzleFlashPrefab: {fileID: 0}
|
||||
muzzleFlashDuration: 1.5
|
||||
targetTag: Player
|
||||
overlayClip: {fileID: 7400088, guid: 3ef453d7877555243997dba1cdaa2958, type: 3}
|
||||
overlaySpeed: 1
|
||||
@@ -635,7 +639,7 @@ MonoBehaviour:
|
||||
- decisions:
|
||||
- trueValue: 0
|
||||
decision: {fileID: 7927421991537792917}
|
||||
isValid: 0
|
||||
isValid: 1
|
||||
validated: 0
|
||||
trueState: {fileID: -312774025800194259}
|
||||
falseState: {fileID: 0}
|
||||
@@ -1092,7 +1096,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: a5fc604039227434d8b4e63ebc5e74a5, type: 3}
|
||||
m_Name: FSM_Demon
|
||||
m_EditorClassIdentifier:
|
||||
selectedNode: {fileID: 9112689765763526057}
|
||||
selectedNode: {fileID: 4162026404432437805}
|
||||
wantConnection: 0
|
||||
connectionNode: {fileID: 0}
|
||||
showProperties: 1
|
||||
@@ -1635,11 +1639,11 @@ MonoBehaviour:
|
||||
- decisions:
|
||||
- trueValue: 0
|
||||
decision: {fileID: -6379838510941931433}
|
||||
isValid: 0
|
||||
isValid: 1
|
||||
validated: 0
|
||||
- trueValue: 1
|
||||
decision: {fileID: 2998305265418220943}
|
||||
isValid: 1
|
||||
isValid: 0
|
||||
validated: 0
|
||||
trueState: {fileID: 4162026404432437805}
|
||||
falseState: {fileID: 0}
|
||||
@@ -1754,14 +1758,14 @@ MonoBehaviour:
|
||||
canEditName: 1
|
||||
canEditColor: 1
|
||||
isOpen: 0
|
||||
isSelected: 0
|
||||
isSelected: 1
|
||||
nodeRect:
|
||||
serializedVersion: 2
|
||||
x: 790
|
||||
x: 785
|
||||
y: 395
|
||||
width: 150
|
||||
height: 30
|
||||
positionRect: {x: 790, y: 395}
|
||||
positionRect: {x: 785, y: 395}
|
||||
rectWidth: 150
|
||||
editingName: 1
|
||||
nodeColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
@@ -1776,17 +1780,17 @@ MonoBehaviour:
|
||||
muteTrue: 0
|
||||
muteFalse: 0
|
||||
transitionType: 0
|
||||
transitionDelay: 0.5
|
||||
transitionDelay: 5
|
||||
parentState: {fileID: 4162026404432437805}
|
||||
trueRect:
|
||||
serializedVersion: 2
|
||||
x: 865
|
||||
x: 860
|
||||
y: 410
|
||||
width: 0
|
||||
height: 0
|
||||
falseRect:
|
||||
serializedVersion: 2
|
||||
x: 865
|
||||
x: 860
|
||||
y: 410
|
||||
width: 0
|
||||
height: 0
|
||||
@@ -1950,7 +1954,7 @@ MonoBehaviour:
|
||||
canEditName: 1
|
||||
canEditColor: 1
|
||||
isOpen: 1
|
||||
isSelected: 1
|
||||
isSelected: 0
|
||||
nodeRect:
|
||||
serializedVersion: 2
|
||||
x: 790
|
||||
|
||||
Reference in New Issue
Block a user