using MalbersAnimations.Scriptables; using UnityEngine; using MalbersAnimations.Events; #if UNITY_EDITOR using UnityEditor; #endif namespace MalbersAnimations.Utilities { /// Used for Animal that have Animated Physics enabled [DefaultExecutionOrder(500)/*,[RequireComponent(typeof(Aim))*/] [AddComponentMenu("Malbers/Utilities/Aiming/Look At")] public class LookAt : MonoBehaviour, IAnimatorListener, ILookAtActivation { [System.Serializable] public class BoneRotation { /// Transform reference for the Bone [RequiredField] public Transform bone; //The bone public Vector3 offset = new Vector3(0, -90, -90); //The offset for the look At [Range(0, 1)] public float weight = 1; //the Weight of the look a internal Quaternion nextRotation; [Tooltip("Is not a bone driven by the Animator")] public bool external; } public BoolReference active = new BoolReference(true); //For Activating and Deactivating the HeadTrack private IGravity a_UpVector; private IAim aimer; /// Max Angle to LookAt [Space, Tooltip("Max Angle to LookAt")] public FloatReference LimitAngle = new FloatReference(80f); /// Smoothness between Enabled and Disable [Tooltip("Smoothness between Enabled and Disable")] public FloatReference Smoothness = new FloatReference(5f); /// Smoothness between Enabled and Disable [Tooltip("Use the LookAt only when there's a Force Target on the Aim... use this when the Animal is AI Controlled")] [SerializeField] private BoolReference onlyTargets = new BoolReference(false) ; public BoolEvent OnLookAtActive = new BoolEvent(); [Space] public BoneRotation[] Bones; //Bone Chain private Quaternion[] LocalRot; //Bone Chain public bool debug = true; private float SP_Weight; /// Angle created between the transform.Forward and the LookAt Point protected float angle; /// Means there's a camera or a Target to look At public bool HasTarget { get; set; } public Vector3 UpVector => a_UpVector != null ? a_UpVector.UpVector : Vector3.up; private Transform EndBone; /// Direction Stored on the Aim Script public Vector3 AimDirection => aimer.AimDirection; private bool isAiming; /// Check if is on the Angle of Aiming public bool IsAiming { get { var check = Active && CameraTarget && ActiveByAnimation && (angle < LimitAngle); if (check != isAiming) { isAiming = check; OnLookAtActive.Invoke(isAiming); if (!isAiming) { ResetBoneLocalRot(); } } return isAiming; } } bool CameraTarget; /// Enable Disable the Look At public bool Active { get => active; set { active.Value = value; //enable disable also the Aimer if (aimer != null) aimer.Active = value; } } /// Enable/Disable the LookAt by the Animator public bool ActiveByAnimation { get; set; } /// When set to True the Look At will only function with Targets gameobjects only instead of the Camera forward Direction public bool OnlyTargets { get => onlyTargets.Value; set => onlyTargets.Value = value; } void Awake() { a_UpVector = gameObject.FindInterface(); //Get the main camera aimer = gameObject.FindInterface(); //Get the main camera aimer.IgnoreTransform = transform; ActiveByAnimation = true; } void Start() { if (Bones != null && Bones.Length > 0) EndBone = Bones[Bones.Length - 1].bone; if (aimer.AimOrigin == null || aimer.AimOrigin == EndBone) aimer.AimOrigin = Bones[0].bone.parent; LocalRot = new Quaternion[Bones.Length]; for (int i = 0; i < Bones.Length; i++) { LocalRot[i] = Bones[i].bone.localRotation; //Save the Local Rotation of the Bone } } void ResetBoneLocalRot() { for (int i = 0; i < Bones.Length; i++) { Bones[i].bone.localRotation = LocalRot[i]; //Save the Local Rotation of the Bone } } void LateUpdate() { if (Time.time < float.Epsilon || Time.timeScale <=0) return; if (OnlyTargets) CameraTarget = (aimer.AimTarget != null); //If Only Target is true then Disable it because we do not have any target if (!OnlyTargets) CameraTarget = (aimer.MainCamera != null); //If Only Target is false and there's no Camera then Disable it because we do not have any target angle = Vector3.Angle(transform.forward, AimDirection); SP_Weight = Mathf.MoveTowards(SP_Weight, IsAiming ? 1 : 0, Time.deltaTime * Smoothness / 2); //if (UseLerp) // LookAtBoneSet_AnimatePhysics_Lerp(); //Rotate the bones //else LookAtBoneSet_AnimatePhysics(); //Rotate the bones } /// Enable Look At from the Animator (Needs Layer) public void EnableLookAt(int layer) => EnableByPriority(layer + 1); /// Disable Look At from the Animator (Needs Layer) public void DisableLookAt(int layer) => DisableByPriority(layer + 1); public virtual void SetTargetOnly(bool val) => OnlyTargets = val; public virtual void EnableByPriority(int priority) { if (priority >= DisablePriority) { EnablePriority = priority; if (DisablePriority == EnablePriority) DisablePriority = 0; } ActiveByAnimation = (EnablePriority > DisablePriority); //Debug.Log("ActiveByAnimation: "+ ActiveByAnimation); } public virtual void ResetByPriority(int priority) { if (EnablePriority == priority) EnablePriority = 0; if (DisablePriority == priority) DisablePriority = 0; ActiveByAnimation = (EnablePriority > DisablePriority); // Debug.Log("Res"); } public virtual void DisableByPriority(int priority) { if (priority >= EnablePriority) { DisablePriority = priority; if (DisablePriority == EnablePriority) EnablePriority = 0; } // Debug.Log("Dis"); ActiveByAnimation = (EnablePriority > DisablePriority); } //bool OverridePriority; //bool lastActivation; public int EnablePriority { get; private set; } public int DisablePriority { get; private set; } //private int[] LayersPriority = new int[20]; /// Rotates the bones to the Look direction for FIXED UPTADE ANIMALS void LookAtBoneSet_AnimatePhysics() { // CalculateAiming(); for (int i = 0; i < Bones.Length; i++) { var bn = Bones[i]; if (!bn.bone) continue; if (IsAiming) { var BoneAim = Vector3.Slerp(transform.forward, AimDirection, bn.weight).normalized; var TargetTotation = Quaternion.LookRotation(BoneAim, UpVector) * Quaternion.Euler(bn.offset); bn.nextRotation = Quaternion.Lerp(bn.nextRotation, TargetTotation, SP_Weight); } else { bn.nextRotation = Quaternion.Lerp(bn.bone.rotation, bn.nextRotation, SP_Weight); } if (SP_Weight != 0) { if (!bn.external || bn.external && IsAiming) bn.bone.rotation = bn.nextRotation; } } } //private void CalculateAiming() //{ // IsAiming = Active && CameraTarget && ActiveByAnimation && (angle < LimitAngle); //} //void LookAtBoneSet_AnimatePhysics_Lerp() //{ // Anim.Update(0); //Sadly this needs to be done first // for (int i = 0; i < Bones.Length; i++) // { // var bn = Bones[i]; // if (!bn.bone) continue; // if (IsAiming) // { // var TargetTotation = Quaternion.LookRotation(AimDirection, UpVector) * Quaternion.Euler(bn.offset); // bn.nextRotation = Quaternion.Lerp(bn.bone.rotation, TargetTotation, bn.weight); // } // if (SP_Weight != 0) // { // if (!bn.external || bn.external && IsAiming) // bn.bone.rotation = Quaternion.Lerp(bn.bone.rotation, bn.nextRotation, SP_Weight); // } // } //} /// This is used to listen the Animator asociated to this gameObject public virtual bool OnAnimatorBehaviourMessage(string message, object value) => this.InvokeWithParams(message, value); void OnValidate() { if (Bones != null && Bones.Length > 0) { EndBone = Bones[Bones.Length - 1].bone; } } void Reset() { aimer = gameObject.FindInterface(); if (aimer == null) aimer = gameObject.AddComponent(); } #if UNITY_EDITOR private void OnDrawGizmos() { bool AppIsPlaying = Application.isPlaying; if (debug) { UnityEditor.Handles.color = IsAiming || !AppIsPlaying ? new Color(0, 1, 0, 0.1f) : new Color(1, 0, 0, 0.1f); if (EndBone != null) { UnityEditor.Handles.DrawSolidArc(EndBone.position, UpVector, Quaternion.Euler(0, -LimitAngle, 0) * transform.forward, LimitAngle * 2, 1); UnityEditor.Handles.color = IsAiming || !AppIsPlaying ? Color.green : Color.red; UnityEditor.Handles.DrawWireArc(EndBone.position, UpVector, Quaternion.Euler(0, -LimitAngle, 0) * transform.forward, LimitAngle * 2, 1); } } } #endif } #if UNITY_EDITOR [CustomEditor(typeof(LookAt))] public class LookAtED : Editor { LookAt M; void OnEnable() { M = (LookAt) target; } public override void OnInspectorGUI() { base.OnInspectorGUI(); EditorGUI.BeginDisabledGroup(true); if (Application.isPlaying) { EditorGUILayout.Toggle("Active by Animation", M.ActiveByAnimation); EditorGUILayout.IntField("Enable Priority", M.EnablePriority); EditorGUILayout.IntField("Disable Priority", M.DisablePriority); Repaint(); } EditorGUI.EndDisabledGroup(); } } #endif }