using Invector.vCharacterController.vActions; using System.Collections; using System.Collections.Generic; using Invector; using Invector.vCharacterController; using UnityEngine; using UnityEngine.Events; namespace Beyond { [vClassHeader("Footbridge Action", false)] public class vFootbridgeAction : vActionListener { private static readonly int FootbridgeBalans = Animator.StringToHash("FootbridgeBalans"); #region public variables [vEditorToolbar("Settings", overrideChildOrder: true, order = 0)] public float balanceSpeed = 0.5f; public float playerBalanceStrength = 0.2f; [Tooltip("Tag of the object you want to access")] public string actionTag = "FootbridgeTrigger"; [vHelpBox("Disable Selected HUD. Script ActionTriggerEvent.cs")] public bool disableHUD = true; [Tooltip("Speed multiplier for the climb ladder animations")] public float footbridgeSpeed = 1f; [Tooltip("Input to use going forward or backwards")] public GenericInput verticallInput = new GenericInput("Vertical", "LeftAnalogVertical", "Vertical"); [Tooltip("Input to use Balance")] public GenericInput horizontalInput = new GenericInput("Horizontal", "RightAnalogVertical", "Horizontal"); [Tooltip("Input to enter")] public GenericInput enterInput = new GenericInput("E", "A", "A"); [Tooltip("Input to exit")] public GenericInput exitInput = new GenericInput("Space", "X", "B"); [Tooltip("whether we allow exit from interaction on request.")] public bool allowExit; public bool snapLocalPositionX; public bool snapLocalPositionY; public bool snapLocalPositionZ; [Min(0f)] public float snapPositionSpeed = 1f; public Vector3 snapLocalPosition = Vector3.zero; public bool snapLocalRotationToParent; [Min(0f)] public float snapRotationSpeed = 1f; [vEditorToolbar("Events")] public UnityEvent OnEnter; public UnityEvent OnExit; public UnityEvent OnEnterTrigger; public UnityEvent OnExitTrigger; [vEditorToolbar("Debug")] public bool debugMode; [vReadOnly(false)] [SerializeField] protected float balance = 0f; [vReadOnly(false)] [SerializeField] public float playerBalanceInput; private float balanceX; [vReadOnly(false)] [SerializeField] protected vTriggerFootbridgeAction targetAction; [vReadOnly(false)] [SerializeField] protected vTriggerFootbridgeAction currentAction; [vReadOnly(false)] [SerializeField] protected List actionTriggers = new List(); [vReadOnly(false)] [SerializeField] protected bool isUsing; [vReadOnly(false)] [SerializeField] protected bool triggerEnterOnce; [vReadOnly(false)] [SerializeField] protected bool triggerExitOnce; [vReadOnly(false)] [SerializeField] protected bool enterStarted; [vReadOnly(false)] [SerializeField] protected bool inEnterAnimation; [vReadOnly(false)] [SerializeField] protected bool inExitingAnimation; [vReadOnly(false)] [SerializeField] protected float currentSpeed; [vReadOnly(false)] [SerializeField] protected float speed; #endregion protected vThirdPersonInput tpInput; private TriggerDescriptor m_descriptor; protected virtual void Awake() { actionStay = true; actionExit = true; m_descriptor = new TriggerDescriptor(gameObject, TriggerDescriptor.TriggerType.Generic); } private void OnFootbridgeEnter(GameObject gameObject) { if (disableHUD) { balanceX = 0f; //ActionTriggerEvent.EnterFootbridge?.Invoke(gameObject); ActionTriggerEvent.ActionTriggerEnter?.Invoke(m_descriptor); } } private void OnFootbridgeExit(GameObject gameObject) { if (disableHUD) { //ActionTriggerEvent.ExitFootbridge?.Invoke(gameObject); ActionTriggerEvent.ActionTriggerExit?.Invoke(m_descriptor); } } protected override void Start() { base.Start(); OnEnter.AddListener(() => OnFootbridgeEnter(gameObject)); OnExit.AddListener(() => OnFootbridgeExit(gameObject)); tpInput = GetComponent(); if (tpInput) { tpInput.onUpdate -= UpdateBehavior; tpInput.onUpdate += UpdateBehavior; tpInput.onAnimatorMove -= Using; tpInput.onAnimatorMove += Using; } } protected virtual void UpdateBehavior() { AutoEnter(); EnterInput(); ExitInput(); SnapPlayerPosition(); SnapPlayerRotation(); UpdateBalans(); } private void UpdateBalans() { if (tpInput == null) return; if (!isUsing) return; playerBalanceInput = horizontalInput.GetAxis() * playerBalanceStrength; balanceX += Time.deltaTime * balanceSpeed + playerBalanceInput; balance = 1.1f * Mathf.Sin(balanceX); // hack 1.1f* because balance balue must be greater than 1 or less than -1 tpInput.cc.animator.SetFloat(FootbridgeBalans, balance); } private void SnapPlayerRotation() { if (tpInput == null) return; if (isUsing && snapLocalRotationToParent && tpInput.cc.IsAnimatorTag("Snap")) { transform.localRotation = Quaternion.Lerp(transform.localRotation, transform.parent.localRotation, Time.deltaTime * snapRotationSpeed); } } private void SnapPlayerPosition() { if (tpInput == null) return; if (isUsing && (snapLocalPositionX || snapLocalPositionY || snapLocalPositionZ) && tpInput.cc.IsAnimatorTag("Snap")) { Vector3 curentLocalPosition = transform.localPosition; if (snapLocalPositionX) { curentLocalPosition.x = Mathf.Lerp(curentLocalPosition.x, snapLocalPosition.x, Time.deltaTime * snapPositionSpeed); } if (snapLocalPositionY) { curentLocalPosition.y = Mathf.Lerp(curentLocalPosition.y, snapLocalPosition.y, Time.deltaTime * snapPositionSpeed); } if (snapLocalPositionZ) { curentLocalPosition.z = Mathf.Lerp(curentLocalPosition.z, snapLocalPosition.z, Time.deltaTime * snapPositionSpeed); } transform.localPosition = curentLocalPosition; } } protected virtual void EnterInput() { if (targetAction == null || tpInput.cc.customAction || tpInput.cc.isJumping || !tpInput.cc.isGrounded || tpInput.cc.isRolling) { return; } if (enterInput.GetButtonDown() && !enterStarted && !isUsing && !targetAction.autoAction) { TriggerEnter(); } } protected virtual void ExitInput() { if (!isUsing) { return; } if (tpInput.cc.baseLayerInfo.IsName("EnterFootbridge")) { return; } if (targetAction == null && allowExit) { if (tpInput.cc.IsAnimatorTag("Footbridge")) { // exit at any moment by pressing the cancelInput if (exitInput.GetButtonDown()) { if (debugMode) { Debug.Log("Quick Exit..." + currentAction.name + "_" + currentAction.transform.parent.gameObject.name); } tpInput.cc.animator.speed = 1; tpInput.cc.animator.CrossFadeInFixedTime("QuickExitFootbridge", 0.1f); Invoke("ResetPlayerSettings", .5f); } } } else if (targetAction != null) { currentAction = targetAction; var animationClip = targetAction.Entrance; if (animationClip) { // exit when reach the start by pressing the cancelInput or pressing down at if (exitInput.GetButtonDown() && !triggerExitOnce || (speed <= -0.05f && !triggerExitOnce) && targetAction != null && !triggerExitOnce) { if (debugMode) { Debug.Log("Exit Start..." + currentAction.name + "_" + currentAction.transform.parent.gameObject.name); } triggerExitOnce = true; tpInput.cc.animator.CrossFadeInFixedTime(targetAction.exitAnimationStart, targetAction.exitStartTransitionDuration); // trigger the animation clip } } else if (!animationClip && tpInput.cc.IsAnimatorTag("Footbridge")) // exit from the end { if ((speed >= 0.05f) && !triggerExitOnce && !tpInput.cc.animator.IsInTransition(0)) // trigger the exit animation by pressing up { if (debugMode) { Debug.Log("Exit End..." + currentAction.name + "_" + currentAction.transform.parent.gameObject.name); } triggerExitOnce = true; tpInput.cc.animator.CrossFadeInFixedTime(targetAction.exitAnimationEnd, targetAction.exitEndTransitionDuration); // trigger the animation clip } } } } public virtual void ResetPlayerSettings() { if (debugMode) { Debug.Log("Reset Player Settings"); } speed = 0f; targetAction = null; isUsing = false; OnExit.Invoke(); triggerExitOnce = false; triggerEnterOnce = false; inEnterAnimation = false; enterStarted = 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; } } protected virtual void AutoEnter() { if (targetAction == null || !targetAction.autoAction) { return; } if (tpInput.cc.customAction || isUsing || tpInput.cc.animator.IsInTransition(0)) { return; } // enter the ladder automatically if checked with autoAction if (targetAction.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, targetAction.transform.forward); if (dist < 0.8f) { TriggerEnter(); } } } protected virtual void TriggerEnter() { if (debugMode) { Debug.Log("Enter Footbridge"); } OnExitTrigger.Invoke(); if (targetAction.targetCharacterParent) { transform.parent = targetAction.targetCharacterParent; } targetAction.Entrance = true; tpInput.cc.isCrouching = false; tpInput.cc.ControlCapsuleHeight(); tpInput.UpdateCameraStates(); tpInput.cc.UpdateAnimator(); OnEnter.Invoke(); triggerEnterOnce = true; enterStarted = true; tpInput.cc.animator.SetInteger(vAnimatorParameters.ActionState, 1); // set actionState 1 to avoid falling transitions tpInput.SetLockAllInput(true); tpInput.cc.ResetInputAnimatorParameters(); targetAction.OnDoAction.Invoke(); currentAction = targetAction; tpInput.cc.animator.updateMode = AnimatorUpdateMode.Normal; if (!string.IsNullOrEmpty(currentAction.playAnimation)) { if (debugMode) { Debug.Log("TriggerAnimation " + currentAction.name + "_" + currentAction.transform.parent.gameObject.name); } tpInput.cc.animator.CrossFadeInFixedTime(currentAction.playAnimation, currentAction.playTransitionDuration); // trigger the action animation clip isUsing = true; tpInput.cc.disableAnimations = true; tpInput.cc.StopCharacter(); } } protected virtual void Using() { if (!isUsing) { return; } // update the base layer to know what animations are being played tpInput.cc.AnimatorLayerControl(); tpInput.cc.ActionsControl(); // update camera movement tpInput.CameraInput(); // go forward or backwards currentSpeed = footbridgeSpeed; 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, currentSpeed, 2f * Time.deltaTime); } else { tpInput.cc.animator.speed = Mathf.Lerp(tpInput.cc.animator.speed, 1f, 2f * Time.deltaTime); } // enter behaviour var _inEnterAnimation = tpInput.cc.baseLayerInfo.IsName("EnterFootbridge") && !tpInput.cc.animator.IsInTransition(0); if (_inEnterAnimation) { inEnterAnimation = true; tpInput.cc.DisableGravityAndCollision(); // disable gravity & turn collision trigger // disable ingame hud //if (currentAction != null) //{ // //currentAction.OnPlayerExit.Invoke(); //} if (currentAction.useTriggerRotation) { if (debugMode) { Debug.Log("Rotating to target..." + currentAction.name + "_" + currentAction.transform.parent.gameObject.name); } EvaluateToRotation(currentAction.enterRotationCurve, currentAction.matchTarget.transform.rotation, tpInput.cc.baseLayerInfo.normalizedTime); } if (currentAction.matchTarget != null) { if (transform.parent != currentAction.targetCharacterParent) { transform.parent = currentAction.targetCharacterParent; } if (debugMode) { Debug.Log("Match Target to Enter..." + currentAction.name + "_" + currentAction.transform.parent.gameObject.name); } EvaluateToPosition(currentAction.enterPositionXZCurve, currentAction.enterPositionYCurve, currentAction.matchTarget.position, tpInput.cc.baseLayerInfo.normalizedTime); } } if (!_inEnterAnimation && inEnterAnimation) { enterStarted = false; inEnterAnimation = false; } TriggerExit(); } protected virtual void TriggerExit() { // exit ladder behaviour inExitingAnimation = tpInput.cc.IsAnimatorTag("Exit"); if (inExitingAnimation) { tpInput.cc.animator.speed = 1; if (currentAction.exitMatchTarget != null && !tpInput.cc.IsAnimatorTag("Exit")) { if (debugMode) { Debug.Log("Match Target to exit..." + currentAction.name + "_" + currentAction.transform.parent.gameObject.name); } EvaluateToPosition(currentAction.exitPositionXZCurve, currentAction.exitPositionYCurve, currentAction.exitMatchTarget.position, tpInput.cc.baseLayerInfo.normalizedTime); } var newRot = new Vector3(0, tpInput.animator.rootRotation.eulerAngles.y, 0); EvaluateToRotation(currentAction.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 CheckForTriggerAction(Collider other) { // assign the component - it will be null when he exit the trigger area var _Action = other.GetComponent(); if (!_Action) { return; } // check the maxAngle too see if the character can do the action var dist = Vector3.Distance(transform.forward, _Action.transform.forward); if (isUsing && _Action != null) { if (targetAction != _Action) { targetAction = _Action; if (!actionTriggers.Contains(targetAction)) { actionTriggers.Add(targetAction); } } } else if ((_Action.activeFromForward == false || dist <= 0.8f) && !isUsing) { AddTrigger(_Action); OnEnterTrigger.Invoke(); } else { RemoveTrigger(_Action); } } protected virtual void RemoveTrigger(vTriggerFootbridgeAction _Action) { if (_Action == targetAction) { targetAction = null; } if (actionTriggers.Contains(_Action)) { actionTriggers.Remove(_Action); _Action.OnPlayerExit.Invoke(); } } protected virtual void AddTrigger(vTriggerFootbridgeAction _Action) { if (targetAction != _Action) { targetAction = _Action; if (debugMode) { Debug.Log("TriggerStay " + targetAction.name + "_" + targetAction.transform.parent.gameObject.name); } } if (!actionTriggers.Contains(targetAction)) { actionTriggers.Add(targetAction); targetAction.OnPlayerEnter.Invoke(); } } public override void OnActionStay(Collider other) { if (other.gameObject.CompareTag(actionTag) && !enterStarted) { CheckForTriggerAction(other); } } public override void OnActionExit(Collider other) { if (other.gameObject.CompareTag(actionTag)) { var _ladderAction = other.GetComponent(); if (!_ladderAction) { return; } RemoveTrigger(_ladderAction); if (debugMode) { Debug.Log("TriggerExit " + other.name + "_" + other.transform.parent.gameObject.name); } OnExitTrigger.Invoke(); } } } }