根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:

```
docs(readme): 更新文档说明

- 添加项目使用指南
- 完善API接口描述
- 修正错误的配置示例
```

注意:由于您提供的code differences部分为空,以上是一个示例模板。请提供具体的代码变更信息以便生成更准确的commit message。
This commit is contained in:
huadexuan committed 2026-08-26 23:36:30 +08:00
1 parent e532a6c8ce
commit fe3877dedb
16 files changed
+951 -113

No files matched your search

+122
View File
@@ -0,0 +1,122 @@
using UnityEngine;
public class Projectile : MonoBehaviour
{
private GameObject target;
private GameObject owner;
private float damage;
private float speed;
private float lifetime;
private bool initialized;
public void Initialize(
GameObject targetObject,
float damageAmount,
GameObject ownerObject,
float moveSpeed,
float lifeTime)
{
target = targetObject;
damage = damageAmount;
owner = ownerObject;
speed = moveSpeed;
lifetime = lifeTime;
initialized = true;
if (lifetime > 0f)
{
Destroy(gameObject, lifetime);
}
}
private void Update()
{
if (!initialized)
{
return;
}
if (target == null)
{
Destroy(gameObject);
return;
}
Vector3 offset = target.transform.position - transform.position;
if (offset.sqrMagnitude <= 0.01f)
{
HitTarget(target);
return;
}
Vector3 direction = offset.normalized;
float distance = speed * Time.deltaTime;
if (distance >= offset.magnitude)
{
HitTarget(target);
return;
}
if (Physics.Raycast(transform.position, direction, out RaycastHit hit, distance))
{
TryHit(hit.collider.gameObject);
if (hit.collider.transform.root == target.transform.root)
{
return;
}
}
transform.forward = direction;
transform.position += direction * distance;
}
private void OnTriggerEnter(Collider other)
{
TryHit(other.gameObject);
}
private void OnCollisionEnter(Collision collision)
{
TryHit(collision.gameObject);
}
private void TryHit(GameObject hitObject)
{
if (!initialized || hitObject == null || IsOwner(hitObject))
{
return;
}
if (hitObject.transform.root == target.transform.root)
{
HitTarget(hitObject);
}
}
private void HitTarget(GameObject hitObject)
{
WorldHealthBar healthBar = hitObject.GetComponentInParent<WorldHealthBar>();
if (healthBar == null)
{
return;
}
healthBar.TakeDamage(damage);
if (owner != null)
{
UnitBase unit = owner.GetComponent<UnitBase>();
if (unit != null)
{
unit.RegisterAttackHit();
}
}
Destroy(gameObject);
}
private bool IsOwner(GameObject hitObject)
{
return owner != null &&
(hitObject == owner || hitObject.transform.root == owner.transform.root);
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: BygavHz+By46EKJGDuv5Bnn4/4HcaL5YCS0EUQ96cZEDuWogq0Yq0gM=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+123 -36
View File
@@ -1,4 +1,3 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
@@ -28,6 +27,32 @@ public class UnitBase : MonoBehaviour
[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)]
@@ -57,8 +82,9 @@ public class UnitBase : MonoBehaviour
//垂直速度
private float verticalVelocity;
//当前攻击的伤害结算协程。
private Coroutine attackDamageCoroutine;
//当前攻击开始时间,用于在没有结束 Event 时释放攻击状态。
private float attackStartTime;
//状态枚举
public enum State
@@ -95,11 +121,12 @@ public class UnitBase : MonoBehaviour
// Update is called once per frame
void Update()
{
//攻击状态直接返回
//攻击状态下等待Animator报告攻击动画播放完成。
if (state == State.Attack)
{
animator.SetInteger("speed", 0);
applyGravity();
CheckAttackAnimationFinished();
return;
}
@@ -275,36 +302,64 @@ public class UnitBase : MonoBehaviour
animator.SetInteger("speed", 0);
animator.SetTrigger("attack");
state = State.Attack;
//在攻击动画中段结算伤害,而不是在动画开始瞬间扣血。
if (attackDamageCoroutine != null)
{
StopCoroutine(attackDamageCoroutine);
}
attackDamageCoroutine = StartCoroutine(ApplyAttackDamageInMiddle(friend));
attackStartTime = Time.time;
}
//等待半个完整攻击时长后,对目标扣除攻击力。
private IEnumerator ApplyAttackDamageInMiddle(GameObject friend)
//由攻击动画中最合适的帧通过 Animation Event 调用。
public void OnAttackEffect()
{
float fullAttackDuration = attackDuration > 0f
? attackDuration
: Mathf.Max(GetAttackAnimationDuration(), 0.01f);
yield return new WaitForSeconds(fullAttackDuration * 0.5f);
if (friend != null)
if (state != State.Attack || target == null)
{
WorldHealthBar healthBar = friend.GetComponentInParent<WorldHealthBar>();
if (healthBar != null)
{
healthBar.TakeDamage(attackPower);
AddAttackEnergy();
}
return;
}
attackDamageCoroutine = null;
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<WorldHealthBar>();
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<Projectile>();
if (projectile != null)
{
projectile.Initialize(opponent, attackPower, gameObject, projectileSpeed, projectileLifetime);
}
}
// 普通攻击命中后积攒能量,满能量时清空能量并调用技能占位方法。
@@ -330,19 +385,51 @@ public class UnitBase : MonoBehaviour
}
// 在 Animator 的 Attack 动画结束时调用此方法
public void OnAttackAnimationEnd()
// 根据当前攻击动画的播放进度自动结束攻击,不依赖 Animation Event。
private void CheckAttackAnimationFinished()
{
if (attackDamageCoroutine != null)
float animationDuration = attackDuration > 0f
? attackDuration
: GetAttackAnimationDuration();
if (animationDuration > 0f && Time.time - attackStartTime >= animationDuration)
{
StopCoroutine(attackDamageCoroutine);
attackDamageCoroutine = null;
FinishAttack();
return;
}
//攻击动画结束后恢复Animator速度
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))
{
@@ -350,7 +437,7 @@ public class UnitBase : MonoBehaviour
return;
}
//目标离开攻击范围,回到待机逻辑
//目标离开攻击范围,回到待机逻辑。
state = State.Alert;
animator.SetInteger("speed", 0);
applyGravity();