该项目的目的是同时测试和演示来自 Unity DOTS 技术堆栈的多个新包。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

321 行
15 KiB

using System;
using Unity.Animation;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.DataFlowGraph;
using Unity.Mathematics;
using Unity.Sample.Core;
using UnityEngine;
public class AnimSourceSprint
{
[Serializable]
public struct Settings : IComponentData
{
public BlobAssetReference<BlendTree1D> LocoBlendTreeAsset;
public BlobAssetReference<Clip> animAimDownToUp;
public BlobAssetReference<Clip> additiveRefPose;
public float changeDirSpeed;
public float stateResetWindow;
}
public struct SystemState : ISystemStateComponentData
{
public static SystemState Default => new SystemState();
public float PlaySpeed;
public float RunClipDuration;
public float AimClipDuration;
public NodeHandle<ClipNode> AdditiveRefPose;
public NodeHandle<BlendTree1DNode> BlendNode;
public NodeHandle<ClipNode> AimClipNode;
public NodeHandle<LayerMixerNode> AimMixerNode;
public NodeHandle<DeltaNode> AimDeltaNode;
}
[UpdateInGroup(typeof(AnimSourceInitializationGroup))]
[DisableAutoCreation]
[AlwaysSynchronizeSystem]
class InitSystem : JobComponentSystem
{
AnimationGraphSystem m_AnimationGraphSystem;
protected override void OnCreate()
{
base.OnCreate();
m_AnimationGraphSystem = World.GetExistingSystem<AnimationGraphSystem>();
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(World, 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 animationGraphSystem = m_AnimationGraphSystem;
// Handle created entities
Entities
.WithoutBurst() // Can be removed once NodeSets are Burst-friendly
.WithNone<SystemState>()
.ForEach((Entity entity, ref AnimSource.Data animSource, ref Settings settings) =>
{
GameDebug.Log(World,AnimSource.ShowLifetime,"InitSystem Sprint entity:{0} state entity:{1}", entity, animSource.animStateEntity);
settings.LocoBlendTreeAsset = BlendTreeEntityStoreHelper.CreateBlendTree1DFromComponents(EntityManager, entity);
var state = SystemState.Default;
state.BlendNode = AnimationGraphHelper.CreateNode<BlendTree1DNode>(animationGraphSystem, "BlendNode");
state.AimClipNode = AnimationGraphHelper.CreateNode<ClipNode>(animationGraphSystem, "AimClipNode");
state.AimDeltaNode = AnimationGraphHelper.CreateNode<DeltaNode>(animationGraphSystem, "AimDeltaNode");
state.AdditiveRefPose = AnimationGraphHelper.CreateNode<ClipNode>(animationGraphSystem, "AdditiveRefPose");
state.AimMixerNode = AnimationGraphHelper.CreateNode<LayerMixerNode>(animationGraphSystem, "AimMixerNode");
nodeSet.Connect(state.BlendNode, BlendTree1DNode.KernelPorts.Output, state.AimMixerNode, LayerMixerNode.KernelPorts.Input0);
nodeSet.Connect(state.AimClipNode, ClipNode.KernelPorts.Output, state.AimDeltaNode, DeltaNode.KernelPorts.Input);
nodeSet.Connect(state.AdditiveRefPose, ClipNode.KernelPorts.Output, state.AimDeltaNode, DeltaNode.KernelPorts.Subtract);
nodeSet.Connect(state.AimDeltaNode, DeltaNode.KernelPorts.Output, state.AimMixerNode, LayerMixerNode.KernelPorts.Input1);
nodeSet.SendMessage(state.AimMixerNode, LayerMixerNode.SimulationPorts.WeightInput0, 1f);
nodeSet.SendMessage(state.AimMixerNode, LayerMixerNode.SimulationPorts.WeightInput1, 1f);
nodeSet.SendMessage(state.AimMixerNode, LayerMixerNode.SimulationPorts.BlendModeInput0, BlendingMode.Override);
nodeSet.SendMessage(state.AimMixerNode, LayerMixerNode.SimulationPorts.BlendModeInput1, BlendingMode.Additive);
// Load clips and store clip info
state.AimClipDuration = settings.animAimDownToUp.Value.Duration;
// All clips are the same length so we can use a static clip duration
state.RunClipDuration = settings.LocoBlendTreeAsset.Value.Motions[0].Clip.Value.Duration;
// Expose input and outputs
animSource.outputNode = state.AimMixerNode;
animSource.outputPortID = (OutputPortID)LayerMixerNode.KernelPorts.Output;
commands.AddComponent(entity, state);
}).Run();
// Handled deleted entities
Entities
.WithoutBurst() // Can be removed once NodeSets are Burst-friendly
.WithNone<Settings>()
.ForEach((Entity entity, ref SystemState state) =>
{
Deinitialize(World, commands, entity, animationGraphSystem, state);
}).Run();
commands.Playback(EntityManager);
commands.Dispose();
return default;
}
static void Deinitialize(World world,EntityCommandBuffer cmdBuffer, Entity entity, AnimationGraphSystem animGraphSys, SystemState state)
{
GameDebug.Log(world,AnimSource.ShowLifetime,"Deinit Sprint entity:{0}", entity);
AnimationGraphHelper.DestroyNode(animGraphSys,state.BlendNode);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AimClipNode);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AimMixerNode);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AimDeltaNode);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AdditiveRefPose);
cmdBuffer.RemoveComponent<SystemState>(entity);
}
}
[UpdateInGroup(typeof(AnimSourceUpdateBGroup))]
[DisableAutoCreation]
[AlwaysSynchronizeSystem]
class UpdateSystem : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
inputDeps.Complete();
var characterInterpolatedDataFromEntity = GetComponentDataFromEntity<Character.InterpolatedData>(false);
var abilityInterpolatedStateFromEntity = GetComponentDataFromEntity<AbilityMovement.InterpolatedState>(true);
var ownedAbilityBufferFromEntity = GetBufferFromEntity<AbilityOwner.OwnedAbility>(true);
// TODO (mogensh) find cleaner way to get time
var globalTime = GetEntityQuery(ComponentType.ReadOnly<GlobalGameTime>()).GetSingleton<GlobalGameTime>();
var time = globalTime.gameTime;
var deltaTime = globalTime.frameDuration;
// 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];
var abilityMovementEntity = Ability.FindAbility(ownedAbilityBufferFromEntity, animSource.animStateEntity, AbilityMovement.Tag);
if (abilityMovementEntity == Entity.Null)
return;
var abilityMovement = abilityInterpolatedStateFromEntity[abilityMovementEntity];
if (allowWrite.FirstUpdate)
{
// Do phase projection for time not spent in state
var ticksSincePreviousGroundMove = time.tick - charInterpolatedState.lastGroundMoveTick;
if (ticksSincePreviousGroundMove > 1)
{
// TODO: This relies on the state entity being persistent. If it is not, find another way
charInterpolatedState.locomotionPhase += state.PlaySpeed * (ticksSincePreviousGroundMove - 1f);
}
// Reset the phase if appropriate
var timeSincePreviousGroundMove = ticksSincePreviousGroundMove / (float)time.tickRate;
if (abilityMovement.previousCharLocoState != AbilityMovement.LocoState.GroundMove &&
timeSincePreviousGroundMove > settings.stateResetWindow)
{
// Debug.Log("Reset movement sprint! (Ticks since: " + ticksSincePreviousGroundMove + " Time since: " + timeSincePreviousGroundMove + ")");
charInterpolatedState.locomotionPhase = 0f;
}
allowWrite.FirstUpdate = false;
}
charInterpolatedState.lastGroundMoveTick = time.tick;
charInterpolatedState.rotation = charInterpolatedState.aimYaw;
var moveAngleLocal = MathHelper.DeltaAngle(charInterpolatedState.rotation, charInterpolatedState.moveYaw);
// Damp local move angle
var speed = settings.changeDirSpeed * 1000f;
var deltaAngle = deltaTime * speed;
var diff = math.abs(MathHelper.DeltaAngle(charInterpolatedState.moveAngleLocal, moveAngleLocal));
var t = deltaAngle >= diff ? 1.0f : deltaAngle / diff;
var dampedAngle = MathHelper.LerpAngle(charInterpolatedState.moveAngleLocal + 180, moveAngleLocal + 180, t);
while (dampedAngle > 360) dampedAngle -= 360;
while (dampedAngle < 0) dampedAngle += 360;
charInterpolatedState.moveAngleLocal = dampedAngle - 180;
/*
// If the clip duration of the blend space is variable, you would need something like this. Since
bursting with node sets is currently not possible, you should defer message sending till out of the job.
var blendParameter = new Parameter
{
Id = settings.LocoBlendTreeAsset.Value.BlendParameter,
Value = charInterpolatedState.moveAngleLocal
};
nodeSet.SendMessage(state.BlendNode, BlendTree1DNode.SimulationPorts.Parameter, blendParameter);
var f = nodeSet.GetFunctionality(state.BlendNode);
var duration = f.GetDuration(state.BlendNode);
*/
// Update phase
state.PlaySpeed = 1f / state.RunClipDuration * deltaTime;
charInterpolatedState.locomotionPhase += state.PlaySpeed;
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<AnimationGraphSystem>().Set;
var cmdBuffer = new EntityCommandBuffer(Allocator.TempJob);
// Handle rig changes
Entities
.WithNone<AnimSource.HasValidRig>()
.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<SharedRigDefinition>(animSource.animStateEntity))
return;
var sharedRigDef = EntityManager.GetSharedComponentData<SharedRigDefinition>(animSource.animStateEntity);
var rig = sharedRigDef.Value;
var aimClipInstance = ClipManager.Instance.GetClipFor(rig, settings.animAimDownToUp);
var addRefPoseClipInstance = ClipManager.Instance.GetClipFor(rig, settings.additiveRefPose);
nodeSet.SendMessage(state.AimClipNode, ClipNode.SimulationPorts.ClipInstance, aimClipInstance);
nodeSet.SendMessage(state.AdditiveRefPose, ClipNode.SimulationPorts.ClipInstance, addRefPoseClipInstance);
nodeSet.SendMessage(state.BlendNode, BlendTree1DNode.SimulationPorts.RigDefinition, rig);
nodeSet.SendMessage(state.BlendNode, BlendTree1DNode.SimulationPorts.BlendTree, settings.LocoBlendTreeAsset);
nodeSet.SendMessage(state.AimMixerNode, LayerMixerNode.SimulationPorts.RigDefinition, rig);
nodeSet.SendMessage(state.AimDeltaNode, DeltaNode.SimulationPorts.RigDefinition, rig);
cmdBuffer.AddComponent<AnimSource.HasValidRig>(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<Character.InterpolatedData>(animSource.animStateEntity))
return;
var charInterpolatedState = EntityManager.GetComponentData<Character.InterpolatedData>(animSource.animStateEntity);
// Blend the animations based on move direction
var blendParameter = new Parameter {
Id = settings.LocoBlendTreeAsset.Value.BlendParameter,
Value = charInterpolatedState.moveAngleLocal
};
nodeSet.SendMessage(state.BlendNode, BlendTree1DNode.SimulationPorts.Parameter, blendParameter);
// Set the phase of the animation
// TODO: Remove fmod once/if blendtree uses loopable clip node
var phase = math.fmod(charInterpolatedState.locomotionPhase, 1.0f);
nodeSet.SetData(state.BlendNode, BlendTree1DNode.KernelPorts.NormalizedTime, phase);
nodeSet.SetData(state.AimClipNode, ClipNode.KernelPorts.Time,
charInterpolatedState.aimPitch * state.AimClipDuration / 180f);
}).Run();
cmdBuffer.Playback(EntityManager);
cmdBuffer.Dispose();
return default;
}
}
}