diff --git a/Assets/AI/Demon/SA_CallMeteor.cs b/Assets/AI/Demon/SA_CallMeteor.cs index dc397bd07..d1e358377 100644 --- a/Assets/AI/Demon/SA_CallMeteor.cs +++ b/Assets/AI/Demon/SA_CallMeteor.cs @@ -1,12 +1,13 @@ using Invector.vCharacterController.AI.FSMBehaviour; using Lean.Pool; using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Playables; namespace DemonBoss.Magic { /// - /// Spawns a meteor behind the BOSS and launches it toward the player's position - /// Similar mechanics to FireballProjectile but coming from above + /// Spawns a meteor behind the BOSS and launches it toward the player's position. /// [CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Call Meteor")] public class SA_CallMeteor : vStateAction @@ -31,18 +32,37 @@ namespace DemonBoss.Magic [Tooltip("Tag used to find the target (usually Player)")] public string targetTag = "Player"; + [Header("One-off Overlay Clip (No Animator Params)")] + public AnimationClip overlayClip; + + [Tooltip("Playback speed (1 = normal)")] public float overlaySpeed = 1f; + [Tooltip("Blend-in seconds (instant in this minimal impl)")] public float overlayFadeIn = 0.10f; + [Tooltip("Blend-out seconds (instant in this minimal impl)")] public float overlayFadeOut = 0.10f; + [Header("Debug")] public bool enableDebug = false; private Transform _boss; private Transform _target; + // --- Playables runtime --- + private PlayableGraph _overlayGraph; + + private AnimationPlayableOutput _overlayOutput; + private AnimationClipPlayable _overlayPlayable; + private bool _overlayPlaying; + private float _overlayStopAtTime; + public override void DoAction(vIFSMBehaviourController fsm, vFSMComponentExecutionType execType = vFSMComponentExecutionType.OnStateUpdate) { if (execType == vFSMComponentExecutionType.OnStateEnter) { OnEnter(fsm); } + else if (execType == vFSMComponentExecutionType.OnStateUpdate) + { + if (_overlayPlaying && Time.time >= _overlayStopAtTime) StopOverlayWithFade(); + } } private void OnEnter(vIFSMBehaviourController fsm) @@ -58,7 +78,14 @@ namespace DemonBoss.Magic if (enableDebug) Debug.Log($"[SA_CallMeteor] Boss: {_boss.name}, Target: {_target.name}"); - SpawnMeteor(); + // Fire overlay clip (no Animator params) + PlayOverlayOnce(_boss); + + // Optional: wait for castDelay before spawning (simple timer via Invoke) + if (castDelay > 0f) + _boss.gameObject.AddComponent().Init(castDelay, SpawnMeteor); + else + SpawnMeteor(); } private void SpawnMeteor() @@ -101,5 +128,79 @@ namespace DemonBoss.Magic if (enableDebug) Debug.LogError("[SA_CallMeteor] Meteor prefab missing MeteorProjectile component!"); } } + + // -------- Playables helpers (no Animator params) -------- + private void PlayOverlayOnce(Transform owner) + { + if (overlayClip == null) return; + + var animator = owner.GetComponent(); + if (animator == null) return; + + StopOverlayImmediate(); + + _overlayGraph = PlayableGraph.Create("ActionOverlay(CallMeteor)"); + _overlayGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); + + _overlayPlayable = AnimationClipPlayable.Create(_overlayGraph, overlayClip); + _overlayPlayable.SetApplyFootIK(false); + _overlayPlayable.SetApplyPlayableIK(false); + _overlayPlayable.SetSpeed(Mathf.Max(0.0001f, overlaySpeed)); + + _overlayOutput = AnimationPlayableOutput.Create(_overlayGraph, "AnimOut", animator); + _overlayOutput.SetSourcePlayable(_overlayPlayable); + + _overlayOutput.SetWeight(1f); + _overlayGraph.Play(); + _overlayPlaying = true; + + float len = overlayClip.length / Mathf.Max(0.0001f, overlaySpeed); + _overlayStopAtTime = Time.time + len; + + if (enableDebug) Debug.Log("[SA_CallMeteor] Overlay clip started via Playables"); + } + + private void StopOverlayImmediate() + { + if (_overlayGraph.IsValid()) + { + _overlayGraph.Stop(); + _overlayGraph.Destroy(); + } + _overlayPlaying = false; + } + + private void StopOverlayWithFade() + { + if (!_overlayPlaying) { StopOverlayImmediate(); return; } + if (_overlayOutput.IsOutputNull() == false) _overlayOutput.SetWeight(0f); + StopOverlayImmediate(); + if (enableDebug) Debug.Log("[SA_CallMeteor] Overlay clip stopped"); + } + + /// + /// Tiny helper MonoBehaviour to delay a callback without coroutines here. + /// + private sealed class DelayedInvoker : MonoBehaviour + { + private float _timeLeft; + private System.Action _callback; + + public void Init(float delay, System.Action callback) + { + _timeLeft = delay; + _callback = callback; + } + + private void Update() + { + _timeLeft -= Time.deltaTime; + if (_timeLeft <= 0f) + { + try { _callback?.Invoke(); } + finally { Destroy(this); } + } + } + } } } \ No newline at end of file diff --git a/Assets/AI/Demon/SA_CastShield.cs b/Assets/AI/Demon/SA_CastShield.cs index ea1789c72..3d96a678a 100644 --- a/Assets/AI/Demon/SA_CastShield.cs +++ b/Assets/AI/Demon/SA_CastShield.cs @@ -1,187 +1,376 @@ using Invector.vCharacterController.AI.FSMBehaviour; using Lean.Pool; using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Playables; namespace DemonBoss.Magic { /// - /// StateAction for Magic Shield spell - boss casts magical shield for 5 seconds - /// During casting boss stands still, after completion returns to Combat + /// Magic Shield + /// Spawns shield FX, holds for 'shieldDuration', then plays End and cleans up. /// - [CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Cast Shield")] + [CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Cast Shield (Start/Keep/End)")] public class SA_CastShield : vStateAction { public override string categoryName => "DemonBoss/Magic"; public override string defaultName => "Cast Shield"; - [Header("Shield Configuration")] + [Header("Shield Logic")] [Tooltip("Prefab with magical shield particle effect")] public GameObject shieldFXPrefab; - [Tooltip("Shield duration in seconds")] + [Tooltip("Shield duration in seconds (time spent in Keep phase before End)")] public float shieldDuration = 5f; - [Tooltip("Animator bool parameter name for blocking state")] + [Tooltip("Animator bool parameter name for blocking state (optional)")] public string animatorBlockingBool = "IsBlocking"; + [Header("Animation Clips (no Animator params)")] + [Tooltip("One-shot intro")] + public AnimationClip startClip; + + [Tooltip("Looping hold/maintain")] + public AnimationClip keepClip; + + [Tooltip("One-shot outro")] + public AnimationClip endClip; + + [Header("Playback & Fades")] + [Tooltip("Global playback speed for all clips")] + public float clipSpeed = 1f; + + [Tooltip("Seconds to crossfade between phases")] + public float crossfadeTime = 0.12f; + + [Tooltip("If the state exits early, still play End quickly before full teardown")] + public bool playEndOnEarlyExit = true; + [Header("Debug")] - [Tooltip("Enable debug logging")] - public bool enableDebug = false; + public bool debugLogs = false; - private GameObject spawnedShield; - private Animator npcAnimator; - private Transform npcTransform; - private float shieldStartTime; - private bool shieldActive = false; + // --- Runtime (shield/FX) --- + private Transform _npc; - /// - /// Main action execution method called by FSM - /// - public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate) + private Animator _anim; + private GameObject _spawnedShieldFX; + private float _shieldStartTime; + private bool _shieldActive; + + // --- Playables graph state --- + private PlayableGraph _graph; + + private AnimationPlayableOutput _output; + private AnimationMixerPlayable _mixer; // 2-way mixer for crossfades + + private AnimationClipPlayable _pStart; + private AnimationClipPlayable _pKeep; + private AnimationClipPlayable _pEnd; + + private enum Phase + { None, Start, Keep, End, Done } + + private Phase _phase = Phase.None; + + // crossfade bookkeeping + private int _fromInput = -1; // 0 or 1 + + private int _toInput = -1; // 0 or 1 + private float _fadeT0; // Time.time at fade start + private float _fadeDur; // seconds + private bool _fading; + + // which clip is bound to each input + private AnimationClipPlayable _input0; + + private AnimationClipPlayable _input1; + + // timers for auto-advance + private float _phaseScheduledEnd = 0f; // absolute Time.time when Start/End should be done + + public override void DoAction(vIFSMBehaviourController fsm, vFSMComponentExecutionType execType = vFSMComponentExecutionType.OnStateUpdate) { - if (executionType == vFSMComponentExecutionType.OnStateEnter) + if (execType == vFSMComponentExecutionType.OnStateEnter) OnEnter(fsm); + else if (execType == vFSMComponentExecutionType.OnStateUpdate) OnUpdate(fsm); + else if (execType == vFSMComponentExecutionType.OnStateExit) OnExit(fsm); + } + + // ------------------ FSM Hooks ------------------ + + private void OnEnter(vIFSMBehaviourController fsm) + { + _npc = fsm.transform; + _anim = _npc.GetComponent(); + + if (_anim != null && !string.IsNullOrEmpty(animatorBlockingBool)) + _anim.SetBool(animatorBlockingBool, true); + + SpawnShieldFX(); + + _shieldStartTime = Time.time; + _shieldActive = true; + + BuildGraphIfNeeded(); + + if (startClip != null) { - OnStateEnter(fsmBehaviour); + EnterStart(); } - else if (executionType == vFSMComponentExecutionType.OnStateUpdate) + else if (keepClip != null) { - OnStateUpdate(fsmBehaviour); + EnterKeep(); } - else if (executionType == vFSMComponentExecutionType.OnStateExit) + else { - OnStateExit(fsmBehaviour); + if (debugLogs) Debug.Log("[SA_CastShield] No Start/Keep clips; waiting to End."); + _phase = Phase.Keep; // logical keep (no anim) } } - /// - /// Called when entering state - starts shield casting - /// - private void OnStateEnter(vIFSMBehaviourController fsmBehaviour) + private void OnUpdate(vIFSMBehaviourController fsm) { - if (enableDebug) Debug.Log("[SA_CastShield] Entering shield casting state"); - - // Store NPC references - npcTransform = fsmBehaviour.transform; - npcAnimator = npcTransform.GetComponent(); - - var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI; - if (aiController != null) + // handle crossfade + if (_fading) { - aiController.Stop(); - if (enableDebug) Debug.Log("[SA_CastShield] AI stopped"); + float u = Mathf.Clamp01((Time.time - _fadeT0) / Mathf.Max(0.0001f, _fadeDur)); + if (_fromInput >= 0) _mixer.SetInputWeight(_fromInput, 1f - u); + if (_toInput >= 0) _mixer.SetInputWeight(_toInput, u); + if (u >= 1f) _fading = false; } - if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool)) + // auto-advance phases based on timers + if (_phase == Phase.Start && Time.time >= _phaseScheduledEnd) { - npcAnimator.SetBool(animatorBlockingBool, true); - if (enableDebug) Debug.Log($"[SA_CastShield] Set bool: {animatorBlockingBool} = true"); + if (keepClip != null) CrossfadeToKeep(); + else BeginEnd(); // no keep; go straight to End window } - - SpawnShieldEffect(fsmBehaviour); - - shieldStartTime = Time.time; - shieldActive = true; - } - - /// - /// Called every frame during state duration - /// - private void OnStateUpdate(vIFSMBehaviourController fsmBehaviour) - { - if (shieldActive && Time.time - shieldStartTime >= shieldDuration) + else if (_phase == Phase.Keep) { - if (enableDebug) Debug.Log("[SA_CastShield] Shield time passed, finishing state"); - FinishShield(fsmBehaviour); + // When shield timer is up, begin End + if (_shieldActive && (Time.time - _shieldStartTime) >= shieldDuration) + { + BeginEnd(); + } + } + else if (_phase == Phase.End && Time.time >= _phaseScheduledEnd) + { + // End completed + _phase = Phase.Done; + TeardownGraph(); + CleanupShieldFX(); + _shieldActive = false; } } - /// - /// Called when exiting state - cleanup - /// - private void OnStateExit(vIFSMBehaviourController fsmBehaviour) + private void OnExit(vIFSMBehaviourController fsm) { - if (enableDebug) Debug.Log("[SA_CastShield] Exiting shield state"); + if (_anim != null && !string.IsNullOrEmpty(animatorBlockingBool)) + _anim.SetBool(animatorBlockingBool, false); - if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool)) + // If we left early, optionally play End briefly (best-effort) + if (playEndOnEarlyExit && _phase != Phase.End && _phase != Phase.Done && endClip != null) { - npcAnimator.SetBool(animatorBlockingBool, false); - if (enableDebug) Debug.Log($"[SA_CastShield] Set bool: {animatorBlockingBool} = false"); + if (debugLogs) Debug.Log("[SA_CastShield] Early exit: playing End quickly."); + // build graph if it was never built (e.g., no Start/Keep) + BuildGraphIfNeeded(); + CrossfadeTo(endClip, out _pEnd, quick: true); + _phase = Phase.End; + _phaseScheduledEnd = Time.time + Mathf.Min(endClip.length / SafeSpeed(), 0.25f); // quick outro + } + else + { + // otherwise normal cleanup + TeardownGraph(); } - if (shieldActive) - { - CleanupShield(); - } - - var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI; - if (aiController != null) - { - if (enableDebug) Debug.Log("[SA_CastShield] AI resumed"); - } + CleanupShieldFX(); + _shieldActive = false; } - /// - /// Spawns magical shield particle effect - /// - private void SpawnShieldEffect(vIFSMBehaviourController fsmBehaviour) + // ------------------ Phase Transitions ------------------ + + private void EnterStart() { - if (shieldFXPrefab == null) + CrossfadeTo(startClip, out _pStart, quick: false); + _phase = Phase.Start; + _phaseScheduledEnd = Time.time + (startClip.length / SafeSpeed()); + if (debugLogs) Debug.Log("[SA_CastShield] Start phase."); + } + + private void CrossfadeToKeep() + { + if (keepClip == null) { - Debug.LogWarning("[SA_CastShield] Missing shieldFXPrefab!"); + BeginEnd(); return; } - - // Spawn shield at NPC's position and rotation - Vector3 spawnPosition = npcTransform.position; - Quaternion spawnRotation = npcTransform.rotation; - - spawnedShield = LeanPool.Spawn(shieldFXPrefab, spawnPosition, spawnRotation); - - if (enableDebug) Debug.Log($"[SA_CastShield] Shield spawned at NPC position: {spawnPosition}"); + CrossfadeTo(keepClip, out _pKeep, quick: false, loop: true); + _phase = Phase.Keep; + if (debugLogs) Debug.Log("[SA_CastShield] Switched to Keep (loop)."); } - /// - /// Finishes shield operation and transitions to next state - /// - private void FinishShield(vIFSMBehaviourController fsmBehaviour) + private void EnterKeep() { - shieldActive = false; - CleanupShield(); - - DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Shield", 15f); - - // End state - FSM will transition to next state - // FYI: In Invector FSM, state completion is handled automatically + CrossfadeTo(keepClip, out _pKeep, quick: false, loop: true); + _phase = Phase.Keep; + if (debugLogs) Debug.Log("[SA_CastShield] Entered Keep directly."); } - /// - /// Cleans up spawned shield - /// - private void CleanupShield() + private void BeginEnd() { - if (spawnedShield != null) + if (endClip == null) { - LeanPool.Despawn(spawnedShield); - spawnedShield = null; - if (enableDebug) Debug.Log("[SA_CastShield] Shield despawned"); + // No end clip; just finish + _phase = Phase.Done; + TeardownGraph(); + CleanupShieldFX(); + _shieldActive = false; + if (debugLogs) Debug.Log("[SA_CastShield] No End clip; finished."); + return; + } + CrossfadeTo(endClip, out _pEnd, quick: false); + _phase = Phase.End; + _phaseScheduledEnd = Time.time + (endClip.length / SafeSpeed()); + if (debugLogs) Debug.Log("[SA_CastShield] End phase."); + } + + // ------------------ Graph Setup / Crossfade ------------------ + + private void BuildGraphIfNeeded() + { + if (_graph.IsValid()) return; + + if (_anim == null) + { + _anim = _npc ? _npc.GetComponent() : null; + if (_anim == null) + { + if (debugLogs) Debug.LogWarning("[SA_CastShield] No Animator found; animation disabled."); + return; + } + } + + _graph = PlayableGraph.Create("ShieldOverlayGraph"); + _graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); + + _mixer = AnimationMixerPlayable.Create(_graph, 2); // 2-way mixer + _mixer.SetInputWeight(0, 0f); + _mixer.SetInputWeight(1, 0f); + + _output = AnimationPlayableOutput.Create(_graph, "AnimOut", _anim); + _output.SetSourcePlayable(_mixer); + _output.SetWeight(1f); + + _graph.Play(); + } + + private void TeardownGraph() + { + if (_graph.IsValid()) + { + _graph.Stop(); + _graph.Destroy(); + } + _input0 = default; + _input1 = default; + _fromInput = _toInput = -1; + _fading = false; + } + + private float SafeSpeed() => Mathf.Max(0.0001f, clipSpeed); + + /// + /// Fades from whatever is currently audible to the given clip. + /// Reuses the 2 inputs; swaps playables as needed. + /// + private void CrossfadeTo(AnimationClip clip, + out AnimationClipPlayable playableOut, + bool quick, + bool loop = false) + { + playableOut = default; + + if (!_graph.IsValid() || clip == null) return; + + float speed = SafeSpeed(); + + // Prepare or reuse a slot (two inputs that we swap between) + // Choose the silent input as target; if none is silent, pick the opposite of the currently "from". + int targetInput = (_mixer.GetInputWeight(0) < 0.0001f) ? 0 : (_mixer.GetInputWeight(1) < 0.0001f) ? 1 : (_toInput == 0 ? 1 : 0); + + // Destroy existing playable on that input if any + var currentPlayableOnTarget = (AnimationClipPlayable)_mixer.GetInput(targetInput); + if (currentPlayableOnTarget.IsValid()) + { + _graph.Disconnect(_mixer, targetInput); + currentPlayableOnTarget.Destroy(); + } + + // Create new clip playable + var newPlayable = AnimationClipPlayable.Create(_graph, clip); + newPlayable.SetApplyFootIK(false); + newPlayable.SetApplyPlayableIK(false); + newPlayable.SetSpeed(speed); + + // Connect to mixer + _graph.Connect(newPlayable, 0, _mixer, targetInput); + _mixer.SetInputWeight(targetInput, 0f); + + // Cache which playable is on which slot for optional debug + if (targetInput == 0) _input0 = newPlayable; else _input1 = newPlayable; + + // Determine current audible input to fade from + int sourceInput = (targetInput == 0) ? 1 : 0; + float wSource = _mixer.GetInputWeight(sourceInput); + bool hasSource = wSource > 0.0001f && _mixer.GetInput(sourceInput).IsValid(); + + // Start from beginning for new phase + newPlayable.SetTime(0); + playableOut = newPlayable; + + // Configure fade + _fromInput = hasSource ? sourceInput : -1; + _toInput = targetInput; + _fadeDur = Mathf.Max(0f, quick ? Mathf.Min(0.06f, crossfadeTime) : crossfadeTime); + _fadeT0 = Time.time; + _fading = _fadeDur > 0f && hasSource; + + // Set immediate weights if not fading + if (!_fading) + { + if (_fromInput >= 0) _mixer.SetInputWeight(_fromInput, 0f); + _mixer.SetInputWeight(_toInput, 1f); } } - /// - /// Checks if shield is currently active - /// - public bool IsShieldActive() + // ------------------ FX Helpers ------------------ + + private void SpawnShieldFX() { - return shieldActive; + if (shieldFXPrefab == null) return; + _spawnedShieldFX = LeanPool.Spawn(shieldFXPrefab, _npc.position, _npc.rotation); + if (debugLogs) Debug.Log("[SA_CastShield] Shield FX spawned."); } - /// - /// Returns remaining shield time - /// + private void CleanupShieldFX() + { + if (_spawnedShieldFX != null) + { + LeanPool.Despawn(_spawnedShieldFX); + _spawnedShieldFX = null; + if (debugLogs) Debug.Log("[SA_CastShield] Shield FX despawned."); + } + } + + // ------------------ Public Query ------------------ + + public bool IsShieldActive() => _shieldActive; + public float GetRemainingShieldTime() { - if (!shieldActive) return 0f; - return Mathf.Max(0f, shieldDuration - (Time.time - shieldStartTime)); + if (!_shieldActive) return 0f; + float t = Mathf.Max(0f, shieldDuration - (Time.time - _shieldStartTime)); + return (_phase == Phase.End || _phase == Phase.Done) ? 0f : t; } } } \ No newline at end of file diff --git a/Assets/AI/Demon/SA_SpawnTurretSmart.cs b/Assets/AI/Demon/SA_SpawnTurretSmart.cs index c00440bc0..31c8d2f51 100644 --- a/Assets/AI/Demon/SA_SpawnTurretSmart.cs +++ b/Assets/AI/Demon/SA_SpawnTurretSmart.cs @@ -1,13 +1,14 @@ using Invector.vCharacterController.AI.FSMBehaviour; using Lean.Pool; using UnityEngine; +using UnityEngine.Animations; +using UnityEngine.Playables; namespace DemonBoss.Magic { /// /// Spawns exactly 3 crystal turrets around the opponent. - /// Now with per-spawn randomness: global rotation offset and per-turret angle/radius jitter. - /// Validates positions (ground/obstacles) and enforces minimum separation. + /// Now with per-spawn randomness and position validation. /// [CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Spawn 3 Turrets Radial")] public class SA_SpawnTurretSmart : vStateAction @@ -43,6 +44,13 @@ namespace DemonBoss.Magic [Tooltip("Per-turret radius jitter (meters). 0 = disabled.")] public float perTurretRadiusJitter = 0.75f; + [Header("One-off Overlay Clip (No Animator Params)")] + public AnimationClip overlayClip; + + [Tooltip("Playback speed (1 = normal)")] public float overlaySpeed = 1f; + [Tooltip("Blend-in seconds (instant in this minimal impl)")] public float overlayFadeIn = 0.10f; + [Tooltip("Blend-out seconds (instant in this minimal impl)")] public float overlayFadeOut = 0.10f; + [Header("Debug")] public bool enableDebug = false; @@ -52,11 +60,25 @@ namespace DemonBoss.Magic private Transform npcTransform; private Transform playerTransform; - private readonly System.Collections.Generic.List _lastPlanned = new System.Collections.Generic.List(3); + private readonly System.Collections.Generic.List _lastPlanned = + new System.Collections.Generic.List(3); + + // --- Playables runtime --- + private PlayableGraph _overlayGraph; + + private AnimationPlayableOutput _overlayOutput; + private AnimationClipPlayable _overlayPlayable; + private bool _overlayPlaying; + private float _overlayStopAtTime; public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate) { if (executionType == vFSMComponentExecutionType.OnStateEnter) OnStateEnter(fsmBehaviour); + else if (executionType == vFSMComponentExecutionType.OnStateUpdate) + { + // Auto-stop overlay when finished + if (_overlayPlaying && Time.time >= _overlayStopAtTime) StopOverlayWithFade(); + } else if (executionType == vFSMComponentExecutionType.OnStateExit) OnStateExit(fsmBehaviour); } @@ -70,6 +92,8 @@ namespace DemonBoss.Magic if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool)) npcAnimator.SetBool(animatorBlockingBool, true); + PlayOverlayOnce(npcTransform); + SpawnThreeTurretsRadial(fsmBehaviour); DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Turret", 12f); @@ -79,6 +103,8 @@ namespace DemonBoss.Magic { if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool)) npcAnimator.SetBool(animatorBlockingBool, false); + + StopOverlayWithFade(); } private void FindPlayer(vIFSMBehaviourController fsmBehaviour) @@ -270,5 +296,54 @@ namespace DemonBoss.Magic prevPoint = newPoint; } } + + private void PlayOverlayOnce(Transform owner) + { + if (overlayClip == null) return; + + if (npcAnimator == null) + npcAnimator = owner.GetComponent(); + if (npcAnimator == null) return; + + StopOverlayImmediate(); // safety + + _overlayGraph = PlayableGraph.Create("ActionOverlay(SpawnTurret)"); + _overlayGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); + + _overlayPlayable = AnimationClipPlayable.Create(_overlayGraph, overlayClip); + _overlayPlayable.SetApplyFootIK(false); + _overlayPlayable.SetApplyPlayableIK(false); + _overlayPlayable.SetSpeed(Mathf.Max(0.0001f, overlaySpeed)); + + _overlayOutput = AnimationPlayableOutput.Create(_overlayGraph, "AnimOut", npcAnimator); + _overlayOutput.SetSourcePlayable(_overlayPlayable); + + _overlayOutput.SetWeight(1f); + _overlayGraph.Play(); + _overlayPlaying = true; + + float len = overlayClip.length / Mathf.Max(0.0001f, overlaySpeed); + _overlayStopAtTime = Time.time + len; + + if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Overlay clip started via Playables"); + } + + private void StopOverlayImmediate() + { + if (_overlayGraph.IsValid()) + { + _overlayGraph.Stop(); + _overlayGraph.Destroy(); + } + _overlayPlaying = false; + } + + private void StopOverlayWithFade() + { + if (!_overlayPlaying) { StopOverlayImmediate(); return; } + if (_overlayOutput.IsOutputNull() == false) _overlayOutput.SetWeight(0f); + StopOverlayImmediate(); + if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Overlay clip stopped"); + } } } \ No newline at end of file diff --git a/Assets/AI/FSM/FSM_Demon.asset b/Assets/AI/FSM/FSM_Demon.asset index 8605f614b..45c8a25e4 100644 --- a/Assets/AI/FSM/FSM_Demon.asset +++ b/Assets/AI/FSM/FSM_Demon.asset @@ -134,7 +134,13 @@ MonoBehaviour: type: 3} shieldDuration: 10 animatorBlockingBool: IsBlocking - enableDebug: 1 + startClip: {fileID: 7400082, guid: 3ef453d7877555243997dba1cdaa2958, type: 3} + keepClip: {fileID: 7400084, guid: 3ef453d7877555243997dba1cdaa2958, type: 3} + endClip: {fileID: 7400086, guid: 3ef453d7877555243997dba1cdaa2958, type: 3} + clipSpeed: 1 + crossfadeTime: 0.12 + playEndOnEarlyExit: 1 + debugLogs: 0 --- !u!114 &-6568372008305276654 MonoBehaviour: m_ObjectHideFlags: 1 @@ -158,11 +164,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: -445 - y: 30 + x: -595 + y: 70 width: 150 height: 62 - positionRect: {x: -445, y: 30} + positionRect: {x: -595, y: 70} rectWidth: 150 editingName: 1 nodeColor: {r: 0, g: 1, b: 1, a: 1} @@ -189,14 +195,14 @@ MonoBehaviour: parentState: {fileID: -6568372008305276654} trueRect: serializedVersion: 2 - x: -295 - y: 60 + x: -445 + y: 100 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -295 - y: 70 + x: -445 + y: 110 width: 10 height: 10 selectedTrue: 0 @@ -233,6 +239,10 @@ MonoBehaviour: aboveBossHeight: 20 castDelay: 1.5 targetTag: Player + overlayClip: {fileID: 7400088, guid: 3ef453d7877555243997dba1cdaa2958, type: 3} + overlaySpeed: 1 + overlayFadeIn: 0.1 + overlayFadeOut: 0.1 enableDebug: 1 --- !u!114 &-6379838510941931433 MonoBehaviour: @@ -287,11 +297,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: -410 - y: 185 + x: -560 + y: 220 width: 150 height: 106 - positionRect: {x: -410, y: 185} + positionRect: {x: -560, y: 220} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 0, b: 0, a: 1} @@ -318,14 +328,14 @@ MonoBehaviour: parentState: {fileID: -6144582714324757854} trueRect: serializedVersion: 2 - x: -260 - y: 215 + x: -410 + y: 250 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -260 - y: 225 + x: -410 + y: 260 width: 10 height: 10 selectedTrue: 0 @@ -346,14 +356,14 @@ MonoBehaviour: parentState: {fileID: -6144582714324757854} trueRect: serializedVersion: 2 - x: -260 - y: 237 + x: -410 + y: 272 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -260 - y: 247 + x: -410 + y: 282 width: 10 height: 10 selectedTrue: 0 @@ -382,14 +392,14 @@ MonoBehaviour: parentState: {fileID: -6144582714324757854} trueRect: serializedVersion: 2 - x: -260 - y: 259 + x: -410 + y: 294 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -260 - y: 269 + x: -410 + y: 304 width: 10 height: 10 selectedTrue: 0 @@ -540,11 +550,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: -130 - y: 35 + x: -285 + y: 75 width: 150 height: 62 - positionRect: {x: -130, y: 35} + positionRect: {x: -285, y: 75} rectWidth: 150 editingName: 1 nodeColor: {r: 0.10323405, g: 1, b: 0, a: 1} @@ -563,14 +573,14 @@ MonoBehaviour: parentState: {fileID: -3177478727897100882} trueRect: serializedVersion: 2 - x: 20 - y: 65 + x: -135 + y: 105 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 20 - y: 75 + x: -135 + y: 115 width: 10 height: 10 selectedTrue: 0 @@ -609,11 +619,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: 165 - y: -5 + x: 0 + y: 35 width: 150 height: 62 - positionRect: {x: 165, y: -5} + positionRect: {x: 0, y: 35} rectWidth: 150 editingName: 1 nodeColor: {r: 0, g: 1, b: 0.004989147, a: 1} @@ -625,7 +635,7 @@ MonoBehaviour: - decisions: - trueValue: 0 decision: {fileID: 7927421991537792917} - isValid: 0 + isValid: 1 validated: 0 trueState: {fileID: -312774025800194259} falseState: {fileID: 0} @@ -636,14 +646,14 @@ MonoBehaviour: parentState: {fileID: -2904979146780567904} trueRect: serializedVersion: 2 - x: 155 - y: 25 + x: -10 + y: 65 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 315 - y: 35 + x: 150 + y: 75 width: 10 height: 10 selectedTrue: 0 @@ -751,6 +761,10 @@ MonoBehaviour: globalStartAngleRandom: 1 perTurretAngleJitter: 10 perTurretRadiusJitter: 0.75 + overlayClip: {fileID: 7400080, guid: 3ef453d7877555243997dba1cdaa2958, type: 3} + overlaySpeed: 1 + overlayFadeIn: 0.1 + overlayFadeOut: 0.1 enableDebug: 1 showGizmos: 1 --- !u!114 &-712571192746352845 @@ -776,11 +790,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: -130 - y: -20 + x: -285 + y: 20 width: 150 height: 30 - positionRect: {x: -130, y: -20} + positionRect: {x: -285, y: 20} rectWidth: 150 editingName: 0 nodeColor: {r: 0, g: 1, b: 0, a: 1} @@ -852,11 +866,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: -325 - y: 445 + x: -480 + y: 480 width: 150 height: 150 - positionRect: {x: -325, y: 445} + positionRect: {x: -480, y: 480} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 0.95132554, b: 0, a: 1} @@ -883,14 +897,14 @@ MonoBehaviour: parentState: {fileID: -312774025800194259} trueRect: serializedVersion: 2 - x: -175 - y: 475 + x: -330 + y: 510 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -175 - y: 485 + x: -330 + y: 520 width: 10 height: 10 selectedTrue: 0 @@ -915,14 +929,14 @@ MonoBehaviour: parentState: {fileID: -312774025800194259} trueRect: serializedVersion: 2 - x: -175 - y: 497 + x: -330 + y: 532 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -175 - y: 507 + x: -330 + y: 542 width: 10 height: 10 selectedTrue: 0 @@ -955,14 +969,14 @@ MonoBehaviour: parentState: {fileID: -312774025800194259} trueRect: serializedVersion: 2 - x: -335 - y: 519 + x: -490 + y: 554 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -175 - y: 529 + x: -330 + y: 564 width: 10 height: 10 selectedTrue: 0 @@ -991,14 +1005,14 @@ MonoBehaviour: parentState: {fileID: -312774025800194259} trueRect: serializedVersion: 2 - x: -175 - y: 541 + x: -330 + y: 576 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -175 - y: 551 + x: -330 + y: 586 width: 10 height: 10 selectedTrue: 0 @@ -1023,14 +1037,14 @@ MonoBehaviour: parentState: {fileID: -312774025800194259} trueRect: serializedVersion: 2 - x: -175 - y: 563 + x: -330 + y: 598 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -175 - y: 573 + x: -330 + y: 608 width: 10 height: 10 selectedTrue: 0 @@ -1077,7 +1091,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a5fc604039227434d8b4e63ebc5e74a5, type: 3} m_Name: FSM_Demon m_EditorClassIdentifier: - selectedNode: {fileID: 4162026404432437805} + selectedNode: {fileID: 766956384951898899} wantConnection: 0 connectionNode: {fileID: 0} showProperties: 1 @@ -1094,7 +1108,7 @@ MonoBehaviour: - {fileID: 9112689765763526057} - {fileID: 766956384951898899} - {fileID: 4162026404432437805} - panOffset: {x: -1205, y: 130} + panOffset: {x: -860, y: -90} overNode: 0 actions: - {fileID: 0} @@ -1178,11 +1192,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: 685 - y: 675 + x: 515 + y: 710 width: 150 height: 62 - positionRect: {x: 685, y: 675} + positionRect: {x: 515, y: 710} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 1, b: 1, a: 1} @@ -1201,14 +1215,14 @@ MonoBehaviour: parentState: {fileID: 762670965814380212} trueRect: serializedVersion: 2 - x: 675 - y: 705 + x: 505 + y: 740 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 835 - y: 715 + x: 665 + y: 750 width: 10 height: 10 selectedTrue: 0 @@ -1245,14 +1259,14 @@ MonoBehaviour: canEditName: 1 canEditColor: 1 isOpen: 1 - isSelected: 0 + isSelected: 1 nodeRect: serializedVersion: 2 - x: 750 - y: 125 + x: 580 + y: 165 width: 150 height: 62 - positionRect: {x: 750, y: 125} + positionRect: {x: 580, y: 165} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 1, b: 1, a: 1} @@ -1271,14 +1285,14 @@ MonoBehaviour: parentState: {fileID: 766956384951898899} trueRect: serializedVersion: 2 - x: 740 - y: 155 + x: 570 + y: 195 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 900 - y: 165 + x: 730 + y: 205 width: 10 height: 10 selectedTrue: 0 @@ -1367,11 +1381,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: 475 - y: 675 + x: 305 + y: 710 width: 150 height: 62 - positionRect: {x: 475, y: 675} + positionRect: {x: 305, y: 710} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 1, b: 1, a: 1} @@ -1390,14 +1404,14 @@ MonoBehaviour: parentState: {fileID: 2691300596403639167} trueRect: serializedVersion: 2 - x: 465 - y: 705 + x: 295 + y: 740 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 625 - y: 715 + x: 455 + y: 750 width: 10 height: 10 selectedTrue: 0 @@ -1437,11 +1451,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: 220 - y: 395 + x: 50 + y: 430 width: 150 height: 150 - positionRect: {x: 220, y: 395} + positionRect: {x: 50, y: 430} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 0, b: 0, a: 1} @@ -1464,14 +1478,14 @@ MonoBehaviour: parentState: {fileID: 2986668563461644515} trueRect: serializedVersion: 2 - x: 210 - y: 425 + x: 40 + y: 460 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 370 - y: 435 + x: 200 + y: 470 width: 10 height: 10 selectedTrue: 0 @@ -1496,14 +1510,14 @@ MonoBehaviour: parentState: {fileID: 2986668563461644515} trueRect: serializedVersion: 2 - x: 210 - y: 447 + x: 40 + y: 482 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 370 - y: 457 + x: 200 + y: 492 width: 10 height: 10 selectedTrue: 0 @@ -1517,11 +1531,11 @@ MonoBehaviour: - decisions: - trueValue: 0 decision: {fileID: -6379838510941931433} - isValid: 0 + isValid: 1 validated: 0 - trueValue: 1 decision: {fileID: -7938248970223304488} - isValid: 1 + isValid: 0 validated: 0 trueState: {fileID: 766956384951898899} falseState: {fileID: 0} @@ -1532,14 +1546,14 @@ MonoBehaviour: parentState: {fileID: 2986668563461644515} trueRect: serializedVersion: 2 - x: 370 - y: 469 + x: 200 + y: 504 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 370 - y: 479 + x: 200 + y: 514 width: 10 height: 10 selectedTrue: 0 @@ -1553,7 +1567,7 @@ MonoBehaviour: - decisions: - trueValue: 0 decision: {fileID: -6379838510941931433} - isValid: 0 + isValid: 1 validated: 0 - trueValue: 1 decision: {fileID: 8113515040269600600} @@ -1568,14 +1582,14 @@ MonoBehaviour: parentState: {fileID: 2986668563461644515} trueRect: serializedVersion: 2 - x: 370 - y: 491 + x: 200 + y: 526 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 370 - y: 501 + x: 200 + y: 536 width: 10 height: 10 selectedTrue: 0 @@ -1604,14 +1618,14 @@ MonoBehaviour: parentState: {fileID: 2986668563461644515} trueRect: serializedVersion: 2 - x: 370 - y: 513 + x: 200 + y: 548 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 370 - y: 523 + x: 200 + y: 558 width: 10 height: 10 selectedTrue: 0 @@ -1708,14 +1722,14 @@ MonoBehaviour: canEditName: 1 canEditColor: 1 isOpen: 0 - isSelected: 1 + isSelected: 0 nodeRect: serializedVersion: 2 - x: 745 - y: 340 + x: 575 + y: 375 width: 150 height: 30 - positionRect: {x: 745, y: 340} + positionRect: {x: 575, y: 375} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 1, b: 1, a: 1} @@ -1734,14 +1748,14 @@ MonoBehaviour: parentState: {fileID: 4162026404432437805} trueRect: serializedVersion: 2 - x: 820 - y: 355 + x: 650 + y: 390 width: 0 height: 0 falseRect: serializedVersion: 2 - x: 820 - y: 355 + x: 650 + y: 390 width: 0 height: 0 selectedTrue: 0 @@ -1907,11 +1921,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: 755 - y: -50 + x: 585 + y: -5 width: 150 height: 62 - positionRect: {x: 755, y: -50} + positionRect: {x: 585, y: -5} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 1, b: 1, a: 1} @@ -1930,14 +1944,14 @@ MonoBehaviour: parentState: {fileID: 9112689765763526057} trueRect: serializedVersion: 2 - x: 745 - y: -20 + x: 575 + y: 25 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 905 - y: -10 + x: 735 + y: 35 width: 10 height: 10 selectedTrue: 0 diff --git a/Assets/ThirdParty/Invector-3rdPersonController/Melee Combat/Animator/Invector@Bonel_Warrior.controller b/Assets/ThirdParty/Invector-3rdPersonController/Melee Combat/Animator/Invector@Bonel_Warrior.controller index 088d33cd5..73c213864 100644 --- a/Assets/ThirdParty/Invector-3rdPersonController/Melee Combat/Animator/Invector@Bonel_Warrior.controller +++ b/Assets/ThirdParty/Invector-3rdPersonController/Melee Combat/Animator/Invector@Bonel_Warrior.controller @@ -18027,7 +18027,7 @@ BlendTree: m_Name: Run m_Childs: - serializedVersion: 2 - m_Motion: {fileID: 7400012, guid: 37c6cfe59f56e8a4799011397a870a8b, type: 3} + m_Motion: {fileID: 7400106, guid: 3ef453d7877555243997dba1cdaa2958, type: 3} m_Threshold: 0 m_Position: {x: 0, y: 1} m_TimeScale: 1 @@ -18153,7 +18153,7 @@ BlendTree: m_DirectBlendParameter: InputHorizontal m_Mirror: 0 - serializedVersion: 2 - m_Motion: {fileID: 7400012, guid: 37c6cfe59f56e8a4799011397a870a8b, type: 3} + m_Motion: {fileID: 7400106, guid: 3ef453d7877555243997dba1cdaa2958, type: 3} m_Threshold: 0 m_Position: {x: 0, y: 0} m_TimeScale: 1 @@ -34993,7 +34993,7 @@ AnimatorStateMachine: m_ChildStateMachines: - serializedVersion: 1 m_StateMachine: {fileID: 1107736847571817844} - m_Position: {x: 540, y: 192, z: 0} + m_Position: {x: 540, y: 190, z: 0} - serializedVersion: 1 m_StateMachine: {fileID: 1107135120639222350} m_Position: {x: 324, y: 132, z: 0}