您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
685 行
28 KiB
685 行
28 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.Entities;
|
|
using UnityEngine;
|
|
using UnityEngine.Animations;
|
|
using UnityEngine.Experimental.Animations;
|
|
using UnityEngine.Playables;
|
|
using UnityEngine.Profiling;
|
|
using Debug = UnityEngine.Debug;
|
|
|
|
[CreateAssetMenu(fileName = "Stand", menuName = "FPS Sample/Animation/AnimGraph/Stand")]
|
|
public class AnimGraph_Stand : AnimGraphAsset
|
|
{
|
|
[ConfigVar(Name = "char.standik", DefaultValue = "1", Description = "Enable stand foot ik")]
|
|
public static ConfigVar useFootIk;
|
|
|
|
[ConfigVar(Name = "debug.char.standik", DefaultValue = "0", Description = "Debug foot ik raycast")]
|
|
public static ConfigVar debugStandIk;
|
|
|
|
public AnimationClip animIdle;
|
|
public AnimationClip animTurnL;
|
|
public AnimationClip animTurnR;
|
|
public AnimationClip animAimLeft;
|
|
public AnimationClip animAimMid;
|
|
public AnimationClip animAimRight;
|
|
public AnimationClip animShootPose;
|
|
|
|
public float animTurnAngle = 90.0f; // Total turn in turn anim
|
|
public float aimTurnLocalThreshold = 90; // Turn threshold
|
|
public float aimYawAngle = 180; // Total yaw in aim animation
|
|
public float turnSpeed = 250;
|
|
public float turnThreshold = 100;
|
|
public float turnTransitionSpeed = 7.5f;
|
|
|
|
[Range(0, 2)]
|
|
public float shootPoseMagnitude = 1.0f;
|
|
[Range(0f, 10f)]
|
|
public float shootPoseEnterSpeed = 5f;
|
|
[Range(0f, 10f)]
|
|
public float shootPoseExitSpeed = 5f;
|
|
public AnimationCurve shootPoseEnter;
|
|
public AnimationCurve shootPoseExit;
|
|
|
|
[Range(0, 1)]
|
|
public float blendOutAimOnReloadPitch = 0.5f;
|
|
[Range(0, 1)]
|
|
public float blendOutAimOnReloadYaw = 0.5f;
|
|
public AnimationCurve blendOutAimOnReload;
|
|
|
|
[Space(10)]
|
|
public FootIkJob.JobSettings footIK;
|
|
public string leftToeBone;
|
|
public string rightToeBone;
|
|
|
|
[Space(10)]
|
|
public ActionAnimationDefinition[] actionAnimations;
|
|
|
|
public override IAnimGraphInstance Instatiate(EntityManager entityManager, Entity owner, PlayableGraph graph)
|
|
{
|
|
var animState = new CharacterAnimGraph_3PStand(entityManager, owner, graph, this);
|
|
return animState;
|
|
}
|
|
|
|
class CharacterAnimGraph_3PStand : IAnimGraphInstance, IGraphState
|
|
{
|
|
public CharacterAnimGraph_3PStand(EntityManager entityManager, Entity owner, PlayableGraph graph, AnimGraph_Stand template)
|
|
{
|
|
if (s_Instances == null)
|
|
{
|
|
s_Instances = new List<CharacterAnimGraph_3PStand>(16);
|
|
}
|
|
|
|
s_Instances.Add(this);
|
|
|
|
|
|
m_template = template;
|
|
m_EntityManager = entityManager;
|
|
m_Owner = owner;
|
|
|
|
GameDebug.Assert(entityManager.HasComponent<Animator>(owner), "Owner has no Animator component");
|
|
var animator = entityManager.GetComponentObject<Animator>(owner);
|
|
|
|
GameDebug.Assert(entityManager.HasComponent<Skeleton>(owner), "Owner has no Skeleton component");
|
|
var skeleton = entityManager.GetComponentObject<Skeleton>(owner);
|
|
|
|
GameDebug.Assert(entityManager.HasComponent<CharacterPredictedState>(owner), "Owner has no Character component");
|
|
m_character = entityManager.GetComponentObject<CharacterPredictedState>(owner);
|
|
|
|
var leftToes = skeleton.bones[skeleton.GetBoneIndex(template.leftToeBone.GetHashCode())];
|
|
var rightToes = skeleton.bones[skeleton.GetBoneIndex(template.rightToeBone.GetHashCode())];
|
|
|
|
// Locomotion mixer and loco animation
|
|
m_locomotionMixer = AnimationMixerPlayable.Create(graph, (int)LocoMixerPort.Count);
|
|
|
|
// Idle
|
|
m_animIdle = AnimationClipPlayable.Create(graph, template.animIdle);
|
|
m_animIdle.SetApplyFootIK(true);
|
|
graph.Connect(m_animIdle, 0, m_locomotionMixer, (int)LocoMixerPort.Idle);
|
|
m_locomotionMixer.SetInputWeight((int)LocoMixerPort.Idle, 1.0f);
|
|
|
|
// Turns and trasitions
|
|
m_animTurnL = CreateTurnAnim(graph, template.animTurnL, LocoMixerPort.TurnL);
|
|
m_animTurnR = CreateTurnAnim(graph, template.animTurnR, LocoMixerPort.TurnR);
|
|
|
|
var ports = new int[] { (int)LocoMixerPort.Idle, (int)LocoMixerPort.TurnL, (int)LocoMixerPort.TurnR };
|
|
m_Transition = new SimpleTranstion<AnimationMixerPlayable>(m_locomotionMixer, ports);
|
|
|
|
// Foot IK
|
|
if (m_template.animTurnL.events.Length != 0)
|
|
{
|
|
m_LeftTurnFootFalls = ExtractFootFalls(m_template.animTurnL);
|
|
m_RightTurnFootFalls = ExtractFootFalls(m_template.animTurnR);
|
|
}
|
|
|
|
var ikJob = new FootIkJob
|
|
{
|
|
settings = m_template.footIK,
|
|
leftToe = animator.BindStreamTransform(leftToes),
|
|
rightToe = animator.BindStreamTransform(rightToes)
|
|
};
|
|
|
|
m_footIk = AnimationScriptPlayable.Create(graph, ikJob, 1);
|
|
graph.Connect(m_locomotionMixer, 0, m_footIk, 0);
|
|
m_footIk.SetInputWeight(0, 1f);
|
|
|
|
m_defaultLayer = LayerMask.NameToLayer("Default");
|
|
m_playerLayer = LayerMask.NameToLayer("collision_player");
|
|
m_platformLayer = LayerMask.NameToLayer("Platform");
|
|
|
|
m_mask = 1 << m_defaultLayer | 1 << m_playerLayer | 1 << m_platformLayer;
|
|
|
|
// Aim and Aim mixer
|
|
m_aimMixer = AnimationMixerPlayable.Create(graph, (int)AimMixerPort.Count, true);
|
|
|
|
m_animAimLeft = CreateAimAnim(graph, template.animAimLeft, AimMixerPort.AimLeft);
|
|
m_animAimMid = CreateAimAnim(graph, template.animAimMid, AimMixerPort.AimMid);
|
|
m_animAimRight = CreateAimAnim(graph, template.animAimRight, AimMixerPort.AimRight);
|
|
|
|
// Setup other additive mixer
|
|
m_additiveMixer = AnimationLayerMixerPlayable.Create(graph);
|
|
|
|
var locoMixerPort = m_additiveMixer.AddInput(m_footIk, 0);
|
|
m_additiveMixer.SetInputWeight(locoMixerPort, 1);
|
|
|
|
var aimMixerPort = m_additiveMixer.AddInput(m_aimMixer, 0);
|
|
m_additiveMixer.SetInputWeight(aimMixerPort, 1);
|
|
m_additiveMixer.SetLayerAdditive((uint)aimMixerPort, true);
|
|
|
|
// Actions
|
|
m_actionAnimationHandler = new ActionAnimationHandler(m_additiveMixer, template.actionAnimations);
|
|
|
|
m_ReloadActionAnimation = m_actionAnimationHandler.GetActionAnimation(CharacterPredictedState.StateData.Action.Reloading);
|
|
|
|
// Shoot pose
|
|
m_animShootPose = AnimationClipPlayable.Create(graph, template.animShootPose);
|
|
m_animShootPose.SetApplyFootIK(false);
|
|
m_animShootPose.SetDuration(template.animShootPose.length);
|
|
m_animShootPose.Pause();
|
|
m_ShootPosePort = m_additiveMixer.AddInput(m_animShootPose, 0);
|
|
m_additiveMixer.SetInputWeight(m_ShootPosePort, 0.0f);
|
|
m_additiveMixer.SetLayerAdditive((uint)m_ShootPosePort, true);
|
|
}
|
|
|
|
public void Shutdown()
|
|
{
|
|
s_Instances.Remove(this);
|
|
}
|
|
|
|
public void SetPlayableInput(int index, Playable playable, int playablePort) { }
|
|
|
|
public void GetPlayableOutput(int index, ref Playable playable, ref int playablePort)
|
|
{
|
|
playable = m_additiveMixer;
|
|
playablePort = 0;
|
|
}
|
|
|
|
public void UpdatePresentationState(bool firstUpdate, GameTime time, float deltaTime)
|
|
{
|
|
var animState = m_EntityManager.GetComponentData<CharAnimState>(m_Owner);
|
|
Profiler.BeginSample("3PMove8Dir.UpdatePresentationState");
|
|
|
|
if (firstUpdate)
|
|
{
|
|
animState.turnDirection = 0;
|
|
animState.turnStartAngle = animState.rotation;
|
|
}
|
|
|
|
var aimYawLocal = Mathf.DeltaAngle(animState.rotation, animState.aimYaw);
|
|
var absAimYawLocal = Mathf.Abs(aimYawLocal);
|
|
|
|
// Non turning update
|
|
if (animState.turnDirection == 0)
|
|
{
|
|
// Test for local yaw angle exeding threshold so we need to turn
|
|
if (absAimYawLocal > m_template.aimTurnLocalThreshold) // TODO: (sunek) Document why we need local vs non local turn tolerances?
|
|
{
|
|
var sign = Mathf.Sign(aimYawLocal);
|
|
animState.turnStartAngle = animState.rotation;
|
|
animState.turnDirection = (short)sign;
|
|
}
|
|
}
|
|
|
|
// Turning update
|
|
float absAngleRemaining = 0f;
|
|
|
|
if (animState.turnDirection != 0)
|
|
{
|
|
var rotateAngleRemaining = Mathf.DeltaAngle(animState.rotation, animState.turnStartAngle) + m_template.animTurnAngle * animState.turnDirection;
|
|
|
|
if (rotateAngleRemaining * animState.turnDirection <= 0)
|
|
{
|
|
animState.turnDirection = 0;
|
|
}
|
|
else
|
|
{
|
|
var turnSpeed = m_template.turnSpeed;
|
|
if (absAimYawLocal > m_template.turnThreshold)
|
|
{
|
|
var factor = 1.0f - (180 - absAimYawLocal) / m_template.turnThreshold;
|
|
turnSpeed = turnSpeed + factor * 300;
|
|
}
|
|
|
|
var deltaAngle = deltaTime * turnSpeed;
|
|
absAngleRemaining = Mathf.Abs(rotateAngleRemaining);
|
|
if (deltaAngle > absAngleRemaining)
|
|
{
|
|
deltaAngle = absAngleRemaining;
|
|
}
|
|
|
|
var sign = Mathf.Sign(rotateAngleRemaining);
|
|
|
|
animState.rotation += sign * deltaAngle;
|
|
while (animState.rotation > 360.0f)
|
|
animState.rotation -= 360.0f;
|
|
while (animState.rotation < 0.0f)
|
|
animState.rotation += 360.0f;
|
|
}
|
|
}
|
|
|
|
// Shoot pose update
|
|
if (animState.charAction == CharacterPredictedState.StateData.Action.PrimaryFire)
|
|
{
|
|
animState.shootPoseWeight += m_template.shootPoseEnterSpeed * deltaTime;
|
|
}
|
|
else
|
|
{
|
|
animState.shootPoseWeight -= m_template.shootPoseExitSpeed * deltaTime;
|
|
}
|
|
|
|
animState.shootPoseWeight = Mathf.Clamp01(animState.shootPoseWeight);
|
|
|
|
// Foot IK update
|
|
var footIkJob = m_footIk.GetJobData<FootIkJob>();
|
|
|
|
if (m_template.footIK.enabled && useFootIk.IntValue > 0)
|
|
{
|
|
// Figure out stand state
|
|
if (m_character.State.velocity.magnitude > 0.001f)
|
|
m_StandState = StandState.Moving;
|
|
else if (animState.turnDirection != 0 && m_StandState != StandState.TurnStart && m_StandState != StandState.Turning)
|
|
m_StandState = StandState.TurnStart;
|
|
else if (animState.turnDirection != 0)
|
|
m_StandState = StandState.Turning;
|
|
else if (animState.turnDirection == 0 && m_StandState == StandState.Turning)
|
|
m_StandState = StandState.TurnEnd;
|
|
else
|
|
m_StandState = StandState.Standing;
|
|
|
|
// Update foot position
|
|
if (m_StandState == StandState.Moving || firstUpdate)
|
|
{
|
|
var rotation = Quaternion.Euler(0f, animState.rotation, 0f);
|
|
m_LeftFootPos = rotation * m_template.footIK.leftToeStandPos + animState.position;
|
|
m_RightFootPos = rotation * m_template.footIK.rightToeStandPos + animState.position;
|
|
}
|
|
else if (m_StandState == StandState.TurnStart)
|
|
{
|
|
// Predict foot placement after turn
|
|
var predictedRotation = Quaternion.Euler(0f, animState.turnStartAngle + m_template.animTurnAngle * animState.turnDirection, 0f);
|
|
m_LeftFootPos = predictedRotation * m_template.footIK.leftToeStandPos + animState.position;
|
|
m_RightFootPos = predictedRotation * m_template.footIK.rightToeStandPos + animState.position;
|
|
}
|
|
|
|
// Do raycasts
|
|
var rayEmitOffset = Vector3.up * m_template.footIK.emitRayOffset;
|
|
if (m_StandState == StandState.Moving || m_StandState == StandState.TurnStart)
|
|
{
|
|
var maxRayDistance = m_template.footIK.emitRayOffset + m_template.footIK.maxRayDistance;
|
|
m_LeftHitSuccess = Physics.Raycast(m_LeftFootPos + rayEmitOffset, Vector3.down, out m_LeftHit, maxRayDistance, m_mask);
|
|
m_RightHitSuccess = Physics.Raycast(m_RightFootPos + rayEmitOffset, Vector3.down, out m_RightHit, maxRayDistance, m_mask);
|
|
}
|
|
|
|
// Update foot offsets
|
|
if (firstUpdate)
|
|
{
|
|
footIkJob.ikWeight = 0.0f;
|
|
}
|
|
|
|
if (m_StandState == StandState.Moving || m_StandState == StandState.TurnEnd)
|
|
{
|
|
animState.footIkOffset = GetClampedOffset();
|
|
animState.footIkNormalLeft = m_LeftHit.normal;
|
|
animState.footIkNormaRight = m_RightHit.normal;
|
|
|
|
m_TurnStartOffset.x = animState.footIkOffset.x;
|
|
m_TurnStartOffset.y = animState.footIkOffset.y;
|
|
m_TurnStartNormals[0] = m_LeftHit.normal;
|
|
m_TurnStartNormals[1] = m_RightHit.normal;
|
|
}
|
|
|
|
else if (m_StandState == StandState.TurnStart)
|
|
{
|
|
m_TurnEndOffset = GetClampedOffset();
|
|
m_TurnEndNormals[0] = m_LeftHit.normal;
|
|
m_TurnEndNormals[1] = m_RightHit.normal;
|
|
}
|
|
|
|
if (m_StandState == StandState.TurnStart || m_StandState == StandState.Turning)
|
|
{
|
|
var turnFraction = (-absAngleRemaining + m_template.animTurnAngle) / m_template.animTurnAngle;
|
|
var footFalls = animState.turnDirection == -1 ? m_LeftTurnFootFalls : m_RightTurnFootFalls;
|
|
|
|
var leftFootFraction = GetFootFraction(turnFraction, footFalls.leftFootUp, footFalls.leftFootDown);
|
|
animState.footIkOffset.x = Mathf.Lerp(m_TurnStartOffset.x, m_TurnEndOffset.x, leftFootFraction);
|
|
animState.footIkNormalLeft = Vector3.Lerp(m_TurnStartNormals[0], m_TurnEndNormals[0], leftFootFraction);
|
|
|
|
var rightFootFraction = GetFootFraction(turnFraction, footFalls.rightFootUp, footFalls.rightFootDown);
|
|
animState.footIkOffset.y = Mathf.Lerp(m_TurnStartOffset.y, m_TurnEndOffset.y, rightFootFraction);
|
|
animState.footIkNormaRight = Vector3.Lerp(m_TurnStartNormals[1], m_TurnEndNormals[1], rightFootFraction);
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
footIkJob.settings = m_template.footIK;
|
|
DebugSceneView(animState);
|
|
#endif
|
|
DebugApplyPresentation();
|
|
|
|
m_footIk.SetJobData(footIkJob);
|
|
m_EntityManager.SetComponentData(m_Owner, animState);
|
|
Profiler.EndSample();
|
|
}
|
|
|
|
public void ApplyPresentationState(GameTime time, float deltaTime)
|
|
{
|
|
Profiler.BeginSample("CharacterAnimGraph_3PMove8Dir.UpdateNetwork");
|
|
|
|
var animState = m_EntityManager.GetComponentData<CharAnimState>(m_Owner);
|
|
|
|
// Handle turning
|
|
float rotateAngleRemaining = 0f;
|
|
if (animState.turnDirection != 0)
|
|
rotateAngleRemaining = Mathf.DeltaAngle(animState.rotation, animState.turnStartAngle) + m_template.animTurnAngle * animState.turnDirection;
|
|
|
|
if (animState.turnDirection == 0)
|
|
{
|
|
m_Transition.Update((int)LocoMixerPort.Idle, m_template.turnTransitionSpeed, Time.deltaTime);
|
|
}
|
|
else
|
|
{
|
|
var fraction = 1f - Mathf.Abs(rotateAngleRemaining / m_template.animTurnAngle);
|
|
var mixerPort = (animState.turnDirection == -1) ? (int)LocoMixerPort.TurnL : (int)LocoMixerPort.TurnR;
|
|
var anim = (animState.turnDirection == -1) ? m_animTurnL : m_animTurnR;
|
|
|
|
m_Transition.Update(mixerPort, m_template.turnTransitionSpeed, Time.deltaTime);
|
|
anim.SetTime(anim.GetAnimationClip().length * fraction);
|
|
|
|
// Reset the time of the idle, so it's reset when we transition back
|
|
if (m_locomotionMixer.GetInputWeight((int)LocoMixerPort.Idle) < 0.01f)
|
|
m_animIdle.SetTime(0f);
|
|
}
|
|
|
|
// Update aim
|
|
//TODO: Take care of cases where the clip time is 0
|
|
var aimMultiplier = 1f;
|
|
if (animState.charAction == CharacterPredictedState.StateData.Action.Reloading && m_template.blendOutAimOnReload != null)
|
|
{
|
|
var normalizedTime = (float)(m_ReloadActionAnimation.animation.GetTime() / m_ReloadActionAnimation.animation.GetDuration());
|
|
aimMultiplier = m_template.blendOutAimOnReload.Evaluate(normalizedTime);
|
|
}
|
|
|
|
float aimPitchFraction = animState.aimPitch / 180.0f;
|
|
var aimPitchMult = Mathf.Lerp(m_template.blendOutAimOnReloadPitch, 1f, aimMultiplier);
|
|
aimPitchFraction = Mathf.Lerp(0.5f, aimPitchFraction, aimPitchMult);
|
|
|
|
m_animAimLeft.SetTime(aimPitchFraction * m_animAimLeft.GetDuration());
|
|
m_animAimMid.SetTime(aimPitchFraction * m_animAimMid.GetDuration());
|
|
m_animAimRight.SetTime(aimPitchFraction * m_animAimRight.GetDuration());
|
|
|
|
float aimYawLocal = Mathf.DeltaAngle(animState.rotation, animState.aimYaw);
|
|
float aimYawFraction = Mathf.Abs(aimYawLocal / m_template.aimYawAngle);
|
|
var aimYawMult = Mathf.Lerp(m_template.blendOutAimOnReloadYaw, 1f, aimMultiplier);
|
|
aimYawFraction = Mathf.Lerp(0.0f, aimYawFraction, aimYawMult);
|
|
|
|
m_aimMixer.SetInputWeight((int)AimMixerPort.AimMid, 1.0f - aimYawFraction);
|
|
if (aimYawLocal < 0)
|
|
{
|
|
m_aimMixer.SetInputWeight((int)AimMixerPort.AimLeft, aimYawFraction);
|
|
m_aimMixer.SetInputWeight((int)AimMixerPort.AimRight, 0.0f);
|
|
}
|
|
else
|
|
{
|
|
m_aimMixer.SetInputWeight((int)AimMixerPort.AimLeft, 0.0f);
|
|
m_aimMixer.SetInputWeight((int)AimMixerPort.AimRight, aimYawFraction);
|
|
}
|
|
|
|
var characterActionDuration = time.DurationSinceTick(animState.charActionTick);
|
|
m_actionAnimationHandler.UpdateAction(animState.charAction, characterActionDuration);
|
|
m_additiveMixer.SetInputWeight(m_ShootPosePort, m_ShootPoseCurvedWeight * m_template.shootPoseMagnitude);
|
|
|
|
// Shoot pose update
|
|
if (animState.charAction == CharacterPredictedState.StateData.Action.PrimaryFire)
|
|
{
|
|
m_ShootPoseCurvedWeight = m_template.shootPoseEnter.Evaluate(animState.shootPoseWeight);
|
|
}
|
|
else
|
|
{
|
|
m_ShootPoseCurvedWeight = m_template.shootPoseExit.Evaluate(animState.shootPoseWeight);
|
|
}
|
|
|
|
// Update Foot IK
|
|
var job = m_footIk.GetJobData<FootIkJob>();
|
|
job.normalLeftFoot = animState.footIkNormalLeft;
|
|
job.normalRightFoot = animState.footIkNormaRight;
|
|
job.ikOffset = animState.footIkOffset;
|
|
m_footIk.SetJobData(job);
|
|
|
|
Profiler.EndSample();
|
|
|
|
DebugUpdatePresentation(animState);
|
|
}
|
|
|
|
AnimationClipPlayable CreateAimAnim(PlayableGraph graph, AnimationClip clip, AimMixerPort mixerPort)
|
|
{
|
|
AnimationClipPlayable playable = AnimationClipPlayable.Create(graph, clip);
|
|
playable.SetApplyFootIK(false);
|
|
playable.Pause();
|
|
playable.SetDuration(clip.length);
|
|
graph.Connect(playable, 0, m_aimMixer, (int)mixerPort);
|
|
return playable;
|
|
}
|
|
|
|
AnimationClipPlayable CreateTurnAnim(PlayableGraph graph, AnimationClip clip, LocoMixerPort mixerPort)
|
|
{
|
|
AnimationClipPlayable playable = AnimationClipPlayable.Create(graph, clip);
|
|
playable.SetApplyFootIK(true);
|
|
playable.Pause();
|
|
playable.SetDuration(clip.length);
|
|
|
|
graph.Connect(playable, 0, m_locomotionMixer, (int)mixerPort);
|
|
m_locomotionMixer.SetInputWeight((int)mixerPort, 0.0f);
|
|
|
|
return playable;
|
|
}
|
|
|
|
Vector2 GetClampedOffset()
|
|
{
|
|
var leftOffset = 0.0f;
|
|
var rightOffset = 0.0f;
|
|
|
|
if (m_LeftHitSuccess)
|
|
{
|
|
leftOffset = Mathf.Clamp(m_LeftHit.point.y - m_LeftFootPos.y + m_template.footIK.leftToeStandPos.y, -m_template.footIK.maxStepSize, m_template.footIK.maxStepSize);
|
|
}
|
|
|
|
if (m_RightHitSuccess)
|
|
{
|
|
rightOffset = Mathf.Clamp(m_RightHit.point.y - m_RightFootPos.y + m_template.footIK.rightToeStandPos.y, -m_template.footIK.maxStepSize, m_template.footIK.maxStepSize);
|
|
}
|
|
|
|
var stepMag = Mathf.Abs(leftOffset - rightOffset);
|
|
|
|
if (stepMag > m_template.footIK.maxStepSize)
|
|
{
|
|
leftOffset = (leftOffset / stepMag) * m_template.footIK.maxStepSize;
|
|
rightOffset = (rightOffset / stepMag) * m_template.footIK.maxStepSize;
|
|
}
|
|
|
|
return new Vector2(leftOffset, rightOffset);
|
|
}
|
|
|
|
static FootFalls ExtractFootFalls(AnimationClip animation)
|
|
{
|
|
var footFalls = new FootFalls();
|
|
foreach (var e in animation.events)
|
|
{
|
|
if (e.functionName != "OnCharEvent")
|
|
continue;
|
|
|
|
switch (e.stringParameter)
|
|
{
|
|
case nameof(FootFallType.LeftFootDown):
|
|
footFalls.leftFootDown = e.time;
|
|
break;
|
|
case nameof(FootFallType.LeftFootUp):
|
|
footFalls.leftFootUp = e.time;
|
|
break;
|
|
case nameof(FootFallType.RightFootDown):
|
|
footFalls.rightFootDown = e.time;
|
|
break;
|
|
case nameof(FootFallType.RightFootUp):
|
|
footFalls.rightFootUp = e.time;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return footFalls;
|
|
}
|
|
|
|
static float GetFootFraction(float turnFraction, float footUp, float footDown)
|
|
{
|
|
if (turnFraction <= footUp)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
if (turnFraction < footDown)
|
|
{
|
|
return (turnFraction - footUp) / (footDown - footUp);
|
|
}
|
|
|
|
return 1f;
|
|
}
|
|
|
|
void DebugSceneView(CharAnimState animState)
|
|
{
|
|
if (m_template.footIK.debugIdlePos)
|
|
{
|
|
var rotation = Quaternion.Euler(0f, animState.rotation, 0f);
|
|
var leftIdlePos = rotation * m_template.footIK.leftToeStandPos + animState.position;
|
|
var rightIdlePos = rotation * m_template.footIK.rightToeStandPos + animState.position;
|
|
|
|
DebugDraw.Sphere(leftIdlePos, 0.01f, Color.green);
|
|
DebugDraw.Sphere(leftIdlePos, 0.04f, Color.green);
|
|
DebugDraw.Sphere(rightIdlePos, 0.01f, Color.red);
|
|
DebugDraw.Sphere(rightIdlePos, 0.04f, Color.red);
|
|
}
|
|
|
|
if (m_template.footIK.debugRayCast)
|
|
{
|
|
DebugDraw.Sphere(m_LeftFootPos, 0.025f, Color.yellow);
|
|
DebugDraw.Sphere(m_RightFootPos, 0.025f, Color.yellow);
|
|
|
|
DebugDraw.Sphere(m_LeftHit.point, 0.015f);
|
|
DebugDraw.Sphere(m_RightHit.point, 0.015f);
|
|
|
|
Debug.DrawLine(m_LeftHit.point, m_LeftHit.point + m_LeftHit.normal, Color.green);
|
|
Debug.DrawLine(m_RightHit.point, m_RightHit.point + m_RightHit.normal, Color.red);
|
|
}
|
|
}
|
|
|
|
void DebugUpdatePresentation(CharAnimState animState)
|
|
{
|
|
if (debugStandIk.IntValue > 0)
|
|
{
|
|
var charIndex = s_Instances.IndexOf(this);
|
|
var lineIndex = charIndex * 3 + 3;
|
|
|
|
var debugString = "Char " + charIndex + " - IK Offset: " + animState.footIkOffset.x.ToString("0.000") +
|
|
", " + animState.footIkOffset.y.ToString("0.000");
|
|
DebugOverlay.Write(s_DebugColors[charIndex % s_DebugColors.Length], 2, lineIndex, debugString);
|
|
GameDebug.Log(debugString);
|
|
}
|
|
}
|
|
|
|
void DebugApplyPresentation()
|
|
{
|
|
if (debugStandIk.IntValue > 0)
|
|
{
|
|
var charIndex = s_Instances.IndexOf(this);
|
|
var lineIndex = charIndex * 3 + 1;
|
|
|
|
var color = s_DebugColors[charIndex % s_DebugColors.Length];
|
|
var leftHitString = "Char " + charIndex + " - Left XForm hit: Nothing";
|
|
if (m_LeftHitSuccess)
|
|
leftHitString = "Char " + charIndex + " - Left XForm hit: " + m_LeftHit.transform.name;
|
|
|
|
DebugOverlay.Write(color, 2, lineIndex, leftHitString);
|
|
GameDebug.Log(leftHitString);
|
|
|
|
var rightHitString = "Char " + charIndex + " - Right XForm hit: Nothing";
|
|
if (m_RightHitSuccess)
|
|
rightHitString = "Char " + charIndex + " - Right XForm hit: " + m_RightHit.transform.name;
|
|
|
|
DebugOverlay.Write(color, 2, lineIndex + 1, rightHitString);
|
|
GameDebug.Log(rightHitString);
|
|
}
|
|
}
|
|
|
|
|
|
enum LocoMixerPort
|
|
{
|
|
Idle,
|
|
TurnL,
|
|
TurnR,
|
|
Count
|
|
}
|
|
|
|
enum AimMixerPort
|
|
{
|
|
AimLeft,
|
|
AimMid,
|
|
AimRight,
|
|
Count
|
|
}
|
|
|
|
enum FootFallType
|
|
{
|
|
LeftFootUp,
|
|
LeftFootDown,
|
|
RightFootUp,
|
|
RightFootDown
|
|
}
|
|
|
|
enum StandState
|
|
{
|
|
Moving,
|
|
Standing,
|
|
Turning,
|
|
TurnStart,
|
|
TurnEnd
|
|
}
|
|
|
|
readonly int m_defaultLayer;
|
|
readonly int m_playerLayer;
|
|
readonly int m_platformLayer;
|
|
readonly int m_mask;
|
|
|
|
AnimGraph_Stand m_template;
|
|
EntityManager m_EntityManager;
|
|
Entity m_Owner;
|
|
CharacterPredictedState m_character;
|
|
AnimationScriptPlayable m_footIk;
|
|
|
|
AnimationMixerPlayable m_locomotionMixer;
|
|
AnimationClipPlayable m_animIdle;
|
|
AnimationClipPlayable m_animTurnL;
|
|
AnimationClipPlayable m_animTurnR;
|
|
|
|
AnimationMixerPlayable m_aimMixer;
|
|
AnimationClipPlayable m_animAimLeft;
|
|
AnimationClipPlayable m_animAimMid;
|
|
AnimationClipPlayable m_animAimRight;
|
|
|
|
AnimationLayerMixerPlayable m_additiveMixer;
|
|
ActionAnimationHandler m_actionAnimationHandler;
|
|
|
|
AnimationClipPlayable m_animShootPose;
|
|
ActionAnimationHandler.ActionAnimation m_ReloadActionAnimation;
|
|
int m_ShootPosePort;
|
|
float m_ShootPoseCurvedWeight;
|
|
|
|
Vector3 m_LeftFootPos;
|
|
Vector3 m_RightFootPos;
|
|
|
|
FootFalls m_LeftTurnFootFalls;
|
|
FootFalls m_RightTurnFootFalls;
|
|
|
|
RaycastHit m_LeftHit;
|
|
RaycastHit m_RightHit;
|
|
bool m_LeftHitSuccess;
|
|
bool m_RightHitSuccess;
|
|
|
|
Vector2 m_TurnStartOffset;
|
|
Vector2 m_TurnEndOffset;
|
|
Vector3[] m_TurnStartNormals = new Vector3[2];
|
|
Vector3[] m_TurnEndNormals = new Vector3[2];
|
|
|
|
StandState m_StandState;
|
|
|
|
SimpleTranstion<AnimationMixerPlayable> m_Transition;
|
|
}
|
|
|
|
public struct FootFalls
|
|
{
|
|
public float leftFootUp;
|
|
public float leftFootDown;
|
|
public float rightFootUp;
|
|
public float rightFootDown;
|
|
}
|
|
|
|
// Used for nicely ordered debug logging
|
|
static List<CharacterAnimGraph_3PStand> s_Instances;
|
|
static Color[] s_DebugColors = {Color.blue, Color.red, Color.green};
|
|
}
|