Files
Click-PointRPG/Assets/Scripts/Character.cs

404 lines
10 KiB
C#
Raw Permalink Normal View History

2026-02-10 21:27:46 +00:00
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.AI;
public abstract class Character : MonoBehaviour
{
2026-02-11 17:30:49 +00:00
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
}
2026-02-10 21:27:46 +00:00
[Header("Stats")]
2026-02-11 17:30:49 +00:00
[SerializeField]
protected int CurHp = 10;
[SerializeField]
protected int MaxHp = 10;
2026-02-10 21:27:46 +00:00
[Header("Components")]
// Optional movement controller reference, assign in inspector if used
public NavMeshMovementController MovementController;
2026-02-11 17:30:49 +00:00
[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";
2026-02-10 21:27:46 +00:00
protected Character target;
public event UnityAction onTakeDamage;
2026-02-11 17:30:49 +00:00
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<NavMeshAgent>();
}
ApplyStaticAnimatorParams();
}
protected virtual void OnValidate()
{
if (animator == null)
{
animator = FindAnimatorWithParams();
}
if (navMeshAgent == null)
{
navMeshAgent = GetComponent<NavMeshAgent>();
}
}
protected virtual void Update()
{
UpdateMovementAnimatorParams();
}
2026-02-10 21:27:46 +00:00
public void TakeDamage(int damageToTake)
{
2026-02-11 17:30:49 +00:00
if (damageToTake < 0) damageToTake = 0;
2026-02-10 21:27:46 +00:00
CurHp -= damageToTake;
onTakeDamage?.Invoke();
2026-02-11 17:30:49 +00:00
2026-02-10 21:27:46 +00:00
if (CurHp <= 0) Die();
}
public virtual void Die()
{
2026-02-11 17:30:49 +00:00
SetDeathAnimation();
2026-02-10 21:27:46 +00:00
Destroy(gameObject);
}
public void SetTarget(Character t)
{
target = t;
}
public Character GetTarget() => target;
2026-02-11 17:30:49 +00:00
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<Animator>(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
}
2026-02-10 21:27:46 +00:00
}