Files
2026-03-04 09:37:33 +08:00

457 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using KINEMATION.KAnimationCore.Runtime.Rig;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Experimental.Animations;
using UnityEngine.Playables;
namespace KINEMATION.MagicBlend.Runtime
{
[HelpURL("https://kinemation.gitbook.io/magic-blend-documentation/")]
public class MagicBlending : MonoBehaviour
{
public PlayableGraph playableGraph;
[Tooltip("This asset controls the blending weights.")]
[SerializeField]
private MagicBlendAsset blendAsset;
[Tooltip("Will update weights every frame.")]
[SerializeField]
private bool forceUpdateWeights = true;
[Tooltip("Will process the Overlay pose. Keep it on most of the time.")]
[SerializeField]
private bool alwaysAnimatePoses = true;
private const ushort PlayableSortingPriority = 900;
private Animator _animator;
private KRigComponent _rigComponent;
private AnimationLayerMixerPlayable _playableMixer;
private NativeArray<BlendStreamAtom> _atoms;
private PoseJob _poseJob;
private OverlayJob _overlayJob;
private LayeringJob _layeringJob;
private AnimationScriptPlayable _poseJobPlayable;
private AnimationScriptPlayable _overlayJobPlayable;
private AnimationScriptPlayable _layeringJobPlayable;
private bool _isInitialized;
private float _blendPlayback = 1f;
private float _blendTime;
private AnimationCurve _blendCurve;
private MagicBlendingData _blendData;
private MagicBlendingData _desiredBlendData;
private MagicBlendAsset _previousBlendAsset;
private List<int> _blendedIndexes = new List<int>();
private Dictionary<string, int> _hierarchyMap;
private RuntimeAnimatorController _cachedController;
private AnimationPlayableOutput _magicBlendOutput;
private bool _forceBlendOut;
private bool _wasAnimatorActive;
public MagicBlendAsset BlendAsset => blendAsset;
public void UpdateMagicBlendAsset(MagicBlendAsset newAsset)
{
UpdateMagicBlendAsset(newAsset, MagicBlendingData.Empty);
}
public void UpdateMagicBlendAsset(MagicBlendAsset newAsset, MagicBlendingData blendingData, bool ignoreBlending = false)
{
if (newAsset == null || !_isInitialized)
{
return;
}
_desiredBlendData = blendingData;
_desiredBlendData.blendAsset = newAsset;
if ((blendingData.blendTime > 0f || newAsset.blendTime > 0f) && !ignoreBlending)
{
_layeringJob.cachePose = true;
_layeringJobPlayable.SetJobData(_layeringJob);
return;
}
_layeringJob.blendWeight = 1f;
_layeringJobPlayable.SetJobData(_layeringJob);
SetNewAsset();
if (!alwaysAnimatePoses)
{
_poseJob.readPose = true;
_overlayJob.cachePose = true;
_poseJobPlayable.SetJobData(_poseJob);
_overlayJobPlayable.SetJobData(_overlayJob);
}
}
[Obsolete("Use the alternative override!")]
public void UpdateMagicBlendAsset(MagicBlendAsset newAsset, bool useBlending = true, float blendTime = -1f, bool useCurve = true, AnimationClip overlayOverride = null)
{
UpdateMagicBlendAsset(newAsset, new MagicBlendingData
{
blendTime = blendTime,
useLinear = !useCurve,
overlayPose = overlayOverride
});
}
public void StopMagicBlending()
{
_forceBlendOut = true;
_blendPlayback = 0f;
}
public void SetOverlayTime(float newTime)
{
Playable input = _overlayJobPlayable.GetInput(0);
if (input.IsValid())
{
input.SetTime(newTime);
}
}
public float GetOverlayTime(bool isNormalized = true)
{
Playable input = _overlayJobPlayable.GetInput(0);
if (!input.IsValid() || !blendAsset.isAnimation)
{
return 0f;
}
float num = (float)input.GetDuration();
if (Mathf.Approximately(num, 0f))
{
return 0f;
}
float num2 = (float)input.GetTime();
if (!isNormalized)
{
return num2;
}
return Mathf.Clamp01(num2 / num);
}
protected virtual void SetProcessJobs(bool isActive)
{
_poseJobPlayable.SetProcessInputs(isActive);
_overlayJobPlayable.SetProcessInputs(isActive);
}
protected virtual void SetNewAsset()
{
_blendData = _desiredBlendData;
if (blendAsset != null && blendAsset.blendOutType == MagicBlendOutType.DoNotBlendOut)
{
_previousBlendAsset = blendAsset;
}
blendAsset = _blendData.blendAsset;
if (_previousBlendAsset == null && blendAsset.blendOutType == MagicBlendOutType.DoNotBlendOut)
{
_previousBlendAsset = blendAsset;
}
if (_blendData.overlayPose == null)
{
_blendData.overlayPose = blendAsset.overlayPose;
}
_blendCurve = (_blendData.useLinear ? null : blendAsset.blendCurve);
_blendTime = ((_blendData.blendTime > 0f) ? _blendData.blendTime : blendAsset.blendTime);
MagicBlendLibrary.ConnectPose(_poseJobPlayable, playableGraph, blendAsset.basePose);
float speed = (blendAsset.isAnimation ? blendAsset.overlaySpeed : 0f);
if (blendAsset.HasOverrides())
{
MagicBlendLibrary.ConnectOverlays(_overlayJobPlayable, playableGraph, _blendData.overlayPose, blendAsset.overrideOverlays, speed);
}
else
{
MagicBlendLibrary.ConnectPose(_overlayJobPlayable, playableGraph, _blendData.overlayPose, speed);
}
for (int i = 0; i < _hierarchyMap.Count; i++)
{
BlendStreamAtom value = _atoms[i];
value.baseWeight = (value.additiveWeight = (value.localWeight = 0f));
_atoms[i] = value;
}
_blendedIndexes.Clear();
foreach (LayeredBlend layeredBlend in blendAsset.layeredBlends)
{
foreach (KRigElement item in layeredBlend.layer.elementChain)
{
_hierarchyMap.TryGetValue(item.name, out var value2);
_blendedIndexes.Add(value2);
}
}
SetProcessJobs(isActive: true);
_forceBlendOut = false;
UpdateBlendWeights();
}
protected virtual void BuildMagicMixer()
{
if (!_playableMixer.IsValid())
{
_playableMixer = AnimationLayerMixerPlayable.Create(playableGraph, 3);
InitializeJobs();
_playableMixer.ConnectInput(0, _poseJobPlayable, 0, 1f);
_playableMixer.ConnectInput(1, _overlayJobPlayable, 0, 1f);
_playableMixer.ConnectInput(2, _layeringJobPlayable, 0, 1f);
}
_magicBlendOutput.SetSourcePlayable(_playableMixer);
_magicBlendOutput.SetSortingOrder(900);
int outputCount = playableGraph.GetOutputCount();
int index = 0;
for (int i = 0; i < outputCount; i++)
{
if (!(playableGraph.GetOutput(i).GetSourcePlayable().GetPlayableType() != typeof(AnimatorControllerPlayable)))
{
index = i;
}
}
Playable sourcePlayable = playableGraph.GetOutput(index).GetSourcePlayable();
if (_layeringJobPlayable.IsValid())
{
_layeringJobPlayable.DisconnectInput(0);
}
_layeringJobPlayable.ConnectInput(0, sourcePlayable, 0, 1f);
if (blendAsset != null)
{
UpdateMagicBlendAsset(blendAsset, new MagicBlendingData
{
blendTime = -1f
});
}
}
protected virtual void InitializeMagicBlending()
{
playableGraph = _animator.playableGraph;
_atoms = MagicBlendLibrary.SetupBlendAtoms(_animator, _rigComponent);
_magicBlendOutput = AnimationPlayableOutput.Create(playableGraph, "MagicBlendOutput", _animator);
_isInitialized = true;
BuildMagicMixer();
playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
playableGraph.Play();
}
private void InitializeJobs()
{
TransformSceneHandle root = _animator.BindSceneTransform(_animator.transform);
_poseJob = new PoseJob
{
atoms = _atoms,
root = root,
alwaysAnimate = alwaysAnimatePoses,
readPose = false
};
_poseJobPlayable = AnimationScriptPlayable.Create(playableGraph, _poseJob, 1);
_overlayJob = new OverlayJob
{
atoms = _atoms,
root = root,
alwaysAnimate = alwaysAnimatePoses,
cachePose = false
};
_overlayJobPlayable = AnimationScriptPlayable.Create(playableGraph, _overlayJob, 1);
_layeringJob = new LayeringJob
{
atoms = _atoms,
root = root,
blendWeight = 1f,
cachePose = false
};
_layeringJobPlayable = AnimationScriptPlayable.Create(playableGraph, _layeringJob, 1);
}
private void OnEnable()
{
if (_isInitialized)
{
BuildMagicMixer();
}
}
private void Start()
{
_animator = GetComponent<Animator>();
_cachedController = _animator.runtimeAnimatorController;
_wasAnimatorActive = _animator.isActiveAndEnabled;
_rigComponent = GetComponentInChildren<KRigComponent>();
_hierarchyMap = new Dictionary<string, int>();
Transform[] rigTransforms = _rigComponent.GetRigTransforms();
for (int i = 0; i < rigTransforms.Length; i++)
{
_hierarchyMap.Add(rigTransforms[i].name, i);
}
InitializeMagicBlending();
}
protected virtual void UpdateBlendWeights(float globalWeight = 1f)
{
int num = 0;
foreach (LayeredBlend layeredBlend in blendAsset.layeredBlends)
{
foreach (KRigElement item in layeredBlend.layer.elementChain)
{
_ = item;
int index = _blendedIndexes[num];
BlendStreamAtom value = _atoms[index];
value.baseWeight = layeredBlend.baseWeight * blendAsset.globalWeight * globalWeight;
value.additiveWeight = layeredBlend.additiveWeight * blendAsset.globalWeight * globalWeight;
value.localWeight = layeredBlend.localWeight * blendAsset.globalWeight * globalWeight;
_atoms[index] = value;
num++;
}
}
Playable input = _overlayJobPlayable.GetInput(0);
int inputCount = input.GetInputCount();
if (inputCount != 0)
{
for (int i = 1; i < inputCount; i++)
{
input.SetInputWeight(i, blendAsset.overrideOverlays[i - 1].weight);
}
}
}
protected virtual void Update()
{
if (_blendData.blendAsset == null || blendAsset == null)
{
return;
}
RuntimeAnimatorController runtimeAnimatorController = _animator.runtimeAnimatorController;
if (_cachedController != runtimeAnimatorController || _wasAnimatorActive != _animator.isActiveAndEnabled)
{
BuildMagicMixer();
}
_cachedController = runtimeAnimatorController;
_wasAnimatorActive = _animator.isActiveAndEnabled;
if (blendAsset.isAnimation)
{
int inputCount = _overlayJobPlayable.GetInput(0).GetInputCount();
Playable playable = ((inputCount == 0) ? _overlayJobPlayable.GetInput(0) : _overlayJobPlayable.GetInput(0).GetInput(0));
if (playable.GetTime() >= (double)_blendData.overlayPose.length)
{
if (blendAsset.blendOutType > MagicBlendOutType.DoNotBlendOut)
{
if (blendAsset.blendOutType == MagicBlendOutType.AutoBlendOut || _previousBlendAsset == null)
{
if (!_forceBlendOut)
{
StopMagicBlending();
}
}
else
{
UpdateMagicBlendAsset(_previousBlendAsset);
}
}
else if (_blendData.overlayPose.isLooping)
{
playable.SetTime(0.0);
}
}
if (inputCount > 1)
{
for (int i = 1; i < inputCount; i++)
{
Playable input = _overlayJobPlayable.GetInput(0).GetInput(i);
AnimationClip overlay = blendAsset.overrideOverlays[i - 1].overlay;
if (overlay.isLooping && !(input.GetTime() < (double)overlay.length) && blendAsset.blendOutType <= MagicBlendOutType.DoNotBlendOut)
{
input.SetTime(0.0);
}
}
}
}
float num = 1f;
if (_forceBlendOut)
{
_blendPlayback = Mathf.Clamp(_blendPlayback + Time.deltaTime, 0f, _blendTime);
float num2 = _blendPlayback / _blendTime;
num = 1f - (_blendCurve?.Evaluate(num2) ?? num2);
}
if (forceUpdateWeights || _forceBlendOut)
{
UpdateBlendWeights(num);
}
if (Mathf.Approximately(num, 0f))
{
SetProcessJobs(isActive: false);
blendAsset = null;
}
if (!_forceBlendOut && !Mathf.Approximately(_blendPlayback, _blendTime))
{
_blendPlayback = Mathf.Clamp(_blendPlayback + Time.deltaTime, 0f, _blendTime);
float num3 = ((_blendTime > 0f) ? (_blendPlayback / _blendTime) : 1f);
_layeringJob.blendWeight = _blendCurve?.Evaluate(num3) ?? num3;
_layeringJobPlayable.SetJobData(_layeringJob);
}
}
protected virtual void LateUpdate()
{
if (!alwaysAnimatePoses && _poseJob.readPose)
{
_poseJob.readPose = false;
_overlayJob.cachePose = false;
_poseJobPlayable.SetJobData(_poseJob);
_overlayJobPlayable.SetJobData(_overlayJob);
}
if (_layeringJob.cachePose)
{
SetNewAsset();
_blendPlayback = 0f;
_layeringJob.cachePose = false;
_layeringJob.blendWeight = 0f;
_layeringJobPlayable.SetJobData(_layeringJob);
if (!alwaysAnimatePoses)
{
_poseJob.readPose = true;
_overlayJob.cachePose = true;
_poseJobPlayable.SetJobData(_poseJob);
_overlayJobPlayable.SetJobData(_overlayJob);
}
}
}
protected virtual void OnDestroy()
{
if (playableGraph.IsValid() && playableGraph.IsPlaying())
{
playableGraph.Stop();
}
if (_atoms.IsCreated)
{
_atoms.Dispose();
}
}
public void SetMagicBlendAsset(MagicBlendAsset newAsset)
{
blendAsset = newAsset;
}
}
}