298 lines
13 KiB
C#
298 lines
13 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 NeoFPS.CharacterMotion.Parameters;
|
|||
|
using NeoSaveGames.Serialization;
|
|||
|
|
|||
|
namespace NeoFPS.CharacterMotion.States
|
|||
|
{
|
|||
|
[MotionGraphElement("Wall Movement/Wall Run", "WallRun")]
|
|||
|
[HelpURL("https://docs.neofps.com/manual/motiongraphref-mgs-wallrunstate.html")]
|
|||
|
public class WallRunState : MotionGraphState
|
|||
|
{
|
|||
|
[SerializeField, Tooltip("The vector parameter containing the wall normal. This will be read AND written to each frame")]
|
|||
|
private VectorParameter m_WallNormal = null;
|
|||
|
[SerializeField, Tooltip("A multiplier applied to gravity acceleration when moving up the wall.")]
|
|||
|
private FloatDataReference m_ClimbGravityMultiplier = new FloatDataReference(1f);
|
|||
|
[SerializeField, Tooltip("A multiplier applied to gravity acceleration when moving down the wall. If set to 0, the vertical character velocity will be clamped to >= 0")]
|
|||
|
private FloatDataReference m_FallGravityMultiplier = new FloatDataReference(0.25f);
|
|||
|
|
|||
|
[SerializeField, Tooltip("How the vertical speed is calculated when entering the wall run.")]
|
|||
|
private VerticalStartSpeed m_VerticalMode = VerticalStartSpeed.VerticalBoost;
|
|||
|
[SerializeField, Tooltip("The target vertical speed.")]
|
|||
|
private FloatDataReference m_VerticalTarget = new FloatDataReference(0f);
|
|||
|
[SerializeField, Tooltip("An upward speed boost when first entering the state.")]
|
|||
|
private FloatDataReference m_VerticalBoost = new FloatDataReference(2f);
|
|||
|
[SerializeField, Tooltip("The maximum downward speed the character can reach while wall running.")]
|
|||
|
private FloatDataReference m_MaxFallSpeed = new FloatDataReference(20f);
|
|||
|
[SerializeField, Tooltip("Should the downwards speed be limited.")]
|
|||
|
private bool m_CapFallSpeed = false;
|
|||
|
|
|||
|
[SerializeField, Tooltip("How the horizontal wall run speed is calculated.")]
|
|||
|
private HorizontalSpeed m_HorizontalMode = HorizontalSpeed.MaintainExisting;
|
|||
|
[SerializeField, Tooltip("The target horizontal speed.")]
|
|||
|
private FloatDataReference m_HorizontalSpeed = new FloatDataReference(10f);
|
|||
|
[SerializeField, Tooltip("The acceleration up to target horizontal speed.")]
|
|||
|
private FloatDataReference m_Acceleration = new FloatDataReference(50f);
|
|||
|
[SerializeField, Tooltip("The deceleration down to target horizontal speed.")]
|
|||
|
private FloatDataReference m_Deceleration = new FloatDataReference(10f);
|
|||
|
[SerializeField, Range(0f, 1f), Tooltip("The amount of damping to apply when changing direction or speed")]
|
|||
|
private float m_HorizontalDamping = 0.25f;
|
|||
|
|
|||
|
public enum VerticalStartSpeed
|
|||
|
{
|
|||
|
VerticalBoost,
|
|||
|
CappedBoost,
|
|||
|
Minimum,
|
|||
|
MaintainExisting,
|
|||
|
FixedSpeed
|
|||
|
}
|
|||
|
|
|||
|
public enum HorizontalSpeed
|
|||
|
{
|
|||
|
MaintainExisting,
|
|||
|
TargetSpeed,
|
|||
|
MinimumSpeed
|
|||
|
}
|
|||
|
|
|||
|
private Vector3 m_MotorAcceleration = Vector3.zero;
|
|||
|
private Vector3 m_OutVelocity = Vector3.zero;
|
|||
|
private bool m_Completed = false;
|
|||
|
private bool m_ContactFrame = false;
|
|||
|
|
|||
|
public override bool completed
|
|||
|
{
|
|||
|
get { return m_Completed; }
|
|||
|
}
|
|||
|
|
|||
|
public override Vector3 moveVector
|
|||
|
{
|
|||
|
get { return m_OutVelocity * Time.deltaTime; }
|
|||
|
}
|
|||
|
|
|||
|
public override bool applyGravity
|
|||
|
{
|
|||
|
get { return false; }
|
|||
|
}
|
|||
|
|
|||
|
public override bool applyGroundingForce
|
|||
|
{
|
|||
|
get { return false; }
|
|||
|
}
|
|||
|
|
|||
|
public override bool ignorePlatformMove
|
|||
|
{
|
|||
|
get { return false; }
|
|||
|
}
|
|||
|
|
|||
|
public override void OnValidate()
|
|||
|
{
|
|||
|
base.OnValidate();
|
|||
|
}
|
|||
|
|
|||
|
public override void OnEnter()
|
|||
|
{
|
|||
|
base.OnEnter();
|
|||
|
|
|||
|
m_Completed = false;
|
|||
|
m_ContactFrame = true;
|
|||
|
}
|
|||
|
|
|||
|
public override void OnExit()
|
|||
|
{
|
|||
|
base.OnExit();
|
|||
|
m_Completed = false;
|
|||
|
m_OutVelocity = Vector3.zero;
|
|||
|
}
|
|||
|
|
|||
|
public override void Update()
|
|||
|
{
|
|||
|
base.Update();
|
|||
|
|
|||
|
// Check if valid
|
|||
|
if (m_WallNormal == null)
|
|||
|
{
|
|||
|
m_Completed = true;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (m_WallNormal.value.sqrMagnitude < 0.25f)
|
|||
|
{
|
|||
|
Debug.LogError("Zero wall normal. ID: " + m_WallNormal.GetInstanceID());
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Perform cast to check for contact
|
|||
|
RaycastHit hit;
|
|||
|
bool didHit = controller.characterController.RayCast(0.25f, -m_WallNormal.value, Space.World, out hit, PhysicsFilter.Masks.CharacterBlockers, QueryTriggerInteraction.Ignore);
|
|||
|
if (didHit)
|
|||
|
{
|
|||
|
//Debug.Log("didHit = true");
|
|||
|
m_WallNormal.value = hit.normal;
|
|||
|
|
|||
|
m_OutVelocity = characterController.velocity;
|
|||
|
var wallUp = Vector3.ProjectOnPlane(characterController.up, m_WallNormal.value).normalized;
|
|||
|
|
|||
|
if (m_ContactFrame)
|
|||
|
{
|
|||
|
switch (m_VerticalMode)
|
|||
|
{
|
|||
|
case VerticalStartSpeed.VerticalBoost:
|
|||
|
// Add the vertical boost
|
|||
|
m_OutVelocity += wallUp * m_VerticalBoost.value;
|
|||
|
break;
|
|||
|
case VerticalStartSpeed.CappedBoost:
|
|||
|
{
|
|||
|
// Check if up-speed is below target and boost up to the target
|
|||
|
Vector3 up = characterController.up;
|
|||
|
float upSpeed = Vector3.Dot(m_OutVelocity, up);
|
|||
|
if (upSpeed < m_VerticalTarget.value)
|
|||
|
{
|
|||
|
m_OutVelocity -= up * upSpeed;
|
|||
|
m_OutVelocity += up * Mathf.Min(upSpeed + m_VerticalBoost.value, m_VerticalTarget.value);
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
case VerticalStartSpeed.Minimum:
|
|||
|
{
|
|||
|
// Check if up-speed is below target and add the difference if so
|
|||
|
Vector3 up = characterController.up;
|
|||
|
float upSpeed = Vector3.Dot(m_OutVelocity, up);
|
|||
|
if (upSpeed < m_VerticalTarget.value)
|
|||
|
m_OutVelocity += up * (m_VerticalTarget.value - upSpeed);
|
|||
|
}
|
|||
|
break;
|
|||
|
case VerticalStartSpeed.FixedSpeed:
|
|||
|
{
|
|||
|
// Set up speed to fixed values
|
|||
|
Vector3 up = characterController.up;
|
|||
|
float upSpeed = Vector3.Dot(m_OutVelocity, up);
|
|||
|
m_OutVelocity += up * (m_VerticalTarget.value - upSpeed);
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
m_ContactFrame = false;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Decompose veocity
|
|||
|
Vector3 up = characterController.up;
|
|||
|
float upSpeed = Vector3.Dot(m_OutVelocity, up);
|
|||
|
Vector3 horizontal = m_OutVelocity - up * upSpeed;
|
|||
|
|
|||
|
// Get custom gravity effect
|
|||
|
if (upSpeed <= 0.0001f)
|
|||
|
{
|
|||
|
if (m_FallGravityMultiplier.value > 0.0001f)
|
|||
|
{
|
|||
|
// Check if the fall speed is capped
|
|||
|
if (m_CapFallSpeed && upSpeed < -m_MaxFallSpeed.value)
|
|||
|
{
|
|||
|
upSpeed = -m_MaxFallSpeed.value;
|
|||
|
m_OutVelocity = up * upSpeed;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
m_OutVelocity = up * upSpeed;
|
|||
|
m_OutVelocity += Vector3.Project(characterController.gravity, wallUp) * Time.deltaTime * m_FallGravityMultiplier.value;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
m_OutVelocity = Vector3.zero;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (m_ClimbGravityMultiplier.value > 0.0001f)
|
|||
|
{
|
|||
|
m_OutVelocity = up * upSpeed;
|
|||
|
m_OutVelocity += Vector3.Project(characterController.gravity, wallUp) * Time.deltaTime * m_ClimbGravityMultiplier.value;
|
|||
|
}
|
|||
|
else
|
|||
|
m_OutVelocity = Vector3.zero;
|
|||
|
}
|
|||
|
|
|||
|
// Calculate horizontal velocity
|
|||
|
switch (m_HorizontalMode)
|
|||
|
{
|
|||
|
case HorizontalSpeed.TargetSpeed:
|
|||
|
{
|
|||
|
// Damp the velocity to the target
|
|||
|
float hSpeed = horizontal.magnitude;
|
|||
|
float target = m_HorizontalSpeed.value;
|
|||
|
float acceleration = 0f;
|
|||
|
if (hSpeed < target)
|
|||
|
acceleration = m_Acceleration.value;
|
|||
|
else
|
|||
|
acceleration = m_Deceleration.value;
|
|||
|
|
|||
|
Vector3 targetHorizontal = horizontal * (target / hSpeed);
|
|||
|
horizontal = Vector3.SmoothDamp(horizontal, targetHorizontal, ref m_MotorAcceleration, Mathf.Lerp(0.05f, 0.25f, m_HorizontalDamping), acceleration);
|
|||
|
}
|
|||
|
break;
|
|||
|
case HorizontalSpeed.MinimumSpeed:
|
|||
|
{
|
|||
|
// Boost the velocity if below the target
|
|||
|
float hSpeed = horizontal.magnitude;
|
|||
|
float target = m_HorizontalSpeed.value;
|
|||
|
if (hSpeed < target)
|
|||
|
{
|
|||
|
Vector3 targetHorizontal = horizontal * (target / hSpeed);
|
|||
|
horizontal = Vector3.SmoothDamp(horizontal, targetHorizontal, ref m_MotorAcceleration, Mathf.Lerp(0.05f, 0.25f, m_HorizontalDamping), m_Acceleration.value);
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// Add the revised horizontal back onto the velocity
|
|||
|
m_OutVelocity += horizontal;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//Debug.Log("didHit = false");
|
|||
|
m_Completed = true;
|
|||
|
m_OutVelocity = characterController.velocity;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public override void CheckReferences(IMotionGraphMap map)
|
|||
|
{
|
|||
|
m_WallNormal = map.Swap(m_WallNormal);
|
|||
|
m_ClimbGravityMultiplier.CheckReference(map);
|
|||
|
m_FallGravityMultiplier.CheckReference(map);
|
|||
|
m_VerticalTarget.CheckReference(map);
|
|||
|
m_VerticalBoost.CheckReference(map);
|
|||
|
m_MaxFallSpeed.CheckReference(map);
|
|||
|
m_HorizontalSpeed.CheckReference(map);
|
|||
|
m_Acceleration.CheckReference(map);
|
|||
|
m_Deceleration.CheckReference(map);
|
|||
|
base.CheckReferences(map);
|
|||
|
}
|
|||
|
|
|||
|
#region SAVE / LOAD
|
|||
|
|
|||
|
private static readonly NeoSerializationKey k_CompletedKey = new NeoSerializationKey("completed");
|
|||
|
private static readonly NeoSerializationKey k_VelocityKey = new NeoSerializationKey("velocity");
|
|||
|
|
|||
|
public override void WriteProperties(INeoSerializer writer)
|
|||
|
{
|
|||
|
base.WriteProperties(writer);
|
|||
|
writer.WriteValue(k_VelocityKey, m_OutVelocity);
|
|||
|
writer.WriteValue(k_CompletedKey, m_Completed);
|
|||
|
}
|
|||
|
|
|||
|
public override void ReadProperties(INeoDeserializer reader)
|
|||
|
{
|
|||
|
base.ReadProperties(reader);
|
|||
|
reader.TryReadValue(k_VelocityKey, out m_OutVelocity, m_OutVelocity);
|
|||
|
reader.TryReadValue(k_CompletedKey, out m_Completed, m_Completed);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|