projectEli/Assets/NeoFPS/Core/MotionGraphs/States/SteepSlideState.cs
2022-11-06 20:28:33 -05:00

219 lines
9.7 KiB
C#

#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;
namespace NeoFPS.CharacterMotion.States
{
[MotionGraphElement("Ground Movement/Steep Slide", "Slide")]
[HelpURL("https://docs.neofps.com/manual/motiongraphref-mgs-steepslidestate.html")]
public class SteepSlideState : MotionGraphState
{
[SerializeField, Tooltip("The angle above which a character loses motor control and is in pure slide mode.")]
private FloatDataReference m_SlideAngle = new FloatDataReference(60f);
[SerializeField, Tooltip("The sliding speed the character will reach (downwards only) at the lowest slope angle for a full slide.")]
private FloatDataReference m_SpeedMinimum = new FloatDataReference(25f);
[SerializeField, Tooltip("The fastest possible sliding speed the character can reach (downwards only) during a near vertical slide.")]
private FloatDataReference m_SpeedMaximum = new FloatDataReference(40f);
[SerializeField, Tooltip("The down-slope acceleration multiplier applied to a character during a shallow slide.")]
private FloatDataReference m_AccelerationMinimum = new FloatDataReference(0.25f);
[SerializeField, Tooltip("The down-slope acceleration multiplier applied to a character during a near vertical slide.")]
private FloatDataReference m_AccelerationMaximum = new FloatDataReference(0.75f);
[SerializeField, Tooltip("The top speed the character can reach against the slide (side to side).")]
private FloatDataReference m_HorizontalSpeedLimit = new FloatDataReference(5f);
[SerializeField, Tooltip("The across slope accleration when trying to redirect slide sideways (0 is instant).")]
private FloatDataReference m_HorizontalAcceleration = new FloatDataReference(10f);
private const float k_TinyValue = 0.001f;
private Vector3 m_SlideVelocity = Vector3.zero;
private float m_VerticalSlideSpeed = 0f;
private float m_VerticalSlideAcceleration = 0f;
private float m_HorizontalSlideSpeed = 0f;
private float m_HorizontalSlideAcceleration = 0f;
private float m_SlopeFriction = 0f;
public override Vector3 moveVector
{
get { return m_SlideVelocity * Time.deltaTime; }
}
public override bool applyGravity
{
get { return false; }
}
public override bool applyGroundingForce
{
get { return true; }
}
public override void OnValidate()
{
base.OnValidate();
m_SlideAngle.ClampValue(30f, 80f);
m_SpeedMinimum.ClampValue(1f, 50f);
m_SpeedMaximum.ClampValue(1f, 50f);
m_AccelerationMinimum.ClampValue(0f, 1f);
m_AccelerationMaximum.ClampValue(0f, 1f);
m_HorizontalSpeedLimit.ClampValue(0f, 20f);
m_HorizontalAcceleration.ClampValue(0f, 1000f);
}
public override void OnEnter()
{
base.OnEnter();
m_VerticalSlideAcceleration = 0f;
m_HorizontalSlideAcceleration = 0f;
Vector3 upSlope = Vector3.ProjectOnPlane(characterController.up, characterController.groundSurfaceNormal).normalized;
m_VerticalSlideSpeed = Vector3.Dot(characterController.rawVelocity, upSlope);
// Remove slope friction
m_SlopeFriction = characterController.slopeFriction;
characterController.slopeFriction = 0f;
}
public override void OnExit()
{
base.OnExit();
m_SlideVelocity = Vector3.zero;
// Reset slope friction
characterController.slopeFriction = m_SlopeFriction;
}
public override void Update()
{
base.Update();
// Get the up slope vector & angle
Vector3 up = characterController.up;
Vector3 upSlope = Vector3.ProjectOnPlane(up, characterController.groundSurfaceNormal).normalized;
float slopeAngle = Vector3.Angle(up, characterController.groundSurfaceNormal);
// Calculate the scale of the slide (0 to 1 = starting slide angle to straight down)
float slopeMaxAngle = m_SlideAngle.value;
float slideScale = Mathf.Clamp01 ((slopeAngle - slopeMaxAngle) / (90f - slopeMaxAngle));
// Calculate target slide speed & acceleration based on slope
float targetVerticalSlideSpeed = Mathf.Lerp(m_SpeedMinimum.value, m_SpeedMaximum.value, slideScale);
float accelerationMultiplier = Mathf.Lerp(m_AccelerationMinimum.value, m_AccelerationMaximum.value, slideScale);
// Update the current down-slope slide speed
m_VerticalSlideSpeed = -Vector3.Dot (characterController.velocity, upSlope);
// Prevent players using steep slopes to boost jumps (due to slower downward acceleration than gravity)
if (m_VerticalSlideSpeed < 0f)
{
var upVel = Vector3.Project(characterController.velocity, up);
m_VerticalSlideSpeed = -Vector3.Dot(upVel, upSlope);
accelerationMultiplier += 1f;
}
// Accelerate down-slope towards slide speed
if (!Mathf.Approximately(m_VerticalSlideSpeed, targetVerticalSlideSpeed))
m_VerticalSlideSpeed = Mathf.SmoothDamp(m_VerticalSlideSpeed, targetVerticalSlideSpeed, ref m_VerticalSlideAcceleration, 0.01f, -Vector3.Dot(characterController.gravity, characterController.up) * accelerationMultiplier);
m_VerticalSlideSpeed = Mathf.Clamp(m_VerticalSlideSpeed, -targetVerticalSlideSpeed, targetVerticalSlideSpeed);
// Apply down-slope to move vector
m_SlideVelocity = upSlope * -m_VerticalSlideSpeed;
// Get the across slope vector
Vector3 acrossSlope = Vector3.Cross(up, upSlope).normalized;
// Check if across slope control is enabled
float hSpeedLimit = m_HorizontalSpeedLimit.value;
if (hSpeedLimit > 0f)
{
// Update the current across-slope slide speed
m_HorizontalSlideSpeed = Vector3.Dot (characterController.velocity, acrossSlope);
// Get the target across-slope speed (based on look & input)
float targetHorizontalSlideSpeed = 0f;
if (controller.inputMoveScale > 0f)
{
Vector3 inputTarget = Vector3.zero;
inputTarget += controller.localTransform.forward * controller.inputMoveDirection.y;
inputTarget += controller.localTransform.right * controller.inputMoveDirection.x;
float acrossInput = Vector3.Dot(inputTarget, acrossSlope);
targetHorizontalSlideSpeed = acrossInput * hSpeedLimit;
}
// Accelerate across-slope towards target speed (positive = rhs)
float hAcceleration = m_HorizontalAcceleration.value;
if (hAcceleration < k_TinyValue)
{
// Don't use acceleration (instant)
m_HorizontalSlideSpeed = targetHorizontalSlideSpeed;
}
else
{
// Accelerate if required
if (!Mathf.Approximately (m_HorizontalSlideSpeed, targetHorizontalSlideSpeed))
m_HorizontalSlideSpeed = Mathf.SmoothDamp (m_HorizontalSlideSpeed, targetHorizontalSlideSpeed, ref m_HorizontalSlideAcceleration, 0.1f, hAcceleration);
}
// Apply to move vector
m_SlideVelocity += acrossSlope * m_HorizontalSlideSpeed;
}
}
public override void CheckReferences(IMotionGraphMap map)
{
base.CheckReferences(map);
m_SlideAngle.CheckReference(map);
m_SpeedMinimum.CheckReference(map);
m_SpeedMaximum.CheckReference(map);
m_AccelerationMinimum.CheckReference(map);
m_AccelerationMaximum.CheckReference(map);
m_HorizontalSpeedLimit.CheckReference(map);
m_HorizontalAcceleration.CheckReference(map);
}
#region SAVE / LOAD
private static readonly NeoSerializationKey k_FrictionKey = new NeoSerializationKey("friction");
private static readonly NeoSerializationKey k_SlideKey = new NeoSerializationKey("slide");
private static readonly NeoSerializationKey k_VelocityKey = new NeoSerializationKey("velocity");
public override void WriteProperties(INeoSerializer writer)
{
base.WriteProperties(writer);
writer.WriteValue(k_VelocityKey, m_SlideVelocity);
writer.WriteValue(k_FrictionKey, m_SlopeFriction);
writer.WriteValue(k_SlideKey, new Vector4(m_VerticalSlideSpeed, m_VerticalSlideAcceleration, m_HorizontalSlideSpeed, m_HorizontalSlideAcceleration));
}
public override void ReadProperties(INeoDeserializer reader)
{
base.ReadProperties(reader);
reader.TryReadValue(k_VelocityKey, out m_SlideVelocity, m_SlideVelocity);
reader.TryReadValue(k_FrictionKey, out m_SlopeFriction, m_SlopeFriction);
Vector4 slide;
if (reader.TryReadValue(k_SlideKey, out slide, Vector4.zero))
{
m_VerticalSlideSpeed = slide.x;
m_VerticalSlideAcceleration = slide.y;
m_HorizontalSlideSpeed = slide.z;
m_HorizontalSlideAcceleration = slide.w;
}
}
#endregion
}
}