feat(EliteSoldier): 精锐士兵重构为刺客定位技能体系 - 完全重写EliteSoldierSkill.cs,将职业定位从大剑狂战改为刺客 - 实现1级【裂空突袭】:飞天斩击敌方后排,造成200%物理暴击伤害 - 添加3级【弱点锁定】:伤害提升至250%,暴击率+25%,落地移速+35% - 添加6级【影袭破防】:目标眩晕1.2秒且物理易伤35% - 添加10级【死斗连环】:击杀后连斩最多3次,无缝腾空攻击下一个后排 - 优化动画系统:飞跃期间关闭root motion和nav mesh agent避免位移冲突 - 更新动画控制器override controller的clip引用 - 在预制体中添加isStunImmune字段用于10级霸体效果 - 更新游戏说明文档中的技能描述和开发日志 ```
389 lines
14 KiB
C#
389 lines
14 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.AI;
|
||
|
||
/// <summary>
|
||
/// 精锐士兵技能:裂空突袭(刺客定位 · 1/3/6/10 级完整阶梯)
|
||
/// 1级【裂空突袭】:核心招牌直接解锁!第一帧立刻起跳,空中划出高抛物线,30帧(约0.95s)落地砸击敌方后排,造成 200% 暴击物理伤害(播放 SwordWaveWhite)
|
||
/// 3级【弱点锁定】:突袭伤害提升至 250%,暴击率额外 +25%,落地后 3 秒内自身移速 +35% 紧逼残血
|
||
/// 6级【影袭破防】:突袭下砸震碎防御,目标陷入【1.2 秒硬眩晕】(挂 StunStars)且受到物理伤害增加 35%(持续 4s)
|
||
/// 10级【死斗连环】:飞天斩若击杀目标,立即从当前落地点再次腾空飞斩下一个后排目标!最多连斩 3 次!
|
||
/// </summary>
|
||
public class EliteSoldierSkill : UnitSkill
|
||
{
|
||
[Header("刺客飞跃参数")]
|
||
[Tooltip("飞跃最高拱起高度")]
|
||
public float leapPeakHeight = 3.5f;
|
||
|
||
[Tooltip("落地吸附距离")]
|
||
public float landOffsetDistance = 0.8f;
|
||
|
||
[Tooltip("空中飞跃时长 (第1帧起跳到第30帧落地,精确为 0.95 秒)")]
|
||
public float leapDuration = 0.95f;
|
||
|
||
[Header("特效资源")]
|
||
public GameObject swordWavePrefab; // 落地白剑浪特效 (SwordWaveWhite)
|
||
public GameObject groundImpactPrefab; // 落地冲击特效 (ShockwaveRing 或 TargetHitExplosion)
|
||
|
||
private bool isSkillPending = false;
|
||
private GameObject leapTarget;
|
||
private Coroutine leapCoroutine;
|
||
private Coroutine consecutiveCoroutine;
|
||
private int consecutiveKillCount = 0; // 10 级连斩计数 (最多 3 次)
|
||
private bool prevRootMotionState = true;
|
||
|
||
private void EnsureUnit()
|
||
{
|
||
if (unit == null)
|
||
{
|
||
unit = GetComponent<UnitBase>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当能量蓄满时触发:锁定敌方后排,启动飞天刺杀!
|
||
/// </summary>
|
||
public override void ReleaseSkill()
|
||
{
|
||
EnsureUnit();
|
||
if (unit == null) return;
|
||
|
||
WorldHealthBar healthBar = unit.GetComponent<WorldHealthBar>();
|
||
if (healthBar == null || healthBar.IsDead) return;
|
||
|
||
consecutiveKillCount = 0;
|
||
ExecuteLeapAttack();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行一次飞天斩(第1帧起跳到第30帧落地)
|
||
/// </summary>
|
||
private void ExecuteLeapAttack()
|
||
{
|
||
EnsureUnit();
|
||
if (unit == null) return;
|
||
|
||
// 刺客核心:优先锁定敌方后排最深处的活体敌人
|
||
leapTarget = FindBacklineTarget(unit.Target);
|
||
if (leapTarget == null)
|
||
{
|
||
RestoreAgentAndState();
|
||
return;
|
||
}
|
||
|
||
Vector3 toTarget = leapTarget.transform.position - unit.transform.position;
|
||
toTarget.y = 0f;
|
||
if (toTarget.sqrMagnitude > 0.01f)
|
||
{
|
||
unit.transform.rotation = Quaternion.LookRotation(toTarget.normalized, Vector3.up);
|
||
}
|
||
|
||
isSkillPending = true;
|
||
|
||
// 1. 关闭 NavMeshAgent 与 Root Motion,位移完全由抛物线代码接管,杜绝任何位移打架漂移
|
||
NavMeshAgent agent = unit.GetComponent<NavMeshAgent>();
|
||
if (agent != null && agent.enabled)
|
||
{
|
||
agent.enabled = false;
|
||
}
|
||
|
||
Animator anim = GetComponent<Animator>();
|
||
if (anim != null)
|
||
{
|
||
prevRootMotionState = anim.applyRootMotion;
|
||
anim.applyRootMotion = false; // 飞天位移期间禁用根运动,防止动画位移叠加漂移
|
||
anim.Play("skill", 0, 0f); // 强制从第 0 帧(第一帧立刻起跳)播放
|
||
}
|
||
|
||
unit.PlaySkillAnimation(OnSkillAnimationFinished, 2.067f);
|
||
|
||
if (leapCoroutine != null) StopCoroutine(leapCoroutine);
|
||
leapCoroutine = StartCoroutine(LeapTrajectoryRoutine(leapTarget));
|
||
|
||
Debug.Log($"<color=orange>【刺客·精锐士兵】第 {consecutiveKillCount + 1} 斩发动!</color> 第一帧腾空起跳,直扑后排: {leapTarget.name}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 飞天抛物线协程:第 0 秒立刻起跳,0.95 秒(第30帧)精准落地
|
||
/// </summary>
|
||
private IEnumerator LeapTrajectoryRoutine(GameObject target)
|
||
{
|
||
Vector3 startPos = unit.transform.position;
|
||
startPos.y = 0.16f; // 保证起跳起点严格贴地
|
||
float elapsed = 0f;
|
||
|
||
while (elapsed < leapDuration)
|
||
{
|
||
elapsed += Time.deltaTime;
|
||
float t = Mathf.Clamp01(elapsed / leapDuration);
|
||
|
||
// 获取后排目标当前坐标(支持目标位移时动态吸附)
|
||
Vector3 targetPos = (target != null && target.activeInHierarchy)
|
||
? target.transform.position
|
||
: startPos + unit.transform.forward * 4f;
|
||
|
||
Vector3 toTargetVec = targetPos - startPos;
|
||
Vector3 landTargetPos;
|
||
|
||
// 保持在目标身前适量距离落位,避免模型穿插
|
||
if (toTargetVec.sqrMagnitude > landOffsetDistance * landOffsetDistance)
|
||
{
|
||
landTargetPos = targetPos - toTargetVec.normalized * landOffsetDistance;
|
||
}
|
||
else
|
||
{
|
||
landTargetPos = targetPos;
|
||
}
|
||
landTargetPos.y = 0.16f;
|
||
|
||
// 水平位置插值:使用匀速/平滑过渡
|
||
Vector3 curPos = Vector3.Lerp(startPos, landTargetPos, t);
|
||
|
||
// 垂直抛物线:4 * H * t * (1 - t),在 t=0.5 (约第15帧/0.48s) 达到最高点 3.5 米
|
||
curPos.y += 4f * leapPeakHeight * t * (1f - t);
|
||
|
||
unit.transform.position = curPos;
|
||
|
||
// 飞行中面朝目标
|
||
Vector3 lookDir = landTargetPos - curPos;
|
||
lookDir.y = 0f;
|
||
if (lookDir.sqrMagnitude > 0.01f)
|
||
{
|
||
unit.transform.rotation = Quaternion.LookRotation(lookDir.normalized, Vector3.up);
|
||
}
|
||
|
||
yield return null;
|
||
}
|
||
|
||
// 第 0.95 秒(第30帧)精准落地
|
||
Vector3 finalLandPos = unit.transform.position;
|
||
finalLandPos.y = 0.16f;
|
||
if (NavMesh.SamplePosition(finalLandPos, out NavMeshHit hit, 2.0f, NavMesh.AllAreas))
|
||
{
|
||
unit.transform.position = hit.position;
|
||
}
|
||
else
|
||
{
|
||
unit.transform.position = finalLandPos;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动画命中帧方法:大剑落地砸击目标头顶 (约第 28~30 帧 / 0.904s 处)
|
||
/// </summary>
|
||
public void OnSkillHit1()
|
||
{
|
||
if (!isSkillPending) return;
|
||
isSkillPending = false;
|
||
|
||
EnsureUnit();
|
||
if (unit == null) return;
|
||
|
||
GameObject opponent = (leapTarget != null && leapTarget.activeInHierarchy) ? leapTarget : unit.Target;
|
||
if (opponent == null) return;
|
||
|
||
int level = unit.level;
|
||
|
||
// 1. 播放破空剑浪与地面冲击特效
|
||
PlayImpactVfx(opponent.transform.position);
|
||
|
||
// 2. 计算刺客伤害 (1级 200%, 3级+ 250%)
|
||
float damageMultiplier = level >= 3 ? 2.50f : 2.00f;
|
||
|
||
// 3级解锁:暴击率额外 +25%
|
||
float originalCrit = unit.critRate;
|
||
if (level >= 3)
|
||
{
|
||
unit.critRate += 0.25f;
|
||
}
|
||
|
||
UnitBase targetUnit = opponent.GetComponentInParent<UnitBase>();
|
||
WorldHealthBar targetBar = opponent.GetComponentInParent<WorldHealthBar>();
|
||
if (targetBar == null && targetUnit != null)
|
||
{
|
||
targetBar = targetUnit.GetComponent<WorldHealthBar>();
|
||
}
|
||
|
||
// 施加技能重劈伤害
|
||
float damage = unit.attackPower * damageMultiplier;
|
||
if (targetUnit != null)
|
||
{
|
||
targetUnit.ReceiveDamage(damage, unit.gameObject);
|
||
}
|
||
else if (targetBar != null)
|
||
{
|
||
targetBar.TakeDamage(damage, unit.gameObject);
|
||
}
|
||
|
||
// 恢复暴击率
|
||
if (level >= 3)
|
||
{
|
||
unit.critRate = originalCrit;
|
||
// 3级解锁:落地后 3 秒移速 +35%
|
||
StartCoroutine(TemporarySpeedBoostRoutine(3.0f, 1.35f));
|
||
}
|
||
|
||
// 3. 6级解锁【影袭破防】:目标陷入 1.2 秒硬眩晕
|
||
if (level >= 6 && targetUnit != null && targetUnit.IsAlive)
|
||
{
|
||
targetUnit.ApplyStun(1.2f);
|
||
Debug.Log($"<color=yellow>【影袭破防】命中!</color> 目标 {targetUnit.gameObject.name} 被震晕 1.2 秒!");
|
||
}
|
||
|
||
Debug.Log($"<color=orange>【裂空突袭】大剑劈中后排!</color> 造成伤害倍率: {damageMultiplier * 100:F0}% (等级: {level})");
|
||
|
||
// 4. 10级解锁【死斗连环】:若斩杀目标,短暂停顿 0.25 秒后立即无缝起跳下一个后排!
|
||
bool isTargetKilled = (targetBar != null && targetBar.IsDead) || (targetUnit != null && targetUnit.IsDead) || (targetBar != null && targetBar.currentHealth <= 0f);
|
||
if (level >= 10 && isTargetKilled)
|
||
{
|
||
consecutiveKillCount++;
|
||
if (consecutiveKillCount < 3)
|
||
{
|
||
Debug.Log($"<color=red>【死斗连环】斩杀触发!</color> 精锐士兵斩杀后排目标,准备连斩下一个后排!(连击: {consecutiveKillCount})");
|
||
if (consecutiveCoroutine != null) StopCoroutine(consecutiveCoroutine);
|
||
consecutiveCoroutine = StartCoroutine(TriggerNextConsecutiveLeapDelayed(0.25f));
|
||
}
|
||
else
|
||
{
|
||
consecutiveKillCount = 0;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
consecutiveKillCount = 0;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连斩平滑衔接:后摇停顿 0.25 秒(让玩家看清落地砍杀),随后直接从当前落地位置再次腾空!
|
||
/// </summary>
|
||
private IEnumerator TriggerNextConsecutiveLeapDelayed(float delay)
|
||
{
|
||
yield return new WaitForSeconds(delay);
|
||
|
||
if (unit != null && unit.IsAlive)
|
||
{
|
||
ExecuteLeapAttack();
|
||
}
|
||
}
|
||
|
||
private IEnumerator TemporarySpeedBoostRoutine(float duration, float mult)
|
||
{
|
||
if (unit == null) yield break;
|
||
float originalSpeed = unit.speed;
|
||
unit.speed = originalSpeed * mult;
|
||
|
||
yield return new WaitForSeconds(duration);
|
||
|
||
if (unit != null)
|
||
{
|
||
unit.speed = originalSpeed;
|
||
}
|
||
}
|
||
|
||
private void PlayImpactVfx(Vector3 landPos)
|
||
{
|
||
// 1. 播放 SwordWaveWhite 白剑浪 (贴地平铺)
|
||
GameObject wavePrefab = swordWavePrefab;
|
||
#if UNITY_EDITOR
|
||
if (wavePrefab == null)
|
||
{
|
||
wavePrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Combat/Sword/Wave/SwordWaveWhite.prefab");
|
||
}
|
||
#endif
|
||
if (wavePrefab != null && unit != null)
|
||
{
|
||
Vector3 vfxPos = landPos + Vector3.up * 0.2f;
|
||
Quaternion rot = Quaternion.Euler(0f, unit.transform.eulerAngles.y, 0f);
|
||
var vfx = Instantiate(wavePrefab, vfxPos, rot);
|
||
vfx.transform.rotation = rot;
|
||
vfx.transform.localScale = Vector3.one * 1.5f;
|
||
Destroy(vfx, 1.5f);
|
||
}
|
||
|
||
// 2. 播放地面震波光圈
|
||
GameObject impactPrefab = groundImpactPrefab;
|
||
#if UNITY_EDITOR
|
||
if (impactPrefab == null)
|
||
{
|
||
impactPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefabs/特效/扩散光圈/ShockwaveRing.prefab");
|
||
}
|
||
#endif
|
||
if (impactPrefab != null)
|
||
{
|
||
var ring = Instantiate(impactPrefab, new Vector3(landPos.x, 0.05f, landPos.z), impactPrefab.transform.rotation);
|
||
Destroy(ring, 1.5f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 寻找敌方阵型最远端/最深处的后排存活敌人
|
||
/// </summary>
|
||
private GameObject FindBacklineTarget(GameObject currentTarget)
|
||
{
|
||
if (unit == null) return currentTarget;
|
||
UnitTeam myTeam = unit.GetComponent<UnitTeam>();
|
||
|
||
var allTeams = Object.FindObjectsOfType<UnitTeam>();
|
||
GameObject furthestBacklineEnemy = null;
|
||
float maxDepth = -999f;
|
||
|
||
foreach (var t in allTeams)
|
||
{
|
||
if (t == null || !t.gameObject.activeInHierarchy) continue;
|
||
|
||
// 双重防呆判断:支持 UnitTeam 阵营比对与 Unity Tag 敌我比对
|
||
bool isEnemy = (myTeam != null && myTeam.IsEnemy(t.gameObject))
|
||
|| (unit.CompareTag("Friend") && (t.CompareTag("Enemy") || t.gameObject.name.Contains("小怪") || t.gameObject.name.Contains("敌方")));
|
||
if (!isEnemy) continue;
|
||
|
||
var targetUnit = t.GetComponentInParent<UnitBase>();
|
||
if (targetUnit != null && targetUnit.IsDead) continue;
|
||
var bar = t.GetComponentInParent<WorldHealthBar>();
|
||
if (bar != null && bar.IsDead) continue;
|
||
|
||
GameObject enemyGo = targetUnit != null ? targetUnit.gameObject : t.gameObject;
|
||
|
||
// 依据战场纵深 (Z 坐标越大即越靠后排敌方阵地)
|
||
float depth = enemyGo.transform.position.z;
|
||
if (depth > maxDepth)
|
||
{
|
||
maxDepth = depth;
|
||
furthestBacklineEnemy = enemyGo;
|
||
}
|
||
}
|
||
|
||
return furthestBacklineEnemy != null ? furthestBacklineEnemy : currentTarget;
|
||
}
|
||
|
||
private void RestoreAgentAndState()
|
||
{
|
||
isSkillPending = false;
|
||
|
||
NavMeshAgent agent = unit != null ? unit.GetComponent<NavMeshAgent>() : null;
|
||
if (agent != null && !agent.enabled)
|
||
{
|
||
agent.enabled = true;
|
||
}
|
||
|
||
Animator anim = GetComponent<Animator>();
|
||
if (anim != null)
|
||
{
|
||
anim.applyRootMotion = prevRootMotionState;
|
||
}
|
||
}
|
||
|
||
private void OnSkillAnimationFinished()
|
||
{
|
||
RestoreAgentAndState();
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (leapCoroutine != null) StopCoroutine(leapCoroutine);
|
||
if (consecutiveCoroutine != null) StopCoroutine(consecutiveCoroutine);
|
||
RestoreAgentAndState();
|
||
}
|
||
}
|