Files
beyond/Assets/ThirdParty/Invector-3rdPersonController/Basic Locomotion/Scripts/HeadTrack/Scripts/vHeadTrack.cs
2024-11-20 15:21:28 +01:00

607 lines
23 KiB
C#

using Invector.vEventSystems;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace Invector.vCharacterController
{
[vClassHeader("HEAD TRACK", iconName = "headTrackIcon")]
public class vHeadTrack : vMonoBehaviour
{
#region variables
[vEditorToolbar("Settings")]
[vHelpBox("If your character is not looking up/down, try changing the axis", vHelpBoxAttribute.MessageType.Info)]
public Vector3 upDownAxis = Vector3.right;
[Header("Head & Body Weight")]
public float strafeHeadWeight = 0.6f;
public float strafeBodyWeight = 0.6f;
public float aimingHeadWeight = 0.8f;
public float aimingBodyWeight = 0.8f;
public float freeHeadWeight = 0.6f;
public float freeBodyWeight = 0.6f;
[SerializeField] protected float smooth = 10f;
[Header("Default Offsets ")]
[SerializeField] protected Vector2 defaultOffsetSpine;
[SerializeField] protected Vector2 defaultOffsetHead;
[vReadOnly(true)]
public Vector2 offsetSpine;
[vReadOnly(true)]
public Vector2 offsetHead;
[Header("Tracking")]
[Tooltip("Follow the Camera Forward")]
public bool followCamera = true;
public bool _freezeLookPoint = false;
[vHideInInspector("followCamera")]
[Tooltip("Force to follow camera")]
public bool alwaysFollowCamera = false;
[Tooltip("Ignore the Limits and continue to follow the camera")]
public bool cancelTrackOutOfAngle = true;
[Tooltip("Considerer the head animation forward while tracking, try it to see different results")]
public bool considerHeadAnimationForward;
[Header("Limits")]
[vMinMax(minLimit = -180f, maxLimit = 180f)] public Vector2 horizontalAngleLimit = new Vector2(-100, 100);
[vMinMax(minLimit = -90f, maxLimit = 90f)] public Vector2 verticalAngleLimit = new Vector2(-80, 80);
[vHelpBox("Animations with vAnimatorTag Behavior will ignore the HeadTrack while is being played")]
[Header("Ignore AnimatorTags")]
public List<string> animatorIgnoreTags = new List<string>() { "Attack", "LockMovement", "CustomAction", "IsEquipping", "IgnoreHeadtrack" };
[vEditorToolbar("Bones")]
[vHelpBox("Auto Find Bones using Humanoid")]
public bool autoFindBones = true;
public Transform head;
public List<Transform> spine = new List<Transform>();
[vEditorToolbar("Detection")]
public float updateTargetInteration = 1;
public float distanceToDetect = 10f;
public LayerMask obstacleLayer = 1 << 0;
[vHelpBox("Gameobjects Tags to detect")]
public List<string> tagsToDetect = new List<string>() { "LookAt" };
internal UnityEvent onInitUpdate = new UnityEvent();
internal UnityEvent onFinishUpdate = new UnityEvent();
internal Camera cameraMain;
internal vLookTarget currentLookTarget;
internal vLookTarget lastLookTarget;
internal Quaternion currentLookRotation;
internal List<vLookTarget> targetsInArea = new List<vLookTarget>();
internal bool ignoreSmooth = false;
private float yRotation, xRotation;
private float _currentHeadWeight, _currentbodyWeight;
private Animator animator;
private vIAnimatorStateInfoController animatorStateInfo;
private float headHeight;
private Transform simpleTarget;
private Vector3 temporaryLookPoint;
private float temporaryLookTime;
private vHeadTrackSensor sensor;
private float interation;
private vICharacter vchar;
private float yAngle, xAngle;
private float _yAngle, _xAngle;
private Transform forwardReference;
#endregion
public float Smooth
{
get
{
return ignoreSmooth ? 1f : smooth * Time.deltaTime;
}
}
protected Vector3 _currentLocalLookPosition;
protected Vector3 _lastLocalLookPosition;
public virtual bool freezeLookPoint { get => _freezeLookPoint; set => _freezeLookPoint = value; }
public virtual Vector3 currentLookPosition
{
get => freezeLookPoint ? transform.TransformPoint(_lastLocalLookPosition) : transform.TransformPoint(_currentLocalLookPosition);
protected set
{
_currentLocalLookPosition = transform.InverseTransformPoint(value);
if (!freezeLookPoint)
{
_lastLocalLookPosition = _currentLocalLookPosition;
}
}
}
void Start()
{
if (!sensor)
{
var sensorObj = new GameObject("HeadTrackSensor");
sensor = sensorObj.AddComponent<vHeadTrackSensor>();
}
// updates the headtrack using the late update of the tpinput so we don't need to create another one
var tpInput = GetComponent<vThirdPersonInput>();
if (tpInput)
{
tpInput.onLateUpdate -= UpdateHeadTrack;
tpInput.onLateUpdate += UpdateHeadTrack;
}
vchar = GetComponent<vICharacter>();
sensor.headTrack = this;
cameraMain = Camera.main;
var layer = LayerMask.NameToLayer("HeadTrack");
sensor.transform.parent = transform;
sensor.gameObject.layer = layer;
sensor.gameObject.tag = transform.tag;
animatorStateInfo = GetComponent<vIAnimatorStateInfoController>();
Init();
}
public void Init()
{
currentLookPosition = GetLookPoint();
_lastLocalLookPosition = _currentLocalLookPosition;
if (animator == null)
{
animator = GetComponentInChildren<Animator>();
}
if (autoFindBones)
{
spine.Clear();
head = animator.GetBoneTransform(HumanBodyBones.Head);
if (head)
{
if (!forwardReference)
{
forwardReference = new GameObject("FWRF").transform;
}
forwardReference.SetParent(head);
forwardReference.transform.localPosition = Vector3.zero;
forwardReference.transform.rotation = transform.rotation;
var hips = animator.GetBoneTransform(HumanBodyBones.Hips);
if (hips)
{
var target = head;
for (int i = 0; i < 4; i++)
{
if (target.parent && target.parent.gameObject != hips.gameObject)
{
spine.Add(target.parent);
target = target.parent;
}
else
{
break;
}
}
}
}
}
if (head)
{
headHeight = Vector3.Distance(transform.position, head.position);
sensor.transform.position = head.transform.position;
}
else
{
headHeight = 1f;
sensor.transform.position = transform.position;
}
if (spine.Count == 0)
{
Debug.Log("Headtrack Spines missing");
}
spine.Reverse();
}
Vector3 headPoint { get { return transform.position + (transform.up * headHeight); } }
public virtual void UpdateHeadTrack()
{
if (animator == null || !animator.enabled)
{
return;
}
if (vchar != null && vchar.currentHealth > 0f && animator != null && !vchar.ragdolled)
{
onInitUpdate.Invoke();
if (!freezeLookPoint)
{
currentLookPosition = GetLookPoint();
}
SetLookAtPosition(currentLookPosition, _currentHeadWeight, _currentbodyWeight);
onFinishUpdate.Invoke();
}
}
public virtual void SetLookAtPosition(Vector3 point, float headWeight, float spineWeight)
{
var lookRotation = Quaternion.LookRotation(point - headPoint);
currentLookRotation = lookRotation;
var euler = lookRotation.eulerAngles - transform.rotation.eulerAngles;
var y = NormalizeAngle(euler.y);
var x = NormalizeAngle(euler.x);
var eulerB = considerHeadAnimationForward ? forwardReference.eulerAngles - transform.eulerAngles : Vector3.zero;
xAngle = Mathf.Clamp(Mathf.Lerp(xAngle, ((x) - eulerB.NormalizeAngle().x) + Quaternion.Euler(offsetSpine + defaultOffsetSpine).eulerAngles.NormalizeAngle().x, Smooth), verticalAngleLimit.x, verticalAngleLimit.y);
yAngle = Mathf.Clamp(Mathf.Lerp(yAngle, ((y) - eulerB.NormalizeAngle().y) + Quaternion.Euler(offsetSpine + defaultOffsetSpine).eulerAngles.NormalizeAngle().y, Smooth), horizontalAngleLimit.x, horizontalAngleLimit.y);
var xSpine = NormalizeAngle(xAngle);
var ySpine = NormalizeAngle(yAngle);
foreach (Transform segment in spine)
{
var rotY = Quaternion.AngleAxis((ySpine * spineWeight) / spine.Count, segment.InverseTransformDirection(transform.up));
segment.rotation *= rotY;
var rotX = Quaternion.AngleAxis((xSpine * spineWeight) / spine.Count, segment.InverseTransformDirection(transform.TransformDirection(upDownAxis)));
segment.rotation *= rotX;
}
if (head)
{
var xHead = NormalizeAngle(xAngle - (xSpine * spineWeight) + Quaternion.Euler(offsetHead + defaultOffsetHead).eulerAngles.NormalizeAngle().x);
var yHead = NormalizeAngle(yAngle - (ySpine * spineWeight) + Quaternion.Euler(offsetHead + defaultOffsetHead).eulerAngles.NormalizeAngle().y);
var _rotY = Quaternion.AngleAxis(yHead * headWeight, head.InverseTransformDirection(transform.up));
head.rotation *= _rotY;
var _rotX = Quaternion.AngleAxis(xHead * headWeight, head.InverseTransformDirection(transform.TransformDirection(upDownAxis)));
head.rotation *= _rotX;
}
}
bool lookConditions
{
get
{
if (!cameraMain)
{
cameraMain = Camera.main;
}
return head != null && (followCamera && cameraMain != null) || (!followCamera && (currentLookTarget || simpleTarget)) || temporaryLookTime > 0;
}
}
Vector3 GetLookPoint()
{
if(animator == null)
{
return Vector3.zero;
}
var distanceToLook = 100;
if (lookConditions && !IgnoreHeadTrack())
{
var dir = transform.forward;
if (temporaryLookTime <= 0)
{
var lookPosition = headPoint + (transform.forward * distanceToLook);
if (followCamera)
{
lookPosition = (cameraMain.transform.position + (cameraMain.transform.forward * distanceToLook));
}
dir = lookPosition - headPoint;
if ((followCamera && !alwaysFollowCamera) || !followCamera)
{
if (simpleTarget != null)
{
dir = simpleTarget.position - headPoint;
if (currentLookTarget && currentLookTarget == lastLookTarget)
{
currentLookTarget.ExitLook(this);
lastLookTarget = null;
}
}
else if (currentLookTarget != null && (currentLookTarget.ignoreHeadTrackAngle || TargetIsOnRange(currentLookTarget.lookPoint - headPoint)) && currentLookTarget.IsVisible(headPoint, obstacleLayer))
{
dir = currentLookTarget.lookPoint - headPoint;
if (currentLookTarget != lastLookTarget)
{
currentLookTarget.EnterLook(this);
lastLookTarget = currentLookTarget;
}
}
else if (currentLookTarget && currentLookTarget == lastLookTarget)
{
currentLookTarget.ExitLook(this);
lastLookTarget = null;
}
}
}
else
{
dir = temporaryLookPoint - headPoint;
temporaryLookTime -= Time.deltaTime;
if (currentLookTarget && currentLookTarget == lastLookTarget)
{
currentLookTarget.ExitLook(this);
lastLookTarget = null;
}
}
var angle = GetTargetAngle(dir);
if (cancelTrackOutOfAngle && (lastLookTarget == null || !lastLookTarget.ignoreHeadTrackAngle))
{
if (TargetIsOnRange(dir))
{
if (animator.GetBool("IsStrafing") && !IsAnimatorTag("Upperbody Pose"))
{
SmoothValues(strafeHeadWeight, strafeBodyWeight, angle.x, angle.y);
}
else if (animator.GetBool("IsStrafing") && IsAnimatorTag("Upperbody Pose"))
{
SmoothValues(aimingHeadWeight, aimingBodyWeight, angle.x, angle.y);
}
else
{
SmoothValues(freeHeadWeight, freeBodyWeight, angle.x, angle.y);
}
}
else
{
SmoothValues();
}
}
else
{
if (animator.GetBool("IsStrafing") && !IsAnimatorTag("Upperbody Pose"))
{
SmoothValues(strafeHeadWeight, strafeBodyWeight, angle.x, angle.y);
}
else if (animator.GetBool("IsStrafing") && IsAnimatorTag("Upperbody Pose"))
{
SmoothValues(aimingHeadWeight, aimingBodyWeight, angle.x, angle.y);
}
else
{
SmoothValues(freeHeadWeight, freeBodyWeight, angle.x, angle.y);
}
}
if (targetsInArea.Count > 1)
{
SortTargets();
}
}
else
{
SmoothValues();
if (targetsInArea.Count > 1)
{
SortTargets();
}
}
var rotA = Quaternion.AngleAxis(yRotation, transform.up);
var rotB = Quaternion.AngleAxis(xRotation, transform.right);
var finalRotation = (rotA * rotB);
var lookDirection = finalRotation * transform.forward;
return headPoint + (lookDirection * distanceToLook);
}
Vector2 GetTargetAngle(Vector3 direction)
{
var lookRotation = Quaternion.LookRotation(direction, transform.up);
var angleResult = lookRotation.eulerAngles - transform.eulerAngles;
return new Vector2(angleResult.NormalizeAngle().x, angleResult.NormalizeAngle().y);
}
bool TargetIsOnRange(Vector3 direction)
{
var angle = GetTargetAngle(direction);
return (angle.x >= verticalAngleLimit.x && angle.x <= verticalAngleLimit.y && angle.y >= horizontalAngleLimit.x && angle.y <= horizontalAngleLimit.y);
}
public virtual void SetAlwaysFollowCamera(bool value)
{
alwaysFollowCamera = value;
}
/// <summary>
/// Set vLookTarget
/// </summary>
/// <param name="target"></param>
public virtual void SetLookTarget(vLookTarget target, bool priority = false)
{
if (!targetsInArea.Contains(target))
{
targetsInArea.Add(target);
}
if (priority)
{
currentLookTarget = target;
}
}
/// <summary>
/// Set Simple target
/// </summary>
/// <param name="target"></param>
public virtual void SetLookTarget(Transform target)
{
simpleTarget = target;
}
/// <summary>
/// Set a temporary look point to headtrack
/// </summary>
/// <param name="point">look point</param>
/// <param name="time">time to stay looking</param>
public virtual void SetTemporaryLookPoint(Vector3 point, float time = 1f)
{
temporaryLookPoint = point;
temporaryLookTime = time;
}
public virtual void RemoveLookTarget(vLookTarget target)
{
if (targetsInArea.Contains(target))
{
targetsInArea.Remove(target);
}
if (currentLookTarget == target)
{
currentLookTarget = null;
}
}
public virtual void RemoveLookTarget(Transform target)
{
if (simpleTarget == target)
{
simpleTarget = null;
}
}
/// <summary>
/// Make angle to work with -180 and 180
/// </summary>
/// <param name="angle"></param>
/// <returns></returns>
float NormalizeAngle(float angle)
{
if (angle > 180)
{
angle -= 360;
}
else if (angle < -180)
{
angle += 360;
}
return angle;
}
void ResetValues()
{
_currentHeadWeight = 0;
_currentbodyWeight = 0;
yRotation = 0;
xRotation = 0;
}
void SmoothValues(float _headWeight = 0, float _bodyWeight = 0, float _x = 0, float _y = 0)
{
_currentHeadWeight = Mathf.Lerp(_currentHeadWeight, _headWeight, Smooth);
_currentbodyWeight = Mathf.Lerp(_currentbodyWeight, _bodyWeight, Smooth);
yRotation = Mathf.Lerp(yRotation, _y, Smooth);
xRotation = Mathf.Lerp(xRotation, _x, Smooth);
yRotation = Mathf.Clamp(yRotation, horizontalAngleLimit.x, horizontalAngleLimit.y);
xRotation = Mathf.Clamp(xRotation, verticalAngleLimit.x, verticalAngleLimit.y);
}
void SortTargets()
{
interation += Time.deltaTime;
if (interation > updateTargetInteration)
{
interation -= updateTargetInteration;
if (targetsInArea == null || targetsInArea.Count < 2)
{
if (targetsInArea != null && targetsInArea.Count > 0)
{
currentLookTarget = targetsInArea[0];
}
return;
}
for (int i = targetsInArea.Count - 1; i >= 0; i--)
{
if (targetsInArea[i] == null)
{
targetsInArea.RemoveAt(i);
}
}
targetsInArea.Sort(delegate (vLookTarget c1, vLookTarget c2)
{
return Vector3.Distance(this.transform.position, c1 != null ? c1.transform.position : Vector3.one * Mathf.Infinity).CompareTo
((Vector3.Distance(this.transform.position, c2 != null ? c2.transform.position : Vector3.one * Mathf.Infinity)));
});
if (targetsInArea.Count > 0)
{
currentLookTarget = targetsInArea[0];
}
}
}
public virtual void OnDetect(Collider other)
{
if (tagsToDetect.Contains(other.gameObject.tag) && other.GetComponent<vLookTarget>() != null)
{
currentLookTarget = other.GetComponent<vLookTarget>();
var headTrack = other.GetComponentInParent<vHeadTrack>();
if (!targetsInArea.Contains(currentLookTarget) && (headTrack == null || headTrack != this))
{
targetsInArea.Add(currentLookTarget);
SortTargets();
currentLookTarget = targetsInArea[0];
}
}
}
public virtual void OnLost(Collider other)
{
if (tagsToDetect.Contains(other.gameObject.tag) && other.GetComponentInParent<vLookTarget>() != null)
{
var _currentLookTarget = other.GetComponentInParent<vLookTarget>();
if (targetsInArea.Contains(_currentLookTarget))
{
targetsInArea.Remove(_currentLookTarget);
if (_currentLookTarget == lastLookTarget)
{
_currentLookTarget.ExitLook(this);
}
}
SortTargets();
if (targetsInArea.Count > 0)
{
currentLookTarget = targetsInArea[0];
}
else
{
currentLookTarget = null;
}
}
}
public virtual bool IgnoreHeadTrack()
{
if (animatorIgnoreTags.Exists(tag => IsAnimatorTag(tag)))
{
return true;
}
return false;
}
public virtual bool IsAnimatorTag(string tag)
{
if (animator == null)
{
return false;
}
if (animatorStateInfo.isValid())
{
if (animatorStateInfo.animatorStateInfos.HasTag(tag))
{
return true;
}
}
return false;
}
}
}