595 lines
22 KiB
C#
595 lines
22 KiB
C#
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<vTriggerFootbridgeAction> actionTriggers = new List<vTriggerFootbridgeAction>();
|
|
[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<vThirdPersonInput>();
|
|
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.Fixed;
|
|
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<vTriggerFootbridgeAction>();
|
|
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<vTriggerFootbridgeAction>();
|
|
if (!_ladderAction)
|
|
{
|
|
return;
|
|
}
|
|
RemoveTrigger(_ladderAction);
|
|
if (debugMode)
|
|
{
|
|
Debug.Log("TriggerExit " + other.name + "_" + other.transform.parent.gameObject.name);
|
|
}
|
|
|
|
OnExitTrigger.Invoke();
|
|
}
|
|
}
|
|
}
|
|
}
|