Meteor changes

This commit is contained in:
Szymon Miś
2025-09-19 17:36:00 +02:00
parent 9d45a48fe5
commit da6ca9cc4c
2 changed files with 192 additions and 34 deletions

View File

@@ -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>

View File

@@ -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