projectEli/Assets/NeoFPS/Core/MotionGraphs/Conditions/ClimbableCondition.cs

224 lines
8.8 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 System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NeoFPS.CharacterMotion.Parameters;
using NeoCC;
using NeoFPS.CharacterMotion.MotionData;
namespace NeoFPS.CharacterMotion.Conditions
{
[MotionGraphElement("Physics/Climbable")]
public class ClimbableCondition : MotionGraphCondition
{
[SerializeField, Tooltip("The vector parameter to store the wall normal in once the check is complete")]
private VectorParameter m_OutputWallNormal = null;
[SerializeField, Tooltip("The direction to check in. YawForward casts directly forward in the direction the character is facing. InverseWallNormal takes the value already set in OutputWallNormal and casts back along it.")]
private CheckDirection m_CheckDirection = CheckDirection.YawForward;
[SerializeField, Range(0.05f, 0.5f), Tooltip("The distance to perform the initial wall check.")]
private FloatDataReference m_CheckDistance = new FloatDataReference(0.25f);
[SerializeField, Tooltip("The collision mask to use when checking the wall normal")]
private LayerMask m_WallCollisionMask = PhysicsFilter.Masks.StaticCharacterBlockers;
[SerializeField, Tooltip("The maximum height the character can move up to climb over the ledge")]
private float m_MaxClimbHeight = 1.5f;
[SerializeField, Tooltip("The distance the character should be able to move forwards past the edge of the climbable surface")]
private float m_ClimbForward = 0.25f;
[SerializeField, Tooltip("An optional parameter to store the climb height")]
private FloatParameter m_OutputClimbHeight = null;
public enum CheckDirection
{
YawForward,
InverseWallNormal
}
public override bool CheckCondition(MotionGraphConnectable connectable)
{
// Check wall vector
if (m_OutputWallNormal == null)
return false;
var ncc = controller.characterController;
float radius = ncc.radius;
float height = ncc.height;
RaycastHit hit;
// Get the check direction
Vector3 checkForward = Vector3.zero;
if (m_CheckDirection == CheckDirection.YawForward)
{
// Check collision flags
//if ((ncc.collisionFlags & (NeoCharacterCollisionFlags.MaskFront | NeoCharacterCollisionFlags.Below)) == NeoCharacterCollisionFlags.None)
// return false;
// Use the character forward
checkForward = ncc.forward;
}
else
{
// Only works if the stored wall normal has length
float mag = m_OutputWallNormal.value.magnitude;
if (mag < 0.5f)
return false;
// Use the inverse of the normal
checkForward = m_OutputWallNormal.value / -mag;
}
#if NEOFPS_LIGHTWEIGHT
// Cast and get the initial wall contact
if (ncc.CapsuleCast(checkForward * m_CheckDistance.value, Space.World, out hit, m_WallCollisionMask, QueryTriggerInteraction.Ignore))
{
// Record the wall normal
m_OutputWallNormal.value = hit.normal;
// Get head point at wall contact
Vector3 top = controller.localTransform.position;
top.y += height - radius;
top += checkForward * hit.distance;
// Sphere cast up the wall from the head point
if (PhysicsExtensions.SphereCastNonAllocSingle(
new Ray(top, Vector3.up),
radius - ncc.skinWidth,
out hit,
m_MaxClimbHeight,
PhysicsFilter.Masks.CharacterBlockers,
controller.localTransform,
QueryTriggerInteraction.Ignore
))
{
// Hit a ceiling
top = hit.point + hit.normal * radius;
}
else
{
// No obstruction overhead
top.y += m_MaxClimbHeight;
}
// Get forward direction
checkForward = -m_OutputWallNormal.value;
checkForward.y = 0f;
checkForward.Normalize();
// Cast forward
if (PhysicsExtensions.CapsuleCastNonAllocSingle(
top,
top - ncc.up * (height - radius * 2f),
radius - ncc.skinWidth,
checkForward,
out hit,
radius + m_ClimbForward,
PhysicsFilter.Masks.CharacterBlockers,
controller.localTransform,
QueryTriggerInteraction.Ignore
))
{
// No ledge to step onto, or ledge obstructed
return false;
}
// Route clear
return true;
}
#else
// Cast and get the initial wall contact
if (ncc.CapsuleCast(checkForward * m_CheckDistance.value, Space.World, out hit, m_WallCollisionMask, QueryTriggerInteraction.Ignore))
{
// Record the wall normal
m_OutputWallNormal.value = hit.normal;
// Get wall up vector
Vector3 projectedUp = Vector3.ProjectOnPlane(ncc.up, hit.normal).normalized;
// Get head point at wall contact
Vector3 top = controller.localTransform.position + ncc.up * (height - radius);
top += checkForward * hit.distance;
float verticalTravel = 0f;
// Sphere cast up the wall from the head point
if (PhysicsExtensions.SphereCastNonAllocSingle(
new Ray(top, projectedUp),
radius - ncc.skinWidth,
out hit,
m_MaxClimbHeight,
PhysicsFilter.Masks.CharacterBlockers,
controller.localTransform,
QueryTriggerInteraction.Ignore
))
{
// Hit a ceiling
top = hit.point + hit.normal * radius;
verticalTravel += hit.distance;
}
else
{
// No obstruction overhead
top = top + projectedUp * m_MaxClimbHeight;
verticalTravel += m_MaxClimbHeight;
}
// Get forward direction
checkForward = Vector3.ProjectOnPlane(-m_OutputWallNormal.value, ncc.up).normalized;
// Cast forward
var bottom = top - ncc.up * (height - radius * 2f);
if (PhysicsExtensions.CapsuleCastNonAllocSingle(
top,
bottom,
radius - ncc.skinWidth,
checkForward,
out hit,
radius + m_ClimbForward,
PhysicsFilter.Masks.CharacterBlockers,
controller.localTransform,
QueryTriggerInteraction.Ignore
))
{
// No ledge to step onto, or ledge obstructed
return false;
}
// If required, get the climb height
if (m_OutputClimbHeight != null)
{
// Raycast down to get a rough approximation of the height to climb
bottom += checkForward;
if (PhysicsExtensions.SphereCastNonAllocSingle(
new Ray(bottom, -ncc.up),
radius - ncc.skinWidth,
out hit,
verticalTravel,
PhysicsFilter.Masks.CharacterBlockers,
controller.localTransform,
QueryTriggerInteraction.Ignore
))
{
m_OutputClimbHeight.value = verticalTravel - hit.distance;
}
}
// Route clear
return true;
}
#endif
// No wall to climb
return false;
}
public override void CheckReferences(IMotionGraphMap map)
{
m_OutputWallNormal = map.Swap(m_OutputWallNormal);
m_OutputClimbHeight = map.Swap(m_OutputClimbHeight);
}
}
}