You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
525 lines
21 KiB
C#
525 lines
21 KiB
C#
using MalbersAnimations.Utilities;
|
|
using MalbersAnimations.Scriptables;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using System;
|
|
using UnityEngine.Serialization;
|
|
//using Physics = RotaryHeart.Lib.PhysicsExtension.Physics;
|
|
|
|
namespace MalbersAnimations.Controller
|
|
{
|
|
[HelpURL("https://malbersanimations.gitbook.io/animal-controller/main-components/manimal-controller/states/climb")]
|
|
/// <summary>Climb Logic </summary>
|
|
public class Climb : State
|
|
{
|
|
public override string StateName => "Climb/Free Climb";
|
|
|
|
|
|
/// <summary>Air Resistance while falling</summary>
|
|
[Header("Climb Parameters"), Space]
|
|
[Tooltip("Layer to identify climbable surfaces")]
|
|
public LayerReference ClimbLayer = new LayerReference(1);
|
|
|
|
//[Tooltip("Tag used to identify climbable surfaces. Default: [Climb]")]
|
|
//public StringReference SurfaceTag = new StringReference("Climb");
|
|
public PhysicMaterial Surface;
|
|
[Tooltip("Climb automatically when is near a climbable surface")]
|
|
public BoolReference automatic = new BoolReference();
|
|
|
|
/// <summary>Air Resistance while falling</summary>
|
|
[Header("Climb Pivot"), Space]
|
|
|
|
[Tooltip("Pivot to Cast a Ray from the Chest")]
|
|
public Vector3 ClimbChest = Vector3.up;
|
|
[Tooltip("Pivot to Cast a Ray from the Hip")]
|
|
public Vector3 ClimbHip = Vector3.zero;
|
|
[Tooltip("Distance of the Climb Point Ray")]
|
|
public float ClimbRayLength = 1f;
|
|
|
|
[Tooltip("Radius of the Ray to find a Climable Wall")]
|
|
[Min(0.01f)] public float m_rayRadius= 0.1f;
|
|
|
|
[Header("Wall Detection"), Space]
|
|
[Tooltip("When aligning the Animal to the Wall, this will be the distance needed to separate it from it ")]
|
|
public float WallDistance = 0.2f;
|
|
[Tooltip("Smoothness value to align the animal to the wall")]
|
|
public float AlignSmoothness = 10f;
|
|
[Tooltip("Distance from the Hip Pivot to the Ground")]
|
|
public float GroundDistance = 0.5f;
|
|
[Tooltip("Length of the Horizontal Rays to detect Inner Corners")]
|
|
[Min(0)] public float InnerCorner = 0.4f;
|
|
[Tooltip("Speed Multiplier to move faster around outter corners")]
|
|
[Min(0)] public float OuterCornerSpeed = 0.4f;
|
|
[Range(0,90)] public float ExitSlope = 30f;
|
|
|
|
|
|
[Header("Ledge Detection")]
|
|
[Tooltip("Offset Position to Cast the First Ray on top on the animal to find the ledge")]
|
|
public Vector3 RayLedgeOffset = Vector3.up;
|
|
[Tooltip("Length of the Ledge Ray")]
|
|
[Min(0)] public float RayLedgeLength = 0.4f;
|
|
[Tooltip("MinDistance to exit Climb over a ledge")]
|
|
[FormerlySerializedAs("LedgeMinExit")]
|
|
[Min(0)] public float LedgeExitDistance = 0.175f;
|
|
|
|
|
|
[Header("Exit State Status")]
|
|
[Tooltip("When the Exit Condition Climb Up an edge is executed. The State Exit Status will change to this value")]
|
|
public int ClimbLedge = 1;
|
|
[Tooltip("When the Exit Condition Climb Off is executed. The State Exit Status will change to this value")]
|
|
public int ClimbOff = 2;
|
|
[Tooltip("If the character is Climbing Down and a Ground is found. The State Exit Status will change to this value to execute the ClimbDown Animation")]
|
|
public int ClimbDown = 3;
|
|
[Tooltip("When the Exit Condition Climb Down to the Ground executed. The State Exit Status will change to this value")]
|
|
public int ClimbExitSlope = 4;
|
|
|
|
public float RayRadius => m_rayRadius * animal.ScaleFactor;
|
|
|
|
/// <summary> Reference for the current Climbable surface</summary>
|
|
public Transform WallChest { get; private set; }
|
|
|
|
/// <summary> The Animal is on an Inner Corner</summary>
|
|
private bool InInnerCorner;
|
|
///// <summary> The Animal is on an Outer Corner</summary>
|
|
//private bool InOuterCorner;
|
|
|
|
private readonly RaycastHit[] EdgeHit = new RaycastHit[1];
|
|
|
|
private RaycastHit HitChest;
|
|
private RaycastHit HitHip;
|
|
private RaycastHit HitSide;
|
|
|
|
// private bool DefaultCameraInput;
|
|
//private bool WallClimbTag;
|
|
|
|
/// <summary> World Position of the Climb Pivot </summary>
|
|
public Vector3 ClimbPivotChest(Transform transform) => transform.TransformPoint(ClimbChest);
|
|
public Vector3 ClimbPivotHip(Transform transform) => transform.TransformPoint(ClimbHip);
|
|
|
|
|
|
/// <summary> Average Normal of the Wall</summary>
|
|
public Vector3 AverageNormal { get; private set; }
|
|
|
|
/// <summary>Check if the Current Wall its a valid wall</summary>
|
|
public bool ValidWall { get; private set; }
|
|
|
|
/// <summary>Valid Exit on Ledge</summary>
|
|
public bool ExitOnLedge { get; private set; }
|
|
|
|
/// <summary> Angle From the Gravity and the Wall Normal</summary>
|
|
public float WallAngle { get; private set; }
|
|
|
|
//public override void AwakeState()
|
|
//{
|
|
// DefaultCameraInput = animal.UseCameraInput; //Store the Animal Current CameraInput
|
|
// base.AwakeState();
|
|
//}
|
|
|
|
public override void StatebyInput()
|
|
{
|
|
if (InputValue && !ExitInputValue && CheckClimbRay())
|
|
{
|
|
Activate();
|
|
}
|
|
}
|
|
|
|
public override void StateExitByInput()
|
|
{
|
|
AllowExit(StateEnum.Fall, ClimbOff);
|
|
Debugging($"Exit with Climb Input [{ExitInput.Value}]");
|
|
}
|
|
|
|
public override void Activate()
|
|
{
|
|
base.Activate();
|
|
animal.UseCameraInput = false; //Climb cannot use Camera Input
|
|
animal.DisablePivotChest();
|
|
InInnerCorner = /*InOuterCorner =*/ false;
|
|
}
|
|
|
|
public override bool TryActivate()
|
|
{
|
|
if (automatic && !ExitInputValue)
|
|
{
|
|
return CheckClimbRay();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
public override void ResetStateValues()
|
|
{
|
|
WallChest = null;
|
|
WallAngle = 90;
|
|
LedgeHitDifference = 0;
|
|
ExitOnLedge = false;
|
|
animal.ResetCameraInput();
|
|
InInnerCorner = /*InOuterCorner =*/false;
|
|
}
|
|
|
|
public override void RestoreAnimalOnExit()
|
|
{
|
|
animal.ResetPivotChest();
|
|
animal.ResetCameraInput();
|
|
}
|
|
|
|
|
|
|
|
/// <summary>Current Direction Speed Applied to the Additional Speed, by default is the Animal Forward Direction</summary>
|
|
public override Vector3 Speed_Direction()
|
|
{
|
|
return Up * (animal.VerticalSmooth) + Right * animal.HorizontalSmooth; //IMPORTANT OF ADDITIONAL SPEED
|
|
}
|
|
|
|
private bool CheckClimbRay()
|
|
{
|
|
if (InInnerCorner) return false; //Do nothing when the Animal is changing on the inner corner
|
|
|
|
|
|
var Point_Chest = ClimbPivotChest(transform) + DeltaPos;
|
|
var Point_Hip = ClimbPivotHip(transform) + DeltaPos;
|
|
var Length = animal.ScaleFactor * ClimbRayLength;
|
|
|
|
HitChest = new RaycastHit();
|
|
HitHip = new RaycastHit();
|
|
|
|
|
|
Debug.DrawRay(Point_Chest, Forward * ClimbRayLength, Color.green);
|
|
Debug.DrawRay(Point_Chest, Forward * WallDistance, Color.red);
|
|
|
|
Debug.DrawRay(Point_Hip, Forward * ClimbRayLength, Color.green);
|
|
Debug.DrawRay(Point_Hip, Forward * WallDistance, Color.red);
|
|
|
|
ValidWall = false;
|
|
AverageNormal = Forward;
|
|
|
|
if (Physics.SphereCast(Point_Chest, RayRadius, Forward, out HitChest, Length, ClimbLayer.Value, IgnoreTrigger))
|
|
{
|
|
ValidWall = HitChest.collider.sharedMaterial == Surface;
|
|
|
|
if (ValidWall)
|
|
{
|
|
AverageNormal = HitChest.normal;
|
|
DebugRays(HitChest.point, HitChest.normal);
|
|
ValidWall = false; //Reset again just to check that the second cast works too
|
|
|
|
if (Physics.SphereCast(Point_Hip, RayRadius, Forward, out HitHip, Length, ClimbLayer.Value, IgnoreTrigger))
|
|
{
|
|
ValidWall = HitHip.collider.sharedMaterial == Surface;
|
|
DebugRays(HitHip.point, HitHip.normal);
|
|
AverageNormal += HitHip.normal;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (animal.platform != HitChest.transform && HitChest.transform != null) animal.SetPlatform(HitChest.transform); //Set new Platform
|
|
|
|
WallAngle = Vector3.SignedAngle(AverageNormal, animal.UpVector, animal.Right);
|
|
return ValidWall;
|
|
}
|
|
|
|
private void DebugRays(Vector3 p,Vector3 Normal)
|
|
{
|
|
MTools.DrawWireSphere(p + (Normal * RayRadius), Color.green, RayRadius);
|
|
Debug.DrawRay(p, Normal * RayRadius * 2, Color.green);
|
|
}
|
|
|
|
public override void OnStateMove(float deltatime)
|
|
{
|
|
if (ExitOnLedge) //Meaning It can Exit on Ledge so smooth the exit
|
|
{
|
|
var dif = LedgeHitDifference * deltatime * AlignSmoothness;
|
|
animal.AdditivePosition -= dif * animal.UpVector;
|
|
LedgeHitDifference -= Mathf.Clamp(dif, 0, dif);
|
|
//Debug.Log("LedgeHitDifference: " + LedgeHitDifference);
|
|
animal.PlatformMovement();
|
|
return;
|
|
}
|
|
|
|
if (InCoreAnimation)
|
|
{
|
|
var Right = animal.Right;
|
|
|
|
if (CheckClimbRay())
|
|
{
|
|
if (HitChest.transform != null)
|
|
animal.PlatformMovement();
|
|
//CheckMovingWall(HitHip.transform, deltatime);
|
|
|
|
if (MovementRaw.x > 0) //Means is going Right
|
|
{
|
|
CalculateSideClimbHit(Right);
|
|
}
|
|
else if (MovementRaw.x < 0)//Means is going Left
|
|
{
|
|
CalculateSideClimbHit(-Right);
|
|
}
|
|
else //Not moving Horizontally
|
|
{
|
|
}
|
|
|
|
AlignToWall(HitChest.distance, deltatime);
|
|
OrientToWall(AverageNormal, deltatime);
|
|
}
|
|
else if (InInnerCorner)
|
|
{
|
|
OrientToWall(AverageNormal, deltatime);
|
|
var Angle = Vector3.Angle(animal.Forward, -AverageNormal);
|
|
if (Angle < 5f) InInnerCorner = false;
|
|
}
|
|
}
|
|
//Climb from ground.
|
|
else if (InEnterAnimation/* || InExitAnimation*/) //If we are on Climb Start do a quick alignment to the Wall.
|
|
{
|
|
if (CheckClimbRay())
|
|
{
|
|
OrientToWall(AverageNormal, deltatime);
|
|
AlignToWall(HitChest.distance, deltatime);
|
|
animal.PlatformMovement();
|
|
}
|
|
}
|
|
}
|
|
|
|
private float LedgeHitDifference;
|
|
|
|
private void CalculateSideClimbHit(Vector3 Direction)
|
|
{
|
|
var Ray1 = Color.blue;
|
|
var Ray2 = Color.blue;
|
|
var Ray3 = Color.blue;
|
|
|
|
var Forward = animal.Forward;
|
|
// var ScaleFactor = animal.ScaleFactor;
|
|
var CornerLength = InnerCorner * animal.ScaleFactor;
|
|
var point = ClimbPivotChest(transform);
|
|
|
|
MovementAxisMult.x = 1;
|
|
|
|
if (Physics.Raycast(point, Direction, out HitSide, CornerLength, ClimbLayer, IgnoreTrigger))
|
|
{
|
|
Ray1 = Color.green;
|
|
|
|
if (HitSide.collider.sharedMaterial == Surface) //Next Surface is Climbable
|
|
{
|
|
AverageNormal = HitSide.normal;
|
|
InInnerCorner = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var SecondPoint = point + Direction * CornerLength;
|
|
if (Physics.Raycast(SecondPoint, Forward, out HitSide, CornerLength, ClimbLayer, IgnoreTrigger))
|
|
{
|
|
Ray2 = Color.green;
|
|
if (HitSide.transform != HitChest.transform) //Stop if the Surface is not climbable
|
|
{
|
|
if (HitSide.collider.sharedMaterial != Surface)
|
|
{
|
|
MovementAxisMult.x = 0;
|
|
animal.additivePosition.x = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var ThirdPoint = SecondPoint + Forward * CornerLength;
|
|
|
|
if (Physics.Raycast(ThirdPoint, -Direction, out HitSide, CornerLength, ClimbLayer, IgnoreTrigger))
|
|
{
|
|
Ray3 = Color.green;
|
|
if (HitSide.collider.sharedMaterial != Surface)
|
|
{
|
|
MovementAxisMult.x = 0;
|
|
Ray3 = Color.red;
|
|
}
|
|
else
|
|
{
|
|
animal.AdditivePosition += Direction * animal.DeltaTime * OuterCornerSpeed; //Make a Fast Movement to quickly move to the next corner
|
|
}
|
|
}
|
|
|
|
Debug.DrawRay(ThirdPoint, -Direction * CornerLength, Ray3);
|
|
}
|
|
|
|
Debug.DrawRay(SecondPoint, Forward * CornerLength, Ray2);
|
|
}
|
|
|
|
Debug.DrawRay(point, Direction * CornerLength, Ray1);
|
|
}
|
|
|
|
|
|
//Align the Animal to the Wall
|
|
private void AlignToWall(float distance, float deltatime)
|
|
{
|
|
float difference = distance - WallDistance * animal.ScaleFactor;
|
|
|
|
if (!Mathf.Approximately(distance, WallDistance * animal.ScaleFactor))
|
|
{
|
|
Vector3 align = animal.Forward * difference * deltatime * AlignSmoothness;
|
|
animal.AdditivePosition += align;
|
|
}
|
|
}
|
|
|
|
|
|
private void OrientToWall(Vector3 normal, float deltatime)
|
|
{
|
|
Quaternion AlignRot = Quaternion.FromToRotation(Forward, -normal) * transform.rotation; //Calculate the orientation to Terrain
|
|
Quaternion Inverse_Rot = Quaternion.Inverse(transform.rotation);
|
|
Quaternion Target = Inverse_Rot * AlignRot;
|
|
Quaternion Delta = Quaternion.Lerp(Quaternion.identity, Target, deltatime * AlignSmoothness); //Calculate the Delta Align Rotation
|
|
animal.AdditiveRotation *= Delta;
|
|
|
|
//Update the Rotation to always look Upwards
|
|
var UP = Vector3.Cross(Forward, UpVector);
|
|
UP = Vector3.Cross(UP, Forward);
|
|
AlignRot = Quaternion.FromToRotation(transform.up, UP) * transform.rotation; //Calculate the orientation to Terrain
|
|
Inverse_Rot = Quaternion.Inverse(transform.rotation);
|
|
Target = Inverse_Rot * AlignRot;
|
|
animal.AdditiveRotation *= Target;
|
|
}
|
|
|
|
public override void TryExitState(float DeltaTime)
|
|
{
|
|
var MainPivot = ClimbPivotChest(transform) + animal.AdditivePosition;
|
|
|
|
|
|
if (Mathf.Abs(WallAngle) < ExitSlope)//Exit when the angle is max from the slope
|
|
{
|
|
Debugging("[Allow Exit] Slope is walkable");
|
|
AllowExit(StateEnum.Locomotion, ClimbExitSlope); //Force the Idle State to be the next State
|
|
animal.CheckIfGrounded();
|
|
return;
|
|
}
|
|
|
|
//PRESSING DOWN
|
|
if (MovementRaw.z < 0) //Means the animal is going down
|
|
{
|
|
Debug.DrawRay(MainPivot, -Up * ScaleFactor * GroundDistance, Color.white);
|
|
|
|
|
|
//Means that the Animal is going down and touching the ground
|
|
if (Physics.Raycast(MainPivot, -Up, out _, ScaleFactor * GroundDistance, animal.GroundLayer, IgnoreTrigger))
|
|
{
|
|
Debugging("[Allow Exit] when Grounded and pressing Down and touched the ground");
|
|
AllowExit(StateEnum.Idle, ClimbDown); //Force the Idle State to be the next State
|
|
animal.CheckIfGrounded();
|
|
}
|
|
}
|
|
else if (!ValidWall) //Means the Animal did not touch a Wall Tagged Climb
|
|
{
|
|
Debugging("[Allow Exit] Exit when Wall is not Climbable");
|
|
AllowExit();
|
|
}
|
|
else if (!ExitOnLedge) //Means the Animal going Up
|
|
{
|
|
CheckLedgeExit();
|
|
}
|
|
}
|
|
|
|
private void CheckLedgeExit()
|
|
{
|
|
var LedgePivotUP = transform.TransformPoint(ClimbChest+RayLedgeOffset);
|
|
|
|
//Check Upper Ground legde Detection
|
|
bool LedgeHit = Physics.RaycastNonAlloc(LedgePivotUP, Forward, EdgeHit, ScaleFactor * RayLedgeLength, ClimbLayer.Value, IgnoreTrigger) > 0;
|
|
var SecondRayPivot = new Ray(LedgePivotUP, Forward).GetPoint(RayLedgeLength);
|
|
|
|
Debug.DrawRay(LedgePivotUP, Forward * RayLedgeLength, Color.green);
|
|
Debug.DrawRay(SecondRayPivot, Gravity * RayLedgeLength*2, Color.green);
|
|
Debug.DrawRay(SecondRayPivot, Gravity * LedgeExitDistance, Color.red);
|
|
|
|
if (!LedgeHit)
|
|
{
|
|
LedgeHit = Physics.RaycastNonAlloc(SecondRayPivot, Gravity, EdgeHit, ScaleFactor * RayLedgeLength*2, ClimbLayer.Value, IgnoreTrigger) > 0;
|
|
|
|
if (LedgeHit)
|
|
{
|
|
var hit = EdgeHit[0];
|
|
if (hit.distance > LedgeExitDistance * ScaleFactor)
|
|
{
|
|
LedgeHitDifference = (hit.distance - LedgeExitDistance * ScaleFactor);
|
|
ExitOnLedge = true; //Activate Exit OnLedge
|
|
Debugging("Allow Exit - Exit on a Ledge");
|
|
SetExitStatus(ClimbLedge); //Keep this State as the Active State
|
|
//AllowExit(StateEnum.Locomotion, ClimbLedge); //Force Locomotion State to be the next state, it also set the Exit Status
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void StateGizmos(MAnimal animal)
|
|
{
|
|
if (debug && !Application.isPlaying)
|
|
{
|
|
var Forward = animal.Forward;
|
|
var Right = animal.Right;
|
|
var t = animal.transform;
|
|
var Gravity = animal.Gravity;
|
|
var ScaleFactor = animal.ScaleFactor;
|
|
|
|
|
|
var Chest_Point = ClimbPivotChest(t);
|
|
var Hip_Point = ClimbPivotHip(t);
|
|
|
|
var LedgePivotUP = t.TransformPoint(ClimbChest+RayLedgeOffset);
|
|
var SecondRayPivot = new Ray(LedgePivotUP, animal.Forward * ScaleFactor).GetPoint(RayLedgeLength);
|
|
|
|
|
|
Gizmos.color = Color.green;
|
|
Gizmos.DrawRay(SecondRayPivot, Gravity * ScaleFactor * RayLedgeLength*2);
|
|
Gizmos.color = Color.red;
|
|
Gizmos.DrawRay(SecondRayPivot, Gravity * ScaleFactor * LedgeExitDistance);
|
|
|
|
Gizmos.color = Color.green;
|
|
Gizmos.DrawRay(LedgePivotUP, Forward * ScaleFactor * RayLedgeLength);
|
|
|
|
Gizmos.DrawRay(Chest_Point, Forward * ScaleFactor * ClimbRayLength);
|
|
Gizmos.DrawRay(Hip_Point, Forward * ScaleFactor * ClimbRayLength);
|
|
Gizmos.color = Color.red;
|
|
Gizmos.DrawRay(Chest_Point, Forward * ScaleFactor * WallDistance);
|
|
Gizmos.DrawRay(Hip_Point, Forward * ScaleFactor * WallDistance);
|
|
|
|
|
|
Gizmos.color = Color.green;
|
|
Gizmos.DrawWireSphere(Chest_Point + Forward * (ClimbRayLength - (m_rayRadius * ScaleFactor)), m_rayRadius * ScaleFactor);
|
|
Gizmos.DrawWireSphere(Hip_Point + Forward * (ClimbRayLength - (m_rayRadius * ScaleFactor)), m_rayRadius * ScaleFactor);
|
|
Gizmos.DrawRay(Chest_Point, Right * ScaleFactor * InnerCorner);
|
|
Gizmos.DrawRay(Chest_Point, -Right * ScaleFactor * InnerCorner);
|
|
Gizmos.color = Color.white;
|
|
var MainPivot = ClimbPivotChest(t);
|
|
Gizmos.DrawRay(MainPivot, -animal.Up * ScaleFactor * GroundDistance);
|
|
}
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
LedgeExitDistance = Mathf.Clamp(LedgeExitDistance, 0, RayLedgeLength);
|
|
}
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
void Reset()
|
|
{
|
|
ID = MTools.GetInstance<StateID>("Climb");
|
|
|
|
Surface = MTools.GetResource<PhysicMaterial>("Climbable");
|
|
|
|
General = new AnimalModifier()
|
|
{
|
|
modify = (modifier)(-1),
|
|
RootMotion = true,
|
|
AdditivePosition = true,
|
|
AdditiveRotation = false,
|
|
Grounded = false,
|
|
Sprint = true,
|
|
OrientToGround = false,
|
|
Gravity = false,
|
|
CustomRotation = true,
|
|
FreeMovement = false,
|
|
IgnoreLowerStates = true,
|
|
};
|
|
}
|
|
#endif
|
|
}
|
|
} |