```
feat(WorldHealthBar): 添加技能能量系统并重构血条UI布局 - 新增技能能量相关属性(maxEnergy、currentEnergy) - 添加能量条UI组件和布局管理 - 实现AddEnergy和ResetEnergy方法支持能量管理 - 调整血条整体布局以容纳能量条 - 移除已废弃的EnemyBaseMove脚本及其元文件 ```
This commit is contained in:
1 parent
98176721d0
commit
e532a6c8ce
24 files changed
+7525
-6
No files matched your search
@@ -0,0 +1,411 @@
|
||||
using System.Collections;
|
||||
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;
|
||||
|
||||
//每次普通攻击命中后增加的技能能量。
|
||||
[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;
|
||||
|
||||
//当前攻击的伤害结算协程。
|
||||
private Coroutine attackDamageCoroutine;
|
||||
|
||||
//状态枚举
|
||||
public enum State
|
||||
{
|
||||
//警戒巡逻
|
||||
Alert,
|
||||
|
||||
//追踪状态
|
||||
Track,
|
||||
|
||||
//攻击状态
|
||||
Attack
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
//初始化
|
||||
animator = GetComponent<Animator>();
|
||||
controller = GetComponent<CharacterController>();
|
||||
worldHealthBar = GetComponent<WorldHealthBar>();
|
||||
//记录初始坐标
|
||||
startPos = transform.position;
|
||||
randomTargetPos();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
//攻击状态直接返回
|
||||
if (state == State.Attack)
|
||||
{
|
||||
animator.SetInteger("speed", 0);
|
||||
applyGravity();
|
||||
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<CharacterController>();
|
||||
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<KeyValuePair<AnimationClip, AnimationClip>>();
|
||||
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;
|
||||
|
||||
//在攻击动画中段结算伤害,而不是在动画开始瞬间扣血。
|
||||
if (attackDamageCoroutine != null)
|
||||
{
|
||||
StopCoroutine(attackDamageCoroutine);
|
||||
}
|
||||
|
||||
attackDamageCoroutine = StartCoroutine(ApplyAttackDamageInMiddle(friend));
|
||||
}
|
||||
|
||||
//等待半个完整攻击时长后,对目标扣除攻击力。
|
||||
private IEnumerator ApplyAttackDamageInMiddle(GameObject friend)
|
||||
{
|
||||
float fullAttackDuration = attackDuration > 0f
|
||||
? attackDuration
|
||||
: Mathf.Max(GetAttackAnimationDuration(), 0.01f);
|
||||
|
||||
yield return new WaitForSeconds(fullAttackDuration * 0.5f);
|
||||
|
||||
if (friend != null)
|
||||
{
|
||||
WorldHealthBar healthBar = friend.GetComponentInParent<WorldHealthBar>();
|
||||
if (healthBar != null)
|
||||
{
|
||||
healthBar.TakeDamage(attackPower);
|
||||
AddAttackEnergy();
|
||||
}
|
||||
}
|
||||
|
||||
attackDamageCoroutine = null;
|
||||
}
|
||||
|
||||
// 普通攻击命中后积攒能量,满能量时清空能量并调用技能占位方法。
|
||||
private void AddAttackEnergy()
|
||||
{
|
||||
if (worldHealthBar == null || energyPerAttack <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!worldHealthBar.AddEnergy(energyPerAttack))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
worldHealthBar.ResetEnergy();
|
||||
ReleaseSkill();
|
||||
}
|
||||
|
||||
// 技能占位方法,后续由具体单位脚本重写。
|
||||
protected virtual void ReleaseSkill()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// 在 Animator 的 Attack 动画结束时调用此方法
|
||||
public void OnAttackAnimationEnd()
|
||||
{
|
||||
if (attackDamageCoroutine != null)
|
||||
{
|
||||
StopCoroutine(attackDamageCoroutine);
|
||||
attackDamageCoroutine = null;
|
||||
}
|
||||
|
||||
//攻击动画结束后恢复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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user