feat(PlayerSkillSystem): 实现天雷狂轰技能特效和伤害机制 - 添加技能2特效配置,包括落雷击中特效和雷暴全屏光环特效 - 更新技能2名称从"全屏AOE伤害"为"全屏天雷狂轰伤害" - 实现屏幕中央触发全屏雷暴光环效果 - 添加交错落雷机制,对敌方单位进行精确打击 - 支持无敌人时在敌方阵地随机散落震撼天雷 - 优化伤害计算和冷却逻辑 - 同步更新技能日志信息和代码注释 ```
402 lines
13 KiB
C#
402 lines
13 KiB
C#
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
/// <summary>
|
||
/// 玩家技能系统(召唤师技能)
|
||
/// 管理技能释放和冷却
|
||
/// </summary>
|
||
public class PlayerSkillSystem : MonoBehaviour
|
||
{
|
||
public static PlayerSkillSystem Instance { get; private set; }
|
||
|
||
[Header("战术增益加成倍率")]
|
||
public float cooldownMultiplier = 1f; // 冷却缩减倍率 (如 0.9 = CD 缩减 10%)
|
||
public float powerMultiplier = 1f; // 威力加成倍率 (如 1.15 = 效果提升 15%)
|
||
|
||
[Header("技能1:群体治疗")]
|
||
public Button skill1Button;
|
||
public Text skill1CooldownText;
|
||
public float skill1Cooldown = 20f;
|
||
public float skill1HealAmount = 100f;
|
||
private float skill1RemainingCooldown = 0f;
|
||
|
||
[Header("技能1特效配置")]
|
||
public GameObject cureBallPrefab; // 飞球预制体 (Assets/Prefabs/特效/飞球/cureBall.prefab)
|
||
public GameObject healNovaPrefab; // 治疗冲击波预制体 (Assets/TJGeneratorLibEffects/Prefabs/Interactive/Healing/HealNova.prefab)
|
||
public Transform castStartTransform; // 技能施放起点 (如未指定默认摄像机下方或召唤师位置)
|
||
public Vector3 castTargetPosition = new Vector3(0f, 0.1f, -9f); // 我方阵地中心落地点
|
||
|
||
[Header("技能2:全屏AOE")]
|
||
public Button skill2Button;
|
||
public Text skill2CooldownText;
|
||
public float skill2Cooldown = 20f;
|
||
public float skill2Damage = 80f;
|
||
private float skill2RemainingCooldown = 0f;
|
||
|
||
[Header("技能2特效配置")]
|
||
public GameObject lightningStrikePrefab; // 落雷击中特效 (Assets/TJGeneratorLibEffects/Prefabs/Combat/Explosions/LightningExplosion/LightningExplosionBlue.prefab)
|
||
public GameObject lightningNovaPrefab; // 雷暴全屏光环特效 (Assets/TJGeneratorLibEffects/Prefabs/Combat/Nova/Lightning/NovaLightningBlue.prefab)
|
||
|
||
private void Awake()
|
||
{
|
||
Instance = this;
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
// 自动从场景或父节点绑定技能按钮与图标引用
|
||
EnsureSkillUIReferences();
|
||
|
||
// 从 PlayerData 加载玩家装备的 2 个召唤师技能图标
|
||
LoadEquippedSkillIcons();
|
||
|
||
// 绑定按钮点击事件
|
||
if (skill1Button != null)
|
||
{
|
||
skill1Button.onClick.AddListener(UseSkill1);
|
||
}
|
||
|
||
if (skill2Button != null)
|
||
{
|
||
skill2Button.onClick.AddListener(UseSkill2);
|
||
}
|
||
}
|
||
|
||
private void EnsureSkillUIReferences()
|
||
{
|
||
var canvas = GameObject.Find("TowerDefenseCanvas");
|
||
if (canvas == null) return;
|
||
|
||
var skillsPanel = canvas.transform.Find("BottomUI/SkillsPanel");
|
||
if (skillsPanel == null) return;
|
||
|
||
if (skill1Button == null && skillsPanel.childCount > 0)
|
||
{
|
||
var item1 = skillsPanel.GetChild(0);
|
||
skill1Button = item1.GetComponent<Button>();
|
||
if (skill1Button == null) skill1Button = item1.gameObject.AddComponent<Button>();
|
||
}
|
||
|
||
if (skill2Button == null && skillsPanel.childCount > 1)
|
||
{
|
||
var item2 = skillsPanel.GetChild(1);
|
||
skill2Button = item2.GetComponent<Button>();
|
||
if (skill2Button == null) skill2Button = item2.gameObject.AddComponent<Button>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 PlayerData 读取玩家配置的 2 个召唤师技能名称,并绑定图标到 UI 位置
|
||
/// </summary>
|
||
public void LoadEquippedSkillIcons()
|
||
{
|
||
string skill1Name = "神圣赞歌";
|
||
string skill2Name = "陨石轰炸";
|
||
|
||
if (PlayerDataManager.Instance != null && PlayerDataManager.Instance.equippedSummonerSkills != null && PlayerDataManager.Instance.equippedSummonerSkills.Length >= 2)
|
||
{
|
||
skill1Name = PlayerDataManager.Instance.equippedSummonerSkills[0];
|
||
skill2Name = PlayerDataManager.Instance.equippedSummonerSkills[1];
|
||
}
|
||
|
||
SetSkillIcon(skill1Button, skill1Name);
|
||
SetSkillIcon(skill2Button, skill2Name);
|
||
}
|
||
|
||
private void SetSkillIcon(Button skillBtn, string skillName)
|
||
{
|
||
if (skillBtn == null || string.IsNullOrEmpty(skillName)) return;
|
||
|
||
Image img = skillBtn.GetComponent<Image>();
|
||
if (img != null)
|
||
{
|
||
Sprite icon = GetSkillIconSprite(skillName);
|
||
if (icon != null)
|
||
{
|
||
img.sprite = icon;
|
||
}
|
||
}
|
||
}
|
||
|
||
public static Sprite GetSkillIconSprite(string skillName)
|
||
{
|
||
if (string.IsNullOrEmpty(skillName)) return null;
|
||
|
||
Sprite icon = Resources.Load<Sprite>($"Picture/UI/召唤师技能图标/{skillName}");
|
||
#if UNITY_EDITOR
|
||
if (icon == null)
|
||
{
|
||
icon = UnityEditor.AssetDatabase.LoadAssetAtPath<Sprite>($"Assets/Picture/UI/召唤师技能图标/{skillName}.png");
|
||
}
|
||
#endif
|
||
return icon;
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
// 更新技能1冷却
|
||
if (skill1RemainingCooldown > 0)
|
||
{
|
||
skill1RemainingCooldown -= Time.deltaTime;
|
||
if (skill1Button != null)
|
||
{
|
||
skill1Button.interactable = false;
|
||
}
|
||
if (skill1CooldownText != null)
|
||
{
|
||
skill1CooldownText.text = Mathf.CeilToInt(skill1RemainingCooldown).ToString();
|
||
skill1CooldownText.gameObject.SetActive(true);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (skill1Button != null)
|
||
{
|
||
skill1Button.interactable = true;
|
||
}
|
||
if (skill1CooldownText != null)
|
||
{
|
||
skill1CooldownText.gameObject.SetActive(false);
|
||
}
|
||
}
|
||
|
||
// 更新技能2冷却
|
||
if (skill2RemainingCooldown > 0)
|
||
{
|
||
skill2RemainingCooldown -= Time.deltaTime;
|
||
if (skill2Button != null)
|
||
{
|
||
skill2Button.interactable = false;
|
||
}
|
||
if (skill2CooldownText != null)
|
||
{
|
||
skill2CooldownText.text = Mathf.CeilToInt(skill2RemainingCooldown).ToString();
|
||
skill2CooldownText.gameObject.SetActive(true);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (skill2Button != null)
|
||
{
|
||
skill2Button.interactable = true;
|
||
}
|
||
if (skill2CooldownText != null)
|
||
{
|
||
skill2CooldownText.gameObject.SetActive(false);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用技能1:群体治疗
|
||
/// </summary>
|
||
public void UseSkill1()
|
||
{
|
||
if (skill1RemainingCooldown > 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
float finalHeal = skill1HealAmount * powerMultiplier;
|
||
Debug.Log($"释放技能1:群体治疗(恢复 {finalHeal:F1} 生命,威力倍率: {powerMultiplier:F2})");
|
||
|
||
// 智能获取友方阵地中心或平均位置
|
||
Vector3 targetPos = GetAllyCenterPosition();
|
||
Vector3 startPos = castStartTransform != null ? castStartTransform.position : new Vector3(targetPos.x, targetPos.y + 10f, targetPos.z - 8f);
|
||
|
||
// 加载/实例化抛物线 cureBall
|
||
GameObject ballPrefab = cureBallPrefab;
|
||
if (ballPrefab == null)
|
||
{
|
||
ballPrefab = Resources.Load<GameObject>("Prefabs/特效/飞球/cureBall");
|
||
}
|
||
#if UNITY_EDITOR
|
||
if (ballPrefab == null)
|
||
{
|
||
ballPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefabs/特效/飞球/cureBall.prefab");
|
||
}
|
||
#endif
|
||
|
||
GameObject novaPrefab = healNovaPrefab;
|
||
#if UNITY_EDITOR
|
||
if (novaPrefab == null)
|
||
{
|
||
novaPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Interactive/Healing/HealNova.prefab");
|
||
}
|
||
#endif
|
||
|
||
if (ballPrefab != null)
|
||
{
|
||
GameObject ballGo = Instantiate(ballPrefab, startPos, Quaternion.identity);
|
||
var parabola = ballGo.GetComponent<ParabolaFlyObject>();
|
||
if (parabola == null)
|
||
{
|
||
parabola = ballGo.AddComponent<ParabolaFlyObject>();
|
||
}
|
||
|
||
parabola.Launch(startPos, targetPos, 0.8f, 4.5f, novaPrefab, () =>
|
||
{
|
||
// 对所有友方单位施加治疗
|
||
BuffSystem.Instance.ApplyGroupHeal(finalHeal);
|
||
});
|
||
}
|
||
else
|
||
{
|
||
// 特效缺失兜底直接治疗
|
||
BuffSystem.Instance.ApplyGroupHeal(finalHeal);
|
||
}
|
||
|
||
// 开始冷却
|
||
skill1RemainingCooldown = skill1Cooldown * cooldownMultiplier;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算场上己方英雄的实时中心点位置,无单位时回退默认网格中心
|
||
/// </summary>
|
||
private Vector3 GetAllyCenterPosition()
|
||
{
|
||
var heroes = FindObjectsOfType<UnitBase>();
|
||
Vector3 sumPos = Vector3.zero;
|
||
int count = 0;
|
||
|
||
foreach (var hero in heroes)
|
||
{
|
||
if (hero == null || !hero.gameObject.activeInHierarchy) continue;
|
||
UnitTeam team = hero.GetComponent<UnitTeam>();
|
||
if (team != null && team.IsLocalPlayerTeam())
|
||
{
|
||
sumPos += hero.transform.position;
|
||
count++;
|
||
}
|
||
}
|
||
|
||
if (count > 0)
|
||
{
|
||
Vector3 center = sumPos / count;
|
||
center.y = 0.1f;
|
||
return center;
|
||
}
|
||
|
||
return new Vector3(0f, 0.1f, -10f); // 网格阵地真正中心点
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用技能2:全屏天雷狂轰伤害
|
||
/// </summary>
|
||
public void UseSkill2()
|
||
{
|
||
if (skill2RemainingCooldown > 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
float finalDamage = skill2Damage * powerMultiplier;
|
||
Debug.Log($"释放技能2:天雷狂轰(造成 {finalDamage:F1} 伤害,威力倍率: {powerMultiplier:F2})");
|
||
|
||
// 加载特效资源
|
||
GameObject strikePrefab = lightningStrikePrefab;
|
||
#if UNITY_EDITOR
|
||
if (strikePrefab == null)
|
||
{
|
||
strikePrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Combat/Explosions/LightningExplosion/LightningExplosionBlue.prefab");
|
||
}
|
||
#endif
|
||
|
||
GameObject novaPrefab = lightningNovaPrefab;
|
||
#if UNITY_EDITOR
|
||
if (novaPrefab == null)
|
||
{
|
||
novaPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/TJGeneratorLibEffects/Prefabs/Combat/Nova/Lightning/NovaLightningBlue.prefab");
|
||
}
|
||
#endif
|
||
|
||
// 1. 屏幕中央触发全屏雷暴光环
|
||
if (novaPrefab != null)
|
||
{
|
||
Vector3 centerPos = new Vector3(0f, 0.1f, 0f);
|
||
var novaGo = Instantiate(novaPrefab, centerPos, novaPrefab.transform.rotation);
|
||
Destroy(novaGo, 3.5f);
|
||
}
|
||
|
||
// 2. 检索所有活着的敌方单位
|
||
var allUnits = FindObjectsOfType<UnitBase>();
|
||
var targetEnemies = new System.Collections.Generic.List<UnitBase>();
|
||
|
||
foreach (var unit in allUnits)
|
||
{
|
||
if (unit == null || !unit.gameObject.activeInHierarchy) continue;
|
||
var team = unit.GetComponent<UnitTeam>();
|
||
if (team != null && team.teamId == 1) // 敌方
|
||
{
|
||
var healthBar = unit.GetComponent<WorldHealthBar>();
|
||
if (healthBar != null && !healthBar.IsDead)
|
||
{
|
||
targetEnemies.Add(unit);
|
||
}
|
||
}
|
||
}
|
||
|
||
StartCoroutine(ExecuteLightningStrikesRoutine(targetEnemies, strikePrefab, finalDamage));
|
||
|
||
// 开始冷却
|
||
skill2RemainingCooldown = skill2Cooldown * cooldownMultiplier;
|
||
}
|
||
|
||
private System.Collections.IEnumerator ExecuteLightningStrikesRoutine(
|
||
System.Collections.Generic.List<UnitBase> targetEnemies,
|
||
GameObject strikePrefab,
|
||
float finalDamage)
|
||
{
|
||
if (targetEnemies.Count > 0)
|
||
{
|
||
// 敌群交错落雷
|
||
for (int i = 0; i < targetEnemies.Count; i++)
|
||
{
|
||
var enemy = targetEnemies[i];
|
||
if (enemy != null && enemy.gameObject.activeInHierarchy)
|
||
{
|
||
Vector3 strikePos = enemy.transform.position;
|
||
strikePos.y = 0.1f;
|
||
|
||
if (strikePrefab != null)
|
||
{
|
||
var strikeGo = Instantiate(strikePrefab, strikePos, strikePrefab.transform.rotation);
|
||
Destroy(strikeGo, 3f);
|
||
}
|
||
|
||
var healthBar = enemy.GetComponent<WorldHealthBar>();
|
||
if (healthBar != null && !healthBar.IsDead)
|
||
{
|
||
healthBar.TakeDamage(finalDamage);
|
||
}
|
||
}
|
||
|
||
yield return new WaitForSeconds(0.08f); // 80毫秒交错落雷节奏
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 无敌人时在敌方阵地随机散落 6 道震撼天雷
|
||
for (int i = 0; i < 6; i++)
|
||
{
|
||
Vector3 randomPos = new Vector3(Random.Range(-5f, 5f), 0.1f, Random.Range(2f, 12f));
|
||
if (strikePrefab != null)
|
||
{
|
||
var strikeGo = Instantiate(strikePrefab, randomPos, strikePrefab.transform.rotation);
|
||
Destroy(strikeGo, 3f);
|
||
}
|
||
yield return new WaitForSeconds(0.08f);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置所有技能冷却(测试用)
|
||
/// </summary>
|
||
public void ResetAllCooldowns()
|
||
{
|
||
skill1RemainingCooldown = 0f;
|
||
skill2RemainingCooldown = 0f;
|
||
}
|
||
}
|