feat(gameplay): 提升单位检测范围并添加法师属性

- 将所有单位的检测范围从默认值提升至100
- 为法师预制体添加护盾、等级等新属性
- 为法师添加暴击、吸血、伤害减免等战斗属性
- 更换法师弹丸预设并调整相关参数
- 添加锚点交战Z线控制功能
- 更新玩家数据中配备的召唤师技能
- 移除多余的纹理配置文件
```
This commit is contained in:
huadexuan committed 2026-10-07 23:59:17 +08:00
1 parent 6089308f66
commit 23c559d9f7
243 files changed
+150612 -1459

No files matched your search

+165 -22
View File
@@ -30,6 +30,13 @@ public class UnitBase : MonoBehaviour
[Min(0f)]
public float attackPower = 10f;
[Header("战术增益扩展属性")]
public float critRate = 0f; // 暴击率 (如 0.15 = 15%)
public float critMultiplier = 1.5f; // 暴击伤害倍数
public float lifestealRate = 0f; // 伤害吸血比例 (如 0.08 = 8%)
public float damageReductionRate = 0f; // 伤害减免比例 (如 0.10 = 10%, 最高 75%)
public float skillChargeMultiplier = 1f; // 技能能量充能倍率 (如 1.25 = +25%)
//完整攻击动作时长,单位为秒;设置为0时使用动画真实时长
[Min(0f)]
public float attackDuration = 0f;
@@ -87,6 +94,13 @@ public class UnitBase : MonoBehaviour
[Tooltip("开启后,只打「离自己阵位不超过 寻敌视野 detectionRange」的敌人,避免一路推到很远的地方。")]
public bool useAnchorLeash = false;
[Header("警戒线出击")]
[Tooltip("开启后,只有当敌人进入警戒线(Z <= engagementZThreshold)后才全员出击;出击后视野全开,战斗中途不回家。")]
public bool useEngagementZLine = true;
public float engagementZThreshold = 10.0f;
private static bool isHeroTeamEngaged = false;
//角色状态
private State state = State.Alert;
@@ -142,10 +156,11 @@ public class UnitBase : MonoBehaviour
// Start is called before the first frame update
public void InitializeFromPlayerData(float health, float attack, float moveSpeed)
public void InitializeFromPlayerData(float health, float attack, float moveSpeed, int unitLvl = 1)
{
attackPower = attack;
speed = moveSpeed;
level = Mathf.Max(1, unitLvl);
// 同步更新 NavMeshAgent 速度
if (navAgent == null)
@@ -166,6 +181,7 @@ public class UnitBase : MonoBehaviour
{
worldHealthBar.maxHealth = health;
worldHealthBar.currentHealth = health;
worldHealthBar.SetLevel(level);
}
}
@@ -261,10 +277,14 @@ public class UnitBase : MonoBehaviour
//攻击状态下等待Animator报告攻击动画播放完成。
if (state == State.Attack)
{
// 攻击时直接禁用 NavMeshAgent,彻底停止移动
if (navAgent != null && navAgent.enabled)
// 攻击时暂停 NavMeshAgent,但保持 enabled 维持物理/寻路障碍排体
if (navAgent != null && navAgent.enabled && navAgent.isOnNavMesh)
{
navAgent.enabled = false;
if (!navAgent.isStopped)
{
navAgent.isStopped = true;
navAgent.velocity = Vector3.zero;
}
}
animator.SetInteger("speed", 0);
@@ -330,12 +350,16 @@ public class UnitBase : MonoBehaviour
return;
}
//场上没有敌人时进入待机,不再巡逻移动
//场上完全没有敌人或整波清理干净时,才进入待机回格位
state = State.Alert;
if (returnToAnchorWhenIdle && anchorInitialized && !IsAtAnchor())
if (!HasAnyEnemyOnField())
{
ReturnToAnchor();
return;
isHeroTeamEngaged = false;
if (returnToAnchorWhenIdle && anchorInitialized && !IsAtAnchor())
{
ReturnToAnchor();
return;
}
}
// 召唤师模式下动画由 SummonerClickMovement 控制(移动时 speed=1,站定后 speed=0)
@@ -418,13 +442,20 @@ public class UnitBase : MonoBehaviour
return false;
}
// 战术增益:伤害减免 (最高 75%)
if (damageReductionRate > 0f)
{
float cappedReduction = Mathf.Clamp(damageReductionRate, 0f, 0.75f);
damage *= (1f - cappedReduction);
}
healthBar.TakeDamage(ModifyIncomingDamage(damage, attacker), attacker);
return true;
}
//根运动协调(applyRootMotion=true 时由动画系统每帧回调):
//走路(agent 驱动):只叠加动画的侧向摇摆和转身差量,前向分量丢弃(防双倍移速);
//攻击/技能(agent 已禁用):root motion 全量生效(前冲、后撤、带位移的攻击动作)。
//攻击/技能:保持 navAgent 开启作为实体阻挡,根运动全量生效,同时实时同步 navAgent.nextPosition 避免拉扯漂移。
private void OnAnimatorMove()
{
if (animator == null) return;
@@ -433,18 +464,32 @@ public class UnitBase : MonoBehaviour
delta.y = 0f;
Quaternion yawDelta = Quaternion.Euler(0f, animator.deltaRotation.eulerAngles.y, 0f);
bool isAttackingOrSkill = state == State.Attack || state == State.Skill || isPlayingSkill;
if (navAgent != null && navAgent.enabled && navAgent.isOnNavMesh)
{
// 移动中:侧向分量(垂直于朝向)保留 = 大摇大摆的摇摆;同步 agent 位置防漂移
if (delta.sqrMagnitude > 1e-10f)
if (isAttackingOrSkill)
{
Vector3 lateral = delta - Vector3.Project(delta, transform.forward);
if (lateral.sqrMagnitude > 1e-10f)
// 攻击/技能期间:全量应用根运动位移,并实时同步 agent 位置,保持阻挡体积且不发生位移偏差
if (delta.sqrMagnitude > 1e-10f)
{
transform.position += lateral;
transform.position += delta;
navAgent.nextPosition = transform.position;
}
}
else
{
// 移动中:侧向分量(垂直于朝向)保留 = 大摇大摆的摇摆;同步 agent 位置防漂移
if (delta.sqrMagnitude > 1e-10f)
{
Vector3 lateral = delta - Vector3.Project(delta, transform.forward);
if (lateral.sqrMagnitude > 1e-10f)
{
transform.position += lateral;
navAgent.nextPosition = transform.position;
}
}
}
}
else
{
@@ -572,6 +617,27 @@ public class UnitBase : MonoBehaviour
return other == transform || other.IsChildOf(transform) || transform.IsChildOf(other);
}
/// <summary>检查全场是否还有存活的敌方单位。</summary>
private bool HasAnyEnemyOnField()
{
var myTeam = GetComponent<UnitTeam>();
if (myTeam == null) return false;
UnitTeam[] allTeams = FindObjectsOfType<UnitTeam>();
foreach (var unit in allTeams)
{
if (unit == null || !unit.gameObject.activeInHierarchy) continue;
if (!myTeam.IsEnemy(unit.gameObject)) continue;
if (IsOwnHierarchy(unit.transform)) continue;
WorldHealthBar healthBar = unit.GetComponentInParent<WorldHealthBar>();
if (healthBar != null && healthBar.IsDead) continue;
return true;
}
return false;
}
//查找 detectionRange 内最近的敌方战斗单位
private GameObject FindNearestEnemyUnit()
{
@@ -582,6 +648,44 @@ public class UnitBase : MonoBehaviour
return null;
}
// 己方英雄团队警戒线出击逻辑:敌人没跨过警戒线(Z <= engagementZThreshold)前按兵不动
if (useEngagementZLine && (myTeam.teamId == 0 || CompareTag("Friend") || CompareTag("Untagged")))
{
if (!HasAnyEnemyOnField())
{
isHeroTeamEngaged = false;
return null;
}
if (!isHeroTeamEngaged)
{
bool enemyCrossedLine = false;
UnitTeam[] allTeamsForCheck = FindObjectsOfType<UnitTeam>();
foreach (var unit in allTeamsForCheck)
{
if (unit == null || !unit.gameObject.activeInHierarchy) continue;
if (!myTeam.IsEnemy(unit.gameObject)) continue;
if (IsOwnHierarchy(unit.transform)) continue;
WorldHealthBar healthBar = unit.GetComponentInParent<WorldHealthBar>();
if (healthBar != null && healthBar.IsDead) continue;
if (unit.transform.position.z <= engagementZThreshold)
{
enemyCrossedLine = true;
break;
}
}
if (!enemyCrossedLine)
{
return null; // 敌人尚未到达警戒线,保持在阵位待命
}
isHeroTeamEngaged = true; // 触发全员出击!
}
}
Vector3 myPos = transform.position;
GameObject nearestUnit = null;
@@ -638,6 +742,15 @@ public class UnitBase : MonoBehaviour
distance = flat.magnitude;
}
}
else
{
// 非建筑目标:扣除双方的阻挡半径,计算贴边距离
float myRadius = navAgent != null ? navAgent.radius : 0.5f;
var targetAgent = opponent.GetComponentInParent<UnityEngine.AI.NavMeshAgent>();
float targetRadius = targetAgent != null ? targetAgent.radius : 0.5f;
distance = Mathf.Max(0f, distance - (myRadius + targetRadius));
}
// 攻击状态下增加容差,避免目标轻微移动导致状态频繁切换
if (state == State.Attack)
@@ -762,10 +875,11 @@ public class UnitBase : MonoBehaviour
{
target = friend;
// 攻击时直接禁用 NavMeshAgent(彻底停止移动)
if (navAgent != null && navAgent.enabled)
// 攻击时暂停 NavMeshAgent,但保持 enabled 维持物理/寻路障碍排体
if (navAgent != null && navAgent.enabled && navAgent.isOnNavMesh)
{
navAgent.enabled = false;
navAgent.isStopped = true;
navAgent.velocity = Vector3.zero;
}
FaceTarget(friend);
@@ -873,9 +987,20 @@ public class UnitBase : MonoBehaviour
private void ApplyDirectDamage(GameObject opponent)
{
UnitBase targetUnit = opponent.GetComponentInParent<UnitBase>();
if (targetUnit != null && targetUnit.ReceiveDamage(attackPower, gameObject))
float damage = attackPower;
if (critRate > 0f && Random.value < critRate)
{
damage *= critMultiplier;
Debug.Log($"{gameObject.name} 触发暴击!造成 {damage:F1} 伤害");
}
UnitBase targetUnit = opponent.GetComponentInParent<UnitBase>();
if (targetUnit != null && targetUnit.ReceiveDamage(damage, gameObject))
{
if (lifestealRate > 0f && worldHealthBar != null)
{
worldHealthBar.Heal(damage * lifestealRate);
}
RegisterAttackHit();
return;
}
@@ -883,7 +1008,11 @@ public class UnitBase : MonoBehaviour
WorldHealthBar targetHealthBar = opponent.GetComponentInParent<WorldHealthBar>();
if (targetHealthBar != null)
{
targetHealthBar.TakeDamage(attackPower, gameObject);
targetHealthBar.TakeDamage(damage, gameObject);
if (lifestealRate > 0f && worldHealthBar != null)
{
worldHealthBar.Heal(damage * lifestealRate);
}
RegisterAttackHit();
}
}
@@ -1065,7 +1194,13 @@ public class UnitBase : MonoBehaviour
Projectile projectile = projectileObject.GetComponent<Projectile>();
if (projectile != null)
{
projectile.Initialize(opponent, attackPower, gameObject, projectileSpeed, projectileLifetime);
float damage = attackPower;
if (critRate > 0f && Random.value < critRate)
{
damage *= critMultiplier;
Debug.Log($"{gameObject.name} 远程攻击触发暴击!造成 {damage:F1} 伤害");
}
projectile.Initialize(opponent, damage, gameObject, projectileSpeed, projectileLifetime);
}
}
@@ -1105,7 +1240,8 @@ public class UnitBase : MonoBehaviour
return;
}
worldHealthBar.AddEnergy(energyPerAttack);
float actualEnergy = energyPerAttack * skillChargeMultiplier;
worldHealthBar.AddEnergy(actualEnergy);
}
private float GetSkillAnimationSpeed(float targetDuration)
@@ -1134,6 +1270,13 @@ public class UnitBase : MonoBehaviour
? targetDuration + 0.5f
: Mathf.Max(animationDuration + 0.5f, 2f);
state = State.Skill;
if (navAgent != null && navAgent.enabled && navAgent.isOnNavMesh)
{
navAgent.isStopped = true;
navAgent.velocity = Vector3.zero;
}
animator.speed = GetSkillAnimationSpeed(targetDuration);
animator.SetInteger("speed", 0);
animator.SetTrigger("skill");