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 _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 _blendedIndexes = new List(); private Dictionary _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(); _cachedController = _animator.runtimeAnimatorController; _wasAnimatorActive = _animator.isActiveAndEnabled; _rigComponent = GetComponentInChildren(); _hierarchyMap = new Dictionary(); 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; } } }