``` docs(readme): 更新文档说明 - 添加项目使用指南 - 完善API接口说明 - 修正错误的配置示例 ``` 注意:由于您提供的code differences部分为空,以上是一个示例模板。当您有具体的代码变更时,请提供详细的变更内容以便生成更准确的commit message。
334 lines
12 KiB
C#
334 lines
12 KiB
C#
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 target, float damageMultiplier, float radius, bool createBurnField)
|
||
{
|
||
yield return new WaitForSeconds(0.25f);
|
||
if (target != null && target.activeInHierarchy && unit != null)
|
||
{
|
||
LaunchFireball(target, damageMultiplier, radius, createBurnField);
|
||
Debug.Log($"<color=red>【双发爆裂】追加第二发火球!</color> 伤害倍率: {damageMultiplier * 100:F0}%");
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|