projectEli/Assets/NeoFPS/Core/MotionGraphs/States/MovementState.cs

227 lines
9.1 KiB
C#
Raw Normal View History

2022-11-07 01:28:33 +00:00
#if !NEOFPS_FORCE_QUALITY && (UNITY_ANDROID || UNITY_IOS || UNITY_TIZEN || (UNITY_WSA && NETFX_CORE) || NEOFPS_FORCE_LIGHTWEIGHT)
#define NEOFPS_LIGHTWEIGHT
#endif
using UnityEngine;
using NeoFPS.CharacterMotion.MotionData;
using NeoSaveGames.Serialization;
using UnityEngine.Serialization;
namespace NeoFPS.CharacterMotion.States
{
[MotionGraphElement("Ground Movement/Movement", "Movement")]
[HelpURL("https://docs.neofps.com/manual/motiongraphref-mgs-movementstate.html")]
public class MovementState : MotionGraphState
{
[SerializeField, Tooltip("An optional curve that defines the speed drop off when moving up or down slopes. If this is not set then the character uses the default NeoCharacterController slope speed handling")]
private SlopeSpeedCurve m_SlopeSpeedCurve = null;
[SerializeField, Tooltip("The top movement speed (for keyboard input or max analog input)")]
private FloatDataReference m_TopSpeed = new FloatDataReference(5f);
[SerializeField, Tooltip("The multiplier applied to the max movement speed when strafing")]
private FloatDataReference m_StrafeMultiplier = new FloatDataReference(0.75f);
[SerializeField, Tooltip("The multiplier applied to the max movement speed when moving in reverse")]
private FloatDataReference m_ReverseMultiplier = new FloatDataReference(0.5f);
[SerializeField, Tooltip("The maximum acceleration")]
private FloatDataReference m_Acceleration = new FloatDataReference(50f);
[SerializeField, Tooltip("The maximum deceleration (when no input is applied)."), FormerlySerializedAs("m_Acceleration")]
private FloatDataReference m_Deceleration = new FloatDataReference(50f);
[SerializeField, Tooltip("How should gravity be applied. AlwaysApply gives a more realistic effect and allows for sliding due to ground and ledge friction, but affects slope speed. WhenNotGrounded and NeverApply give a more predictable result, but less realistic")]
private GravityMode m_GravityMode = GravityMode.AlwaysApply;
[SerializeField, Range(0f,1f), Tooltip("The amount of damping to apply when changing direction or speed")]
private float m_Damping = 0.25f;
private const float k_TinyValue = 0.001f;
private const float k_MinSlopeEffectAngle = 3f;
private Vector3 m_MotorAcceleration = Vector3.zero;
private Vector3 m_OutVelocity = Vector3.zero;
public enum GravityMode
{
AlwaysApply,
WhenNotGrounded,
NeverApply
}
public override Vector3 moveVector
{
get { return m_OutVelocity * Time.deltaTime; }
}
public override bool applyGravity
{
get
{
switch (m_GravityMode)
{
case GravityMode.AlwaysApply:
return true;
case GravityMode.WhenNotGrounded:
return !characterController.isGrounded;
case GravityMode.NeverApply:
return false;
default:
return true;
}
}
}
public override void OnValidate()
{
base.OnValidate();
m_TopSpeed.ClampValue(0.1f, 50f);
m_StrafeMultiplier.ClampValue(0f, 2f);
m_ReverseMultiplier.ClampValue(0f, 2f);
m_Acceleration.ClampValue(0f, 1000f);
}
public override void OnEnter()
{
base.OnEnter();
m_MotorAcceleration = Vector3.zero;
}
public override void OnExit()
{
base.OnExit();
m_OutVelocity = Vector3.zero;
}
protected virtual Vector3 GetTargetVelocity(float directionMultiplier)
{
if (controller.inputMoveScale < 0.01f)
return Vector3.zero;
// Get slope directions
Vector3 forward = characterController.forward;
Vector3 right = characterController.right;
Vector3 target = Vector3.zero;
// Different approaches if using a slope speed profile vs not
bool useSlopeSpeedCurve = (m_SlopeSpeedCurve != null && characterController.isGrounded);
if (useSlopeSpeedCurve)
{
Vector3 up = characterController.up;
Vector3 slopeNormal = characterController.groundSurfaceNormal;
// Create slope plane
Plane p = new Plane(slopeNormal, 0f);
// Fit move directions to plane
float yOffset;
p.Raycast(new Ray(forward, up), out yOffset);
Vector3 slopeForward = (forward + up * yOffset).normalized;
p.Raycast(new Ray(right, up), out yOffset);
Vector3 slopeRight = (right + up * yOffset).normalized;
// Get target velocity
float topSpeed = m_TopSpeed.value * controller.inputMoveScale * directionMultiplier;
target += slopeForward * controller.inputMoveDirection.y * topSpeed;
target += slopeRight * controller.inputMoveDirection.x * topSpeed;
// Get the slope angle
float slopeAngle = Vector3.Angle(up, slopeNormal);
if (slopeAngle > k_MinSlopeEffectAngle)
{
// Get the up-slope vector
Vector3 upSlope = Vector3.ProjectOnPlane(up, slopeNormal).normalized;
// Check how much of the target move is up the slope
float dotUp = Vector3.Dot(target.normalized, upSlope);
if (dotUp < 0f)
{
slopeAngle *= -1f;
dotUp *= -1f;
}
// Use to get speed multiplier and apply to target
float slopeMultiplier = Mathf.Lerp(1f, m_SlopeSpeedCurve.curve.Evaluate(Mathf.Clamp(slopeAngle / characterController.slopeLimit, -1f, 1f)), dotUp);
target *= slopeMultiplier;
}
}
else
{
// Get target velocity from input and direction
float topSpeed = m_TopSpeed.value * controller.inputMoveScale * directionMultiplier;
target += forward * controller.inputMoveDirection.y * topSpeed;
target += right * controller.inputMoveDirection.x * topSpeed;
}
return target;
}
public override void Update()
{
base.Update();
// Update the current velocity
Vector3 currentVelocity = characterController.velocity;
// Calculate speed based on move direction
float directionMultiplier = 1f;
if (controller.inputMoveScale > 0.005f)
{
Vector2 inputScaled = controller.inputMoveDirection;
inputScaled.x *= m_StrafeMultiplier.value;
if (inputScaled.y < 0f)
inputScaled.y *= m_ReverseMultiplier.value;
directionMultiplier = inputScaled.magnitude;
}
// Get the target velocity
var targetVelocity = GetTargetVelocity(directionMultiplier);
// Check if accelerating or decelerating
float acceleration = (targetVelocity.sqrMagnitude >= currentVelocity.sqrMagnitude) ? m_Acceleration.value : m_Deceleration.value;
// Accelerate if required
if (acceleration < k_TinyValue)
m_OutVelocity = targetVelocity;
else
{
// Get maximum acceleration
float maxAccel = acceleration * directionMultiplier;
// Accelerate the velocity
m_OutVelocity = Vector3.SmoothDamp(currentVelocity, targetVelocity, ref m_MotorAcceleration, Mathf.Lerp(0.05f, 0.25f, m_Damping), maxAccel);
}
}
public override void CheckReferences(IMotionGraphMap map)
{
base.CheckReferences(map);
m_TopSpeed.CheckReference(map);
m_StrafeMultiplier.CheckReference(map);
m_ReverseMultiplier.CheckReference(map);
m_Acceleration.CheckReference(map);
m_Deceleration.CheckReference(map);
}
#region SAVE / LOAD
private static readonly NeoSerializationKey k_AccelerationKey = new NeoSerializationKey("acceleration");
private static readonly NeoSerializationKey k_VelocityKey = new NeoSerializationKey("velocity");
public override void WriteProperties(INeoSerializer writer)
{
base.WriteProperties(writer);
writer.WriteValue(k_AccelerationKey, m_MotorAcceleration);
writer.WriteValue(k_VelocityKey, m_OutVelocity);
}
public override void ReadProperties(INeoDeserializer reader)
{
base.ReadProperties(reader);
reader.TryReadValue(k_AccelerationKey, out m_MotorAcceleration, m_MotorAcceleration);
reader.TryReadValue(k_VelocityKey, out m_OutVelocity, m_OutVelocity);
}
#endregion
}
}