using System.Collections.Generic; using UnityEngine; public class UnitBase : MonoBehaviour { //移动速度 public float speed = 5f; //重力加速度 public float gravity = -20f; //活动范围 public float range = 500f; //兼容旧场景配置,目标搜索不再受此范围限制 public float detectRange = 200f; //攻击范围 public float attackRange = 5f; //基础攻击力,实际伤害结算逻辑后续接入 [Min(0f)] public float attackPower = 10f; //完整攻击动作时长,单位为秒;设置为0时使用动画真实时长 [Min(0f)] public float attackDuration = 0f; public enum AttackType { //近战锁定目标,攻击事件触发时直接扣血。 MeleeDirect, //近战范围伤害,暂未实现。 MeleeArea, //远程锁定目标,攻击事件触发时生成飞行物。 RangedProjectile, //远程抛物线,不锁定目标,暂未实现。 RangedArc } [Header("攻击类型")] public AttackType attackType = AttackType.MeleeDirect; [Header("远程飞行物")] public GameObject projectilePrefab; public Transform projectileSpawnPoint; [Min(0f)] public float projectileSpeed = 20f; [Min(0f)] public float projectileLifetime = 5f; //每次普通攻击命中后增加的技能能量。 [Header("技能能量")] [Min(0f)] public float energyPerAttack = 20f; //角色状态 private State state = State.Alert; //攻击目标 public GameObject target; //当前目标点 private Vector3 targetPos; //初始位置坐标 private Vector3 startPos; //动画控制器 private Animator animator; // 角色控制器 private CharacterController controller; // 自身的血条组件,同时负责显示技能能量条。 private WorldHealthBar worldHealthBar; //垂直速度 private float verticalVelocity; //当前攻击开始时间,用于在没有结束 Event 时释放攻击状态。 private float attackStartTime; //状态枚举 public enum State { //警戒巡逻 Alert, //追踪状态 Track, //攻击状态 Attack } // Start is called before the first frame update void Start() { //初始化 animator = GetComponent(); controller = GetComponent(); worldHealthBar = GetComponent(); //记录初始坐标 startPos = transform.position; randomTargetPos(); } // Update is called once per frame void Update() { //攻击状态下等待Animator报告攻击动画播放完成。 if (state == State.Attack) { animator.SetInteger("speed", 0); applyGravity(); CheckAttackAnimationFinished(); return; } //查找场景中距离自己最近的敌对目标 target = FindNearestOpponent(); if (target != null) { state = State.Track; //目标在攻击范围外则朝目标移动,否则开始攻击 if (!IsWithinAttackReach(target)) { move(target.transform.position - transform.position); } else { attack(target); } return; } //场上没有敌人时进入待机,不再巡逻移动 state = State.Alert; animator.SetInteger("speed", 0); applyGravity(); } //根据自身Tag找到敌对Tag private string GetOpponentTag() { if (CompareTag("Friend")) { return "Enemy"; } if (CompareTag("Enemy")) { return "Friend"; } return null; } //查找距离自己最近的敌对目标 private GameObject FindNearestOpponent() { string opponentTag = GetOpponentTag(); if (string.IsNullOrEmpty(opponentTag)) { return null; } GameObject nearestOpponent = null; float nearestSqrDistance = float.PositiveInfinity; GameObject[] opponents = GameObject.FindGameObjectsWithTag(opponentTag); foreach (var opponent in opponents) { //排除自己以及自己所在层级中的对象 if (opponent == gameObject || opponent.transform.root == transform.root) { continue; } float sqrDistance = (opponent.transform.position - transform.position).sqrMagnitude; if (sqrDistance < nearestSqrDistance) { nearestSqrDistance = sqrDistance; nearestOpponent = opponent; } } return nearestOpponent; } //判断是否进入可攻击距离 private bool IsWithinAttackReach(GameObject opponent) { if (opponent == null) { return false; } // 将双方控制器半径计入判定,避免碰撞体互相阻挡时无法进入攻击距离。 float selfRadius = GetHorizontalCollisionRadius(controller); CharacterController opponentController = opponent.GetComponent(); float opponentRadius = GetHorizontalCollisionRadius(opponentController); float reachDistance = selfRadius + opponentRadius + attackRange; return Vector3.Distance(transform.position, opponent.transform.position) <= reachDistance; } private float GetHorizontalCollisionRadius(CharacterController characterController) { if (characterController == null) { return 0f; } Bounds bounds = characterController.bounds; return Mathf.Max(bounds.extents.x, bounds.extents.z); } //查找当前Animator中使用的攻击动画时长 private float GetAttackAnimationDuration() { if (animator == null || animator.runtimeAnimatorController == null) { return 0f; } var overrideController = animator.runtimeAnimatorController as AnimatorOverrideController; if (overrideController != null) { var overrides = new List>(); overrideController.GetOverrides(overrides); foreach (var pair in overrides) { if (IsAttackAnimation(pair.Key)) { return pair.Value != null ? pair.Value.length : pair.Key.length; } } } foreach (var clip in animator.runtimeAnimatorController.animationClips) { if (IsAttackAnimation(clip)) { return clip.length; } } return 0f; } //根据动画名称判断是否为攻击动画 private bool IsAttackAnimation(AnimationClip clip) { if (clip == null) { return false; } string clipName = clip.name.ToLowerInvariant(); return clipName.Contains("attack") || clipName.Contains("攻击") || clipName.Contains("挥剑"); } //设置攻击动画播放速度 private void SetAttackAnimationSpeed() { float animationDuration = GetAttackAnimationDuration(); if (animationDuration <= 0f || attackDuration <= 0f) { animator.speed = 1f; return; } animator.speed = animationDuration / attackDuration; } //攻击 private void attack(GameObject friend) { target = friend; //根据目标攻击时长加速或减速完整攻击动画 SetAttackAnimationSpeed(); animator.SetInteger("speed", 0); animator.SetTrigger("attack"); state = State.Attack; attackStartTime = Time.time; } //由攻击动画中最合适的帧通过 Animation Event 调用。 public void OnAttackEffect() { if (state != State.Attack || target == null) { return; } switch (attackType) { case AttackType.MeleeDirect: ApplyDirectDamage(target); break; case AttackType.RangedProjectile: FireProjectile(target); break; case AttackType.MeleeArea: case AttackType.RangedArc: break; } } private void ApplyDirectDamage(GameObject opponent) { WorldHealthBar healthBar = opponent.GetComponentInParent(); if (healthBar == null) { return; } healthBar.TakeDamage(attackPower); RegisterAttackHit(); } public void RegisterAttackHit() { AddAttackEnergy(); } private void FireProjectile(GameObject opponent) { if (projectilePrefab == null || projectileSpawnPoint == null) { return; } GameObject projectileObject = Instantiate( projectilePrefab, projectileSpawnPoint.position, projectileSpawnPoint.rotation); Projectile projectile = projectileObject.GetComponent(); if (projectile != null) { projectile.Initialize(opponent, attackPower, gameObject, projectileSpeed, projectileLifetime); } } // 普通攻击命中后积攒能量,满能量时清空能量并调用技能占位方法。 private void AddAttackEnergy() { if (worldHealthBar == null || energyPerAttack <= 0f) { return; } if (!worldHealthBar.AddEnergy(energyPerAttack)) { return; } worldHealthBar.ResetEnergy(); ReleaseSkill(); } // 技能占位方法,后续由具体单位脚本重写。 protected virtual void ReleaseSkill() { } // 根据当前攻击动画的播放进度自动结束攻击,不依赖 Animation Event。 private void CheckAttackAnimationFinished() { float animationDuration = attackDuration > 0f ? attackDuration : GetAttackAnimationDuration(); if (animationDuration > 0f && Time.time - attackStartTime >= animationDuration) { FinishAttack(); return; } if (!IsPlayingAttackAnimation()) { return; } AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0); if (stateInfo.normalizedTime >= 1f) { FinishAttack(); } } private bool IsPlayingAttackAnimation() { AnimatorClipInfo[] clipInfos = animator.GetCurrentAnimatorClipInfo(0); foreach (AnimatorClipInfo clipInfo in clipInfos) { if (IsAttackAnimation(clipInfo.clip)) { return true; } } return false; } private void FinishAttack() { //攻击动画结束后恢复Animator速度。 animator.speed = 1f; //攻击动画结束后,目标仍在攻击范围内则立即进行下一次攻击。 target = FindNearestOpponent(); if (target != null && IsWithinAttackReach(target)) { attack(target); return; } //目标离开攻击范围,回到待机逻辑。 state = State.Alert; animator.SetInteger("speed", 0); applyGravity(); } //角色移动,持续到目标点 void move(Vector3 movement) { movement.y = 0; if (movement == Vector3.zero) { animator.SetInteger("speed", 0); applyGravity(); return; } //改变动画控制器的参数 animator.SetInteger("speed", 1); //向目标点移动,同时应用垂直重力 Vector3 velocity = movement.normalized * speed; velocity.y = getVerticalVelocity(); controller.Move(velocity * Time.deltaTime); //改变角色朝向目标点,只改变y轴 transform.rotation = Quaternion.LookRotation(movement, Vector3.up); transform.rotation = Quaternion.Euler(0, transform.rotation.eulerAngles.y, 0); } private float getVerticalVelocity() { if (controller.isGrounded && verticalVelocity < 0) { verticalVelocity = -2f; } else { verticalVelocity += gravity * Time.deltaTime; } return verticalVelocity; } private void applyGravity() { controller.Move(Vector3.up * getVerticalVelocity() * Time.deltaTime); } //随机目标点 private void randomTargetPos() { //以startPos为中心,在水平圆形范围内随机一个点 Vector2 randomOffset = Random.insideUnitCircle * range; targetPos = startPos + new Vector3(randomOffset.x, 0, randomOffset.y); } }