using UnityEngine; using UnityEngine.AI; using Invector; using Invector.vEventSystems; using System.Collections; /// /// AnimatorAI: Advanced Invector-style Animator synchronization for EnemyAI. /// Resolves T-pose by ensuring all core Invector parameters are correctly synced. /// public class AnimatorAI : MonoBehaviour, vIAnimatorStateInfoController { protected Animator animator; protected EnemyAI enemyAI; protected NavMeshAgent agent; protected Rigidbody rb; protected vHealthController healthController; [Header("Movement Settings")] public float sprintThreshold = 0.8f; public float dampTime = 0.1f; public float groundDistanceValue = 0.05f; public vAnimatorStateInfos animatorStateInfos { get; protected set; } #region Animator Parameters (Invector Style) protected vAnimatorParameter isDead; protected vAnimatorParameter isGrounded; protected vAnimatorParameter isStrafing; protected vAnimatorParameter isSprinting; protected vAnimatorParameter isAiming; protected vAnimatorParameter verticalVelocity; protected vAnimatorParameter horizontalVelocity; protected vAnimatorParameter groundDistance; protected vAnimatorParameter moveSet_ID; protected vAnimatorParameter attackID; protected vAnimatorParameter hitDirection; protected vAnimatorParameter reactionID; protected vAnimatorParameter triggerReaction; protected vAnimatorParameter resetState; #endregion protected Vector3 lastPosition; protected float currentV; protected float currentH; protected virtual void Awake() { animator = GetComponentInChildren(); enemyAI = GetComponent(); agent = GetComponent(); rb = GetComponent(); healthController = GetComponent(); if (animator) { animatorStateInfos = new vAnimatorStateInfos(animator); InitializeParameters(); } lastPosition = transform.position; } protected virtual void OnEnable() { this.Register(); if (healthController) healthController.onReceiveDamage.AddListener(OnReceiveDamage); } protected virtual void OnDisable() { this.UnRegister(); if (healthController) healthController.onReceiveDamage.RemoveListener(OnReceiveDamage); } protected virtual void InitializeParameters() { isDead = new vAnimatorParameter(animator, "isDead"); isGrounded = new vAnimatorParameter(animator, "IsGrounded"); isStrafing = new vAnimatorParameter(animator, "IsStrafing"); isSprinting = new vAnimatorParameter(animator, "IsSprinting"); isAiming = new vAnimatorParameter(animator, "IsAiming"); verticalVelocity = new vAnimatorParameter(animator, "VerticalVelocity"); horizontalVelocity = new vAnimatorParameter(animator, "HorizontalVelocity"); groundDistance = new vAnimatorParameter(animator, "GroundDistance"); moveSet_ID = new vAnimatorParameter(animator, "MoveSet_ID"); attackID = new vAnimatorParameter(animator, "AttackID"); hitDirection = new vAnimatorParameter(animator, "HitDirection"); reactionID = new vAnimatorParameter(animator, "ReactionID"); triggerReaction = new vAnimatorParameter(animator, "TriggerReaction"); resetState = new vAnimatorParameter(animator, "ResetState"); } protected virtual void Update() { if (animator == null || enemyAI == null || agent == null) return; UpdateMovementParameters(); UpdateCombatParameters(); } protected virtual void UpdateMovementParameters() { // 1. Grounded & GroundDistance (Critical for T-pose prevention) bool grounded = agent.isOnNavMesh || agent.enabled; SetBool(isGrounded, grounded); SetFloat(groundDistance, grounded ? 0f : groundDistanceValue); // 2. Responsive Velocity Calculation // Use a mix of agent velocity and position delta for better responsiveness Vector3 worldVelocity = (transform.position - lastPosition) / Time.deltaTime; lastPosition = transform.position; if (worldVelocity.magnitude < 0.01f) worldVelocity = Vector3.zero; Vector3 localVelocity = transform.InverseTransformDirection(worldVelocity); float targetV = localVelocity.z / enemyAI.moveSpeed; float targetH = localVelocity.x / enemyAI.moveSpeed; // Smooth velocity values currentV = Mathf.Lerp(currentV, targetV, 10f * Time.deltaTime); currentH = Mathf.Lerp(currentH, targetH, 10f * Time.deltaTime); SetFloat(verticalVelocity, currentV); SetFloat(horizontalVelocity, currentH); // 3. Sprinting bool sprinting = worldVelocity.magnitude > (enemyAI.moveSpeed * sprintThreshold); SetBool(isSprinting, sprinting); // 4. Strafing SetBool(isStrafing, enemyAI.playerHasArtifact); } protected virtual void UpdateCombatParameters() { SetBool(isAiming, enemyAI.playerHasArtifact); SetInt(moveSet_ID, enemyAI.playerHasArtifact ? 1 : 0); // Shooting burst if (enemyAI.IsShootingBurst) SetInt(attackID, 1); else SetInt(attackID, 0); // Dodge logic if (enemyAI.IsDodging) { // In Invector, dodges are often handled via triggers or specific IDs SetAnimatorTrigger(triggerReaction); } // Death state if (healthController) SetBool(isDead, healthController.isDead); } protected virtual void OnReceiveDamage(vDamage damage) { if (animator == null || !animator.enabled) return; // Sync damage parameters for hit reactions if (hitDirection.isValid && damage.sender) { float angle = transform.HitAngle(damage.sender.position); animator.SetInteger(hitDirection, (int)angle); } if (reactionID.isValid) animator.SetInteger(reactionID, damage.reaction_id); SetAnimatorTrigger(triggerReaction); SetAnimatorTrigger(resetState); } #region Helpers protected void SetBool(vAnimatorParameter param, bool value) { if (param.isValid && animator.GetBool(param) != value) animator.SetBool(param, value); } protected void SetFloat(vAnimatorParameter param, float value) { if (param.isValid) animator.SetFloat(param, value, dampTime, Time.deltaTime); } protected void SetInt(vAnimatorParameter param, int value) { if (param.isValid && animator.GetInteger(param) != value) animator.SetInteger(param, value); } public void SetAnimatorTrigger(vAnimatorParameter trigger) { if (trigger.isValid) StartCoroutine(SetTriggerRoutine(trigger)); } private IEnumerator SetTriggerRoutine(int targetHash) { animator.SetTrigger(targetHash); yield return new WaitForSeconds(0.1f); animator.ResetTrigger(targetHash); } #endregion }