using UnityEngine; using UnityEngine.Events; using UnityEngine.AI; public abstract class Character : MonoBehaviour { public enum WeaponType { None = 0, Pistol = 1, AssultRifle01 = 2, AssultRifle02 = 3, Shotgun = 4, SniperRifle = 5, Rifle = 6, SubMachineGun = 7, RPG = 8, MiniGun = 9, Grenades = 10, Bow = 11, Melee = 12 } public enum MeleeType { Stab = 0, OneHanded = 1, TwoHanded = 2 } [Header("Stats")] [SerializeField] protected int CurHp = 10; [SerializeField] protected int MaxHp = 10; [Header("Components")] // Optional movement controller reference, assign in inspector if used public NavMeshMovementController MovementController; [SerializeField] private Animator animator; [SerializeField] private NavMeshAgent navMeshAgent; [Header("Animation Settings")] [SerializeField] private WeaponType weaponType = WeaponType.None; [SerializeField] private MeleeType meleeType = MeleeType.Stab; [SerializeField] private WeaponType attackWeaponType = WeaponType.Melee; [SerializeField] private MeleeType attackMeleeType = MeleeType.OneHanded; [SerializeField] private float attackHoldSeconds = 0.15f; [SerializeField] private bool restoreWeaponAfterAttack = true; [SerializeField] [Tooltip("Idle animation index (Animation_int). 0 = normal idle.")] private int idleAnimationIndex = 0; [SerializeField] private bool isGrounded = true; [SerializeField] private bool useRootMotion = true; [SerializeField] private float walkSpeedThreshold = 0.1f; [SerializeField] private float runSpeedThreshold = 2.5f; [Header("Animator Parameter Names")] [SerializeField] private string paramWeaponType = "WeaponType_int"; [SerializeField] private string paramMeleeType = "MeleeType_Int"; [SerializeField] private string paramIdleAnimation = "Animation_int"; [SerializeField] private string paramGrounded = "Grounded"; [SerializeField] private string paramRootMotion = "Static_b"; [SerializeField] private string paramShoot = "Shoot_b"; [SerializeField] private string paramHeadHorizontal = "Head_Horizontal_f"; [SerializeField] private string paramHeadVertical = "Head_Vertical_f"; [SerializeField] private string paramBodyHorizontal = "Body_Horizontal_f"; [SerializeField] private string paramBodyVertical = "Body_Vertical_f"; [SerializeField] private string paramDeath = "Death_b"; [SerializeField] private string paramSpeed = "Speed_f"; protected Character target; public event UnityAction onTakeDamage; private WeaponType lastWeaponType; private MeleeType lastMeleeType; private Coroutine attackRoutine; public int GetCurrentHP() => CurHp; public int GetMaxHP() => MaxHp; protected virtual void Awake() { if (animator == null) { animator = FindAnimatorWithParams(); } if (animator == null) { Debug.LogWarning("Character: No Animator found in children. Assign one in the inspector.", gameObject); } if (navMeshAgent == null) { navMeshAgent = GetComponent(); } ApplyStaticAnimatorParams(); } protected virtual void OnValidate() { if (animator == null) { animator = FindAnimatorWithParams(); } if (navMeshAgent == null) { navMeshAgent = GetComponent(); } } protected virtual void Update() { UpdateMovementAnimatorParams(); } public void TakeDamage(int damageToTake) { if (damageToTake < 0) damageToTake = 0; CurHp -= damageToTake; onTakeDamage?.Invoke(); if (CurHp <= 0) Die(); } public virtual void Die() { SetDeathAnimation(); Destroy(gameObject); } public void SetTarget(Character t) { target = t; } public Character GetTarget() => target; public void SetWeaponType(WeaponType type) { weaponType = type; ApplyStaticAnimatorParams(); } public void SetMeleeType(MeleeType type) { meleeType = type; ApplyStaticAnimatorParams(); } public void SetIdleAnimation(int idleIndex) { idleAnimationIndex = Mathf.Max(0, idleIndex); ApplyStaticAnimatorParams(); } public void SetGrounded(bool grounded) { isGrounded = grounded; if (animator != null) { SetBoolIfExists(paramGrounded, isGrounded); } } public void SetUseRootMotion(bool enableRootMotion) { useRootMotion = enableRootMotion; if (animator != null) { SetBoolIfExists(paramRootMotion, useRootMotion); } } public void TriggerAttack() { if (animator == null) return; if (attackRoutine != null) { StopCoroutine(attackRoutine); } attackRoutine = StartCoroutine(AttackRoutine()); } public void SetHeadLook(float horizontal, float vertical) { if (animator == null) return; SetFloatIfExists(paramHeadHorizontal, horizontal); SetFloatIfExists(paramHeadVertical, vertical); } private void ApplyStaticAnimatorParams() { if (animator == null) return; SetIntIfExists(paramWeaponType, (int)weaponType); SetIntIfExists(paramMeleeType, (int)meleeType); SetIntIfExists(paramIdleAnimation, idleAnimationIndex); SetBoolIfExists(paramGrounded, isGrounded); SetBoolIfExists(paramRootMotion, useRootMotion); } private void UpdateMovementAnimatorParams() { if (animator == null) return; float speed = 0f; if (navMeshAgent != null) { speed = navMeshAgent.velocity.magnitude; if (speed < 0.01f && navMeshAgent.hasPath && !navMeshAgent.pathPending) { // Use desired velocity when actual velocity is near zero speed = navMeshAgent.desiredVelocity.magnitude; } } else if (MovementController != null) { speed = MovementController.GetSpeed(); } MovementState state = MovementState.Idle; if (speed >= runSpeedThreshold) { state = MovementState.Run; } else if (speed >= walkSpeedThreshold) { state = MovementState.Walk; } Vector2 bodyParams = GetBodyParamsForState(weaponType, state); SetFloatIfExists(paramBodyHorizontal, bodyParams.x); SetFloatIfExists(paramBodyVertical, bodyParams.y); SetFloatIfExists(paramSpeed, speed); } private Vector2 GetBodyParamsForState(WeaponType type, MovementState state) { // Defaults per Simple Fantasy ReadMe if (type == WeaponType.None) { return Vector2.zero; } if (type == WeaponType.Pistol) { if (state == MovementState.Run) return new Vector2(0f, 0.2f); return Vector2.zero; } if (type == WeaponType.Grenades) { return Vector2.zero; } // All other weapons if (state == MovementState.Run) return new Vector2(0.3f, 0.6f); return new Vector2(0f, 0.6f); } private void SetDeathAnimation() { if (animator == null) return; SetBoolIfExists(paramDeath, true); } private System.Collections.IEnumerator ResetBoolNextFrame(string boolName) { SetBoolIfExists(boolName, true); yield return null; SetBoolIfExists(boolName, false); } private System.Collections.IEnumerator AttackRoutine() { lastWeaponType = weaponType; lastMeleeType = meleeType; weaponType = attackWeaponType; meleeType = attackMeleeType; ApplyStaticAnimatorParams(); SetBoolIfExists(paramShoot, true); if (attackHoldSeconds > 0f) { yield return new WaitForSeconds(attackHoldSeconds); } SetBoolIfExists(paramShoot, false); if (restoreWeaponAfterAttack) { weaponType = lastWeaponType; meleeType = lastMeleeType; ApplyStaticAnimatorParams(); } attackRoutine = null; } private bool HasParam(string paramName, AnimatorControllerParameterType type) { if (animator == null || string.IsNullOrEmpty(paramName)) return false; foreach (var param in animator.parameters) { if (param.name == paramName && param.type == type) return true; } return false; } private bool HasParam(Animator targetAnimator, string paramName, AnimatorControllerParameterType type) { if (targetAnimator == null || string.IsNullOrEmpty(paramName)) return false; foreach (var param in targetAnimator.parameters) { if (param.name == paramName && param.type == type) return true; } return false; } private Animator FindAnimatorWithParams() { var animators = GetComponentsInChildren(true); if (animators == null || animators.Length == 0) return null; foreach (var anim in animators) { if (HasParam(anim, paramSpeed, AnimatorControllerParameterType.Float) || HasParam(anim, paramWeaponType, AnimatorControllerParameterType.Int)) { return anim; } } return animators[0]; } private void SetIntIfExists(string paramName, int value) { if (HasParam(paramName, AnimatorControllerParameterType.Int)) animator.SetInteger(paramName, value); } private void SetFloatIfExists(string paramName, float value) { if (HasParam(paramName, AnimatorControllerParameterType.Float)) animator.SetFloat(paramName, value); } private void SetBoolIfExists(string paramName, bool value) { if (HasParam(paramName, AnimatorControllerParameterType.Bool)) animator.SetBool(paramName, value); } private enum MovementState { Idle, Walk, Run } }