完善盾卫技能

This commit is contained in:
huadexuan committed 2026-10-11 22:56:46 +08:00
1 parent 195a459c76
commit 9572db06ad
41 files changed
+25241 -351

No files matched your search

+13 -2
View File
@@ -69,8 +69,8 @@ public class WorldHealthBar : MonoBehaviour
public bool IsEnergyFull => !isDead && currentEnergy >= maxEnergy;
// 血条相对于单位头顶的额外高度。
[Min(0f)] public float heightOffset = 0.25f;
// 血条相对于单位头顶的额外高度。增加高度偏移避免遮挡角色头部。
[Min(0f)] public float heightOffset = 0.65f;
// 所有单位使用统一的血条世界宽度。
[Min(0.1f)] public float barWidth = 1.2f;
@@ -179,6 +179,13 @@ public class WorldHealthBar : MonoBehaviour
{
fillImage.color = GetHealthFillColor();
}
// 自动同步 UnitBase 上的等级设置
UnitBase unit = GetComponent<UnitBase>();
if (unit != null)
{
SetLevel(unit.level);
}
}
// 每帧让血条保持在单位头顶,并朝向主摄像机。
@@ -238,6 +245,9 @@ public class WorldHealthBar : MonoBehaviour
}
}
/// <summary>护盾吸收伤害事件:参数为实际吸收的伤害数值</summary>
public System.Action<float> OnShieldAbsorbedDamage;
// 让单位受到伤害,并弹出伤害数字。
// attacker: 攻击者,用于判断受击者(自己)是敌是友,决定数字颜色和浮动方向
public void TakeDamage(float amount, GameObject attacker = null)
@@ -252,6 +262,7 @@ public class WorldHealthBar : MonoBehaviour
currentShield -= absorbed;
amount -= absorbed;
UpdateShieldFill();
OnShieldAbsorbedDamage?.Invoke(absorbed);
Debug.Log($"{gameObject.name} 护盾吸收了 {absorbed:F1} 伤害,剩余护盾: {currentShield:F1}");
}
+215 -57
View File
@@ -2,21 +2,38 @@ using System.Collections;
using UnityEngine;
/// <summary>
/// 盾卫技能:坚固壁垒
/// 1级:生成相当于自身 25% 最大生命值的护盾(持续 5 秒),播放 MagicShield 护盾特效
/// 3级:护盾值提升至 35% 最大生命值,护盾期间减伤 +15%
/// 盾卫技能:圣盾壁垒体系(1/3/6/10 级完整阶梯)
/// 1级【圣盾壁垒】:举盾生成 35% 最大生命值的护盾(持续 5s),挂 MagicShieldBlue 特效
/// 3级【坚不可摧】:护盾厚度提升至 50% 最大生命值,持续时间延长至 6s
/// 6级【战吼嘲讽】:技能拍地 OnSkillHit1 瞬间爆发 NovaBlue 蓝色声波冲击环,强制周围 4 米所有敌人攻击自己 3s!
/// 10级【圣盾清算】:护盾期间霸体免眩晕;当护盾被打破或超时结束时爆发神圣冲击波,将吸收伤害的 50% 转化为真实伤害反弹全场!
/// </summary>
public class ShieldGuardSkill : UnitSkill
{
[Header("护盾配置")]
[Tooltip("护盾持续时间(秒)")]
public float shieldDuration = 5.0f;
[Tooltip("基础护盾持续时间(秒)")]
public float baseShieldDuration = 5.0f;
[Tooltip("护盾特效预制体")]
public GameObject shieldVfxPrefab;
[Tooltip("3级以上护盾持续时间(秒)")]
public float upgradedShieldDuration = 6.0f;
private GameObject activeVfxInstance;
[Header("6级战吼嘲讽配置")]
[Tooltip("嘲讽半径(米)")]
public float tauntRadius = 4.0f;
[Tooltip("嘲讽持续时间(秒)")]
public float tauntDuration = 3.0f;
[Header("特效资源")]
public GameObject shieldVfxPrefab; // 护盾特效 (MagicShieldBlue)
public GameObject tauntWaveVfxPrefab; // 拍地嘲讽声波环特效 (NovaBlue)
public GameObject shieldBurstVfxPrefab; // 破盾清算冲击波特效 (NovaLightningBlue 或 TargetHitExplosion)
private GameObject activeShieldVfx;
private Coroutine activeShieldCoroutine;
private float totalAbsorbedDamage = 0f;
private bool isSkillPending = false;
private float currentMaxShield = 0f;
private void EnsureUnit()
{
@@ -26,6 +43,9 @@ public class ShieldGuardSkill : UnitSkill
}
}
/// <summary>
/// 当能量蓄满时触发:播放盾卫专属技能动画(举盾蓄力后砸地)
/// </summary>
public override void ReleaseSkill()
{
EnsureUnit();
@@ -34,72 +54,109 @@ public class ShieldGuardSkill : UnitSkill
WorldHealthBar healthBar = unit.GetComponent<WorldHealthBar>();
if (healthBar == null || healthBar.IsDead) return;
int level = unit.level; // 从 UnitBase 字段读取 level
// 1级:25% maxHealth,3级+:35% maxHealth
float ratio = level >= 3 ? 0.35f : 0.25f;
float shieldAmount = healthBar.maxHealth * ratio;
unit.FaceCurrentTarget(0f);
isSkillPending = true;
// 1. 添加护盾值
// 播放盾卫技能动画,并在 1.09s 的 OnSkillHit1 帧触发核心效果
unit.PlaySkillAnimation(OnSkillAnimationFinished, 1.87f);
Debug.Log($"<color=cyan>盾卫开始吟唱技能【圣盾壁垒】</color>,等待 OnSkillHit1 拍地!");
}
/// <summary>
/// 动画事件帧方法:在双手持盾猛烈砸地的瞬间触发 (1.095 秒处)
/// </summary>
public void OnSkillHit1()
{
if (!isSkillPending) return;
isSkillPending = false;
EnsureUnit();
if (unit == null) return;
WorldHealthBar healthBar = unit.GetComponent<WorldHealthBar>();
if (healthBar == null || healthBar.IsDead) return;
int level = unit.level;
// 1. 计算护盾数值 (1级 35%, 3级+ 50%)
float ratio = level >= 3 ? 0.50f : 0.35f;
float shieldAmount = healthBar.maxHealth * ratio;
currentMaxShield = shieldAmount;
totalAbsorbedDamage = 0f;
// 2. 赋予自身护盾
healthBar.AddShield(shieldAmount);
// 2. 播放 MagicShield 护盾特效
// 3. 挂载护盾环绕特效 (MagicShieldBlue)
PlayShieldVfx();
// 3. 开启超时自动清盾协程
// 4. 10 级解锁:霸体免疫眩晕
StatusEffectController statusCtrl = unit.GetComponent<StatusEffectController>();
if (level >= 10 && statusCtrl != null)
{
statusCtrl.isStunImmune = true;
}
// 5. 6 级解锁:拍地激发声波光环 + 强力嘲讽周围 4 米所有敌人
if (level >= 6)
{
ExecuteTauntAndWave();
}
// 6. 监听护盾伤害吸收 (用于 10 级破盾清算)
healthBar.OnShieldAbsorbedDamage -= RecordAbsorbedDamage;
healthBar.OnShieldAbsorbedDamage += RecordAbsorbedDamage;
// 7. 启动护盾生命周期协程 (监测破盾与超时结算)
float duration = level >= 3 ? upgradedShieldDuration : baseShieldDuration;
if (activeShieldCoroutine != null)
{
StopCoroutine(activeShieldCoroutine);
}
activeShieldCoroutine = StartCoroutine(ShieldTimeoutRoutine(shieldDuration, shieldAmount));
activeShieldCoroutine = StartCoroutine(ShieldLifecycleRoutine(duration, shieldAmount));
Debug.Log($"<color=cyan>盾卫释放技能【坚固壁垒】</color> 获得 {shieldAmount:F1} 护盾(比例: {ratio * 100}%,等级: {level})");
Debug.Log($"<color=cyan>【盾卫】拍地爆发!</color> 获得护盾 {shieldAmount:F1} (等级: {level})");
}
public override float ModifyIncomingDamage(float damage, GameObject attacker)
private void ExecuteTauntAndWave()
{
EnsureUnit();
// 3级解锁:护盾激活期间享受额外 15% 减伤
if (unit != null && unit.level >= 3)
// 播放声波光环特效 (NovaBlue)
PlayTauntWaveVfx();
// 范围嘲讽所有敌人
UnitTeam myTeam = unit.GetComponent<UnitTeam>();
UnitBase[] allUnits = FindObjectsOfType<UnitBase>();
float radiusSq = tauntRadius * tauntRadius;
int tauntedCount = 0;
foreach (var other in allUnits)
{
WorldHealthBar healthBar = unit.GetComponent<WorldHealthBar>();
if (healthBar != null && healthBar.currentShield > 0f)
if (other == null || other == unit || !other.gameObject.activeInHierarchy || other.IsDead) continue;
UnitTeam otherTeam = other.GetComponent<UnitTeam>();
if (myTeam != null && otherTeam != null && !myTeam.IsEnemy(otherTeam.gameObject)) continue; // 只嘲讽敌方
Vector3 diff = other.transform.position - unit.transform.position;
diff.y = 0f;
if (diff.sqrMagnitude <= radiusSq)
{
return damage * (1f - 0.15f);
other.ApplyTaunt(unit.gameObject, tauntDuration);
tauntedCount++;
}
}
return damage;
Debug.Log($"<color=yellow>【盾卫战吼嘲讽】激发!</color> 强制嘲讽周围 {tauntedCount} 个敌人持续 {tauntDuration:F1} 秒!");
}
private void PlayShieldVfx()
private void RecordAbsorbedDamage(float absorbed)
{
if (activeVfxInstance != null)
{
Destroy(activeVfxInstance);
}
GameObject prefab = shieldVfxPrefab;
if (prefab == null)
{
prefab = Resources.Load<GameObject>("Prefabs/Combat/Magic/Shield/MagicShieldBlue");
}
#if UNITY_EDITOR
if (prefab == null)
{
prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Combat/Magic/Shield/MagicShieldBlue.prefab");
}
#endif
if (prefab != null)
{
activeVfxInstance = Instantiate(prefab, unit.transform.position + Vector3.up * 1.0f, Quaternion.identity);
activeVfxInstance.transform.SetParent(unit.transform, true);
}
totalAbsorbedDamage += absorbed;
}
private IEnumerator ShieldTimeoutRoutine(float duration, float amount)
private IEnumerator ShieldLifecycleRoutine(float duration, float initialShieldAmount)
{
float timer = duration;
WorldHealthBar healthBar = unit != null ? unit.GetComponent<WorldHealthBar>() : null;
StatusEffectController statusCtrl = unit != null ? unit.GetComponent<StatusEffectController>() : null;
while (timer > 0f)
{
@@ -111,24 +168,125 @@ public class ShieldGuardSkill : UnitSkill
yield return null;
}
// 超时或护盾为 0 清理特效与护盾值
if (activeVfxInstance != null)
// 护盾解除/破盾清算
bool isShieldBroken = healthBar != null && healthBar.currentShield <= 0f && timer > 0f;
int level = unit != null ? unit.level : 1;
// 10 级解锁【圣盾清算】:将吸收的伤害按 50% 转化为真实伤害反弹全场周围
if (level >= 10 && totalAbsorbedDamage > 0f && unit != null)
{
Destroy(activeVfxInstance);
activeVfxInstance = null;
TriggerShieldBurst(totalAbsorbedDamage * 0.50f);
}
if (healthBar != null && healthBar.currentShield > 0f && timer <= 0f)
// 清理状态与特效
if (statusCtrl != null)
{
healthBar.RemoveShield(amount);
statusCtrl.isStunImmune = false;
}
if (healthBar != null)
{
healthBar.OnShieldAbsorbedDamage -= RecordAbsorbedDamage;
if (timer <= 0f && healthBar.currentShield > 0f)
{
healthBar.RemoveShield(initialShieldAmount);
}
}
if (activeShieldVfx != null)
{
Destroy(activeShieldVfx);
activeShieldVfx = null;
}
Debug.Log($"<color=cyan>【盾卫】护盾结算完毕</color> (是否被击破: {isShieldBroken}, 总共吸收伤害: {totalAbsorbedDamage:F1})");
}
private void TriggerShieldBurst(float burstDamage)
{
Debug.Log($"<color=yellow>【圣盾清算】护盾爆发!</color> 造成 {burstDamage:F1} 范围反击真实伤害!");
// 播放破盾爆发特效
PlayShieldBurstVfx();
// 对周围 4 米造成反弹伤害
if (unit != null)
{
unit.ApplyAreaSkillDamage(tauntRadius, burstDamage, false);
}
}
private void PlayShieldVfx()
{
if (activeShieldVfx != null) Destroy(activeShieldVfx);
GameObject prefab = shieldVfxPrefab;
#if UNITY_EDITOR
if (prefab == null)
{
prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Combat/Magic/Shield/MagicShieldBlue.prefab");
}
#endif
if (prefab != null && unit != null)
{
activeShieldVfx = Instantiate(prefab, unit.transform.position + Vector3.up * 1.0f, Quaternion.identity);
activeShieldVfx.transform.SetParent(unit.transform, true);
}
}
private void PlayTauntWaveVfx()
{
GameObject prefab = tauntWaveVfxPrefab;
#if UNITY_EDITOR
if (prefab == null)
{
prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Combat/Nova/Standard/NovaBlue.prefab");
}
#endif
if (prefab != null && unit != null)
{
Vector3 spawnPos = unit.transform.position;
spawnPos.y = 0.05f; // 贴地
var vfx = Instantiate(prefab, spawnPos, prefab.transform.rotation);
// 调整 scale 以匹配 4 米嘲讽范围
vfx.transform.localScale = new Vector3(1.6f, 1.6f, 1.6f);
Destroy(vfx, 2.5f);
}
}
private void PlayShieldBurstVfx()
{
GameObject prefab = shieldBurstVfxPrefab;
#if UNITY_EDITOR
if (prefab == null)
{
prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Combat/Nova/Lightning/NovaLightningBlue.prefab");
}
#endif
if (prefab != null && unit != null)
{
Vector3 spawnPos = unit.transform.position;
spawnPos.y = 0.05f;
var vfx = Instantiate(prefab, spawnPos, prefab.transform.rotation);
vfx.transform.localScale = new Vector3(1.5f, 1.5f, 1.5f);
Destroy(vfx, 2.5f);
}
}
private void OnSkillAnimationFinished()
{
isSkillPending = false;
}
private void OnDestroy()
{
if (activeVfxInstance != null)
if (activeShieldVfx != null) Destroy(activeShieldVfx);
if (unit != null)
{
Destroy(activeVfxInstance);
WorldHealthBar healthBar = unit.GetComponent<WorldHealthBar>();
if (healthBar != null) healthBar.OnShieldAbsorbedDamage -= RecordAbsorbedDamage;
StatusEffectController statusCtrl = unit.GetComponent<StatusEffectController>();
if (statusCtrl != null) statusCtrl.isStunImmune = false;
}
}
}
+6 -5
View File
@@ -4,16 +4,17 @@ using UnityEngine;
/// 部署网格的几何参数与坐标换算。
/// 网格与场景里用于标记布阵区域的那个 Plane 完全重合:
/// Plane 世界范围 X -5.75..5.75、Z -15.6..-4.4,中心 (0, ?, -10)。
/// 4 列 × 4 行,正好铺满这块区域;整体位于 NavMesh 覆盖范围内。
/// 5 列 × 4 行,奇数列数可保证中路(第3列/索引2)完美居中对战;
/// 整体位于 NavMesh 覆盖范围内。
/// </summary>
public static class DeployGrid
{
public const int Cols = 4;
public const int Cols = 5;
public const int Rows = 4;
public const float ColStep = 2.875f; // 11.5 / 4
public const float RowStep = 2.8f; // 11.2 / 4
public const float ColStep = 2.30f; // 11.5 / 5 = 2.3
public const float RowStep = 2.80f; // 11.2 / 4 = 2.8
public const float BackZ = -14.2f; // 第 0 排(最靠近镜头)中心 Z = -10 - 1.5*2.8
public const float CellWidth = 2.815f; // 比间距略小,留出缝,看得出是格子
public const float CellWidth = 2.22f; // 2.3 * 0.965,留出网格缝隙
public const float CellDepth = 2.74f;
public const float CellHeight = 0.03f;
public const float GridY = 0.115f; // 格位可视块中心高度(贴在布阵平面之上)
@@ -16,6 +16,9 @@ public class StatusEffectController : MonoBehaviour
public bool IsStunned => stunnedRemaining > 0f;
public float StunnedRemaining => Mathf.Max(0f, stunnedRemaining);
/// <summary>是否处于眩晕免疫/霸体状态</summary>
public bool isStunImmune = false;
private void Awake()
{
animator = GetComponent<Animator>();
@@ -23,7 +26,7 @@ public class StatusEffectController : MonoBehaviour
public void ApplyStun(float duration)
{
if (duration <= 0f)
if (duration <= 0f || isStunImmune)
{
return;
}
+43
View File
@@ -244,6 +244,7 @@ public class UnitBase : MonoBehaviour
{
worldHealthBar.maxHealth = health;
worldHealthBar.currentHealth = health;
worldHealthBar.SetLevel(level);
}
Debug.Log($"{gameObject.name} 从 CharacterData 初始化 (Level {level}): HP={health} ATK={attack} SPD={moveSpeed}");
@@ -313,6 +314,9 @@ public class UnitBase : MonoBehaviour
// Attack 状态在上方单独处理,因此不会被这里打断或切换目标。
target = FindTarget();
// 嘲讽强行覆盖目标判定
UpdateTaunt();
// 阵位守护:目标离自己阵位超过寻敌视野(detectionRange)就不追了,留在阵位附近
if (target != null && useAnchorLeash && anchorInitialized
&& IsBeyondLeash(target.transform.position, detectionRange))
@@ -370,6 +374,45 @@ public class UnitBase : MonoBehaviour
}
}
public bool IsAlive => worldHealthBar != null && !worldHealthBar.IsDead;
public bool IsDead => worldHealthBar != null && worldHealthBar.IsDead;
public void ApplyTaunt(GameObject taunter, float duration)
{
if (taunter == null || !taunter.activeInHierarchy || IsDead) return;
tauntSource = taunter;
tauntTimer = Mathf.Max(tauntTimer, duration);
target = taunter; // 强行把当前攻击/追踪目标切向嘲讽者
if (state == State.Alert)
{
state = State.Track;
}
Debug.Log($"<color=orange>{gameObject.name} 被 {taunter.name} 强制嘲讽 {duration:F1} 秒!</color>");
}
private GameObject tauntSource;
private float tauntTimer = 0f;
private void UpdateTaunt()
{
if (tauntTimer > 0f)
{
tauntTimer -= Time.deltaTime;
if (tauntSource != null && tauntSource.activeInHierarchy)
{
WorldHealthBar bar = tauntSource.GetComponent<WorldHealthBar>();
if (bar != null && !bar.IsDead)
{
target = tauntSource; // 嘲讽期间锁定目标
return;
}
}
tauntTimer = 0f;
tauntSource = null;
}
}
public GameObject Target => target;
private bool IsTargetValid(GameObject candidate)