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

509 行
24 KiB

using System;
using Unity.Animation;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.DataFlowGraph;
using Unity.Sample.Core;
using Unity.Mathematics;
using UnityEngine;
public class AnimSourceStand
{
public struct Settings : IComponentData
{
public BlobAssetReference<Clip> StandClip;
public BlobAssetReference<Clip> TurnLeftClip;
public BlobAssetReference<Clip> TurnRightClip;
public BlobAssetReference<Clip> StandAimLeftClip;
public BlobAssetReference<Clip> StandAimMidClip;
public BlobAssetReference<Clip> StandAimRightClip;
public BlobAssetReference<Clip> AdditiveRefPoseClip;
public float animTurnAngle;
public float aimTurnLocalThreshold;
public float turnSpeed;
public float turnThreshold;
public float turnTransitionSpeed;
public float aimDuringReloadPitch;
public float aimDuringReloadYaw;
}
public struct SystemState : ISystemStateComponentData
{
public static SystemState Default => new SystemState();
public NodeHandle<UberClipNode> Idle;
public NodeHandle<DeltaTimeNode> DeltaTimeNode;
public NodeHandle<TimeCounterNode> TimeCounterNode;
public NodeHandle<ClipNode> TurnL;
public NodeHandle<ClipNode> TurnR;
public NodeHandle<ClipNode> AimLeft;
public NodeHandle<ClipNode> AimMid;
public NodeHandle<ClipNode> AimRight;
public NodeHandle<ClipNode> AdditiveRefPose;
public NodeHandle<DeltaNode> AimLeftDelta;
public NodeHandle<DeltaNode> AimMidDelta;
public NodeHandle<DeltaNode> AimRightDelta;
public NodeHandle<MixerNode> MixerLeft;
public NodeHandle<MixerNode> MixerRight;
public NodeHandle<LayerMixerNode> AimMixer;
public float AimDuration;
public float TurnDuration;
}
[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,"Init Stand entity:{0} state entity:{1}", entity, animSource.animStateEntity);
var state = SystemState.Default;
state.Idle = AnimationGraphHelper.CreateNode<UberClipNode>(animationGraphSystem, "Idle");
state.DeltaTimeNode = AnimationGraphHelper.CreateNode<DeltaTimeNode>(animationGraphSystem, "DeltaTimeNode");
state.TimeCounterNode = AnimationGraphHelper.CreateNode<TimeCounterNode>(animationGraphSystem, "TimeCounterNode");
state.TurnL = AnimationGraphHelper.CreateNode<ClipNode>(animationGraphSystem, "TurnL");
state.TurnR = AnimationGraphHelper.CreateNode<ClipNode>(animationGraphSystem, "TurnR");
state.AimLeft = AnimationGraphHelper.CreateNode<ClipNode>(animationGraphSystem, "AimLeft");
state.AimMid = AnimationGraphHelper.CreateNode<ClipNode>(animationGraphSystem, "AimMid");
state.AimRight = AnimationGraphHelper.CreateNode<ClipNode>(animationGraphSystem, "AimRight");
state.AdditiveRefPose = AnimationGraphHelper.CreateNode<ClipNode>(animationGraphSystem, "AdditiveRefPose");
state.AimLeftDelta = AnimationGraphHelper.CreateNode<DeltaNode>(animationGraphSystem, "AimLeftDelta");
state.AimMidDelta = AnimationGraphHelper.CreateNode<DeltaNode>(animationGraphSystem, "AimMidDelta");
state.AimRightDelta = AnimationGraphHelper.CreateNode<DeltaNode>(animationGraphSystem, "AimRightDelta");
state.MixerLeft = AnimationGraphHelper.CreateNode<MixerNode>(animationGraphSystem, "MixerLeft");
state.MixerRight = AnimationGraphHelper.CreateNode<MixerNode>(animationGraphSystem, "MixerRight");
state.AimMixer = AnimationGraphHelper.CreateNode<LayerMixerNode>(animationGraphSystem, "AimMixer");
nodeSet.SendMessage(state.AimMixer, LayerMixerNode.SimulationPorts.WeightInput0, 1f);
nodeSet.SendMessage(state.AimMixer, LayerMixerNode.SimulationPorts.BlendModeInput1, BlendingMode.Additive);
nodeSet.SendMessage(state.AimMixer, LayerMixerNode.SimulationPorts.BlendModeInput2, BlendingMode.Additive);
nodeSet.SendMessage(state.AimMixer, LayerMixerNode.SimulationPorts.BlendModeInput3, BlendingMode.Additive);
nodeSet.SendMessage(state.Idle, UberClipNode.SimulationPorts.Configuration, new ClipConfiguration { Mask = (int)ClipConfigurationMask.LoopTime });
// TODO: Convert to using cascade mixer or blend space?
nodeSet.Connect(state.DeltaTimeNode, DeltaTimeNode.KernelPorts.DeltaTime, state.TimeCounterNode, TimeCounterNode.KernelPorts.DeltaTime);
nodeSet.Connect(state.TimeCounterNode, TimeCounterNode.KernelPorts.Time, state.Idle, UberClipNode.KernelPorts.Time);
nodeSet.Connect(state.TurnL, ClipNode.KernelPorts.Output, state.MixerLeft , MixerNode.KernelPorts.Input0);
nodeSet.Connect(state.Idle, UberClipNode.KernelPorts.Output, state.MixerLeft , MixerNode.KernelPorts.Input1);
nodeSet.Connect(state.MixerLeft , MixerNode.KernelPorts.Output, state.MixerRight, MixerNode.KernelPorts.Input0);
nodeSet.Connect(state.TurnR, ClipNode.KernelPorts.Output, state.MixerRight, MixerNode.KernelPorts.Input1);
nodeSet.Connect(state.MixerRight, MixerNode.KernelPorts.Output, state.AimMixer, LayerMixerNode.KernelPorts.Input0);
nodeSet.Connect(state.AimLeft, ClipNode.KernelPorts.Output, state.AimLeftDelta, DeltaNode.KernelPorts.Input);
nodeSet.Connect(state.AimMid, ClipNode.KernelPorts.Output, state.AimMidDelta, DeltaNode.KernelPorts.Input);
nodeSet.Connect(state.AimRight, ClipNode.KernelPorts.Output, state.AimRightDelta, DeltaNode.KernelPorts.Input);
nodeSet.Connect(state.AdditiveRefPose, ClipNode.KernelPorts.Output, state.AimLeftDelta, DeltaNode.KernelPorts.Subtract);
nodeSet.Connect(state.AdditiveRefPose, ClipNode.KernelPorts.Output, state.AimMidDelta, DeltaNode.KernelPorts.Subtract);
nodeSet.Connect(state.AdditiveRefPose, ClipNode.KernelPorts.Output, state.AimRightDelta, DeltaNode.KernelPorts.Subtract);
nodeSet.Connect(state.AimLeftDelta, DeltaNode.KernelPorts.Output, state.AimMixer, LayerMixerNode.KernelPorts.Input1);
nodeSet.Connect(state.AimMidDelta, DeltaNode.KernelPorts.Output, state.AimMixer, LayerMixerNode.KernelPorts.Input2);
nodeSet.Connect(state.AimRightDelta, DeltaNode.KernelPorts.Output, state.AimMixer, LayerMixerNode.KernelPorts.Input3);
// TODO: Use pr. clip values (turn direction)?
state.TurnDuration = settings.TurnLeftClip.Value.Duration;
state.AimDuration = settings.StandAimLeftClip.Value.Duration;
// Setup transition data
var numMixerPorts = 3;
for (var i = 0; i < numMixerPorts; i++)
{
var transitionWeights = EntityManager.GetBuffer<SimpleTransition.PortWeights>(entity);
transitionWeights.Add(new SimpleTransition.PortWeights { Value = 1f });
}
// Expose input and outputs
animSource.outputNode = state.AimMixer;
animSource.outputPortID = (OutputPortID)LayerMixerNode.KernelPorts.Output;
commands.AddComponent(entity, state);
}).Run();
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 Stand entity:{0}", entity);
AnimationGraphHelper.DestroyNode(animGraphSys,state.Idle);
AnimationGraphHelper.DestroyNode(animGraphSys,state.DeltaTimeNode);
AnimationGraphHelper.DestroyNode(animGraphSys,state.TimeCounterNode);
AnimationGraphHelper.DestroyNode(animGraphSys,state.TurnL);
AnimationGraphHelper.DestroyNode(animGraphSys,state.TurnR);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AimLeft);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AimMid);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AimRight);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AdditiveRefPose);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AimLeftDelta);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AimMidDelta);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AimRightDelta);
AnimationGraphHelper.DestroyNode(animGraphSys,state.MixerLeft);
AnimationGraphHelper.DestroyNode(animGraphSys,state.MixerRight);
AnimationGraphHelper.DestroyNode(animGraphSys,state.AimMixer);
cmdBuffer.RemoveComponent(entity, typeof(SystemState));
}
}
[UpdateInGroup(typeof(AnimSourceUpdateBGroup))]
[DisableAutoCreation]
[AlwaysSynchronizeSystem]
class UpdateSystem : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
inputDeps.Complete();
// TODO (mogensh) find cleaner way to get time
var globalTime = GetEntityQuery(ComponentType.ReadOnly<GlobalGameTime>()).GetSingleton<GlobalGameTime>();
var deltaTime = globalTime.frameDuration;
var characterInterpolatedDataFromEntity = GetComponentDataFromEntity<Character.InterpolatedData>(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))
{
//GameDebug.LogWarning(World,"AnimSource does not have Character.InterpolatedData components. Has it been deleted?");
return;
}
var charInterpolatedState = characterInterpolatedDataFromEntity[animSource.animStateEntity];
if (allowWrite.FirstUpdate)
{
charInterpolatedState.turnDirection = 0;
charInterpolatedState.rotation = charInterpolatedState.aimYaw;
charInterpolatedState.turnStartAngle = charInterpolatedState.aimYaw;
allowWrite.FirstUpdate = false;
}
var aimYawLocal = MathHelper.DeltaAngle(charInterpolatedState.rotation, charInterpolatedState.aimYaw);
var absAimYawLocal = math.abs(aimYawLocal);
// Non turning update
if (charInterpolatedState.turnDirection == 0)
{
// Test for local yaw angle exeding threshold so we need to turn
if (absAimYawLocal > settings.aimTurnLocalThreshold) // TODO: Document why we need local vs non local turn tolerances?
{
charInterpolatedState.turnStartAngle = charInterpolatedState.rotation;
charInterpolatedState.turnDirection = (short)(aimYawLocal >= 0F ? 1F : -1F);
}
}
else
{
var rotateAngleRemaining = MathHelper.DeltaAngle(charInterpolatedState.rotation,
charInterpolatedState.turnStartAngle) + settings.animTurnAngle * charInterpolatedState.turnDirection;
if (rotateAngleRemaining * charInterpolatedState.turnDirection <= 0)
{
charInterpolatedState.turnDirection = 0;
}
else
{
var turnSpeed = settings.turnSpeed;
if (absAimYawLocal > settings.turnThreshold)
{
var factor = 1.0f - (180 - absAimYawLocal) / settings.turnThreshold;
turnSpeed = turnSpeed + factor * 300;
}
var deltaAngle = deltaTime * turnSpeed;
var absAngleRemaining = math.abs(rotateAngleRemaining);
if (deltaAngle > absAngleRemaining)
{
deltaAngle = absAngleRemaining;
}
// TODO: Is regular sign that returns 0 for value 0 ok here?
var sign = (rotateAngleRemaining >= 0F ? 1F : -1F);
charInterpolatedState.rotation += sign * deltaAngle;
while (charInterpolatedState.rotation > 360.0f)
charInterpolatedState.rotation -= 360.0f;
while (charInterpolatedState.rotation < 0.0f)
charInterpolatedState.rotation += 360.0f;
}
}
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 globalTime = GetEntityQuery(ComponentType.ReadOnly<GlobalGameTime>()).GetSingleton<GlobalGameTime>();
var deltaTime = globalTime.frameDuration;
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 standClipInstance = ClipManager.Instance.GetClipFor(rig, settings.StandClip);
var turnLeftClipInstance = ClipManager.Instance.GetClipFor(rig, settings.TurnLeftClip);
var turnRightClipInstance = ClipManager.Instance.GetClipFor(rig, settings.TurnRightClip);
var standAimLeftClipInstance = ClipManager.Instance.GetClipFor(rig, settings.StandAimLeftClip);
var standAimMidClipInstance = ClipManager.Instance.GetClipFor(rig, settings.StandAimMidClip);
var standAimRightClipInstance = ClipManager.Instance.GetClipFor(rig, settings.StandAimRightClip);
var addRefPoseClipInstance = ClipManager.Instance.GetClipFor(rig, settings.AdditiveRefPoseClip);
nodeSet.SendMessage(state.Idle, UberClipNode.SimulationPorts.ClipInstance, standClipInstance);
nodeSet.SendMessage(state.TurnL, ClipNode.SimulationPorts.ClipInstance, turnLeftClipInstance);
nodeSet.SendMessage(state.TurnR, ClipNode.SimulationPorts.ClipInstance, turnRightClipInstance);
nodeSet.SendMessage(state.AimLeft, ClipNode.SimulationPorts.ClipInstance, standAimLeftClipInstance);
nodeSet.SendMessage(state.AimMid, ClipNode.SimulationPorts.ClipInstance, standAimMidClipInstance);
nodeSet.SendMessage(state.AimRight, ClipNode.SimulationPorts.ClipInstance, standAimRightClipInstance);
nodeSet.SendMessage(state.AdditiveRefPose, ClipNode.SimulationPorts.ClipInstance, addRefPoseClipInstance);
nodeSet.SendMessage(state.AimLeftDelta, DeltaNode.SimulationPorts.RigDefinition, rig);
nodeSet.SendMessage(state.AimMidDelta, DeltaNode.SimulationPorts.RigDefinition, rig);
nodeSet.SendMessage(state.AimRightDelta, DeltaNode.SimulationPorts.RigDefinition, rig);
nodeSet.SendMessage(state.MixerLeft, MixerNode.SimulationPorts.RigDefinition, rig);
nodeSet.SendMessage(state.MixerRight, MixerNode.SimulationPorts.RigDefinition, rig);
nodeSet.SendMessage(state.AimMixer, LayerMixerNode.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);
// Handle turning
float rotateAngleRemaining = 0f;
if (charInterpolatedState.turnDirection != 0)
rotateAngleRemaining = MathHelper.DeltaAngle(charInterpolatedState.rotation,
charInterpolatedState.turnStartAngle) + settings.animTurnAngle * charInterpolatedState.turnDirection;
var portWeights = EntityManager.GetBuffer<SimpleTransition.PortWeights>(entity).AsNativeArray();
if (charInterpolatedState.turnDirection == 0)
{
SimpleTransition.Update((int)LocoMixerPort.Idle, settings.turnTransitionSpeed, deltaTime, ref portWeights);
}
else
{
var fraction = 1f - math.abs(rotateAngleRemaining / settings.animTurnAngle);
var anim = (charInterpolatedState.turnDirection == -1) ? state.TurnL : state.TurnR;
var mixerPort = (charInterpolatedState.turnDirection == -1) ? (int)LocoMixerPort.TurnL : (int)LocoMixerPort.TurnR;
SimpleTransition.Update(mixerPort, settings.turnTransitionSpeed, deltaTime, ref portWeights);
nodeSet.SetData(anim, ClipNode.KernelPorts.Time, state.TurnDuration * fraction);
// Reset the time of the idle, so it's reset when we transition back
if (portWeights[(int)LocoMixerPort.Idle].Value < 0.01f)
nodeSet.SendMessage(state.TimeCounterNode, TimeCounterNode.SimulationPorts.Time, 0f);
}
var idleWeight = portWeights[(int)LocoMixerPort.Idle].Value;
nodeSet.SendMessage(state.MixerLeft, MixerNode.SimulationPorts.Blend, idleWeight);
var turnRightWeight = portWeights[(int)LocoMixerPort.TurnR].Value;
nodeSet.SendMessage(state.MixerRight, MixerNode.SimulationPorts.Blend, turnRightWeight);
// Handle Aim
var aimMultiplier = 1f - charInterpolatedState.blendOutAim;
float aimPitchFraction = charInterpolatedState.aimPitch / 180.0f;
// var aimPitchMult = Mathf.Lerp(m_template.blendOutAimOnReloadPitch, 1f, aimMultiplier);
var aimPitchMult = math.lerp(settings.aimDuringReloadPitch, 1f, aimMultiplier);
aimPitchFraction = math.lerp(0.5f, aimPitchFraction, aimPitchMult);
float aimTime = aimPitchFraction * state.AimDuration;
nodeSet.SetData(state.AimLeft, ClipNode.KernelPorts.Time, aimTime);
nodeSet.SetData(state.AimMid, ClipNode.KernelPorts.Time, aimTime);
nodeSet.SetData(state.AimRight, ClipNode.KernelPorts.Time, aimTime);
float aimYawLocal = MathHelper.DeltaAngle(charInterpolatedState.rotation, charInterpolatedState.aimYaw);
// float aimYawFraction = Mathf.Abs(aimYawLocal / settings.aimYawAngle);
float aimYawFraction = math.abs(aimYawLocal / 90f);
var aimYawMult = math.lerp(settings.aimDuringReloadYaw, 1f, aimMultiplier);
aimYawFraction = math.lerp(0.0f, aimYawFraction, aimYawMult);
nodeSet.SendMessage(state.AimMixer, LayerMixerNode.SimulationPorts.WeightInput2, 1.0f - aimYawFraction);
if (aimYawLocal < 0)
{
nodeSet.SendMessage(state.AimMixer, LayerMixerNode.SimulationPorts.WeightInput1, aimYawFraction);
nodeSet.SendMessage(state.AimMixer, LayerMixerNode.SimulationPorts.WeightInput3, 0.0f);
}
else
{
nodeSet.SendMessage(state.AimMixer, LayerMixerNode.SimulationPorts.WeightInput1, 0.0f);
nodeSet.SendMessage(state.AimMixer, LayerMixerNode.SimulationPorts.WeightInput3, aimYawFraction);
}
}).Run();
cmdBuffer.Playback(EntityManager);
cmdBuffer.Dispose();
return default;
}
}
public struct SimpleTransition
{
public struct PortWeights : IBufferElementData
{
public float Value;
}
public static void Update(int activePort, float blendVelocity, float deltaTime, ref NativeArray<PortWeights> portWeights)
{
// Update current state weight
float weight = portWeights[activePort].Value;
if (weight != 1.0f)
{
weight = math.clamp(weight + blendVelocity * deltaTime, 0, 1);
portWeights[activePort] = new PortWeights { Value = weight };
}
// Adjust weight of other states and ensure total weight is 1
float weightLeft = 1.0f - weight;
float totalWeight = 0;
for (var i = 0; i < portWeights.Length; i++)
{
if (i == activePort)
continue;
totalWeight += portWeights[i].Value;
}
if (totalWeight == 0)
return;
float fraction = weightLeft / totalWeight;
for (int i = 0; i < portWeights.Length; i++)
{
if (i == activePort)
continue;
float w = portWeights[i].Value;
w = w * fraction;
portWeights[i] = new PortWeights { Value = w };
}
}
}
enum LocoMixerPort
{
TurnL,
Idle,
TurnR,
Count
}
}