Animation fixes Demon

This commit is contained in:
Szymon Miś
2025-09-09 03:19:38 +02:00
parent 3b0b20a165
commit 9d6c237088
5 changed files with 633 additions and 254 deletions

View File

@@ -1,12 +1,13 @@
using Invector.vCharacterController.AI.FSMBehaviour;
using Lean.Pool;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
namespace DemonBoss.Magic
{
/// <summary>
/// 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.
/// </summary>
[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<DelayedInvoker>().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<Animator>();
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");
}
/// <summary>
/// Tiny helper MonoBehaviour to delay a callback without coroutines here.
/// </summary>
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); }
}
}
}
}
}

View File

@@ -1,187 +1,376 @@
using Invector.vCharacterController.AI.FSMBehaviour;
using Lean.Pool;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
namespace DemonBoss.Magic
{
/// <summary>
/// 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.
/// </summary>
[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;
/// <summary>
/// Main action execution method called by FSM
/// </summary>
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<Animator>();
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)
}
}
/// <summary>
/// Called when entering state - starts shield casting
/// </summary>
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<Animator>();
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;
}
/// <summary>
/// Called every frame during state duration
/// </summary>
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;
}
}
/// <summary>
/// Called when exiting state - cleanup
/// </summary>
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;
}
/// <summary>
/// Spawns magical shield particle effect
/// </summary>
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).");
}
/// <summary>
/// Finishes shield operation and transitions to next state
/// </summary>
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.");
}
/// <summary>
/// Cleans up spawned shield
/// </summary>
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<Animator>() : 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);
/// <summary>
/// Fades from whatever is currently audible to the given clip.
/// Reuses the 2 inputs; swaps playables as needed.
/// </summary>
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);
}
}
/// <summary>
/// Checks if shield is currently active
/// </summary>
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.");
}
/// <summary>
/// Returns remaining shield time
/// </summary>
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;
}
}
}

View File

@@ -1,13 +1,14 @@
using Invector.vCharacterController.AI.FSMBehaviour;
using Lean.Pool;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
namespace DemonBoss.Magic
{
/// <summary>
/// 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.
/// </summary>
[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<Vector3> _lastPlanned = new System.Collections.Generic.List<Vector3>(3);
private readonly System.Collections.Generic.List<Vector3> _lastPlanned =
new System.Collections.Generic.List<Vector3>(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<Animator>();
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");
}
}
}

View File

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

View File

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