feat(StoneGolemSkill): 实现石巨人技能体系与荒野祭司技能设计 - 完成石巨人技能 StoneGolemSkill 的完整实现,包括 1~10 级技能阶梯 - 实现 1~2 级【大地震击】:3.5 米范围 160% 伤害 + 1.5 秒眩晕 - 实现 3~5 级【震荡强化】:伤害提升至 200%,范围扩大至 4.4 米 + 2 秒眩晕 - 实现 6~9 级【坚石护体】:获得 30% 最大生命值护盾,护盾期间霸体免控 - 实现 10 级【天崩地裂】:4.5 米范围 260% 伤害 + 3 米强力击退 + 2 秒眩晕 + 护盾提升至 45% 最大生命值 + 护盾期间受击减伤 35% - 添加护盾特效、冲击波动画、地面裂纹特效等视觉反馈 - 实
409 lines
15 KiB
C#
409 lines
15 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// 石巨人技能:大地震击 / 天崩地裂体系(重装控制 / 护盾控场 · 1/3/6/10 级完整阶梯)
|
|
/// 1级【大地震击】:双拳猛砸地面引发冲击波,对周围 3.5 米造成 160% 物理伤害并【眩晕 1.5 秒】
|
|
/// 3级【震荡强化】:伤害提升至 200%,范围扩大 25% (4.4米),【眩晕时间延长至 2.0 秒】
|
|
/// 6级【坚石护体】:拍地瞬间获得相当于 30% 最大生命值的坚固护盾(MagicShieldBlue),护盾期间霸体免控,【依然保留 2.0 秒大范围眩晕】
|
|
/// 10级【天崩地裂】:双拳轰砸地面爆发全场重度地裂(GroundCrack),范围 4.5 米造成 260% 巨额物理伤害,
|
|
/// 【强力向外震退 3.0 米 + 眩晕 2.0 秒(击退加眩晕双重效果)】!护盾厚度提升至 45% 最大生命值,且护盾存在期间自身受到伤害降低 35%!
|
|
/// </summary>
|
|
public class StoneGolemSkill : UnitSkill
|
|
{
|
|
[Header("技能基础配置")]
|
|
[Tooltip("基础震荡波半径 (米)")]
|
|
public float baseRadius = 3.5f;
|
|
|
|
[Tooltip("技能动画完整时长 (秒)")]
|
|
public float skillAnimationDuration = 2.3f;
|
|
|
|
[Header("特效资源")]
|
|
[Tooltip("砸地冲击波核心特效 (ToonBodySlam)")]
|
|
public GameObject slamVfxPrefab;
|
|
|
|
[Tooltip("环形扩散光圈特效 (ShockwaveRing)")]
|
|
public GameObject shockwaveEffectPrefab;
|
|
|
|
[Tooltip("地面重度地裂特效 (地裂.prefab)")]
|
|
public GameObject groundCrackPrefab;
|
|
|
|
[Tooltip("坚石护体护盾特效 (MagicShieldBlue)")]
|
|
public GameObject shieldVfxPrefab;
|
|
|
|
private bool isSkillPending = false;
|
|
private GameObject activeShieldVfxInstance;
|
|
private Coroutine activeShieldCoroutine;
|
|
|
|
private void EnsureUnit()
|
|
{
|
|
if (unit == null)
|
|
{
|
|
unit = GetComponent<UnitBase>();
|
|
}
|
|
}
|
|
|
|
protected override void Awake()
|
|
{
|
|
base.Awake();
|
|
EnsureUnit();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 当能量蓄满时触发:播放石巨人双拳高举蓄力猛砸地面动作
|
|
/// </summary>
|
|
public override void ReleaseSkill()
|
|
{
|
|
EnsureUnit();
|
|
if (unit == null) return;
|
|
|
|
WorldHealthBar healthBar = unit.GetComponent<WorldHealthBar>();
|
|
if (healthBar == null || healthBar.IsDead) return;
|
|
|
|
unit.FaceCurrentTarget(0f);
|
|
isSkillPending = true;
|
|
|
|
// 播放石巨人技能动作,由 OnSkillHit1 帧触发双拳砸地命中
|
|
unit.PlaySkillAnimation(OnSkillAnimationFinished, skillAnimationDuration);
|
|
Debug.Log($"<color=orange>【石巨人】释放技能【大地震击/天崩地裂】</color>,蓄力猛砸地面!");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 动画命中帧方法:双拳猛砸接触地面的瞬间触发 (OnSkillHit1)
|
|
/// </summary>
|
|
public void OnSkillHit1()
|
|
{
|
|
ExecuteGroundSlam();
|
|
}
|
|
|
|
public void OnAttackEffect()
|
|
{
|
|
if (isSkillPending)
|
|
{
|
|
ExecuteGroundSlam();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行砸地冲击波伤害、眩晕、击退与生成护盾
|
|
/// </summary>
|
|
private void ExecuteGroundSlam()
|
|
{
|
|
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;
|
|
|
|
float currentRadius = baseRadius;
|
|
float damageMultiplier = 1.60f;
|
|
float stunDuration = 1.5f;
|
|
float knockbackDist = 0f;
|
|
|
|
if (level >= 10)
|
|
{
|
|
currentRadius = 4.5f;
|
|
damageMultiplier = 2.60f;
|
|
stunDuration = 2.0f; // 10级:眩晕 2.0 秒
|
|
knockbackDist = 3.0f; // 10级:击退加眩晕双重效果!
|
|
}
|
|
else if (level >= 3)
|
|
{
|
|
currentRadius = baseRadius * 1.25f; // 4.375 米
|
|
damageMultiplier = 2.00f;
|
|
stunDuration = 2.0f; // 3~9级:眩晕 2.0 秒,无击退
|
|
knockbackDist = 0f;
|
|
}
|
|
else
|
|
{
|
|
currentRadius = baseRadius;
|
|
damageMultiplier = 1.60f;
|
|
stunDuration = 1.5f; // 1~2级:眩晕 1.5 秒,无击退
|
|
knockbackDist = 0f;
|
|
}
|
|
|
|
float totalDamage = unit.attackPower * damageMultiplier;
|
|
|
|
// 1. 播放砸地冲击波特效 (ToonBodySlam + ShockwaveRing)
|
|
PlayShockwaveVfx(currentRadius, level >= 10);
|
|
|
|
// 2. 10级额外播放地面重度地裂特效 (GroundCrack)
|
|
if (level >= 10)
|
|
{
|
|
PlayGroundCrackVfx(currentRadius);
|
|
}
|
|
|
|
// 3. 结算大范围物理伤害、眩晕控制,以及10级强力震退
|
|
ApplyAreaImpact(currentRadius, totalDamage, stunDuration, knockbackDist);
|
|
|
|
// 4. 6级+ 激活【坚石护体】护盾 (6级 30%, 10级 45% 最大血量)
|
|
float shieldRatio = level >= 10 ? 0.45f : (level >= 6 ? 0.30f : 0f);
|
|
if (shieldRatio > 0f)
|
|
{
|
|
float shieldAmount = healthBar.maxHealth * shieldRatio;
|
|
healthBar.AddShield(shieldAmount);
|
|
PlayShieldVfx();
|
|
|
|
if (activeShieldCoroutine != null)
|
|
{
|
|
StopCoroutine(activeShieldCoroutine);
|
|
}
|
|
activeShieldCoroutine = StartCoroutine(ShieldTimeoutRoutine(6.0f, shieldAmount));
|
|
}
|
|
|
|
Debug.Log($"<color=orange>【石巨人·大地震击】轰地!</color> 伤害: {totalDamage:F1} ({damageMultiplier * 100:F0}%), 半径: {currentRadius:F1}m, 眩晕: {stunDuration:F1}s, 震退: {knockbackDist:F1}m (等级: {level})");
|
|
}
|
|
|
|
private void PlayShockwaveVfx(float radius, bool isUltimate)
|
|
{
|
|
// 1. 砸地核心冲击波特效 (ToonBodySlam: 气浪扩散 + 尘烟飞散 + 火星)
|
|
GameObject slamPrefab = slamVfxPrefab;
|
|
#if UNITY_EDITOR
|
|
if (slamPrefab == null)
|
|
{
|
|
slamPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Combat/Brawling/Toon/ToonBodySlam.prefab");
|
|
}
|
|
#endif
|
|
if (slamPrefab != null && unit != null)
|
|
{
|
|
Vector3 slamPos = unit.transform.position + unit.transform.forward * 0.8f;
|
|
slamPos.y = 0.05f;
|
|
var slamObj = Instantiate(slamPrefab, slamPos, Quaternion.identity);
|
|
slamObj.transform.localScale = Vector3.one * (radius / 2.2f);
|
|
Destroy(slamObj, 2.0f);
|
|
}
|
|
|
|
// 2. 地面扩散光圈 (ShockwaveRing)
|
|
GameObject ringPrefab = shockwaveEffectPrefab;
|
|
#if UNITY_EDITOR
|
|
if (ringPrefab == null)
|
|
{
|
|
ringPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefabs/特效/扩散光圈/ShockwaveRing.prefab");
|
|
}
|
|
#endif
|
|
if (ringPrefab != null && unit != null)
|
|
{
|
|
Vector3 effectPos = unit.transform.position;
|
|
effectPos.y = 0.05f;
|
|
var effectObj = Instantiate(ringPrefab, effectPos, ringPrefab.transform.rotation);
|
|
var shockwave = effectObj.GetComponent<ShockwaveEffect>();
|
|
if (shockwave != null)
|
|
{
|
|
Color ringColor = isUltimate ? new Color(1.0f, 0.45f, 0.15f, 1f) : new Color(0.9f, 0.7f, 0.35f, 1f);
|
|
shockwave.SetupEffect(radius, 0.8f, ringColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void PlayGroundCrackVfx(float radius)
|
|
{
|
|
GameObject prefab = groundCrackPrefab;
|
|
#if UNITY_EDITOR
|
|
if (prefab == null)
|
|
{
|
|
prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefabs/特效/地裂特效/地裂.prefab");
|
|
}
|
|
#endif
|
|
if (prefab != null && unit != null)
|
|
{
|
|
Vector3 crackPos = unit.transform.position;
|
|
crackPos.y = 0.06f;
|
|
var crackObj = Instantiate(prefab, crackPos, prefab.transform.rotation);
|
|
crackObj.transform.localScale = Vector3.one * (radius * 1.4f);
|
|
var groundCrack = crackObj.GetComponent<GroundCrack>();
|
|
if (groundCrack != null)
|
|
{
|
|
groundCrack.Play();
|
|
}
|
|
Destroy(crackObj, 3.0f);
|
|
}
|
|
}
|
|
|
|
private void PlayShieldVfx()
|
|
{
|
|
if (activeShieldVfxInstance != null)
|
|
{
|
|
Destroy(activeShieldVfxInstance);
|
|
}
|
|
|
|
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)
|
|
{
|
|
// 调整护盾挂载高度与缩放:与盾卫视觉比例一致协调 (1.15倍),不再过于庞大
|
|
activeShieldVfxInstance = Instantiate(prefab, unit.transform.position + Vector3.up * 1.1f, Quaternion.identity);
|
|
activeShieldVfxInstance.transform.SetParent(unit.transform, true);
|
|
activeShieldVfxInstance.transform.localScale = Vector3.one * 1.15f;
|
|
}
|
|
}
|
|
|
|
private IEnumerator ShieldTimeoutRoutine(float duration, float amount)
|
|
{
|
|
float timer = duration;
|
|
WorldHealthBar healthBar = unit != null ? unit.GetComponent<WorldHealthBar>() : null;
|
|
var status = unit != null ? unit.GetComponent<StatusEffectController>() : null;
|
|
|
|
// 护盾期间霸体免控
|
|
if (status != null)
|
|
{
|
|
status.isStunImmune = true;
|
|
}
|
|
|
|
while (timer > 0f)
|
|
{
|
|
if (unit == null || healthBar == null || healthBar.IsDead || healthBar.currentShield <= 0f)
|
|
{
|
|
break;
|
|
}
|
|
timer -= Time.deltaTime;
|
|
yield return null;
|
|
}
|
|
|
|
// 护盾解除恢复正常免控状态
|
|
if (status != null)
|
|
{
|
|
status.isStunImmune = false;
|
|
}
|
|
|
|
// 超时或护盾清空时销毁护盾特效
|
|
if (activeShieldVfxInstance != null)
|
|
{
|
|
Destroy(activeShieldVfxInstance);
|
|
activeShieldVfxInstance = null;
|
|
}
|
|
|
|
// 超时扣除护盾
|
|
if (healthBar != null && healthBar.currentShield > 0f && timer <= 0f)
|
|
{
|
|
healthBar.RemoveShield(amount);
|
|
}
|
|
}
|
|
|
|
private void ApplyAreaImpact(float radius, float damage, float stunDuration, float knockback)
|
|
{
|
|
UnitTeam myTeam = unit.GetComponent<UnitTeam>();
|
|
if (myTeam == null) return;
|
|
|
|
float sqrRadius = radius * radius;
|
|
Vector3 myPos = unit.transform.position;
|
|
var allTeams = FindObjectsOfType<UnitTeam>();
|
|
HashSet<GameObject> hitRoots = new HashSet<GameObject>();
|
|
|
|
foreach (var t in allTeams)
|
|
{
|
|
if (t == null || !t.gameObject.activeInHierarchy || !myTeam.IsEnemy(t.gameObject)) continue;
|
|
|
|
GameObject opponent = t.gameObject;
|
|
if (opponent == unit.gameObject || opponent.transform.IsChildOf(unit.transform) || unit.transform.IsChildOf(opponent.transform)) continue;
|
|
|
|
Vector3 offset = opponent.transform.position - myPos;
|
|
offset.y = 0f;
|
|
if (offset.sqrMagnitude <= sqrRadius)
|
|
{
|
|
GameObject rootGo = opponent.transform.root.gameObject;
|
|
if (!hitRoots.Add(rootGo)) continue;
|
|
|
|
// 1. 伤害与眩晕结算
|
|
UnitBase targetUnit = opponent.GetComponentInParent<UnitBase>();
|
|
WorldHealthBar targetBar = opponent.GetComponentInParent<WorldHealthBar>();
|
|
|
|
if (targetUnit != null)
|
|
{
|
|
targetUnit.ReceiveDamage(damage, unit.gameObject);
|
|
if (stunDuration > 0f && targetUnit.IsAlive)
|
|
{
|
|
targetUnit.ApplyStun(stunDuration);
|
|
Debug.Log($"<color=yellow>【大地震击】眩晕生效!</color> 目标 {targetUnit.gameObject.name} 眩晕 {stunDuration:F1} 秒!");
|
|
}
|
|
}
|
|
else if (targetBar != null && !targetBar.IsDead)
|
|
{
|
|
targetBar.TakeDamage(damage, unit.gameObject);
|
|
var status = opponent.GetComponentInParent<StatusEffectController>();
|
|
if (status != null && stunDuration > 0f)
|
|
{
|
|
status.ApplyStun(stunDuration);
|
|
}
|
|
}
|
|
|
|
// 2. 10级强力向外震退 (与眩晕并存)
|
|
if (knockback > 0f)
|
|
{
|
|
Vector3 pushDir = offset.sqrMagnitude > 0.001f ? offset.normalized : unit.transform.forward;
|
|
ApplyKnockback(opponent, pushDir * knockback);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyKnockback(GameObject victim, Vector3 displacement)
|
|
{
|
|
UnityEngine.AI.NavMeshAgent agent = victim.GetComponentInParent<UnityEngine.AI.NavMeshAgent>();
|
|
Transform targetTr = agent != null ? agent.transform : victim.transform;
|
|
Vector3 targetPos = targetTr.position + displacement;
|
|
|
|
if (UnityEngine.AI.NavMesh.SamplePosition(targetPos, out UnityEngine.AI.NavMeshHit hit, 2.0f, UnityEngine.AI.NavMesh.AllAreas))
|
|
{
|
|
targetPos = hit.position;
|
|
}
|
|
|
|
if (agent != null && agent.enabled && agent.isOnNavMesh)
|
|
{
|
|
agent.Warp(targetPos);
|
|
}
|
|
else
|
|
{
|
|
targetTr.position = targetPos;
|
|
}
|
|
|
|
Debug.Log($"<color=orange>【天崩地裂·排山倒海】!</color> 目标 {victim.name} 被强力震飞推开 {displacement.magnitude:F1} 米!");
|
|
}
|
|
|
|
public override float ModifyIncomingDamage(float damage, GameObject attacker)
|
|
{
|
|
EnsureUnit();
|
|
// 10级解锁:护盾存在期间自身受到伤害降低 35%
|
|
if (unit != null && unit.level >= 10)
|
|
{
|
|
WorldHealthBar healthBar = unit.GetComponent<WorldHealthBar>();
|
|
if (healthBar != null && healthBar.currentShield > 0f)
|
|
{
|
|
return damage * (1f - 0.35f);
|
|
}
|
|
}
|
|
return damage;
|
|
}
|
|
|
|
private void OnSkillAnimationFinished()
|
|
{
|
|
if (isSkillPending)
|
|
{
|
|
ExecuteGroundSlam(); // 容错兜底:动画结束若未触发命中帧,自动执行
|
|
}
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (activeShieldVfxInstance != null)
|
|
{
|
|
Destroy(activeShieldVfxInstance);
|
|
}
|
|
var status = unit != null ? unit.GetComponent<StatusEffectController>() : null;
|
|
if (status != null)
|
|
{
|
|
status.isStunImmune = false;
|
|
}
|
|
}
|
|
}
|