300 lines
11 KiB
C#
300 lines
11 KiB
C#
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
using Invector;
|
|
using System.Collections;
|
|
|
|
/// <summary>
|
|
/// AnimatorAI: Đồng bộ hóa trạng thái của EnemyAI với Animator.
|
|
/// Tích hợp Simulation Mode để giả lập animation khi chưa có logic di chuyển hoàn chỉnh.
|
|
/// </summary>
|
|
public class AnimatorAI : MonoBehaviour
|
|
{
|
|
protected Animator animator;
|
|
protected EnemyAI enemyAI;
|
|
protected NavMeshAgent agent;
|
|
protected Rigidbody rb;
|
|
|
|
[Header("Debug Settings")]
|
|
public bool debugMode = true;
|
|
public Color debugColor = Color.cyan;
|
|
|
|
[Header("Simulation Mode (Giả lập)")]
|
|
public bool useSimulation = false; // Tích chọn để dùng thông số giả lập bên dưới
|
|
public bool autoCycleSpeed = false; // Tự động chạy/đi bộ/đứng im theo vòng lặp
|
|
[Range(0, 1)] public float simVerticalVelocity = 0f;
|
|
public bool simIsSprinting = false;
|
|
public bool simIsAiming = false;
|
|
public int simMoveSetID = 0;
|
|
|
|
[Header("Movement Settings")]
|
|
public float sprintThreshold = 0.8f;
|
|
public float dampTime = 0.1f;
|
|
|
|
#region Animator Parameters (Invector Style)
|
|
protected vAnimatorParameter isDead;
|
|
protected vAnimatorParameter isGrounded;
|
|
protected vAnimatorParameter isCrouching;
|
|
protected vAnimatorParameter isStrafing;
|
|
protected vAnimatorParameter isSliding;
|
|
protected vAnimatorParameter isSprinting;
|
|
protected vAnimatorParameter isAiming;
|
|
protected vAnimatorParameter canAim;
|
|
protected vAnimatorParameter flipAnimation;
|
|
protected vAnimatorParameter flipEquip;
|
|
protected vAnimatorParameter groundDistance;
|
|
protected vAnimatorParameter groundAngle;
|
|
protected vAnimatorParameter verticalVelocity;
|
|
protected vAnimatorParameter moveSet_ID;
|
|
protected vAnimatorParameter upperBody_ID;
|
|
protected vAnimatorParameter idleRandom;
|
|
protected vAnimatorParameter idleRandomTrigger;
|
|
protected vAnimatorParameter randomAttack;
|
|
protected vAnimatorParameter weakAttack;
|
|
protected vAnimatorParameter strongAttack;
|
|
protected vAnimatorParameter isBlocking;
|
|
protected vAnimatorParameter attackID;
|
|
protected vAnimatorParameter defenseID;
|
|
protected vAnimatorParameter recoilID;
|
|
protected vAnimatorParameter reactionID;
|
|
protected vAnimatorParameter triggerRecoil;
|
|
protected vAnimatorParameter triggerReaction;
|
|
protected vAnimatorParameter hitDirection;
|
|
protected vAnimatorParameter resetState;
|
|
protected vAnimatorParameter reload;
|
|
protected vAnimatorParameter cancelReload;
|
|
protected vAnimatorParameter reloadID;
|
|
protected vAnimatorParameter shoot;
|
|
protected vAnimatorParameter shot_ID;
|
|
protected vAnimatorParameter powerCharger;
|
|
#endregion
|
|
|
|
protected virtual void Start()
|
|
{
|
|
animator = GetComponentInChildren<Animator>();
|
|
enemyAI = GetComponent<EnemyAI>();
|
|
agent = GetComponent<NavMeshAgent>();
|
|
rb = GetComponent<Rigidbody>();
|
|
|
|
if (animator == null)
|
|
{
|
|
Debug.LogError($"<color=red>[AnimatorAI]</color> KHÔNG tìm thấy Animator trên {gameObject.name} hoặc các con của nó!");
|
|
return;
|
|
}
|
|
|
|
// CHẨN ĐOÁN SÂU:
|
|
if (debugMode) StartCoroutine(DeepDiagnosticRoutine());
|
|
|
|
InitializeParameters();
|
|
}
|
|
|
|
private IEnumerator DeepDiagnosticRoutine()
|
|
{
|
|
while (true)
|
|
{
|
|
yield return new WaitForSeconds(2f); // Kiểm tra mỗi 2 giây
|
|
|
|
if (animator == null) yield break;
|
|
|
|
// 1. Kiểm tra Avatar
|
|
if (animator.avatar == null)
|
|
Debug.LogError($"<color=red>[T-POSE ALERT]</color> {animator.gameObject.name} KHÔNG CÓ AVATAR! Đây là lý do bị T-Pose. Hãy kéo Avatar vào component Animator.");
|
|
|
|
// 2. Kiểm tra Speed
|
|
if (animator.speed <= 0)
|
|
Debug.LogWarning($"<color=yellow>[T-POSE ALERT]</color> Tốc độ Animator đang bằng {animator.speed}. Nhân vật sẽ không cử động.");
|
|
|
|
// 3. Kiểm tra Animation Clips
|
|
var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
|
|
var clipInfo = animator.GetCurrentAnimatorClipInfo(0);
|
|
|
|
if (clipInfo.Length == 0)
|
|
{
|
|
Debug.LogError($"<color=red>[T-POSE ALERT]</color> State hiện tại ({stateInfo.fullPathHash}) KHÔNG CÓ Clip animation nào! Hãy kéo file .anim vào ô Motion của State trong Animator Controller.");
|
|
}
|
|
|
|
// 4. Kiểm tra Culling
|
|
if (animator.cullingMode == AnimatorCullingMode.CullCompletely)
|
|
{
|
|
// Đôi khi Unity tự tắt animation nếu camera không nhìn thấy
|
|
Debug.Log($"<color=white>[Info]</color> Culling Mode đang là CullCompletely. Nếu nhân vật ở xa có thể sẽ dừng animation.");
|
|
}
|
|
|
|
if (!useSimulation) yield break; // Nếu không dùng simulation thì chỉ check 1 lần rồi thôi
|
|
}
|
|
}
|
|
|
|
protected virtual void InitializeParameters()
|
|
{
|
|
if (animator == null) return;
|
|
|
|
// Khởi tạo và kiểm tra từng tham số quan trọng
|
|
isDead = ValidateAndInit("isDead");
|
|
isGrounded = ValidateAndInit("IsGrounded");
|
|
isCrouching = ValidateAndInit("IsCrouching");
|
|
isStrafing = ValidateAndInit("IsStrafing");
|
|
isSliding = ValidateAndInit("IsSliding");
|
|
isSprinting = ValidateAndInit("IsSprinting");
|
|
isAiming = ValidateAndInit("IsAiming");
|
|
canAim = ValidateAndInit("CanAim");
|
|
flipAnimation = ValidateAndInit("FlipAnimation");
|
|
flipEquip = ValidateAndInit("FlipEquip");
|
|
groundDistance = ValidateAndInit("GroundDistance");
|
|
groundAngle = ValidateAndInit("GroundAngle");
|
|
verticalVelocity = ValidateAndInit("VerticalVelocity");
|
|
moveSet_ID = ValidateAndInit("MoveSet_ID");
|
|
upperBody_ID = ValidateAndInit("UpperBody_ID");
|
|
idleRandom = ValidateAndInit("IdleRandom");
|
|
idleRandomTrigger = ValidateAndInit("IdleRandomTrigger");
|
|
randomAttack = ValidateAndInit("RandomAttack");
|
|
weakAttack = ValidateAndInit("WeakAttack");
|
|
strongAttack = ValidateAndInit("StrongAttack");
|
|
isBlocking = ValidateAndInit("IsBlocking");
|
|
attackID = ValidateAndInit("AttackID");
|
|
defenseID = ValidateAndInit("DefenseID");
|
|
recoilID = ValidateAndInit("RecoilID");
|
|
reactionID = ValidateAndInit("ReactionID");
|
|
triggerRecoil = ValidateAndInit("TriggerRecoil");
|
|
triggerReaction = ValidateAndInit("TriggerReaction");
|
|
hitDirection = ValidateAndInit("HitDirection");
|
|
resetState = ValidateAndInit("ResetState");
|
|
reload = ValidateAndInit("Reload");
|
|
cancelReload = ValidateAndInit("CancelReload");
|
|
reloadID = ValidateAndInit("ReloadID");
|
|
shoot = ValidateAndInit("Shoot");
|
|
shot_ID = ValidateAndInit("Shot_ID");
|
|
powerCharger = ValidateAndInit("PowerCharger");
|
|
}
|
|
|
|
private vAnimatorParameter ValidateAndInit(string paramName)
|
|
{
|
|
vAnimatorParameter p = new vAnimatorParameter(animator, paramName);
|
|
if (!p.isValid && debugMode)
|
|
{
|
|
// Chỉ cảnh báo những biến cốt lõi nếu thiếu
|
|
if (paramName == "VerticalVelocity" || paramName == "IsGrounded" || paramName == "IsAiming")
|
|
Debug.LogWarning($"<color=yellow>[AnimatorAI]</color> Cảnh báo: Controller thiếu biến quan trọng: <b>{paramName}</b>");
|
|
}
|
|
return p;
|
|
}
|
|
|
|
protected virtual void Update()
|
|
{
|
|
if (animator == null) return;
|
|
|
|
if (useSimulation)
|
|
{
|
|
RunSimulation();
|
|
}
|
|
else
|
|
{
|
|
if (enemyAI == null || agent == null) return;
|
|
UpdateMovementParameters();
|
|
UpdateCombatParameters();
|
|
}
|
|
}
|
|
|
|
protected virtual void RunSimulation()
|
|
{
|
|
// 1. Giả lập tốc độ di chuyển
|
|
if (autoCycleSpeed)
|
|
{
|
|
// Tạo vòng lặp tốc độ từ 0 đến 1 dùng hàm Sin
|
|
simVerticalVelocity = Mathf.Abs(Mathf.Sin(Time.time * 0.5f));
|
|
simIsSprinting = simVerticalVelocity > sprintThreshold;
|
|
}
|
|
|
|
SetFloat(verticalVelocity, simVerticalVelocity, "SIM: VerticalVelocity");
|
|
SetBool(isSprinting, simIsSprinting, "SIM: IsSprinting");
|
|
SetBool(isGrounded, true, "SIM: IsGrounded"); // Luôn giả lập trên mặt đất
|
|
|
|
// 2. Giả lập chiến đấu
|
|
SetBool(isAiming, simIsAiming, "SIM: IsAiming");
|
|
SetInt(moveSet_ID, simMoveSetID, "SIM: MoveSet_ID");
|
|
SetBool(canAim, simIsAiming, "SIM: CanAim");
|
|
}
|
|
|
|
protected virtual void UpdateMovementParameters()
|
|
{
|
|
bool grounded = agent.isOnNavMesh || agent.enabled;
|
|
SetBool(isGrounded, grounded, "IsGrounded");
|
|
|
|
float speed = agent.velocity.magnitude / enemyAI.moveSpeed;
|
|
SetFloat(verticalVelocity, speed, "VerticalVelocity");
|
|
|
|
bool sprinting = agent.velocity.magnitude > (enemyAI.moveSpeed * sprintThreshold);
|
|
SetBool(isSprinting, sprinting, "IsSprinting");
|
|
|
|
bool isDodging = !agent.enabled && !rb.isKinematic;
|
|
SetBool(flipAnimation, isDodging, "FlipAnimation (Dodge)");
|
|
}
|
|
|
|
protected virtual void UpdateCombatParameters()
|
|
{
|
|
bool aiming = enemyAI.playerHasArtifact && agent.isStopped;
|
|
SetBool(isAiming, aiming, "IsAiming");
|
|
|
|
int moveID = enemyAI.playerHasArtifact ? 1 : 0;
|
|
SetInt(moveSet_ID, moveID, "MoveSet_ID");
|
|
|
|
SetBool(canAim, enemyAI.playerHasArtifact, "CanAim");
|
|
}
|
|
|
|
#region Optimized Setters with Debug
|
|
|
|
protected void SetBool(vAnimatorParameter param, bool value, string name)
|
|
{
|
|
if (param.isValid)
|
|
{
|
|
bool current = animator.GetBool(param);
|
|
if (current != value)
|
|
{
|
|
animator.SetBool(param, value);
|
|
if (debugMode) Debug.Log($"<color=cyan>[AnimDebug]</color> {gameObject.name}: <b>{name}</b> -> {value}");
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void SetFloat(vAnimatorParameter param, float value, string name)
|
|
{
|
|
if (param.isValid)
|
|
{
|
|
animator.SetFloat(param, value, dampTime, Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
protected void SetInt(vAnimatorParameter param, int value, string name)
|
|
{
|
|
if (param.isValid)
|
|
{
|
|
int current = animator.GetInteger(param);
|
|
if (current != value)
|
|
{
|
|
animator.SetInteger(param, value);
|
|
if (debugMode) Debug.Log($"<color=orange>[AnimDebug]</color> {gameObject.name}: <b>{name}</b> -> {value}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods (Triggers)
|
|
|
|
public virtual void SetAnimatorTrigger(vAnimatorParameter trigger, string name = "Trigger")
|
|
{
|
|
if (trigger.isValid)
|
|
{
|
|
if (debugMode) Debug.Log($"<color=yellow>[AnimDebug]</color> {gameObject.name}: Kích hoạt <b>{name}</b>");
|
|
StartCoroutine(SetTriggerRoutine(trigger));
|
|
}
|
|
}
|
|
|
|
private IEnumerator SetTriggerRoutine(int targetHash)
|
|
{
|
|
animator.SetTrigger(targetHash);
|
|
yield return new WaitForSeconds(0.1f);
|
|
animator.ResetTrigger(targetHash);
|
|
}
|
|
|
|
#endregion
|
|
}
|