613 lines
21 KiB
C#
613 lines
21 KiB
C#
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Playables;
|
||
|
using UnityEngine.Animations;
|
||
|
using UnityEngine.Events;
|
||
|
|
||
|
namespace BulletHellTemplate
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Stores a base animation clip with speed and transition percent.
|
||
|
/// </summary>
|
||
|
[System.Serializable]
|
||
|
public struct CharacterAnimation
|
||
|
{
|
||
|
[Tooltip("Animation clip to play.")]
|
||
|
public AnimationClip animationClip;
|
||
|
|
||
|
[Tooltip("Playback speed multiplier.")]
|
||
|
public float speed;
|
||
|
|
||
|
[Tooltip("Crossfade transition percent (0.1 means 10%).")]
|
||
|
public float transitionPercent;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Stores an action animation clip with speed, transition time, exit time, etc.
|
||
|
/// </summary>
|
||
|
[System.Serializable]
|
||
|
public struct ActionAnimation
|
||
|
{
|
||
|
[Tooltip("Action key to identify this animation.")]
|
||
|
public string action;
|
||
|
|
||
|
[Tooltip("Animation clip to play.")]
|
||
|
public AnimationClip animationClip;
|
||
|
|
||
|
[Tooltip("Playback speed multiplier.")]
|
||
|
public float speed;
|
||
|
|
||
|
[Tooltip("Crossfade transition (e.g., 0.1 means 0.1 seconds).")]
|
||
|
public float transition;
|
||
|
|
||
|
[Tooltip("If true, this action uses an AvatarMask on layer 1 (partial override).")]
|
||
|
public bool useAvatarMask;
|
||
|
|
||
|
[Tooltip("If true, this action fully ignores the base layer (layer 2 override).")]
|
||
|
public bool playCompletely;
|
||
|
|
||
|
[Tooltip("Exit time fraction (e.g., 0.8 means 80% of the clip must be played).")]
|
||
|
public float exitTime;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Class that stores events for skill usage.
|
||
|
/// </summary>
|
||
|
[System.Serializable]
|
||
|
public class UseSkillEventData
|
||
|
{
|
||
|
[HideInInspector]
|
||
|
public string eventName;
|
||
|
public UnityEvent onUseSkill;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// CharacterModel controlling a three-layer PlayableGraph:
|
||
|
/// Layer 0: Base (Idle, Run)
|
||
|
/// Layer 1: Partial override if useAvatarMask=true, or full override if false
|
||
|
/// Layer 2: Exclusive override for playCompletely
|
||
|
/// </summary>
|
||
|
[AddComponentMenu("Bullet Hell Template/Character Model")]
|
||
|
public class CharacterModel : MonoBehaviour
|
||
|
{
|
||
|
[Header("Animation Settings")]
|
||
|
[Tooltip("If false, fallback to legacy Animator.")]
|
||
|
public bool usePlayableAnimations;
|
||
|
|
||
|
[Tooltip("Global AvatarMask used on layer 1 for partial override if useAvatarMask=true.")]
|
||
|
public AvatarMask actionAvatarMask;
|
||
|
|
||
|
[Header("Animator Reference")]
|
||
|
[Tooltip("Used if not playing via PlayableGraph.")]
|
||
|
public Animator animator;
|
||
|
|
||
|
[Header("Base Animations (Layer 0)")]
|
||
|
public CharacterAnimation idleAnimation;
|
||
|
[Tooltip("Run Forward animation (required).")]
|
||
|
public CharacterAnimation runForwardAnimation;
|
||
|
public CharacterAnimation runBackwardAnimation;
|
||
|
public CharacterAnimation runLeftAnimation;
|
||
|
public CharacterAnimation runRightAnimation;
|
||
|
|
||
|
public CharacterAnimation hitAnimation;
|
||
|
public CharacterAnimation pickUpAnimation;
|
||
|
public CharacterAnimation stunAnimation;
|
||
|
|
||
|
[Header("Action Animations (Layer 1 or 2)")]
|
||
|
public List<ActionAnimation> actions = new List<ActionAnimation>();
|
||
|
|
||
|
[Header("Basic Unity Events")]
|
||
|
public UnityEvent onAttack;
|
||
|
public UnityEvent onDash;
|
||
|
public UnityEvent onReceiveHit;
|
||
|
public UnityEvent onReceiveBuff;
|
||
|
public UnityEvent onReceiveDebuff;
|
||
|
|
||
|
[Header("Use Skill Events")]
|
||
|
public List<UseSkillEventData> useSkillEvents = new List<UseSkillEventData>();
|
||
|
|
||
|
[Header("UI Events")]
|
||
|
public UnityEvent onUpgradeCharacter;
|
||
|
public UnityEvent onCharacterInstantiate;
|
||
|
|
||
|
#region Playable Fields
|
||
|
|
||
|
private PlayableGraph playableGraph;
|
||
|
private AnimationLayerMixerPlayable layerMixerPlayable;
|
||
|
|
||
|
private AnimationMixerPlayable baseMixer;
|
||
|
private AnimationMixerPlayable partialActionMixer;
|
||
|
private AnimationMixerPlayable fullActionMixer;
|
||
|
|
||
|
private Dictionary<string, int> baseClipIndices;
|
||
|
private Dictionary<string, int> partialActionClipIndices;
|
||
|
private Dictionary<string, int> fullActionClipIndices;
|
||
|
|
||
|
private bool isGraphRunning;
|
||
|
private bool forceCompleteAction;
|
||
|
private string currentAction = "";
|
||
|
private bool useRunBlendTree;
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
private void Awake()
|
||
|
{
|
||
|
if (animator == null)
|
||
|
{
|
||
|
animator = GetComponent<Animator>();
|
||
|
if (animator == null)
|
||
|
{
|
||
|
animator = GetComponentInChildren<Animator>();
|
||
|
if (animator == null)
|
||
|
{
|
||
|
animator = GetComponentInParent<Animator>();
|
||
|
}
|
||
|
}
|
||
|
if (animator == null)
|
||
|
{
|
||
|
Debug.LogWarning($"{name}: No Animator found in hierarchy.");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Debug.Log($"{name}: Found Animator -> {animator.name}");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
useRunBlendTree = (runBackwardAnimation.animationClip != null
|
||
|
&& runLeftAnimation.animationClip != null
|
||
|
&& runRightAnimation.animationClip != null);
|
||
|
|
||
|
if (usePlayableAnimations)
|
||
|
{
|
||
|
SetupPlayableGraph();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void OnEnable()
|
||
|
{
|
||
|
onCharacterInstantiate.Invoke();
|
||
|
}
|
||
|
|
||
|
private void OnDestroy()
|
||
|
{
|
||
|
if (isGraphRunning && playableGraph.IsValid())
|
||
|
{
|
||
|
playableGraph.Destroy();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#region Graph Setup
|
||
|
|
||
|
private void SetupPlayableGraph()
|
||
|
{
|
||
|
if (animator == null)
|
||
|
{
|
||
|
Debug.LogError($"{name}: Cannot create PlayableGraph with null Animator.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
playableGraph = PlayableGraph.Create($"{name}_PlayableGraph");
|
||
|
var output = AnimationPlayableOutput.Create(playableGraph, "AnimationOutput", animator);
|
||
|
|
||
|
layerMixerPlayable = AnimationLayerMixerPlayable.Create(playableGraph, 3);
|
||
|
|
||
|
baseMixer = CreateBaseMixer();
|
||
|
partialActionMixer = CreateActionMixer(isFull: false);
|
||
|
fullActionMixer = CreateActionMixer(isFull: true);
|
||
|
|
||
|
playableGraph.Connect(baseMixer, 0, layerMixerPlayable, 0);
|
||
|
playableGraph.Connect(partialActionMixer, 0, layerMixerPlayable, 1);
|
||
|
playableGraph.Connect(fullActionMixer, 0, layerMixerPlayable, 2);
|
||
|
|
||
|
layerMixerPlayable.SetInputWeight(0, 1f);
|
||
|
layerMixerPlayable.SetInputWeight(1, 0f);
|
||
|
layerMixerPlayable.SetInputWeight(2, 0f);
|
||
|
|
||
|
if (actionAvatarMask != null)
|
||
|
{
|
||
|
layerMixerPlayable.SetLayerMaskFromAvatarMask(1, actionAvatarMask);
|
||
|
}
|
||
|
|
||
|
output.SetSourcePlayable(layerMixerPlayable);
|
||
|
playableGraph.Play();
|
||
|
isGraphRunning = true;
|
||
|
|
||
|
if (baseClipIndices.ContainsKey("Idle"))
|
||
|
{
|
||
|
PlayBaseAnimation("Idle");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private AnimationMixerPlayable CreateBaseMixer()
|
||
|
{
|
||
|
baseClipIndices = new Dictionary<string, int>();
|
||
|
var list = new List<AnimationClipPlayable>();
|
||
|
|
||
|
AddBaseAnimation(list, idleAnimation, "Idle");
|
||
|
AddBaseAnimation(list, runForwardAnimation, "RunForward");
|
||
|
AddBaseAnimation(list, runBackwardAnimation, "RunBackward");
|
||
|
AddBaseAnimation(list, runLeftAnimation, "RunLeft");
|
||
|
AddBaseAnimation(list, runRightAnimation, "RunRight");
|
||
|
AddBaseAnimation(list, hitAnimation, "Hit");
|
||
|
AddBaseAnimation(list, pickUpAnimation, "PickUp");
|
||
|
AddBaseAnimation(list, stunAnimation, "Stun");
|
||
|
|
||
|
var mixer = AnimationMixerPlayable.Create(playableGraph, list.Count);
|
||
|
for (int i = 0; i < list.Count; i++)
|
||
|
{
|
||
|
playableGraph.Connect(list[i], 0, mixer, i);
|
||
|
mixer.SetInputWeight(i, 0f);
|
||
|
}
|
||
|
return mixer;
|
||
|
}
|
||
|
|
||
|
private void AddBaseAnimation(List<AnimationClipPlayable> list, CharacterAnimation anim, string key)
|
||
|
{
|
||
|
if (anim.animationClip == null) return;
|
||
|
var clipPlayable = AnimationClipPlayable.Create(playableGraph, anim.animationClip);
|
||
|
clipPlayable.SetApplyFootIK(true);
|
||
|
clipPlayable.SetSpeed(anim.speed <= 0f ? 1f : anim.speed);
|
||
|
list.Add(clipPlayable);
|
||
|
baseClipIndices[key] = list.Count - 1;
|
||
|
}
|
||
|
|
||
|
private AnimationMixerPlayable CreateActionMixer(bool isFull)
|
||
|
{
|
||
|
Dictionary<string, int> dict = new Dictionary<string, int>();
|
||
|
var clips = new List<AnimationClipPlayable>();
|
||
|
|
||
|
for (int i = 0; i < actions.Count; i++)
|
||
|
{
|
||
|
var act = actions[i];
|
||
|
if (act.animationClip == null) continue;
|
||
|
bool belongsHere = act.playCompletely ? isFull : !isFull;
|
||
|
if (!belongsHere) continue;
|
||
|
|
||
|
var clipPlayable = AnimationClipPlayable.Create(playableGraph, act.animationClip);
|
||
|
clipPlayable.SetApplyFootIK(true);
|
||
|
clipPlayable.SetSpeed(act.speed <= 0f ? 1f : act.speed);
|
||
|
clips.Add(clipPlayable);
|
||
|
dict[act.action] = clips.Count - 1;
|
||
|
}
|
||
|
|
||
|
var mixer = AnimationMixerPlayable.Create(playableGraph, clips.Count);
|
||
|
for (int i = 0; i < clips.Count; i++)
|
||
|
{
|
||
|
playableGraph.Connect(clips[i], 0, mixer, i);
|
||
|
mixer.SetInputWeight(i, 0f);
|
||
|
}
|
||
|
|
||
|
if (isFull) fullActionClipIndices = dict;
|
||
|
else partialActionClipIndices = dict;
|
||
|
|
||
|
return mixer;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Public Checks
|
||
|
|
||
|
public bool IsActionPlaying()
|
||
|
{
|
||
|
return !string.IsNullOrEmpty(currentAction);
|
||
|
}
|
||
|
|
||
|
public string GetCurrentAction()
|
||
|
{
|
||
|
return currentAction;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Base Animations (Layer 0)
|
||
|
|
||
|
/// <summary>
|
||
|
/// Plays a base animation by key, resetting any ongoing action layers.
|
||
|
/// </summary>
|
||
|
public void PlayBaseAnimation(string animKey)
|
||
|
{
|
||
|
if (!usePlayableAnimations || !isGraphRunning) return;
|
||
|
if (!baseClipIndices.ContainsKey(animKey)) return;
|
||
|
|
||
|
currentAction = "";
|
||
|
forceCompleteAction = false;
|
||
|
|
||
|
layerMixerPlayable.SetInputWeight(0, 1f);
|
||
|
layerMixerPlayable.SetInputWeight(1, 0f);
|
||
|
layerMixerPlayable.SetInputWeight(2, 0f);
|
||
|
|
||
|
int idx = baseClipIndices[animKey];
|
||
|
float crossFadeTime = GetBaseTransition(animKey);
|
||
|
|
||
|
StartCoroutine(CrossFadeMixer(baseMixer, idx, crossFadeTime, $"Base:{animKey}"));
|
||
|
}
|
||
|
|
||
|
private float GetBaseTransition(string animKey)
|
||
|
{
|
||
|
switch (animKey)
|
||
|
{
|
||
|
case "Idle": return idleAnimation.transitionPercent;
|
||
|
case "RunForward": return runForwardAnimation.transitionPercent;
|
||
|
case "RunBackward": return runBackwardAnimation.transitionPercent;
|
||
|
case "RunLeft": return runLeftAnimation.transitionPercent;
|
||
|
case "RunRight": return runRightAnimation.transitionPercent;
|
||
|
case "Hit": return hitAnimation.transitionPercent;
|
||
|
case "PickUp": return pickUpAnimation.transitionPercent;
|
||
|
case "Stun": return stunAnimation.transitionPercent;
|
||
|
}
|
||
|
return 0.1f;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Action Animations
|
||
|
|
||
|
/// <summary>
|
||
|
/// Trigger-like action. If playCompletely == true => layer 2, else layer 1.
|
||
|
/// Does not guarantee the animation to finish unless you use PlayActionAnimationOnce or respect exitTime logic.
|
||
|
/// </summary>
|
||
|
public void PlayActionAnimation(string animKey)
|
||
|
{
|
||
|
if (!usePlayableAnimations || !isGraphRunning) return;
|
||
|
if (forceCompleteAction) return;
|
||
|
if (currentAction == animKey) return;
|
||
|
|
||
|
var data = FindAction(animKey);
|
||
|
if (data.animationClip == null) return;
|
||
|
|
||
|
currentAction = animKey;
|
||
|
float crossFade = data.transition <= 0f ? 0.1f : data.transition;
|
||
|
|
||
|
if (data.playCompletely)
|
||
|
{
|
||
|
forceCompleteAction = true;
|
||
|
StartCoroutine(FullActionCoroutine(data, crossFade, -1f));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
layerMixerPlayable.SetInputWeight(0, 1f);
|
||
|
layerMixerPlayable.SetInputWeight(2, 0f);
|
||
|
layerMixerPlayable.SetInputWeight(1, 1f);
|
||
|
|
||
|
if (!partialActionClipIndices.ContainsKey(animKey)) return;
|
||
|
int idx = partialActionClipIndices[animKey];
|
||
|
StartCoroutine(CrossFadeMixer(partialActionMixer, idx, crossFade, $"Partial:{animKey}"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Plays an action once, respecting exitTime. After that, it transitions to Idle or Run depending on speed.
|
||
|
/// Another action can interrupt if forceCompleteAction == false.
|
||
|
/// </summary>
|
||
|
public IEnumerator PlayActionAnimationOnce(string animKey, float manualDuration = -1f, string returnAnimKey = "Idle")
|
||
|
{
|
||
|
if (!usePlayableAnimations || !isGraphRunning) yield break;
|
||
|
|
||
|
var data = FindAction(animKey);
|
||
|
if (data.animationClip == null) yield break;
|
||
|
|
||
|
currentAction = animKey;
|
||
|
float crossFade = data.transition <= 0f ? 0.1f : data.transition;
|
||
|
|
||
|
if (data.playCompletely)
|
||
|
{
|
||
|
forceCompleteAction = true;
|
||
|
yield return StartCoroutine(FullActionCoroutine(data, crossFade, manualDuration, returnAnimKey));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
layerMixerPlayable.SetInputWeight(0, 1f);
|
||
|
layerMixerPlayable.SetInputWeight(2, 0f);
|
||
|
layerMixerPlayable.SetInputWeight(1, 1f);
|
||
|
|
||
|
if (!partialActionClipIndices.ContainsKey(animKey)) yield break;
|
||
|
int idx = partialActionClipIndices[animKey];
|
||
|
yield return StartCoroutine(CrossFadeMixer(partialActionMixer, idx, crossFade, $"PartialOnce:{animKey}"));
|
||
|
|
||
|
float clipLen = data.animationClip.length / ((data.speed <= 0f) ? 1f : data.speed);
|
||
|
float duration = (manualDuration > 0f) ? manualDuration : clipLen;
|
||
|
|
||
|
float exitFraction = (data.exitTime <= 0f) ? 1f : Mathf.Clamp01(data.exitTime);
|
||
|
float exitPoint = duration * exitFraction;
|
||
|
|
||
|
float timer = 0f;
|
||
|
while (timer < exitPoint)
|
||
|
{
|
||
|
timer += Time.deltaTime;
|
||
|
yield return null;
|
||
|
}
|
||
|
|
||
|
if (data.transition > 0f)
|
||
|
yield return new WaitForSeconds(data.transition);
|
||
|
|
||
|
layerMixerPlayable.SetInputWeight(1, 0f);
|
||
|
currentAction = "";
|
||
|
forceCompleteAction = false;
|
||
|
PlayBaseAnimation(returnAnimKey);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Internal coroutine for full override. After exitTime, transitions to Run or Idle automatically.
|
||
|
/// </summary>
|
||
|
private IEnumerator FullActionCoroutine(ActionAnimation data, float crossFade, float manualDuration, string returnAnimKey = "Idle")
|
||
|
{
|
||
|
layerMixerPlayable.SetInputWeight(0, 0f);
|
||
|
layerMixerPlayable.SetInputWeight(1, 0f);
|
||
|
layerMixerPlayable.SetInputWeight(2, 1f);
|
||
|
|
||
|
if (!fullActionClipIndices.ContainsKey(data.action))
|
||
|
{
|
||
|
forceCompleteAction = false;
|
||
|
currentAction = "";
|
||
|
yield break;
|
||
|
}
|
||
|
|
||
|
int idx = fullActionClipIndices[data.action];
|
||
|
yield return StartCoroutine(CrossFadeMixer(fullActionMixer, idx, crossFade, $"Full:{data.action}"));
|
||
|
|
||
|
float clipLen = data.animationClip.length;
|
||
|
float spd = (data.speed <= 0f) ? 1f : data.speed;
|
||
|
float finalDuration = (manualDuration > 0f) ? manualDuration : (clipLen / spd);
|
||
|
|
||
|
float exitFrac = (data.exitTime <= 0f) ? 1f : Mathf.Clamp01(data.exitTime);
|
||
|
float exitTime = finalDuration * exitFrac;
|
||
|
|
||
|
float timer = 0f;
|
||
|
while (timer < exitTime)
|
||
|
{
|
||
|
timer += Time.deltaTime;
|
||
|
yield return null;
|
||
|
}
|
||
|
|
||
|
if (data.transition > 0f)
|
||
|
yield return new WaitForSeconds(data.transition);
|
||
|
|
||
|
layerMixerPlayable.SetInputWeight(2, 0f);
|
||
|
layerMixerPlayable.SetInputWeight(0, 1f);
|
||
|
|
||
|
forceCompleteAction = false;
|
||
|
currentAction = "";
|
||
|
|
||
|
PlayBaseAnimation(returnAnimKey);
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Run Logic
|
||
|
|
||
|
/// <summary>
|
||
|
/// Plays a run animation based on the movement direction vs. the model forward.
|
||
|
/// If not all directions are available, fallback to RunForward.
|
||
|
/// </summary>
|
||
|
public void PlayRunAnimation(Vector3 moveDir, Vector3 modelForward)
|
||
|
{
|
||
|
if (moveDir.magnitude < 0.1f)
|
||
|
{
|
||
|
PlayBaseAnimation("Idle");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!useRunBlendTree)
|
||
|
{
|
||
|
PlayBaseAnimation("RunForward");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Vector3 fwd = modelForward.normalized;
|
||
|
Vector3 normalizedMove = moveDir.normalized;
|
||
|
|
||
|
float forwardDot = Vector3.Dot(fwd, normalizedMove);
|
||
|
Vector3 rightDir = Quaternion.Euler(0, 90, 0) * fwd;
|
||
|
float rightDot = Vector3.Dot(rightDir, normalizedMove);
|
||
|
|
||
|
if (forwardDot >= 0.5f && baseClipIndices.ContainsKey("RunForward"))
|
||
|
{
|
||
|
PlayBaseAnimation("RunForward");
|
||
|
}
|
||
|
else if (forwardDot <= -0.5f && baseClipIndices.ContainsKey("RunBackward"))
|
||
|
{
|
||
|
PlayBaseAnimation("RunBackward");
|
||
|
}
|
||
|
else if (rightDot >= 0.5f && baseClipIndices.ContainsKey("RunRight"))
|
||
|
{
|
||
|
PlayBaseAnimation("RunRight");
|
||
|
}
|
||
|
else if (rightDot <= -0.5f && baseClipIndices.ContainsKey("RunLeft"))
|
||
|
{
|
||
|
PlayBaseAnimation("RunLeft");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayBaseAnimation("RunForward");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region CrossFade
|
||
|
|
||
|
/// <summary>
|
||
|
/// Crossfades a mixer from current weights to targetIndex=1 over 'duration' seconds.
|
||
|
/// </summary>
|
||
|
private IEnumerator CrossFadeMixer(AnimationMixerPlayable mixer, int targetIndex, float duration, string dbgKey)
|
||
|
{
|
||
|
if (!mixer.IsValid()) yield break;
|
||
|
|
||
|
int count = mixer.GetInputCount();
|
||
|
float[] startWeights = new float[count];
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
startWeights[i] = mixer.GetInputWeight(i);
|
||
|
}
|
||
|
|
||
|
float elapsed = 0f;
|
||
|
while (elapsed < duration)
|
||
|
{
|
||
|
elapsed += Time.deltaTime;
|
||
|
float t = Mathf.Clamp01(elapsed / duration);
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
float endVal = (i == targetIndex) ? 1f : 0f;
|
||
|
float newWeight = Mathf.Lerp(startWeights[i], endVal, t);
|
||
|
mixer.SetInputWeight(i, newWeight);
|
||
|
}
|
||
|
yield return null;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
float finalVal = (i == targetIndex) ? 1f : 0f;
|
||
|
mixer.SetInputWeight(i, finalVal);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Find Action
|
||
|
|
||
|
private ActionAnimation FindAction(string actionKey)
|
||
|
{
|
||
|
for (int i = 0; i < actions.Count; i++)
|
||
|
{
|
||
|
if (actions[i].action == actionKey && actions[i].animationClip != null)
|
||
|
{
|
||
|
return actions[i];
|
||
|
}
|
||
|
}
|
||
|
return default;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Legacy Animator Fallback
|
||
|
|
||
|
public void SetAnimatorTrigger(string triggerName)
|
||
|
{
|
||
|
if (!usePlayableAnimations && animator != null)
|
||
|
{
|
||
|
animator.SetTrigger(triggerName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void SetAnimatorBool(string boolName, bool value)
|
||
|
{
|
||
|
if (!usePlayableAnimations && animator != null)
|
||
|
{
|
||
|
animator.SetBool(boolName, value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void SetAnimatorFloat(string floatName, float value)
|
||
|
{
|
||
|
if (!usePlayableAnimations && animator != null)
|
||
|
{
|
||
|
animator.SetFloat(floatName, value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
}
|
||
|
}
|