using System; using Unity.Animation; using Unity.Collections; using Unity.Entities; using Unity.Jobs; using Unity.Mathematics; using Unity.DataFlowGraph; using Unity.Sample.Core; using UnityEngine; public class AnimSourceJump { public struct Settings : IComponentData { public BlobAssetReference JumpClip; public BlobAssetReference JumpAimVerticalClip; public BlobAssetReference JumpAimHorizontalClip; public BlobAssetReference AdditiveRefPose; public float jumpHeight; // Jump height of character in last frame of animation public float aimDuringReloadPitch; public float MaxHipOffset; public float HipDragSpeed; } public struct SystemState : ISystemStateComponentData { public static SystemState Default => new SystemState(); public NodeHandle JumpNode; public NodeHandle DeltaTimeNode; public NodeHandle AimVerticalNode; public NodeHandle AimHorizontalNode; public NodeHandle AdditiveRefPoseA; public NodeHandle AdditiveRefPoseB; public NodeHandle AimVerticalDelta; public NodeHandle AimHorizontalDelta; public NodeHandle MixerNode; public float AimVerticalDuration; public float AimHorizontalDuration; public float JumpDuration; public float CurrentRotationVel; public float PlaySpeed; } [UpdateInGroup(typeof(AnimSourceInitializationGroup))] [DisableAutoCreation] [AlwaysSynchronizeSystem] class InitSystem : JobComponentSystem { AnimationGraphSystem m_AnimationGraphSystem; protected override void OnCreate() { base.OnCreate(); m_AnimationGraphSystem = World.GetExistingSystem(); m_AnimationGraphSystem.AddRef(); } protected override void OnDestroy() { base.OnDestroy(); var cmdBuffer = new EntityCommandBuffer(Allocator.Temp); var animationGraphSystem = m_AnimationGraphSystem; Entities .WithoutBurst() // Can be removed once NodeSets are Burst-friendly .ForEach((Entity entity, ref SystemState state) => { Deinitialize(cmdBuffer, entity, animationGraphSystem, state); }).Run(); cmdBuffer.Dispose(); m_AnimationGraphSystem.RemoveRef(); } protected override JobHandle OnUpdate(JobHandle inputDeps) { inputDeps.Complete(); var nodeSet = m_AnimationGraphSystem.Set; var commands = new EntityCommandBuffer(Allocator.TempJob); var ownedAbilityBufferFromEntity = GetBufferFromEntity(true); // Handle created entities Entities .WithoutBurst() // Can be removed once NodeSets are Burst-friendly .WithNone() .ForEach((Entity entity, ref AnimSource.Data animSource, ref Settings settings) => { // GameDebug.Log("Init Run"); var state = SystemState.Default; var abilityMovementEntity = Ability.FindAbility(ownedAbilityBufferFromEntity, animSource.animStateEntity, AbilityMovement.Tag); if (abilityMovementEntity == Entity.Null) return; var abilityMovementSettings = EntityManager.GetComponentData(abilityMovementEntity); state.JumpNode = AnimationGraphHelper.CreateNode(m_AnimationGraphSystem,"JumpNode"); state.DeltaTimeNode = AnimationGraphHelper.CreateNode(m_AnimationGraphSystem,"DeltaTimeNode"); state.AimVerticalNode = AnimationGraphHelper.CreateNode(m_AnimationGraphSystem,"AimVerticalNode"); state.AimHorizontalNode = AnimationGraphHelper.CreateNode(m_AnimationGraphSystem,"AimHorizontalNode"); state.AdditiveRefPoseA = AnimationGraphHelper.CreateNode(m_AnimationGraphSystem,"AdditiveRefPoseA"); state.AdditiveRefPoseB = AnimationGraphHelper.CreateNode(m_AnimationGraphSystem,"AdditiveRefPoseB"); state.AimVerticalDelta = AnimationGraphHelper.CreateNode(m_AnimationGraphSystem,"AimVerticalDelta"); state.AimHorizontalDelta = AnimationGraphHelper.CreateNode(m_AnimationGraphSystem,"AimHorizontalDelta"); state.MixerNode = AnimationGraphHelper.CreateNode(m_AnimationGraphSystem,"MixerNode"); nodeSet.SendMessage(state.JumpNode, ClipPlayerNode.SimulationPorts.Speed, 1.0f); nodeSet.SendMessage(state.MixerNode, LayerMixerNode.SimulationPorts.WeightInput0, 1f); nodeSet.SendMessage(state.MixerNode, LayerMixerNode.SimulationPorts.WeightInput1, 1f); nodeSet.SendMessage(state.MixerNode, LayerMixerNode.SimulationPorts.WeightInput2, 1f); nodeSet.SendMessage(state.MixerNode, LayerMixerNode.SimulationPorts.BlendModeInput1, BlendingMode.Additive); nodeSet.SendMessage(state.MixerNode, LayerMixerNode.SimulationPorts.BlendModeInput2, BlendingMode.Additive); nodeSet.Connect(state.DeltaTimeNode, DeltaTimeNode.KernelPorts.DeltaTime, state.JumpNode, ClipPlayerNode.KernelPorts.DeltaTime); nodeSet.Connect(state.JumpNode, ClipPlayerNode.KernelPorts.Output, state.MixerNode, LayerMixerNode.KernelPorts.Input0); nodeSet.Connect(state.AdditiveRefPoseA, ClipNode.KernelPorts.Output, state.AimVerticalDelta, DeltaNode.KernelPorts.Subtract); nodeSet.Connect(state.AdditiveRefPoseB, ClipNode.KernelPorts.Output, state.AimHorizontalDelta, DeltaNode.KernelPorts.Subtract); nodeSet.Connect(state.AimVerticalNode, ClipNode.KernelPorts.Output, state.AimVerticalDelta, DeltaNode.KernelPorts.Input); nodeSet.Connect(state.AimHorizontalNode, ClipNode.KernelPorts.Output, state.AimHorizontalDelta, DeltaNode.KernelPorts.Input); nodeSet.Connect(state.AimVerticalDelta, DeltaNode.KernelPorts.Output, state.MixerNode, LayerMixerNode.KernelPorts.Input1); nodeSet.Connect(state.AimHorizontalDelta, DeltaNode.KernelPorts.Output, state.MixerNode, LayerMixerNode.KernelPorts.Input2); // Store clip info state.AimVerticalDuration = settings.JumpAimVerticalClip.Value.Duration; state.AimHorizontalDuration = settings.JumpAimHorizontalClip.Value.Duration; state.JumpDuration = settings.JumpClip.Value.Duration; // Adjust play speed so vertical velocity in animation is matched with character velocity (so feet doesnt penetrate ground) var animJumpVel = settings.jumpHeight / settings.JumpClip.Value.Duration; var characterJumpVel = abilityMovementSettings.jumpAscentHeight / abilityMovementSettings.jumpAscentDuration; if (characterJumpVel > 0f && animJumpVel > 0f) { state.PlaySpeed = characterJumpVel / animJumpVel; } else { GameDebug.LogWarning("Cannot set jump anim speed, values need to be more than 0"); } // Expose input and outputs animSource.outputNode = state.MixerNode; animSource.outputPortID = (OutputPortID)LayerMixerNode.KernelPorts.Output; commands.AddComponent(entity, state); }).Run(); // Handled deleted entities var animationGraphSystem = m_AnimationGraphSystem; Entities .WithoutBurst() // Can be removed once NodeSets are Burst-friendly .WithNone() .ForEach((Entity entity, ref SystemState state) => { Deinitialize(commands, entity, animationGraphSystem, state); }).Run(); commands.Playback(EntityManager); commands.Dispose(); return default; } static void Deinitialize(EntityCommandBuffer cmdBuffer, Entity entity, AnimationGraphSystem animGraphSys, SystemState state) { AnimationGraphHelper.DestroyNode(animGraphSys,state.JumpNode); AnimationGraphHelper.DestroyNode(animGraphSys,state.DeltaTimeNode); AnimationGraphHelper.DestroyNode(animGraphSys,state.AimVerticalNode); AnimationGraphHelper.DestroyNode(animGraphSys,state.AimHorizontalNode); AnimationGraphHelper.DestroyNode(animGraphSys,state.AimVerticalDelta); AnimationGraphHelper.DestroyNode(animGraphSys,state.AimHorizontalDelta); AnimationGraphHelper.DestroyNode(animGraphSys,state.AdditiveRefPoseA); AnimationGraphHelper.DestroyNode(animGraphSys,state.AdditiveRefPoseB); AnimationGraphHelper.DestroyNode(animGraphSys,state.MixerNode); cmdBuffer.RemoveComponent(entity); } } [UpdateInGroup(typeof(AnimSourceUpdateBGroup))] [DisableAutoCreation] [AlwaysSynchronizeSystem] class UpdateSystem : JobComponentSystem { private EntityQuery m_GlobalGameTimeQuery; protected override void OnCreate() { m_GlobalGameTimeQuery = GetEntityQuery(ComponentType.ReadOnly()); } protected override JobHandle OnUpdate(JobHandle inputDeps) { inputDeps.Complete(); // TODO (mogensh) find cleaner way to get time var globalTime = m_GlobalGameTimeQuery.GetSingleton(); var deltaTime = globalTime.frameDuration; var characterInterpolatedDataFromEntity = GetComponentDataFromEntity(false); // Update state Entities .ForEach((Entity entity, ref AnimSource.Data animSource, ref Settings settings, ref SystemState state, ref AnimSource.AllowWrite allowWrite) => { if (!characterInterpolatedDataFromEntity.HasComponent(animSource.animStateEntity)) return; var charInterpolatedState = characterInterpolatedDataFromEntity[animSource.animStateEntity]; if (allowWrite.FirstUpdate) { charInterpolatedState.jumpTime = 0; allowWrite.FirstUpdate = false; } else { charInterpolatedState.jumpTime += state.PlaySpeed * deltaTime; } // Update rotation var hipDragSpeed = settings.HipDragSpeed; var maxHipOffset = settings.MaxHipOffset; // TODO: Make sure to test the behavior of this charInterpolatedState.rotation = MathHelper.SmoothDampAngle(charInterpolatedState.rotation, charInterpolatedState.aimYaw, ref state.CurrentRotationVel, hipDragSpeed * -1 + 1, 1000f, deltaTime); // Clamp the hip offset var delta = MathHelper.DeltaAngle(charInterpolatedState.aimYaw, charInterpolatedState.rotation); if (math.abs(delta) > maxHipOffset) { charInterpolatedState.rotation = charInterpolatedState.aimYaw + math.clamp(delta, -maxHipOffset, maxHipOffset); } characterInterpolatedDataFromEntity[animSource.animStateEntity] = charInterpolatedState; }).Run(); return default; } } [UpdateInGroup(typeof(AnimSourceApplyGroup))] [DisableAutoCreation] [AlwaysSynchronizeSystem] class PrepareGraph : JobComponentSystem { protected override JobHandle OnUpdate(JobHandle inputDeps) { inputDeps.Complete(); var nodeSet = World.GetExistingSystem().Set; var cmdBuffer = new EntityCommandBuffer(Allocator.TempJob); // Handle rig changes Entities .WithNone() .WithoutBurst() // Can be removed once NodeSets are Burst-friendly .ForEach((Entity entity, ref AnimSource.Data animSource, ref Settings settings, ref SystemState state) => { if (!EntityManager.HasComponent(animSource.animStateEntity)) return; var sharedRigDef = EntityManager.GetSharedComponentData(animSource.animStateEntity); var rig = sharedRigDef.Value; var jumpClipInstance = ClipManager.Instance.GetClipFor(rig, settings.JumpClip); var runVerticalAimClipInstance = ClipManager.Instance.GetClipFor(rig, settings.JumpAimVerticalClip); var runAimHorizontalClipInstance = ClipManager.Instance.GetClipFor(rig, settings.JumpAimHorizontalClip); var addRefPoseClipInstance = ClipManager.Instance.GetClipFor(rig, settings.AdditiveRefPose); nodeSet.SendMessage(state.JumpNode, ClipPlayerNode.SimulationPorts.ClipInstance, jumpClipInstance); nodeSet.SendMessage(state.AimVerticalNode, ClipNode.SimulationPorts.ClipInstance, runVerticalAimClipInstance); nodeSet.SendMessage(state.AimHorizontalNode, ClipNode.SimulationPorts.ClipInstance, runAimHorizontalClipInstance); nodeSet.SendMessage(state.AdditiveRefPoseA, ClipNode.SimulationPorts.ClipInstance, addRefPoseClipInstance); nodeSet.SendMessage(state.AdditiveRefPoseB, ClipNode.SimulationPorts.ClipInstance, addRefPoseClipInstance); nodeSet.SendMessage(state.MixerNode, LayerMixerNode.SimulationPorts.RigDefinition, rig); nodeSet.SendMessage(state.JumpNode, ClipPlayerNode.SimulationPorts.Speed, state.PlaySpeed); nodeSet.SendMessage(state.AimVerticalDelta, DeltaNode.SimulationPorts.RigDefinition, rig); nodeSet.SendMessage(state.AimHorizontalDelta, DeltaNode.SimulationPorts.RigDefinition, rig); cmdBuffer.AddComponent(entity); }).Run(); // Apply state Entities .WithoutBurst() // Can be removed once NodeSets are Burst-friendly .ForEach((Entity entity, ref AnimSource.Data animSource, ref Settings settings, ref SystemState state) => { if (!EntityManager.HasComponent(animSource.animStateEntity)) return; var charInterpolatedState = EntityManager.GetComponentData(animSource.animStateEntity); nodeSet.SetData(state.AimVerticalNode, ClipNode.KernelPorts.Time, charInterpolatedState.aimPitch * state.AimVerticalDuration / 180f); var timeFactor = state.AimHorizontalDuration / 180f; var time = (90f + MathHelper.DeltaAngle(charInterpolatedState.aimYaw, charInterpolatedState.rotation)) * timeFactor; time = math.max(0f, time); nodeSet.SetData(state.AimHorizontalNode, ClipNode.KernelPorts.Time, time); // Blend in/out aim for reload nodeSet.SendMessage(state.MixerNode, LayerMixerNode.SimulationPorts.WeightInput2, 1f - charInterpolatedState.blendOutAim * (1f - settings.aimDuringReloadPitch)); nodeSet.SendMessage(state.JumpNode, ClipPlayerNode.SimulationPorts.Time, charInterpolatedState.jumpTime); }).Run(); cmdBuffer.Playback(EntityManager); cmdBuffer.Dispose(); return default; } } }