using System; using Rukhanka.Toolbox; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Mathematics; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace Rukhanka { public static partial class ScriptedAnimator { /// /// Clear animation to process component buffer, to clear current animation state. Usually used once per-frame. /// /// Animation to process component buffer of animated entity. public static void ResetAnimationState(ref DynamicBuffer atps) { atps.Clear(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Instruct Rukhanka to play given animation at specified point of the time. You must call this function every /// frame with progressively advancing normalized time value. /// /// Animation to process component buffer used to fill animations to. Use buffer from animated entity. /// Clip to play. /// Normalized play time (0 - beginning of the animation, 1 - end of the animation). /// Weight of the animation. /// Optional avatar mask to use with state animations. public static void PlayAnimation ( ref DynamicBuffer atps, BlobAssetReference clip, float normalizedTime, float weight = 1, BlobAssetReference avatarMask = default ) { var atp = new AnimationToProcessComponent() { animation = clip, time = normalizedTime, weight = weight, avatarMask = avatarMask, blendMode = AnimationBlendingMode.Override, layerIndex = 0, layerWeight = 1, motionId = (uint)atps.Length }; atps.Add(atp); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Instruct Rukhanka to blend given two animations at specified point of the time. You must call this function every /// frame with progressively advancing normalized time value. /// /// Animation to process component buffer used to fill animations to. Use buffer from animated entity. /// First clip of blending operation. /// Second clip of blending operation. /// Normalized time (0 - beginning of the state, 1 - end of the state) of state to play. /// Linear interpolation factor. Interpolate from clip0 to clip1 with range [0..1]. /// Weight of the blending operation. /// Optional avatar mask to use with state animations. public static void BlendTwoAnimations ( ref DynamicBuffer atps, BlobAssetReference clip0, BlobAssetReference clip1, float normalizedTime, float blendFactor, float weight = 1, BlobAssetReference avatarMask = default ) { var atp = new AnimationToProcessComponent() { animation = clip0, time = normalizedTime, weight = (1 - blendFactor) * weight, avatarMask = avatarMask, blendMode = AnimationBlendingMode.Override, layerIndex = 0, layerWeight = 1, motionId = (uint)atps.Length }; atps.Add(atp); atp.animation = clip1; atp.weight = blendFactor * weight; atp.motionId = (uint)atps.Length; atps.Add(atp); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Instruct Rukhanka to play given blend tree at specified point of the time. You must call this function every /// frame with progressively advancing normalized time value /// /// Animation to process component buffer used to fill animations to. Use buffer from animated entity. /// Array of all animation clips that make up the blend tree. /// 1D blend tree coordinate positions of input clips. Size must match blendTreeClips length. /// Current blend tree coordinate position. /// Normalized time (0 - beginning of the state, 1 - end of the state) of state to play. /// Weight of the entire blend tree. /// Optional avatar mask to use with state animations. public static unsafe void PlayBlendTree1D ( ref DynamicBuffer atps, in NativeArray> blendTreeClips, in NativeArray blendTreeThresholds, float blendTreeParameterValue, float normalizedTime, float blendTreeWeight = 1, BlobAssetReference avatarMask = default ) { BurstAssert.IsTrue(blendTreeClips.Length == blendTreeThresholds.Length, "Blend tree clips count must match thresholds array length"); var bttSpan = new ReadOnlySpan(blendTreeThresholds.GetUnsafeReadOnlyPtr(), blendTreeThresholds.Length); var motions = ComputeBlendTree1D(bttSpan, blendTreeParameterValue); for (var i = 0; i < motions.Length; ++i) { var m = motions[i]; var atp = new AnimationToProcessComponent() { animation = blendTreeClips[m.motionIndex], time = normalizedTime, weight = m.weight * blendTreeWeight, avatarMask = avatarMask, blendMode = AnimationBlendingMode.Override, layerIndex = 0, layerWeight = 1, motionId = (uint)atps.Length }; atps.Add(atp); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public struct BlendTree2DMotionElement { // Element 2D coordinates public float2 pos; // Motion index of given element public int motionIndex; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Instruct Rukhanka to play given blend tree at specified point of the time. You must call this function every /// frame with progressively advancing normalized time value /// /// Animation to process component buffer used to fill animations to. Use buffer from animated entity. /// Array of all animation clips that make up the blend tree. /// 2D blend tree coordinate positions of input clips. /// 2D blend coordinate position of current blend tree state. /// Normalized time (0 - beginning of the state, 1 - end of the state) of state to play. /// Blend tree type. /// Weight of the entire blend tree. /// Optional avatar mask to use with state animations. public static unsafe void PlayBlendTree2D ( ref DynamicBuffer atps, in NativeArray> blendTreeClips, in NativeArray blendTreePositions, float2 blendTreeParameterValue, float normalizedTime, MotionBlob.Type blendTreeType, float blendTreeWeight = 1, BlobAssetReference avatarMask = default ) { BurstAssert.IsTrue(blendTreeClips.Length == blendTreePositions.Length, "Blend tree clips and positions array lengths must match."); var bttSpan = new ReadOnlySpan(blendTreePositions.GetUnsafeReadOnlyPtr(), blendTreePositions.Length); var motions = blendTreeType switch { MotionBlob.Type.BlendTree2DSimpleDirectional => ComputeBlendTree2DSimpleDirectional(bttSpan, blendTreeParameterValue), MotionBlob.Type.BlendTree2DFreeformCartesian => ComputeBlendTree2DFreeformCartesian(bttSpan, blendTreeParameterValue), MotionBlob.Type.BlendTree2DFreeformDirectional => ComputeBlendTree2DFreeformDirectional(bttSpan, blendTreeParameterValue), _ => default }; for (var i = 0; i < motions.Length; ++i) { var m = motions[i]; var atp = new AnimationToProcessComponent() { animation = blendTreeClips[m.motionIndex], time = normalizedTime, weight = m.weight * blendTreeWeight, avatarMask = avatarMask, blendMode = AnimationBlendingMode.Override, layerIndex = 0, layerWeight = 1, motionId = (uint)atps.Length }; atps.Add(atp); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Get index of the state in layer with given name hash. /// /// Controller layer blob /// Later index state belongs to /// State name hash. Use "StateName".CalculateHash32() to obtain one. /// public static int GetStateIndexInControllerLayer(BlobAssetReference cb, int layerIndex, uint stateHash) { ref var layerBlob = ref cb.Value.layers[layerIndex]; for (var i = 0; i < layerBlob.states.Length; ++i) { ref var stateBlob = ref layerBlob.states[i]; if (stateBlob.hash == stateHash) return i; } return -1; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Instruct Rukhanka to play given animator state at specified point of the time. You must call this function every /// frame with progressively advancing normalized time value. /// /// Animation to process component buffer used to fill animations from requested state. Use buffer from animated entity. /// Animator runtime parameters buffer of given animator controller. Use buffer from animated entity. /// Animator controller blob asset. Blob asset can be obtained from AnimatorControllerLayerComponent of animated entity. /// Animations blob asset to play with given controller. Usually can be obtained from AnimatorControllerLayerComponent of animated entity. /// Blob database singleton to query requested animations from animations blob. /// Layer index of controller to play. /// State index of layer to play. Index can be obtained using ScriptedAnimator.GetStateIndexInControllerLayer function. /// Normalized time (0 - beginning of the state, 1 - end of the state) of state to play. /// Weight of current state. /// Optional avatar mask to use with state animations. public static void PlayAnimatorState ( ref DynamicBuffer atps, in NativeArray animatorControllerParameters, in BlobAssetReference controllerBlob, in BlobAssetReference animationsBlob, in BlobDatabaseSingleton blobDatabase, int layerIndex, int stateIndex, float normalizedTime, float weight = 1, BlobAssetReference avatarMask = default ) { BurstAssert.IsTrue(controllerBlob.IsCreated, "Controller blob is not valid"); BurstAssert.IsTrue(animationsBlob.IsCreated, "Controller animations blob is not valid"); BurstAssert.IsTrue(controllerBlob.Value.layers.Length > layerIndex, "Layer index is out of range of controller layers array"); if (controllerBlob.Value.layers.Length <= layerIndex || layerIndex < 0) return; ref var lb = ref controllerBlob.Value.layers[layerIndex]; BurstAssert.IsTrue(lb.states.Length > stateIndex, "State index is out of range of controller layer states array"); if (lb.states.Length <= stateIndex || stateIndex < 0) return; ref var sb = ref lb.states[stateIndex]; PlayMotion ( ref atps, ref sb.motion, animatorControllerParameters, animationsBlob, blobDatabase, normalizedTime, weight, avatarMask ); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Start cross fade (linear blend) of current controller layer state and any other layer state with specified transition properties. /// This function is full analog of Unity's Animator.CrossFade API. /// /// This is "fire and forget" function. It needs to be called only once to start transition. Transition flow will continue authomatically. /// Controller layer to perform transition. /// State index to crossfade into. /// Transition duration as a fraction of current state length. I.e. if current state length is 3 sec, and /// normalizedTransitionDuration is 0.5f, then transition duration will be 1.5 sec. /// Offset of start point of target state (defined by state index) in transition. /// Offset of transition start point. /// Unity Animator.CrossFade public static void CrossFade ( ref AnimatorControllerLayerComponent animatorControllerLayer, int stateIndex, float normalizedTransitionDuration, float normalizedTimeOffset = 0, float normalizedTransitionTime = 0 ) { var rt = new RuntimeAnimatorData.TransitionData() { id = 0xffffff, length = -normalizedTransitionDuration, normalizedDuration = normalizedTransitionTime }; animatorControllerLayer.rtd.activeTransition = rt; animatorControllerLayer.rtd.dstState.id = stateIndex; animatorControllerLayer.rtd.dstState.normalizedDuration = normalizedTimeOffset; } } }