```
feat(gameplay): 提升单位检测范围并添加法师属性 - 将所有单位的检测范围从默认值提升至100 - 为法师预制体添加护盾、等级等新属性 - 为法师添加暴击、吸血、伤害减免等战斗属性 - 更换法师弹丸预设并调整相关参数 - 添加锚点交战Z线控制功能 - 更新玩家数据中配备的召唤师技能 - 移除多余的纹理配置文件 ```
This commit is contained in:
1 parent
6089308f66
commit
23c559d9f7
243 files changed
+150612
-1459
No files matched your search
@@ -320,7 +320,7 @@ public class MainCharacterCombat : MonoBehaviour
|
||||
float finalAttack = pendingSummonPlayerData.GetAttackPower(pendingSummonCharacterData);
|
||||
float finalSpeed = pendingSummonPlayerData.GetMoveSpeed(pendingSummonCharacterData);
|
||||
|
||||
unitBase.InitializeFromPlayerData(finalHealth, finalAttack, finalSpeed);
|
||||
unitBase.InitializeFromPlayerData(finalHealth, finalAttack, finalSpeed, pendingSummonPlayerData.level);
|
||||
|
||||
Debug.Log($"主角召唤单位:{summoned.name} Lv.{pendingSummonPlayerData.level} HP={finalHealth} ATK={finalAttack} position={spawnPosition}");
|
||||
}
|
||||
|
||||
+165
-22
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user