457 lines
13 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|