195 lines
8.7 KiB
C#
195 lines
8.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 NeoFPS.CharacterMotion.Parameters;
|
|||
|
using NeoSaveGames.Serialization;
|
|||
|
|
|||
|
namespace NeoFPS.CharacterMotion.States
|
|||
|
{
|
|||
|
[MotionGraphElement("Wall Movement/Mantle", "Mantle")]
|
|||
|
[HelpURL("https://docs.neofps.com/manual/motiongraphref-mgs-mantlestate.html")]
|
|||
|
public class MantleState : MotionGraphState
|
|||
|
{
|
|||
|
[SerializeField, Tooltip("The normal of the wall to climb. This value will be read AND written to each frame")]
|
|||
|
private VectorParameter m_WallNormal = null;
|
|||
|
[SerializeField, Tooltip("The movement speed while climbing the surface")]
|
|||
|
private FloatDataReference m_ClimbSpeed = new FloatDataReference(5f);
|
|||
|
[SerializeField, Tooltip("The collision mask to use when checking the wall normal")]
|
|||
|
private LayerMask m_WallCollisionMask = PhysicsFilter.Masks.StaticCharacterBlockers;
|
|||
|
[SerializeField, Range(0.05f, 0.5f), Tooltip("The distance to perform the wall checks.")]
|
|||
|
private FloatDataReference m_WallCheckDistance = new FloatDataReference(0.25f);
|
|||
|
[SerializeField, Range(0f, 1f), Tooltip("The climb speed multiplier (for the data value above) on entering the state")]
|
|||
|
private float m_StartingSpeedMultiplier = 0.25f;
|
|||
|
[SerializeField, Range(0f, 1f), Tooltip("The climb speed multiplier (for the data value above) on completing the ledge mantle")]
|
|||
|
private float m_EndingSpeedMultiplier = 0.5f;
|
|||
|
[SerializeField, Tooltip("The distance to move past the edge onto flat ground before completing")]
|
|||
|
private float m_OvershootDistance = 0.5f;
|
|||
|
|
|||
|
private Vector3 m_OutVelocity = Vector3.zero;
|
|||
|
private Vector3 m_StartingWallNormal = Vector3.zero;
|
|||
|
private float m_ClimbMotorSpeed = 0f;
|
|||
|
private float m_ClimbMotorAcceleration = 0f;
|
|||
|
private float m_TopDistance = 0f;
|
|||
|
private bool m_Completed = 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 elapsedTime > 0.25f; }
|
|||
|
}
|
|||
|
|
|||
|
public override bool applyGroundingForce
|
|||
|
{
|
|||
|
get { return elapsedTime > 0.25f; }
|
|||
|
}
|
|||
|
|
|||
|
public override bool ignorePlatformMove
|
|||
|
{
|
|||
|
get { return false; }
|
|||
|
}
|
|||
|
|
|||
|
public override void OnValidate()
|
|||
|
{
|
|||
|
base.OnValidate();
|
|||
|
m_OvershootDistance = Mathf.Clamp(m_OvershootDistance, 0.01f, 2f);
|
|||
|
}
|
|||
|
|
|||
|
public override void OnEnter()
|
|||
|
{
|
|||
|
base.OnEnter();
|
|||
|
m_Completed = false;
|
|||
|
m_ClimbMotorSpeed = m_ClimbSpeed.value * m_StartingSpeedMultiplier;
|
|||
|
m_ClimbMotorAcceleration = 0f;
|
|||
|
m_StartingWallNormal = Vector3.zero;
|
|||
|
m_TopDistance = 0f;
|
|||
|
}
|
|||
|
|
|||
|
public override void OnExit()
|
|||
|
{
|
|||
|
base.OnExit();
|
|||
|
m_Completed = false;
|
|||
|
m_OutVelocity = Vector3.zero;
|
|||
|
}
|
|||
|
|
|||
|
public override void Update()
|
|||
|
{
|
|||
|
base.Update();
|
|||
|
|
|||
|
if (!m_Completed)
|
|||
|
{
|
|||
|
if (m_WallNormal != null && m_WallNormal.value.sqrMagnitude > 0.5f)
|
|||
|
{
|
|||
|
// CAst to get updated wall normal
|
|||
|
RaycastHit hit;
|
|||
|
if (characterController.SphereCast(0f, -m_WallNormal.value * m_WallCheckDistance.value, Space.World, out hit, m_WallCollisionMask, QueryTriggerInteraction.Ignore))
|
|||
|
{
|
|||
|
// Check if at top
|
|||
|
bool top = (m_TopDistance > 0f);
|
|||
|
if (!top)
|
|||
|
{
|
|||
|
// Store hit normal
|
|||
|
m_WallNormal.value = hit.normal;
|
|||
|
|
|||
|
// First frame, store it for end of climb
|
|||
|
if (m_StartingWallNormal.sqrMagnitude < 0.25f)
|
|||
|
m_StartingWallNormal = Vector3.ProjectOnPlane(hit.normal, characterController.up).normalized;
|
|||
|
|
|||
|
// Get a speed multiplier from the
|
|||
|
float targetSpeedMultiplier = 1f;
|
|||
|
if (characterController.isGrounded)
|
|||
|
{
|
|||
|
float angle = Vector3.Angle(hit.normal, characterController.groundSurfaceNormal);
|
|||
|
targetSpeedMultiplier = Mathf.Lerp(m_EndingSpeedMultiplier, 1f, angle / 90f);
|
|||
|
|
|||
|
if (angle < 5f)
|
|||
|
top = true;
|
|||
|
}
|
|||
|
|
|||
|
// Get the climb velocity
|
|||
|
m_ClimbMotorSpeed = Mathf.SmoothDamp(m_ClimbMotorSpeed, m_ClimbSpeed.value * targetSpeedMultiplier, ref m_ClimbMotorAcceleration, 0.25f, 25f);
|
|||
|
Vector3 upWall = Vector3.ProjectOnPlane(characterController.up, m_WallNormal.value).normalized;
|
|||
|
m_OutVelocity = upWall * m_ClimbMotorSpeed;
|
|||
|
m_OutVelocity += hit.normal * -0.1f;
|
|||
|
|
|||
|
// Record top distance for next frame
|
|||
|
if (top)
|
|||
|
m_TopDistance += m_ClimbMotorSpeed * Time.deltaTime;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Move forwards onto ledge
|
|||
|
float speed = m_ClimbSpeed.value * m_EndingSpeedMultiplier;
|
|||
|
m_OutVelocity = Vector3.Lerp(characterController.velocity, m_StartingWallNormal * -speed, 0.25f);
|
|||
|
m_OutVelocity += characterController.up * 0.01f;
|
|||
|
|
|||
|
// Increment top distance and set completed if (should have) moved correct amount
|
|||
|
m_TopDistance += speed * Time.deltaTime;
|
|||
|
if (m_TopDistance > m_OvershootDistance)
|
|||
|
m_Completed = true;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
m_Completed = true;
|
|||
|
}
|
|||
|
else
|
|||
|
m_Completed = true;
|
|||
|
}
|
|||
|
|
|||
|
// Give default values if completed
|
|||
|
if (m_Completed)
|
|||
|
m_OutVelocity = characterController.velocity;
|
|||
|
}
|
|||
|
|
|||
|
public override void CheckReferences(IMotionGraphMap map)
|
|||
|
{
|
|||
|
m_WallNormal = map.Swap(m_WallNormal);
|
|||
|
m_ClimbSpeed.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");
|
|||
|
private static readonly NeoSerializationKey k_StartNormalKey = new NeoSerializationKey("wallN");
|
|||
|
private static readonly NeoSerializationKey k_ClimbSpeedKey = new NeoSerializationKey("climbV");
|
|||
|
private static readonly NeoSerializationKey k_ClimbAccelerationKey = new NeoSerializationKey("climbA");
|
|||
|
private static readonly NeoSerializationKey k_TopDistanceKey = new NeoSerializationKey("topD");
|
|||
|
|
|||
|
public override void WriteProperties(INeoSerializer writer)
|
|||
|
{
|
|||
|
base.WriteProperties(writer);
|
|||
|
writer.WriteValue(k_VelocityKey, m_OutVelocity);
|
|||
|
writer.WriteValue(k_CompletedKey, m_Completed);
|
|||
|
writer.WriteValue(k_StartNormalKey, m_StartingWallNormal);
|
|||
|
writer.WriteValue(k_ClimbSpeedKey, m_ClimbMotorSpeed);
|
|||
|
writer.WriteValue(k_ClimbAccelerationKey, m_ClimbMotorAcceleration);
|
|||
|
writer.WriteValue(k_TopDistanceKey, m_TopDistance);
|
|||
|
}
|
|||
|
|
|||
|
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);
|
|||
|
reader.TryReadValue(k_StartNormalKey, out m_StartingWallNormal, m_StartingWallNormal);
|
|||
|
reader.TryReadValue(k_ClimbSpeedKey, out m_ClimbMotorSpeed, m_ClimbMotorSpeed);
|
|||
|
reader.TryReadValue(k_ClimbAccelerationKey, out m_ClimbMotorAcceleration, m_ClimbMotorAcceleration);
|
|||
|
reader.TryReadValue(k_TopDistanceKey, out m_TopDistance, m_TopDistance);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|