Files
BABA_YAGA/Assets/Scripts/AI NPC/AnimatorAI.cs
2026-06-05 23:25:56 +07:00

207 lines
7.0 KiB
C#

using UnityEngine;
using UnityEngine.AI;
using Invector;
using Invector.vEventSystems;
using System.Collections;
/// <summary>
/// AnimatorAI: Advanced Invector-style Animator synchronization for EnemyAI.
/// Resolves T-pose by ensuring all core Invector parameters are correctly synced.
/// </summary>
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<Animator>();
enemyAI = GetComponent<EnemyAI>();
agent = GetComponent<NavMeshAgent>();
rb = GetComponent<Rigidbody>();
healthController = GetComponent<vHealthController>();
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
}