BITFALL/Assets/Plugins/Character Controller Pro/Demo/Scripts/States/LadderClimbing.cs

339 lines
12 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using Lightbug.CharacterControllerPro.Core;
using Lightbug.CharacterControllerPro.Implementation;
using Lightbug.Utilities;
using static UnityEngine.EventSystems.PointerEventData;
namespace Lightbug.CharacterControllerPro.Demo
{
[AddComponentMenu("Character Controller Pro/Demo/Character/States/Ladder Climbing")]
public class LadderClimbing : CharacterState
{
[Header("Offset")]
[SerializeField]
protected bool useIKOffsetValues = false;
[Condition("useIKOffsetValues", ConditionAttribute.ConditionType.IsTrue)]
[SerializeField]
protected Vector3 rightFootOffset = Vector3.zero;
[Condition("useIKOffsetValues", ConditionAttribute.ConditionType.IsTrue)]
[SerializeField]
protected Vector3 leftFootOffset = Vector3.zero;
[Condition("useIKOffsetValues", ConditionAttribute.ConditionType.IsTrue)]
[SerializeField]
protected Vector3 rightHandOffset = Vector3.zero;
[Condition("useIKOffsetValues", ConditionAttribute.ConditionType.IsTrue)]
[SerializeField]
protected Vector3 leftHandOffset = Vector3.zero;
[Header("Activation")]
[SerializeField]
protected bool useInteractAction = true;
[SerializeField]
protected bool filterByAngle = true;
[SerializeField]
protected float maxAngle = 70f;
[Header("Animation")]
[SerializeField]
protected string bottomDownParameter = "BottomDown";
[SerializeField]
protected string bottomUpParameter = "BottomUp";
[SerializeField]
protected string topDownParameter = "TopDown";
[SerializeField]
protected string topUpParameter = "TopUp";
[SerializeField]
protected string upParameter = "Up";
[SerializeField]
protected string downParameter = "Down";
[Space(10f)]
[SerializeField]
protected string entryStateName = "Entry";
[SerializeField]
protected string exitStateName = "Exit";
[SerializeField]
protected string idleStateName = "Idle";
// ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
protected Dictionary<Transform, Ladder> ladders = new Dictionary<Transform, Ladder>();
public enum LadderClimbState
{
Entering,
Exiting,
Idling,
Climbing,
}
protected LadderClimbState state;
protected Ladder currentLadder = null;
protected Vector3 targetPosition = Vector3.zero;
protected int currentClimbingAnimation = 0;
protected bool forceExit = false;
protected AnimatorStateInfo animatorStateInfo;
protected bool isBottom = false;
public override void CheckExitTransition()
{
if (forceExit)
CharacterStateController.EnqueueTransition<NormalMovement>();
}
public override bool CheckEnterTransition(CharacterState fromState)
{
if (!CharacterActor.IsGrounded)
return false;
if (useInteractAction && !CharacterActions.interact.Started)
return false;
for (int i = 0; i < CharacterActor.Triggers.Count; i++)
{
Trigger trigger = CharacterActor.Triggers[i];
Ladder ladder = ladders.GetOrRegisterValue(trigger.transform);
if (ladder != null)
{
if (!useInteractAction && CharacterActor.WasGrounded && !trigger.firstContact)
{
return false;
}
currentLadder = ladder;
// Check if the character is closer to the top of the ladder.
float distanceToTop = Vector3.Distance(CharacterActor.Position, currentLadder.TopReference.position);
float distanceToBottom = Vector3.Distance(CharacterActor.Position, currentLadder.BottomReference.position);
isBottom = distanceToBottom < distanceToTop;
if (filterByAngle)
{
Vector3 ladderToCharacter = CharacterActor.Position - currentLadder.transform.position;
ladderToCharacter = Vector3.ProjectOnPlane(ladderToCharacter, currentLadder.transform.up);
float angle = Vector3.Angle(currentLadder.FacingDirectionVector, ladderToCharacter);
if (isBottom)
{
if (angle >= maxAngle)
return true;
else
continue;
}
else
{
if (angle <= maxAngle)
return true;
else
continue;
}
}
else
{
return true;
}
}
}
return false;
}
public override void EnterBehaviour(float dt, CharacterState fromState)
{
CharacterActor.Velocity = Vector3.zero;
CharacterActor.IsKinematic = true;
CharacterActor.alwaysNotGrounded = true;
currentClimbingAnimation = isBottom ? 0 : currentLadder.ClimbingAnimations;
targetPosition = isBottom ? currentLadder.BottomReference.position : currentLadder.TopReference.position;
CharacterActor.SetYaw(currentLadder.FacingDirectionVector);
CharacterActor.Position = targetPosition;
// Root motion
CharacterActor.SetUpRootMotion(
true,
PhysicsActor.RootMotionVelocityType.SetVelocity,
false
);
CharacterActor.Animator.SetTrigger(isBottom ? bottomUpParameter : topDownParameter);
state = LadderClimbState.Entering;
}
public override void ExitBehaviour(float dt, CharacterState toState)
{
forceExit = false;
CharacterActor.IsKinematic = false;
CharacterActor.alwaysNotGrounded = false;
currentLadder = null;
CharacterStateController.ResetIKWeights();
CharacterActor.Velocity = Vector3.zero;
CharacterActor.ForceGrounded();
}
protected override void Awake()
{
base.Awake();
#if UNITY_2023_1_OR_NEWER
Ladder[] laddersArray = FindObjectsByType<Ladder>(FindObjectsSortMode.None);
#else
Ladder[] laddersArray = FindObjectsOfType<Ladder>();
#endif
for (int i = 0; i < laddersArray.Length; i++)
ladders.Add(laddersArray[i].transform, laddersArray[i]);
}
protected override void Start()
{
base.Start();
if (CharacterActor.Animator == null)
{
Debug.Log("The LadderClimbing state needs the character to have a reference to an Animator component. Destroying this state...");
Destroy(this);
}
}
public override void UpdateBehaviour(float dt)
{
animatorStateInfo = CharacterActor.Animator.GetCurrentAnimatorStateInfo(0);
switch (state)
{
case LadderClimbState.Entering:
// The LadderClimbing state has just begun, Make sure to wait for the "Idle" animation state.
// Important: Note that the animation clip from this state (Animator) is the same as the locomotion idle clip.
if (animatorStateInfo.IsName(idleStateName))
state = LadderClimbState.Idling;
break;
case LadderClimbState.Idling:
// This state is responsible for handling inputs and setting animation triggers.
if (CharacterActions.interact.Started)
{
if (useInteractAction)
forceExit = true;
}
else
{
if (CharacterActions.movement.Up)
{
if (currentClimbingAnimation == currentLadder.ClimbingAnimations)
{
CharacterActor.Animator.SetTrigger(topUpParameter);
state = LadderClimbState.Exiting;
}
else
{
CharacterActor.Animator.SetTrigger(upParameter);
currentClimbingAnimation++;
state = LadderClimbState.Climbing;
}
}
else if (CharacterActions.movement.Down)
{
if (currentClimbingAnimation == 0)
{
CharacterActor.Animator.SetTrigger(bottomDownParameter);
state = LadderClimbState.Exiting;
}
else
{
CharacterActor.Animator.SetTrigger(downParameter);
currentClimbingAnimation--;
state = LadderClimbState.Climbing;
}
}
}
break;
case LadderClimbState.Climbing:
// Do nothing and wait for the "Idle" animation state
if (animatorStateInfo.IsName(idleStateName))
state = LadderClimbState.Idling;
break;
case LadderClimbState.Exiting:
// Do nothing and wait for the "Exit" animation state
// Important: Note that the animation clip from this state (Animator) is the same as the locomotion idle clip.
if (animatorStateInfo.IsName(exitStateName))
{
forceExit = true;
CharacterActor.ForceGrounded();
}
break;
}
}
public override void UpdateIK(int layerIndex)
{
if (!useIKOffsetValues)
return;
UpdateIKElement(AvatarIKGoal.LeftFoot, leftFootOffset);
UpdateIKElement(AvatarIKGoal.RightFoot, rightFootOffset);
UpdateIKElement(AvatarIKGoal.LeftHand, leftHandOffset);
UpdateIKElement(AvatarIKGoal.RightHand, rightHandOffset);
}
void UpdateIKElement(AvatarIKGoal avatarIKGoal, Vector3 offset)
{
// Get the original (weight = 0) ik position.
CharacterActor.Animator.SetIKPositionWeight(avatarIKGoal, 0f);
Vector3 originalRightFootPosition = CharacterActor.Animator.GetIKPosition(avatarIKGoal);
// Affect the original ik position with the offset.
CharacterActor.Animator.SetIKPositionWeight(avatarIKGoal, 1f);
CharacterActor.Animator.SetIKPosition(avatarIKGoal, originalRightFootPosition + offset);
}
}
}