Files
beyond/Assets/ThirdParty/Invector-3rdPersonController/Basic Locomotion/Scripts/CharacterController/Actions/vGenericAction.cs
2024-12-18 15:25:43 +01:00

863 lines
32 KiB
C#

using System.Collections;
using UnityEngine;
namespace Invector.vCharacterController.vActions
{
using System;
using System.Collections.Generic;
using vCharacterController;
[vClassHeader("GENERIC ACTION", "Use the vTriggerGenericAction to trigger a simple animation.\n<b><size=12>You can use <color=red>vGenericActionReceiver</color> component to filter events by action name</size></b>", iconName = "triggerIcon")]
public class vGenericAction : vActionListener
{
[vEditorToolbar("Settings")]
[Tooltip("Tag of the object you want to access")]
public string actionTag = "Action";
[Tooltip("Use root motion of the animation")]
public bool useRootMotion = true;
[vEditorToolbar("Debug")]
[Header("--- Debug Only ---")]
[Tooltip("Check this to enter the debug mode")]
public bool debugMode;
[vReadOnly] public vTriggerGenericAction triggerAction;
[vReadOnly, SerializeField]
protected bool _playingAnimation;
[vReadOnly, SerializeField]
protected bool actionStarted;
[vReadOnly]
public bool isLockTriggerEvents;
[vReadOnly, SerializeField]
protected List<Collider> colliders = new List<Collider>();
[vEditorToolbar("Events")]
public vOnActionHandle OnEnterTriggerAction;
public vOnActionHandle OnExitTriggerAction;
public vOnActionHandle OnStartAction;
public vOnActionHandle OnCancelAction;
public vOnActionHandle OnEndAction;
internal Camera mainCamera;
internal vThirdPersonInput tpInput;
protected float _currentInputDelay;
protected Vector3 _screenCenter;
protected float timeInTrigger;
protected float animationBehaviourDelay;
protected bool finishRotationMatch;
protected bool finishPositionXZMatch;
protected bool finishPositionYMatch;
protected virtual Vector3 screenCenter
{
get
{
_screenCenter.x = Screen.width * 0.5f;
_screenCenter.y = Screen.height * 0.5f;
_screenCenter.z = 0;
return _screenCenter;
}
}
internal Dictionary<Collider, ActionStorage> actions;
internal class ActionStorage
{
internal vTriggerGenericAction action;
internal bool isValid;
internal ActionStorage()
{
}
internal ActionStorage(vTriggerGenericAction action)
{
this.action = action;
action.OnValidate.AddListener((GameObject o) => { isValid = true; });
action.OnInvalidate.AddListener((GameObject o) => { isValid = false; });
}
public static implicit operator vTriggerGenericAction(ActionStorage storage)
{
return storage.action;
}
public static implicit operator ActionStorage(vTriggerGenericAction action)
{
return new ActionStorage(action);
}
}
protected override void SetUpListener()
{
actionEnter = true;
actionStay = true;
actionExit = true;
actions = new Dictionary<Collider, ActionStorage>();
}
protected override void Start()
{
base.Start();
tpInput = GetComponent<vThirdPersonInput>();
if (tpInput != null)
{
tpInput.onUpdate -= CheckForTriggerAction;
tpInput.onUpdate += CheckForTriggerAction;
tpInput.onLateUpdate -= UpdateGenericAction;
tpInput.onLateUpdate += UpdateGenericAction;
}
if (!mainCamera)
{
mainCamera = Camera.main;
}
}
protected virtual void UpdateGenericAction()
{
if (!mainCamera)
{
mainCamera = Camera.main;
}
if (!mainCamera)
{
return;
}
AnimationBehaviour();
HandleColliders();
}
private void HandleColliders()
{
colliders.Clear();
foreach (var key in actions.Keys)
{
colliders.Add(key);
}
if (!doingAction && triggerAction && !isLockTriggerEvents)
{
if (timeInTrigger <= 0)
{
actions.Clear();
triggerAction = null;
}
else
{
timeInTrigger -= Time.deltaTime;
}
}
}
protected virtual bool inActionAnimation
{
get
{
return !string.IsNullOrEmpty(triggerAction.playAnimation)
&& tpInput.cc.animatorStateInfos.stateInfos[triggerAction.animatorLayer].shortPathHash.Equals(Animator.StringToHash(triggerAction.playAnimation));
}
}
protected virtual void CheckForTriggerAction()
{
if (actions.Count == 0 && !triggerAction || isLockTriggerEvents)
{
return;
}
vTriggerGenericAction _triggerAction = GetNearAction();
if (!doingAction && triggerAction != _triggerAction)
{
triggerAction = _triggerAction;
if (triggerAction)
{
triggerAction.OnValidate.Invoke(gameObject);
OnEnterTriggerAction.Invoke(triggerAction);
}
}
TriggerActionInput();
}
protected vTriggerGenericAction GetNearAction()
{
if (isLockTriggerEvents || doingAction || playingAnimation)
{
return null;
}
float distance = Mathf.Infinity;
vTriggerGenericAction _targetAction = null;
foreach (var key in actions.Keys)
{
if (key)
{
try
{
vTriggerGenericAction action = actions[key];
var screenP = mainCamera ? mainCamera.WorldToScreenPoint(key.transform.position) : screenCenter;
if (mainCamera)
{
bool isValid = action.enabled && action.gameObject.activeInHierarchy && (!action.activeFromForward && (screenP - screenCenter).magnitude < distance || IsInForward(action.transform, action.forwardAngle) && (screenP - screenCenter).magnitude < distance);
if (isValid)
{
distance = (screenP - screenCenter).magnitude;
if (_targetAction && _targetAction != action)
{
if (actions[_targetAction._collider].isValid)
{
_targetAction.OnInvalidate.Invoke(gameObject);
}
_targetAction = action;
}
else if (_targetAction == null)
{
_targetAction = action;
}
}
else
{
if (actions[action._collider].isValid)
{
action.OnInvalidate.Invoke(gameObject);
}
OnExitTriggerAction.Invoke(triggerAction);
}
}
else
{
if (!_targetAction)
{
_targetAction = action;
}
else
{
if (actions[action._collider].isValid)
{
action.OnInvalidate.Invoke(gameObject);
}
OnExitTriggerAction.Invoke(triggerAction);
}
}
}catch
{
break;
}
}
else
{
actions.Remove(key);
return null;
}
}
return _targetAction;
}
protected virtual bool IsInForward(Transform target, float angleToCompare)
{
var angle = Vector3.Angle(transform.forward, target.forward);
return angle <= angleToCompare;
}
protected virtual void AnimationBehaviour()
{
if (animationBehaviourDelay > 0 && !playingAnimation)
{
animationBehaviourDelay -= Time.deltaTime; return;
}
if (playingAnimation)
{
if (triggerAction.matchTarget != null)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b><color=blue>Match Target...</color> ");
}
EvaluateToTargetPosition();
}
if (triggerAction.useTriggerRotation)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b><color=blue>Rotate to Target...</color> ");
}
EvaluateToTargetRotation();
}
if (actionStarted && !triggerAction.endActionManualy && (triggerAction.inputType != vTriggerGenericAction.InputType.GetButtonTimer || !triggerAction.playAnimationWhileHoldingButton) && tpInput.cc.animatorStateInfos.GetCurrentNormalizedTime(triggerAction.animatorLayer) >= triggerAction.endExitTimeAnimation)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>Finish Animation ");
}
// triggers the OnEndAnimation Event
EndAction();
}
}
else if (doingAction && actionStarted && (triggerAction == null || !triggerAction.endActionManualy))
{
//when using a GetButtonTimer the ResetTriggerSettings will be automatically called at the end of the timer or by releasing the input
if (triggerAction != null && (triggerAction.inputType == vTriggerGenericAction.InputType.GetButtonTimer && triggerAction.playAnimationWhileHoldingButton))
{
return;
}
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>Force ResetTriggerSettings ");
}
// triggers the OnEndAnimation Event
EndAction();
}
}
protected virtual void EvaluateToTargetPosition()
{
var matchTargetPosition = triggerAction.matchTarget.position;
switch (triggerAction.avatarTarget)
{
case AvatarTarget.LeftHand:
matchTargetPosition = (triggerAction.matchTarget.position - transform.rotation * transform.InverseTransformPoint(tpInput.animator.GetBoneTransform(HumanBodyBones.LeftHand).position));
break;
case AvatarTarget.RightHand:
matchTargetPosition = (triggerAction.matchTarget.position - transform.rotation * transform.InverseTransformPoint(tpInput.animator.GetBoneTransform(HumanBodyBones.RightHand).position));
break;
case AvatarTarget.LeftFoot:
matchTargetPosition = (triggerAction.matchTarget.position - transform.rotation * transform.InverseTransformPoint(tpInput.animator.GetBoneTransform(HumanBodyBones.LeftFoot).position));
break;
case AvatarTarget.RightFoot:
matchTargetPosition = (triggerAction.matchTarget.position - transform.rotation * transform.InverseTransformPoint(tpInput.animator.GetBoneTransform(HumanBodyBones.RightFoot).position));
break;
}
AnimationCurve XZ = triggerAction.matchPositionXZCurve;
AnimationCurve Y = triggerAction.matchPositionYCurve;
float normalizedTime = Mathf.Clamp(tpInput.cc.animatorStateInfos.GetCurrentNormalizedTime(triggerAction.animatorLayer), 0, 1);
var localRelativeToTarget = triggerAction.matchTarget.InverseTransformPoint(matchTargetPosition);
if (!triggerAction.useLocalX)
{
localRelativeToTarget.x = triggerAction.matchTarget.InverseTransformPoint(transform.position).x;
}
if (!triggerAction.useLocalZ)
{
localRelativeToTarget.z = triggerAction.matchTarget.InverseTransformPoint(transform.position).z;
}
matchTargetPosition = triggerAction.matchTarget.TransformPoint(localRelativeToTarget);
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, matchTargetPosition.x, evaluatedXZ);
rootPosition.z = Mathf.Lerp(rootPosition.z, matchTargetPosition.z, evaluatedXZ);
finishPositionXZMatch = true;
}
else if (finishPositionXZMatch)
{
finishPositionXZMatch = false;
rootPosition.x = matchTargetPosition.x;
rootPosition.z = matchTargetPosition.z;
}
if (evaluatedY < 1f)
{
rootPosition.y = Mathf.Lerp(rootPosition.y, matchTargetPosition.y, evaluatedY);
finishPositionYMatch = true;
}
else if (finishPositionYMatch)
{
finishPositionYMatch = false;
rootPosition.y = matchTargetPosition.y;
}
transform.position = rootPosition;
}
protected virtual void EvaluateToTargetRotation()
{
var targetEuler = new Vector3(transform.eulerAngles.x, triggerAction.transform.eulerAngles.y, transform.eulerAngles.z);
Quaternion targetRotation = Quaternion.Euler(targetEuler);
Quaternion rootRotation = tpInput.cc.animator.rootRotation;
AnimationCurve rotationCurve = triggerAction.matchRotationCurve;
float normalizedTime = tpInput.cc.animatorStateInfos.GetCurrentNormalizedTime(triggerAction.animatorLayer);
float evaluatedCurve = rotationCurve.Evaluate(normalizedTime);
if (evaluatedCurve < 1)
{
rootRotation = Quaternion.Lerp(rootRotation, targetRotation, evaluatedCurve);
finishRotationMatch = true;
}
else if (finishRotationMatch)
{
finishRotationMatch = false;
rootRotation = targetRotation;
}
transform.rotation = rootRotation;
}
protected virtual void EndAction()
{
OnEndAction.Invoke(triggerAction);
var trigger = triggerAction;
// triggers the OnEndAnimation Event
trigger.OnEndAnimation.Invoke();
// Exit the trigger
OnExitTriggerAction.Invoke(triggerAction);
// reset GenericAction variables so you can use it again
ResetTriggerSettings();
// Destroy trigger affter reset all settings
if (trigger.destroyAfter)
{
StartCoroutine(DestroyActionDelay(trigger));
}
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>End Action ");
}
}
public virtual bool playingAnimation
{
get
{
if (triggerAction == null || !doingAction)
{
return _playingAnimation = false;
}
if (!_playingAnimation && inActionAnimation)
{
_playingAnimation = true;
triggerAction.OnStartAnimation.Invoke();
DisablePlayerGravityAndCollision();
}
else if (_playingAnimation && !inActionAnimation)
{
_playingAnimation = false;
}
return _playingAnimation;
}
protected set
{
_playingAnimation = true;
}
}
public virtual bool actionConditions
{
get
{
return (!doingAction && !playingAnimation && !tpInput.cc.isJumping && !tpInput.cc.customAction && !tpInput.cc.animator.IsInTransition(triggerAction.animatorLayer));
}
}
public override void OnActionEnter(Collider other)
{
if (isLockTriggerEvents)
{
return;
}
if (other != null && other.gameObject.CompareTag(actionTag))
{
if (!actions.ContainsKey(other))
{
vTriggerGenericAction _triggerAction = other.GetComponent<vTriggerGenericAction>();
if (_triggerAction && _triggerAction.enabled)
{
actions.Add(other, _triggerAction);
_triggerAction.OnPlayerEnter.Invoke(gameObject);
if (debugMode)
{
Debug.Log("<color=green>Enter in Trigger </color>" + other.gameObject, other.gameObject);
}
}
}
}
}
public override void OnActionExit(Collider other)
{
if (isLockTriggerEvents)
{
return;
}
if (other.gameObject.CompareTag(actionTag) && actions.ContainsKey(other) && (!doingAction || other != triggerAction._collider))
{
vTriggerGenericAction action = actions[other];
actions.Remove(other);
action.OnPlayerExit.Invoke(gameObject);
action.OnInvalidate.Invoke(gameObject);
OnExitTriggerAction.Invoke(action);
if (debugMode)
{
Debug.Log("<color=red>Exit of Trigger </color> " + other.gameObject, other.gameObject);
}
}
}
public override void OnActionStay(Collider other)
{
if (isLockTriggerEvents)
{
return;
}
if (other != null && actions.ContainsKey(other))
{
actions[other].action.OnPlayerStay.Invoke(gameObject);
timeInTrigger = .5f;
if (debugMode)
{
Debug.Log("<color=yellow>Stay in Trigger </color>" + other.gameObject, other.gameObject);
}
}
}
/// <summary>
/// End Action Manualy if <see cref="vTriggerGenericAction.endActionManualy"/> equals true
/// </summary>
public virtual void FinishAction()
{
if (triggerAction && actionStarted && triggerAction.endActionManualy)
{
EndAction();
}
}
public virtual void TriggerActionInput()
{
if (triggerAction == null || !triggerAction.gameObject.activeInHierarchy)
{
return;
}
// AutoAction
if (triggerAction.inputType == vTriggerGenericAction.InputType.AutoAction && actionConditions)
{
TriggerActionEvents();
TriggerAnimation();
}
// GetButtonDown
else if (triggerAction.inputType == vTriggerGenericAction.InputType.GetButtonDown && actionConditions)
{
if (triggerAction.actionInput.GetButtonDown())
{
TriggerActionEvents();
TriggerAnimation();
}
}
// GetDoubleButton
else if (triggerAction.inputType == vTriggerGenericAction.InputType.GetDoubleButton && actionConditions)
{
if (triggerAction.actionInput.GetDoubleButtonDown(triggerAction.doubleButtomTime))
{
TriggerActionEvents();
TriggerAnimation();
}
}
// GetButtonTimer (Hold Button)
else if (triggerAction.inputType == vTriggerGenericAction.InputType.GetButtonTimer)
{
if (_currentInputDelay <= 0)
{
var up = false;
var t = 0f;
// this mode will play the animation while you're holding the button
if (triggerAction.playAnimationWhileHoldingButton)
{
TriggerActionEventsInput();
// call the OnFinishActionInput after the buttomTimer is concluded and reset player settings
if (triggerAction.actionInput.GetButtonTimer(ref t, ref up, triggerAction.buttonTimer))
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>Finish Action Input ");
}
triggerAction.UpdateButtonTimer(0);
triggerAction.OnFinishActionInput.Invoke();
ResetActionState();
EndAction();
//ResetTriggerSettings();
}
// trigger the Animation and the ActionEvents while your hold the button
if (triggerAction && triggerAction.actionInput.inButtomTimer)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b><color=blue>Holding Input</color> ");
}
triggerAction.UpdateButtonTimer(t);
TriggerAnimation();
}
// call OnCancelActionInput if the button is released before ending the buttonTimer
if (up && triggerAction)
{
CancelButtonTimer();
}
}
// this mode will play the animation after you finish holding the button
else /*if (!doingAction)*/
{
TriggerActionEventsInput();
// call the OnFinishActionInput after the buttomTimer is concluded and reset player settings
if (triggerAction.actionInput.GetButtonTimer(ref t, ref up, triggerAction.buttonTimer))
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>Finish Action Input ");
}
triggerAction.UpdateButtonTimer(0);
triggerAction.OnFinishActionInput.Invoke();
// destroy the triggerAction if checked with destroyAfter
TriggerAnimation();
}
// trigger the ActionEvents while your hold the button
if (triggerAction && triggerAction.actionInput.inButtomTimer)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b><color=blue>Holding Input</color>");
}
triggerAction.UpdateButtonTimer(t);
}
// call OnCancelActionInput if the button is released before ending the buttonTimer
if (up && triggerAction)
{
CancelButtonTimer();
}
}
}
else
{
_currentInputDelay -= Time.deltaTime;
}
}
}
protected void CancelButtonTimer()
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>Cancel Action ");
}
triggerAction.OnCancelActionInput.Invoke();
_currentInputDelay = triggerAction.inputDelay;
triggerAction.UpdateButtonTimer(0);
OnCancelAction.Invoke(triggerAction);
ResetActionState();
ResetTriggerSettings(false);
}
protected void TriggerActionEventsInput()
{
// trigger the ActionEvents while your hold the button
if (triggerAction && triggerAction.actionInput.GetButtonDown())
{
TriggerActionEvents();
}
}
public virtual void TriggerActionEvents()
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>TriggerAction Events ", gameObject);
}
doingAction = true;
// Call OnStartAction from the Controller's GenericAction inspector
OnStartAction.Invoke(triggerAction);
// Call OnDoAction from the Controller's GenericAction
OnDoAction.Invoke(triggerAction);
// trigger OnDoAction Event, you can add a delay in the inspector
StartCoroutine(triggerAction.OnPressActionDelay(gameObject));
}
public virtual void TriggerAnimation()
{
if (playingAnimation || actionStarted)
{
return;
}
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>TriggerAnimation ", gameObject);
}
if (triggerAction.animatorActionState != 0)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>Applied ActionState: " + triggerAction.animatorActionState + " ", gameObject);
}
tpInput.cc.SetActionState(triggerAction.animatorActionState);
}
// trigger the animation behaviour & match target
if (!string.IsNullOrEmpty(triggerAction.playAnimation))
{
if (!actionStarted)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>PlayAnimation: " + triggerAction.playAnimation + " ", gameObject);
}
actionStarted = true;
playingAnimation = true;
tpInput.cc.animator.CrossFadeInFixedTime(triggerAction.playAnimation, triggerAction.crossFadeTransition); // trigger the action animation clip
if (!string.IsNullOrEmpty(triggerAction.customCameraState))
{
tpInput.ChangeCameraState(triggerAction.customCameraState, true); // change current camera state to a custom
}
}
}
else
{
actionStarted = true;
}
animationBehaviourDelay = triggerAction.crossFadeTransition + 0.1f;
}
public virtual void ResetActionState()
{
if (triggerAction && triggerAction.resetAnimatorActionState)
{
tpInput.cc.SetActionState(0);
}
}
public virtual void ResetTriggerSettings(bool removeTrigger = true)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>Reset Trigger Settings ");
}
// reset player gravity and collision
EnablePlayerGravityAndCollision();
// reset the Animator parameter ActionState back to 0
ResetActionState();
// reset the CameraState to the Default state
if (triggerAction != null && !string.IsNullOrEmpty(triggerAction.customCameraState))
{
tpInput.ResetCameraState();
}
// remove the collider from the actions list
if (triggerAction != null && actions.ContainsKey(triggerAction._collider) && removeTrigger)
{
actions.Remove(triggerAction._collider);
}
triggerAction = null;
doingAction = false;
actionStarted = false;
}
public virtual void DisablePlayerGravityAndCollision()
{
if (triggerAction && triggerAction.disableGravity)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b><color=red>Disable Player's Gravity</color> ");
}
tpInput.cc._rigidbody.useGravity = false;
tpInput.cc._rigidbody.isKinematic = true;
tpInput.cc._rigidbody.linearVelocity = Vector3.zero;
}
if (triggerAction && triggerAction.disableCollision)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b><color=red>Disable Player's Collision</color> ");
}
tpInput.cc._capsuleCollider.isTrigger = true;
}
}
public virtual void EnablePlayerGravityAndCollision()
{
if (triggerAction && triggerAction.disableGravity)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b><color=red>Enable Player's Gravity</color> ");
}
tpInput.cc._rigidbody.useGravity = true;
tpInput.cc._rigidbody.isKinematic = false;
}
if (triggerAction && triggerAction.disableCollision)
{
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b><color=red>Enable Player's Collision</color> ");
}
tpInput.cc._capsuleCollider.isTrigger = false;
}
}
public virtual IEnumerator DestroyActionDelay(vTriggerGenericAction triggerAction)
{
var _triggerAction = triggerAction;
yield return new WaitForSeconds(_triggerAction.destroyDelay);
if (_triggerAction != null && _triggerAction.gameObject != null)
{
OnExitTriggerAction.Invoke(triggerAction);
Destroy(_triggerAction.gameObject);
}
if (debugMode)
{
Debug.Log($"<b>GenericAction: </b>Destroy Trigger ");
}
}
public virtual void SetLockTriggerEvents(bool value)
{
foreach (var key in actions.Keys)
{
if (key)
{
actions[key].action.OnPlayerExit.Invoke(gameObject);
actions[key].action.OnInvalidate.Invoke(gameObject);
}
}
actions.Clear();
isLockTriggerEvents = value;
}
}
}