Files
daluandou/Assets/Scripts/技能脚本/CastleMageSkill.cs
T
huadexuan a9869b7419 ```
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级霸体效果
- 更新游戏说明文档中的技能描述和开发日志
```
2026-10-12 23:10:06 +08:00

400 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 城堡法师技能:爆裂火球体系(1/3/6/10 级完整阶梯)
/// 1级【爆裂火球】:发射强力火球,对目标区域造成 140% 范围伤害(触发 MagicExplosionFire 爆破)
/// 3级【强化爆裂】:火球伤害提升至 175%,爆炸范围 +25%
/// 6级【灼烧火海】:爆裂处留下一片火海,对经过敌人造成 3 秒持续燃烧伤害
/// 10级【双发爆裂】:施法时连续轰出 2 颗爆裂火球,第二颗伤害提升 50% 且范围扩大
/// </summary>
public class CastleMageSkill : UnitSkill
{
[Header("火球配置")]
[Tooltip("火球飞行速度")]
public float projectileSpeed = 16f;
[Tooltip("爆裂基础半径")]
public float baseExplosionRadius = 2.5f;
[Header("特效资源")]
public GameObject fireballMissilePrefab; // 火球飞弹 (FireballSharpMissileFire)
public GameObject explosionVfxPrefab; // 爆破特效 (MagicExplosionFire)
public GameObject burnFieldVfxPrefab; // 灼烧火海地面特效 (FireFieldRed)
private bool isSkillPending = false;
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;
if (unit.Target == null) return;
unit.FaceCurrentTarget(0f);
isSkillPending = true;
// 播放法师施法动作,由 OnSkillHit1 帧触发火球发射
unit.PlaySkillAnimation(OnSkillAnimationFinished, 1.8f);
Debug.Log($"<color=orange>城堡法师释放技能【爆裂火球】</color>,等待 OnSkillHit1 施法帧!");
}
/// <summary>
/// 动画命中帧方法:在法杖前挥法术成型的瞬间触发 (OnSkillHit1)
/// </summary>
public void OnSkillHit1()
{
if (!isSkillPending) return;
isSkillPending = false;
EnsureUnit();
if (unit == null || unit.Target == null) return;
int level = unit.level;
// 1级 140%,3级+ 175%
float damageMultiplier = level >= 3 ? 1.75f : 1.40f;
float radius = level >= 3 ? baseExplosionRadius * 1.25f : baseExplosionRadius;
// 发射第一颗爆裂火球
LaunchFireball(unit.Target, damageMultiplier, radius, level >= 6);
// 10级大招【双发爆裂】:间隔 0.2 秒追加第二颗强化火球
if (level >= 10)
{
StartCoroutine(LaunchSecondFireballDelayed(unit.Target, damageMultiplier * 1.50f, radius * 1.30f, level >= 6));
}
Debug.Log($"<color=orange>【爆裂火球】发射!</color> 伤害倍率: {damageMultiplier * 100:F0}%, 范围: {radius:F1}m (等级: {level})");
}
private IEnumerator LaunchSecondFireballDelayed(GameObject primaryTarget, float damageMultiplier, float radius, bool createBurnField)
{
yield return new WaitForSeconds(0.25f);
if (unit == null) yield break;
// 优先打目标后方的敌人;如果只有一个怪或没有更后方的人,直接打同一个目标,绝不打空
GameObject secondTarget = FindBacklineTarget(primaryTarget);
if (secondTarget != null && secondTarget.activeInHierarchy)
{
LaunchFireball(secondTarget, damageMultiplier, radius, createBurnField);
bool isSameTarget = secondTarget == primaryTarget;
Debug.Log($"<color=red>【双发爆裂】追加第二发火球!</color> 目标: {secondTarget.name} ({(isSameTarget ? "单目标连续轰炸" : "后排敌群轰炸")}),伤害倍率: {damageMultiplier * 100:F0}%");
}
}
/// <summary>
/// 寻找位于当前目标后方的敌人。若无后排敌人,回退返回当前目标。
/// </summary>
private GameObject FindBacklineTarget(GameObject currentTarget)
{
if (unit == null) return currentTarget;
UnitTeam myTeam = unit.GetComponent<UnitTeam>();
if (myTeam == null) return currentTarget;
// 如果原目标已失效/死亡,回退找任意当前有效目标
if (currentTarget == null || !currentTarget.activeInHierarchy)
{
return unit.FindNearestEnemyForSkill();
}
Vector3 magePos = unit.transform.position;
Vector3 targetPos = currentTarget.transform.position;
Vector3 forwardDir = (targetPos - magePos).normalized;
if (forwardDir.sqrMagnitude < 0.001f) forwardDir = unit.transform.forward;
var allTeams = Object.FindObjectsOfType<UnitTeam>();
GameObject bestBacklineTarget = null;
float bestDistance = float.MaxValue;
foreach (var t in allTeams)
{
if (t == null || !t.gameObject.activeInHierarchy) continue;
if (!myTeam.IsEnemy(t.gameObject)) continue;
if (t.gameObject == currentTarget || t.transform.root == currentTarget.transform.root) 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;
// 计算该敌人相对当前目标的深度 (沿法师射向的距离)
Vector3 offset = enemyGo.transform.position - targetPos;
float depth = Vector3.Dot(offset, forwardDir);
// 只要位于目标后方 (深度 > 0.8米)
if (depth > 0.8f)
{
// 优先选择距离当前目标最近的后排敌人(紧邻的后排敌群),形成层次分明的前后封锁
float dist = offset.magnitude;
if (dist < bestDistance)
{
bestDistance = dist;
bestBacklineTarget = enemyGo;
}
}
}
// 如果找到了后排敌人,打后排敌人;如果场上只有一个怪或无后排敌人,打同一个目标
return bestBacklineTarget != null ? bestBacklineTarget : currentTarget;
}
private void LaunchFireball(GameObject target, float damageMultiplier, float radius, bool createBurnField)
{
GameObject missilePrefab = fireballMissilePrefab;
if (missilePrefab == null && unit != null)
{
missilePrefab = unit.projectilePrefab;
}
#if UNITY_EDITOR
if (missilePrefab == null)
{
missilePrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Combat/Missiles/FireballSharp/FireballSharpMissileFire.prefab");
}
#endif
if (missilePrefab == null || target == null || unit == null) return;
Vector3 spawnPos = unit.projectileSpawnPoint != null
? unit.projectileSpawnPoint.position
: unit.transform.position + Vector3.up * 1.2f + unit.transform.forward * 0.5f;
Vector3 dir = (target.transform.position - spawnPos).normalized;
GameObject missileGo = Instantiate(missilePrefab, spawnPos, Quaternion.LookRotation(dir));
float totalDamage = unit.attackPower * damageMultiplier;
// 使用自定义弧线/追踪脚本,并在命中时引发范围爆炸
var runner = missileGo.AddComponent<MageSkillFireballRunner>();
runner.Initialize(target, totalDamage, radius, unit.gameObject, projectileSpeed, explosionVfxPrefab, burnFieldVfxPrefab, createBurnField);
}
private void OnSkillAnimationFinished()
{
isSkillPending = false;
}
}
/// <summary>
/// 负责爆裂火球的飞行、落地爆炸伤害计算与灼烧火海生成
/// </summary>
public class MageSkillFireballRunner : MonoBehaviour
{
private GameObject target;
private float damage;
private float explosionRadius;
private GameObject owner;
private float speed;
private GameObject explosionVfx;
private GameObject burnFieldVfx;
private bool createBurnField;
private bool hasExploded = false;
public void Initialize(GameObject targetObj, float dmg, float radius, GameObject ownerObj, float moveSpeed, GameObject expVfx, GameObject burnVfx, bool burnField)
{
target = targetObj;
damage = dmg;
explosionRadius = radius;
owner = ownerObj;
speed = moveSpeed;
explosionVfx = expVfx;
burnFieldVfx = burnVfx;
createBurnField = burnField;
// 屏蔽原有 Projectile,接管飞行与碰撞
var defaultProj = GetComponent<Projectile>();
if (defaultProj != null) defaultProj.enabled = false;
Destroy(gameObject, 4.0f);
}
private void Update()
{
if (hasExploded) return;
if (target == null)
{
ExplodeAtPosition(transform.position);
return;
}
Vector3 targetPos = target.transform.position + Vector3.up * 0.8f;
Vector3 toTarget = targetPos - transform.position;
float step = speed * Time.deltaTime;
if (toTarget.sqrMagnitude <= step * step || toTarget.sqrMagnitude <= 0.15f)
{
ExplodeAtPosition(targetPos);
return;
}
transform.rotation = Quaternion.LookRotation(toTarget.normalized);
transform.position += toTarget.normalized * step;
}
private void ExplodeAtPosition(Vector3 pos)
{
if (hasExploded) return;
hasExploded = true;
// 1. 生成爆炸视觉特效
GameObject expPrefab = explosionVfx;
#if UNITY_EDITOR
if (expPrefab == null)
{
expPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Combat/Explosions/MagicExplosion/MagicExplosionFire.prefab");
}
#endif
if (expPrefab != null)
{
var expGo = Instantiate(expPrefab, pos, Quaternion.identity);
expGo.transform.localScale = Vector3.one * (explosionRadius / 1.8f);
Destroy(expGo, 3.0f);
}
// 2. 结算范围伤害
ApplyAreaDamage(pos, explosionRadius, damage);
// 3. 6级解锁:生成持续 3 秒的灼烧火海(带有地面燃烧火焰特效)
if (createBurnField)
{
GameObject burnArea = new GameObject("BurnField");
burnArea.transform.position = new Vector3(pos.x, 0.05f, pos.z);
var area = burnArea.AddComponent<MageBurnArea>();
GameObject fieldPrefab = burnFieldVfx;
#if UNITY_EDITOR
if (fieldPrefab == null)
{
fieldPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Environment/Fire/Cartoon/Field Intense/FireFieldIntenseRed.prefab");
}
#endif
area.Initialize(owner, damage * 0.15f, explosionRadius, 3.0f, fieldPrefab);
}
Destroy(gameObject);
}
private void ApplyAreaDamage(Vector3 center, float radius, float dmg)
{
if (owner == null) return;
UnitTeam myTeam = owner.GetComponent<UnitTeam>();
if (myTeam == null) return;
float sqrRadius = radius * radius;
var allUnits = Object.FindObjectsOfType<UnitTeam>();
foreach (var t in allUnits)
{
if (t == null || !t.gameObject.activeInHierarchy || !myTeam.IsEnemy(t.gameObject)) continue;
Vector3 flatOffset = t.transform.position - center;
flatOffset.y = 0f;
if (flatOffset.sqrMagnitude <= sqrRadius)
{
var targetUnit = t.GetComponentInParent<UnitBase>();
if (targetUnit != null)
{
targetUnit.ReceiveDamage(dmg, owner);
}
else
{
var bar = t.GetComponentInParent<WorldHealthBar>();
if (bar != null) bar.TakeDamage(dmg, owner);
}
}
}
}
}
/// <summary>
/// 6 级【灼烧火海】:在地面持续 3 秒,每秒对进入范围的敌人造成持续燃烧伤害
/// </summary>
public class MageBurnArea : MonoBehaviour
{
private GameObject owner;
private float dps;
private float radius;
private float lifetime;
public void Initialize(GameObject ownerObj, float burnDps, float r, float duration, GameObject vfxPrefab)
{
owner = ownerObj;
dps = burnDps;
radius = r;
lifetime = duration;
// 实例化地面烈焰燃烧粒子特效(严格保持 X = -90° 旋转水平平铺地面)
if (vfxPrefab != null)
{
Quaternion rot = Quaternion.Euler(-90f, 0f, 0f);
var vfxInstance = Instantiate(vfxPrefab, transform.position, rot);
vfxInstance.transform.rotation = rot;
vfxInstance.transform.SetParent(transform, true);
vfxInstance.transform.localScale = Vector3.one * (radius / 1.5f);
}
StartCoroutine(BurnRoutine());
Destroy(gameObject, duration + 0.1f);
}
private IEnumerator BurnRoutine()
{
float timer = 0f;
float tickInterval = 0.5f;
while (timer < lifetime)
{
yield return new WaitForSeconds(tickInterval);
timer += tickInterval;
if (owner == null) break;
UnitTeam myTeam = owner.GetComponent<UnitTeam>();
if (myTeam == null) break;
float sqrRadius = radius * radius;
var allUnits = Object.FindObjectsOfType<UnitTeam>();
foreach (var t in allUnits)
{
if (t == null || !t.gameObject.activeInHierarchy || !myTeam.IsEnemy(t.gameObject)) continue;
Vector3 flatOffset = t.transform.position - transform.position;
flatOffset.y = 0f;
if (flatOffset.sqrMagnitude <= sqrRadius)
{
var unit = t.GetComponentInParent<UnitBase>();
if (unit != null)
{
unit.ReceiveDamage(dps * tickInterval, owner);
}
else
{
var bar = t.GetComponentInParent<WorldHealthBar>();
if (bar != null) bar.TakeDamage(dps * tickInterval, owner);
}
}
}
}
}
}