using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; namespace Invector.vCharacterController.vActions { [vClassHeader("Ladder Action", iconName = "ladderIcon")] public class vLadderAction : vActionListener { #region public variables [vEditorToolbar("Settings", overrideChildOrder: true, order = 0)] [Tooltip("Tag of the object you want to access")] public string actionTag = "LadderTrigger"; [Tooltip("Speed multiplier for the climb ladder animations")] public float climbSpeed = 1.5f; [Tooltip("Speed multiplier for the climb ladder animations when the fastClimbInput is pressed")] public float fastClimbSpeed = 3f; [Tooltip("How much Stamina will be consumed when climbing faster")] public float fastClimbStamina = 30f; [Tooltip("Input to use the ladder going up or down")] public GenericInput verticallInput = new GenericInput("Vertical", "LeftAnalogVertical", "Vertical"); [Tooltip("Input to enter the ladder")] public GenericInput enterInput = new GenericInput("E", "A", "A"); [Tooltip("Input to exit the ladder")] public GenericInput exitInput = new GenericInput("Space", "B", "B"); [Tooltip("Input to climb faster")] public GenericInput fastClimbInput = new GenericInput("LeftShift", "LeftStickClick", "LeftStickClick"); [Tooltip("Input to climb faster")] public GenericInput slideDownInput = new GenericInput("Q", "X", "X"); [vEditorToolbar("Events")] public UnityEvent OnEnterLadder; public UnityEvent OnExitLadder; public UnityEvent OnEnterTriggerLadder; public UnityEvent OnExitTriggerLadder; [vEditorToolbar("Debug")] public bool debugMode; [vReadOnly(false)] [SerializeField] protected vTriggerLadderAction targetLadderAction; [vReadOnly(false)] [SerializeField] protected vTriggerLadderAction currentLadderAction; //[vReadOnly(false)] //[SerializeField] protected List actionTriggers = new List(); [vReadOnly(false)] [SerializeField] protected float speed; [vReadOnly(false)] [SerializeField] protected float currentClimbSpeed; [vReadOnly(false)] [SerializeField] protected bool isUsingLadder; [vReadOnly(false)] [SerializeField] protected bool enterLadderStarted; [vReadOnly(false)] [SerializeField] protected bool inEnterLadderAnimation; [vReadOnly(false)] [SerializeField] protected bool inExitingLadderAnimation; [vReadOnly(false)] [SerializeField] protected bool triggerEnterOnce; [vReadOnly(false)] [SerializeField] protected bool triggerExitOnce; #endregion protected vThirdPersonInput tpInput; protected override void SetUpListener() { actionEnter = false; actionStay = true; actionExit = true; } protected override void Start() { base.Start(); tpInput = GetComponent(); if (tpInput) { tpInput.onUpdate -= UpdateLadderBehavior; tpInput.onUpdate += UpdateLadderBehavior; tpInput.onAnimatorMove -= UsingLadder; tpInput.onAnimatorMove += UsingLadder; //tpInput.cc.onDead.AddListener(OnDead); } } /* private void OnDead(GameObject g) { if (isUsingLadder) ResetPlayerSettings(); } */ protected virtual void UpdateLadderBehavior() { AutoEnterLadder(); EnterLadderInput(); ExitLadderInput(); } protected virtual void EnterLadderInput() { if (targetLadderAction == null || tpInput.cc.customAction || tpInput.cc.isJumping || !tpInput.cc.isGrounded || tpInput.cc.isRolling) { return; } if (enterInput.GetButtonDown() && !enterLadderStarted && !isUsingLadder && !targetLadderAction.autoAction) { TriggerEnterLadder(); } } protected virtual void ExitLadderInput() { if (!isUsingLadder) { return; } if (tpInput.cc.baseLayerInfo.IsName("EnterLadderTop") || tpInput.cc.baseLayerInfo.IsName("EnterLadderBottom")) { return; } if (targetLadderAction == null) { if (tpInput.cc.IsAnimatorTag("ClimbLadder")) { if (slideDownInput.GetButtonDown() && !inExitingLadderAnimation) { tpInput.cc.animator.CrossFadeInFixedTime("Ladder_SlideDown", 0.2f); } // exit ladder at any moment by pressing the cancelInput if (exitInput.GetButtonDown()) { if (debugMode) { Debug.Log("Quick Exit..." + currentLadderAction.name + "_" + currentLadderAction.transform.parent.gameObject.name); } tpInput.cc.animator.speed = 1; tpInput.cc.animator.CrossFadeInFixedTime("QuickExitLadder", 0.1f); Invoke("ResetPlayerSettings", .5f); } } } else { currentLadderAction = targetLadderAction; var animationClip = targetLadderAction.exitAnimation; if (animationClip == "ExitLadderBottom") { // exit ladder when reach the bottom by pressing the cancelInput or pressing down at if (exitInput.GetButtonDown() && !triggerExitOnce || (speed <= -0.05f && !triggerExitOnce) || (tpInput.cc.IsAnimatorTag("LadderSlideDown") && targetLadderAction != null && !triggerExitOnce)) { if (debugMode) { Debug.Log("Exit Bottom..." + currentLadderAction.name + "_" + currentLadderAction.transform.parent.gameObject.name); } triggerExitOnce = true; tpInput.cc.animator.CrossFadeInFixedTime(targetLadderAction.exitAnimation, 0.1f); // trigger the animation clip } } else if (animationClip == "ExitLadderTop" && tpInput.cc.IsAnimatorTag("ClimbLadder")) // exit the ladder from the top { if ((speed >= 0.05f) && !triggerExitOnce && !tpInput.cc.animator.IsInTransition(0)) // trigger the exit animation by pressing up { if (debugMode) { Debug.Log("Exit Top..." + currentLadderAction.name + "_" + currentLadderAction.transform.parent.gameObject.name); } triggerExitOnce = true; tpInput.cc.animator.CrossFadeInFixedTime(targetLadderAction.exitAnimation, 0.1f); // trigger the animation clip } } } } protected virtual void AutoEnterLadder() { if (targetLadderAction == null || !targetLadderAction.autoAction) { return; } if (tpInput.cc.customAction || isUsingLadder || tpInput.cc.animator.IsInTransition(0)) { return; } // enter the ladder automatically if checked with autoAction if (targetLadderAction.autoAction && tpInput.cc.input != Vector3.zero && !tpInput.cc.customAction) { var inputDir = Camera.main.transform.TransformDirection(new Vector3(tpInput.cc.input.x, 0f, tpInput.cc.input.z)); inputDir.y = 0f; var dist = Vector3.Distance(inputDir.normalized, targetLadderAction.transform.forward); if (dist < 0.8f) { TriggerEnterLadder(); } } } protected virtual void TriggerEnterLadder() { if (debugMode) { Debug.Log("Enter Ladder"); } OnExitTriggerLadder.Invoke(); if (targetLadderAction.targetCharacterParent) { transform.parent = targetLadderAction.targetCharacterParent; } tpInput.cc.isCrouching = false; tpInput.cc.ControlCapsuleHeight(); tpInput.UpdateCameraStates(); tpInput.cc.UpdateAnimator(); OnEnterLadder.Invoke(); triggerEnterOnce = true; enterLadderStarted = true; tpInput.cc.animator.SetInteger(vAnimatorParameters.ActionState, 1); // set actionState 1 to avoid falling transitions tpInput.SetLockAllInput(true); tpInput.cc.ResetInputAnimatorParameters(); targetLadderAction.OnDoAction.Invoke(); currentLadderAction = targetLadderAction; //tpInput.cc.animator.updateMode = AnimatorUpdateMode.Normal; if (!string.IsNullOrEmpty(currentLadderAction.playAnimation)) { if (debugMode) { Debug.Log("TriggerAnimation " + currentLadderAction.name + "_" + currentLadderAction.transform.parent.gameObject.name); } tpInput.cc.animator.CrossFadeInFixedTime(currentLadderAction.playAnimation, 0.25f); // trigger the action animation clip isUsingLadder = true; tpInput.cc.disableAnimations = true; tpInput.cc.StopCharacter(); } } protected virtual void UsingLadder() { if (!isUsingLadder) { return; } // update the base layer to know what animations are being played tpInput.cc.AnimatorLayerControl(); tpInput.cc.ActionsControl(); // update camera movement tpInput.CameraInput(); // go up or down speed = verticallInput.GetAxis(); tpInput.cc.animator.SetFloat(vAnimatorParameters.InputVertical, speed, 0.1f, Time.deltaTime); if (speed >= 0.05f || speed <= -0.05f) { tpInput.cc.animator.speed = Mathf.Lerp(tpInput.cc.animator.speed, currentClimbSpeed, 2f * Time.deltaTime); } else { tpInput.cc.animator.speed = Mathf.Lerp(tpInput.cc.animator.speed, 1f, 2f * Time.deltaTime); } // increase speed by input and consume stamina if (fastClimbInput.GetButton() && tpInput.cc.currentStamina > 0) { currentClimbSpeed = fastClimbSpeed; StaminaConsumption(); } else { currentClimbSpeed = climbSpeed; } // enter ladder behaviour var _inEnterLadderAnimation = tpInput.cc.baseLayerInfo.IsName("EnterLadderTop") || tpInput.cc.baseLayerInfo.IsName("EnterLadderBottom") && !tpInput.cc.animator.IsInTransition(0); if (_inEnterLadderAnimation) { this.inEnterLadderAnimation = true; tpInput.cc.DisableGravityAndCollision(); // disable gravity & turn collision trigger // disable ingame hud if (currentLadderAction != null) { currentLadderAction.OnPlayerExit.Invoke(); } if (currentLadderAction.useTriggerRotation) { if (debugMode) { Debug.Log("Rotating to target..." + currentLadderAction.name + "_" + currentLadderAction.transform.parent.gameObject.name); } EvaluateToRotation(currentLadderAction.enterRotationCurve, currentLadderAction.matchTarget.transform.rotation, tpInput.cc.baseLayerInfo.normalizedTime); } if (currentLadderAction.matchTarget != null) { if (transform.parent != currentLadderAction.targetCharacterParent) { transform.parent = currentLadderAction.targetCharacterParent; } if (debugMode) { Debug.Log("Match Target to Enter..." + currentLadderAction.name + "_" + currentLadderAction.transform.parent.gameObject.name); } EvaluateToPosition(currentLadderAction.enterPositionXZCurve, currentLadderAction.enterPositionYCurve, currentLadderAction.matchTarget.position, tpInput.cc.baseLayerInfo.normalizedTime); } } if (!_inEnterLadderAnimation && inEnterLadderAnimation) { enterLadderStarted = false; inEnterLadderAnimation = false; } TriggerExitLadder(); } protected virtual void TriggerExitLadder() { // exit ladder behaviour inExitingLadderAnimation = tpInput.cc.baseLayerInfo.IsName("ExitLadderTop") || tpInput.cc.baseLayerInfo.IsName("ExitLadderBottom") || tpInput.cc.baseLayerInfo.IsName("QuickExitLadder"); if (inExitingLadderAnimation) { tpInput.cc.animator.speed = 1; if (currentLadderAction.exitMatchTarget != null && !tpInput.cc.baseLayerInfo.IsName("QuickExitLadder")) { if (debugMode) { Debug.Log("Match Target to exit..." + currentLadderAction.name + "_" + currentLadderAction.transform.parent.gameObject.name); } EvaluateToPosition(currentLadderAction.exitPositionXZCurve, currentLadderAction.exitPositionYCurve, currentLadderAction.exitMatchTarget.position, tpInput.cc.baseLayerInfo.normalizedTime); } var newRot = new Vector3(0, tpInput.animator.rootRotation.eulerAngles.y, 0); EvaluateToRotation(currentLadderAction.exitRotationCurve, Quaternion.Euler(newRot), tpInput.cc.baseLayerInfo.normalizedTime); if (tpInput.cc.baseLayerInfo.normalizedTime >= 0.8f) { // after playing the animation we reset some values ResetPlayerSettings(); } } } protected virtual void EvaluateToPosition(AnimationCurve XZ, AnimationCurve Y, Vector3 targetPosition, float normalizedTime) { Vector3 rootPosition = tpInput.cc.animator.rootPosition; float evaluatedXZ = XZ.Evaluate(normalizedTime); float evaluatedY = Y.Evaluate(normalizedTime); if (evaluatedXZ < 1f) { rootPosition.x = Mathf.Lerp(rootPosition.x, targetPosition.x, evaluatedXZ); rootPosition.z = Mathf.Lerp(rootPosition.z, targetPosition.z, evaluatedXZ); } if (evaluatedY < 1f) { rootPosition.y = Mathf.Lerp(rootPosition.y, targetPosition.y, evaluatedY); } transform.position = rootPosition; } protected virtual void EvaluateToRotation(AnimationCurve curve, Quaternion targetRotation, float normalizedTime) { Quaternion rootRotation = tpInput.cc.animator.rootRotation; float evaluatedCurve = curve.Evaluate(normalizedTime); if (evaluatedCurve < 1) { rootRotation = Quaternion.Lerp(rootRotation, targetRotation, evaluatedCurve); } transform.rotation = rootRotation; } protected virtual void StaminaConsumption() { if (tpInput.cc.currentStamina <= 0) { return; } tpInput.cc.ReduceStamina(fastClimbStamina, true); // call the ReduceStamina method from the player tpInput.cc.currentStaminaRecoveryDelay = 0.25f; // delay to start recovery stamina } protected virtual void AddLadderTrigger(vTriggerLadderAction _ladderAction) { if (targetLadderAction != _ladderAction) { targetLadderAction = _ladderAction; if (debugMode) { Debug.Log("TriggerStay " + targetLadderAction.name + "_" + targetLadderAction.transform.parent.gameObject.name); } } if (!actionTriggers.Contains(targetLadderAction)) { actionTriggers.Add(targetLadderAction); targetLadderAction.OnPlayerEnter.Invoke(); } } protected virtual void RemoveLadderTrigger(vTriggerLadderAction _ladderAction) { if (_ladderAction == targetLadderAction) { targetLadderAction = null; } if (actionTriggers.Contains(_ladderAction)) { actionTriggers.Remove(_ladderAction); _ladderAction.OnPlayerExit.Invoke(); } } protected virtual void CheckForTriggerAction(Collider other) { // assign the component - it will be null when he exit the trigger area var _ladderAction = other.GetComponent(); if (!_ladderAction) { return; } // check the maxAngle too see if the character can do the action var dist = Vector3.Distance(transform.forward, _ladderAction.transform.forward); if (isUsingLadder && _ladderAction != null) { if (targetLadderAction != _ladderAction) { targetLadderAction = _ladderAction; if (!actionTriggers.Contains(targetLadderAction)) { actionTriggers.Add(targetLadderAction); } } } else if ((_ladderAction.activeFromForward == false || dist <= 0.8f) && !isUsingLadder) { AddLadderTrigger(_ladderAction); OnEnterTriggerLadder.Invoke(); } else { RemoveLadderTrigger(_ladderAction); } } public virtual void ResetPlayerSettings() { if (debugMode) { Debug.Log("Reset Player Settings"); } speed = 0f; targetLadderAction = null; isUsingLadder = false; OnExitLadder.Invoke(); triggerExitOnce = false; triggerEnterOnce = false; inEnterLadderAnimation = false; enterLadderStarted = false; tpInput.cc.animator.SetInteger(vAnimatorParameters.ActionState, 0); tpInput.cc.EnableGravityAndCollision(); tpInput.SetLockAllInput(false); tpInput.cc.StopCharacter(); tpInput.cc.disableAnimations = false; //tpInput.cc.animator.updateMode = AnimatorUpdateMode.AnimatePhysics; if (transform.parent != null) { transform.parent = null; } } public override void OnActionStay(Collider other) { if (other.gameObject.CompareTag(actionTag) && !enterLadderStarted) { CheckForTriggerAction(other); } } public override void OnActionExit(Collider other) { if (other.gameObject.CompareTag(actionTag)) { var _ladderAction = other.GetComponent(); if (!_ladderAction) { return; } RemoveLadderTrigger(_ladderAction); if (debugMode) { Debug.Log("TriggerExit " + other.name + "_" + other.transform.parent.gameObject.name); } OnExitTriggerLadder.Invoke(); } } } }