Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/gfxController.cs
T

815 lines
31 KiB
C#

using UnityEngine;
using UnityEngine.UI;
using System;
using System.Collections;
using SmoothShakeFree;
using Random = UnityEngine.Random;
/// <summary>
/// 控制游戏过程中的打击特效及其他图形效果
/// </summary>
public class GfxController : MonoBehaviour
{
public static GfxController Instance { get; private set; }
[Header("Combatant Objects")]
public GameObject enemyMoveObject;
public GameObject ally01MoveObject;
public GameObject ally02MoveObject;
public GameObject ally03MoveObject;
public GameObject ally04MoveObject;
public GameObject ally05MoveObject;
[Header("Hit Effects - Enemy")]
public GameObject enemyObject;
[Header("Hit Effects - Allies")]
public GameObject ally01Object;
public GameObject ally02Object;
public GameObject ally03Object;
public GameObject ally04Object;
public GameObject ally05Object;
[Header("Prefabs")]
public GameObject hitFX;
public GameObject projectileFX;
public GameObject explosionFX;
public GameObject k_o_fx;
public GameObject koFatherObject;
[Header("Hit Effect Settings - Enemy")]
public float enemyOffsetX = 0f;
public float enemyOffsetY = 0f;
public float enemyFxScale = 1f;
public bool useEnemyRandomScale = false;
public float enemyMinFxScale = 0.8f;
public float enemyMaxFxScale = 1.2f;
[Range(0f, 1f)]
public float enemyFxTransparency = 1f;
public int enemyFxSortingOrder = 10;
[Header("Hit Effect Settings - Ally")]
public float allyOffsetX = 0f;
public float allyOffsetY = 0f;
public float allyFxScale = 1f;
public bool useAllyRandomScale = false;
public float allyMinFxScale = 0.8f;
public float allyMaxFxScale = 1.2f;
[Range(0f, 1f)]
public float allyFxTransparency = 1f;
public int allyFxSortingOrder = 10;
[Header("Projectile Settings")]
public float projectileDuration = 0.5f;
public AnimationCurve projectileSpeedCurve = AnimationCurve.Linear(0, 0, 1, 1);
[Range(0f, 1f)]
public float projectileTransparency = 1f;
public float projectileScale = 1f;
[Tooltip("弹道颜色控制 (影响 Trail 和 Particle)")]
public Gradient projectileColor;
[Header("敌人攻击控件")]
[Tooltip("Miss时敌人攻击角色的弹道特效")]
public GameObject enemyMissProjectilePrefab;
[Tooltip("Miss弹道颜色控制")]
public Gradient enemyAttackProjectileColor;
[Tooltip("Miss弹道移动时间")]
public float enemyAttackProjectileDuration = 0.4f;
[Tooltip("Miss弹道缩放")]
public float enemyAttackProjectileScale = 1.0f;
[Tooltip("Miss弹道层级")]
public int enemyAttackProjectileSortingOrder = 15;
[Tooltip("Miss弹道到达时的Hit特效缩放")]
public float enemyAttackHitFxScale = 1.0f;
[Tooltip("Miss弹道速度曲线")]
public AnimationCurve enemyAttackSpeedCurve = AnimationCurve.Linear(0, 0, 1, 1);
public float koFxScale = 1f;
[Tooltip("弹道到达终点时的随机偏移范围 - X轴")]
public float projectileRandomOffsetX = 0f;
[Tooltip("弹道到达终点时的随机偏移范围 - Y轴")]
public float projectileRandomOffsetY = 0f;
[Tooltip("弹道到达终点时的随机偏移范围 - Z轴")]
public float projectileRandomOffsetZ = 0f;
public int projectileSortingOrder = 10;
[Header("Enemy Hurt Shake Settings")]
public Vector3 hitShakeAmplitude = new Vector3(10f, 10f, 0f);
public Vector3 hitShakeFrequency = new Vector3(20f, 20f, 0f);
public float hitShakeDuration = 0.2f;
[Header("Ally Hurt Shake Settings")]
public Vector3 allyHitShakeAmplitude = new Vector3(8f, 8f, 0f);
public Vector3 allyHitShakeFrequency = new Vector3(25f, 25f, 0f);
public float allyHitShakeDuration = 0.15f;
private void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
/// <summary>
/// 在指定目标位置播放受击特效
/// </summary>
/// <param name="target">受击者物体</param>
/// <param name="parent">父物体(特效将生成在该物体下)</param>
/// <param name="isEnemy">是否为敌人受击</param>
/// <param name="customWorldPos">可选:自定义世界坐标播放(如匹配弹道终点)</param>
/// <param name="scaleOverride">可选:强制指定特效缩放倍率</param>
public void PlayHitFX(GameObject target, GameObject parent = null, bool isEnemy = false, Vector3? customWorldPos = null, float? scaleOverride = null)
{
if (target == null || hitFX == null)
{
if (hitFX == null) Debug.LogWarning("[GfxController] hitFX Prefab 未在 Inspector 中分配!");
return;
}
// 触发受击震动效果
TriggerShake(target, isEnemy);
// 如果是敌人,触发闪红效果 (使用 teamUIController 已有的逻辑)
if (isEnemy && teamUIController.Instance != null)
{
teamUIController.Instance.TriggerEnemyHurtFlash();
}
else if (!isEnemy && teamUIController.Instance != null)
{
// 尝试从物体名称或组件中识别槽位并触发闪红
int slot = -1;
var ally = target.GetComponent<AllyCombatant>();
if (ally != null) slot = ally.slotIndex;
else
{
string n = target.name.ToLower();
if (n.Contains("ally_01")) slot = 0;
else if (n.Contains("ally_02")) slot = 1;
else if (n.Contains("ally_03")) slot = 2;
else if (n.Contains("ally_04")) slot = 3;
else if (n.Contains("ally_05")) slot = 4;
}
if (slot != -1)
{
teamUIController.Instance.TriggerAllyHurtFlash(slot);
}
}
// 尝试自动解析挂载点,如果未提供 parent
GameObject visualParent = parent != null ? parent : ResolveGfxMountPoint(target);
// 根据身份选择参数
float offsetX = isEnemy ? enemyOffsetX : allyOffsetX;
float offsetY = isEnemy ? enemyOffsetY : allyOffsetY;
// 计算缩放逻辑
float fxScale;
if (scaleOverride.HasValue)
{
fxScale = scaleOverride.Value;
}
else if (isEnemy)
{
fxScale = useEnemyRandomScale ? Random.Range(enemyMinFxScale, enemyMaxFxScale) : enemyFxScale;
}
else
{
fxScale = useAllyRandomScale ? Random.Range(allyMinFxScale, allyMaxFxScale) : allyFxScale;
}
float transparency = isEnemy ? enemyFxTransparency : allyFxTransparency;
GameObject fx;
Vector3 offsetVector = new Vector3(offsetX, offsetY, 0f);
if (visualParent != null)
{
// 将缩放应用到挂载点物体(visualParent)本身
visualParent.transform.localScale = Vector3.one * fxScale;
// 作为子物体生成
fx = Instantiate(hitFX, visualParent.transform);
// 确保特效在 UI 层级中显示在最前方
fx.transform.SetAsLastSibling();
// 检查是否是 UI 元素 (RectTransform)
RectTransform rt = fx.GetComponent<RectTransform>();
if (rt != null)
{
if (customWorldPos.HasValue)
{
// 如果有自定义世界坐标,将其转换为本地 UI 坐标
rt.position = customWorldPos.Value;
}
else
{
rt.anchoredPosition = new Vector2(offsetX, offsetY);
}
rt.localRotation = Quaternion.identity;
// 确保 UI 特效的原始大小正确,且 localScale 为 1(继承 parent 的缩放)
rt.sizeDelta = hitFX.GetComponent<RectTransform>().sizeDelta;
rt.localScale = Vector3.one;
}
else
{
if (customWorldPos.HasValue)
{
fx.transform.position = customWorldPos.Value;
}
else
{
fx.transform.localPosition = offsetVector;
}
fx.transform.localRotation = Quaternion.identity;
fx.transform.localScale = Vector3.one;
}
}
else
{
Vector3 spawnPos = customWorldPos.HasValue ? customWorldPos.Value : (target.transform.position + offsetVector);
fx = Instantiate(hitFX, spawnPos, Quaternion.identity);
fx.transform.localScale = Vector3.one * fxScale;
}
// 针对粒子系统的特殊处理
ParticleSystem[] allParticles = fx.GetComponentsInChildren<ParticleSystem>();
foreach (var ps in allParticles)
{
var main = ps.main;
// 强制修复缩放:设置 Scaling Mode 为 Hierarchy,使粒子大小跟随 Transform 缩放
main.scalingMode = ParticleSystemScalingMode.Hierarchy;
}
// 应用透明度并处理渲染
ApplyTransparencyAndRender(fx, transparency, isEnemy ? enemyFxSortingOrder : allyFxSortingOrder);
if (allParticles.Length > 0)
{
foreach (var ps in allParticles)
{
// 确保粒子在生成时立即播放
ps.Play(true);
// 设置停止动作,如果是根节点粒子,设置自动销毁
if (ps.gameObject == fx)
{
var m = ps.main;
m.stopAction = ParticleSystemStopAction.Destroy;
}
}
}
else
{
// 如果不是粒子系统(例如动画 Prefab),在固定时间后销毁(默认 2 秒)
Destroy(fx, 2.0f);
}
}
/// <summary>
/// 从发射源向目标发射一个拖尾特效
/// </summary>
/// <param name="source">发射源逻辑物体</param>
/// <param name="target">目标逻辑物体</param>
/// <param name="onComplete">到达终点后的回调(可选,返回弹道终点的世界坐标)</param>
public void PlayProjectile(GameObject source, GameObject target, Action<Vector3> onComplete = null)
{
if (source == null || target == null || projectileFX == null)
{
if (projectileFX == null) Debug.LogWarning("[GfxController] projectileFX Prefab 未分配!");
onComplete?.Invoke(target != null ? target.transform.position : Vector3.zero);
return;
}
// --- 核心修正:将逻辑物体映射到 UI 挂载点 ---
GameObject visualSource = ResolveGfxMountPoint(source);
GameObject visualTarget = ResolveGfxMountPoint(target);
// 如果映射失败,则退而求其次使用原始物体的 Transform
Transform startTransform = visualSource != null ? visualSource.transform : source.transform;
Transform endTransform = visualTarget != null ? visualTarget.transform : target.transform;
// 在源物体位置实例化
GameObject projectile = Instantiate(projectileFX, startTransform.position, Quaternion.identity);
// 如果是在 UI 环境下,设置正确的父物体(通常是 GfxController 所在的 Canvas 层级)
projectile.transform.SetParent(this.transform, true);
projectile.transform.localScale = Vector3.one * projectileScale;
// 应用透明度
ApplyTransparencyAndRender(projectile, projectileTransparency, projectileSortingOrder);
// 应用颜色
ApplyColorToEffect(projectile, projectileColor);
// 启动移动协程
Vector3 targetRandomOffset = Vector3.zero;
if (projectileRandomOffsetX > 0f || projectileRandomOffsetY > 0f || projectileRandomOffsetZ > 0f)
{
targetRandomOffset = new Vector3(
Random.Range(-projectileRandomOffsetX, projectileRandomOffsetX),
Random.Range(-projectileRandomOffsetY, projectileRandomOffsetY),
Random.Range(-projectileRandomOffsetZ, projectileRandomOffsetZ)
);
}
StartCoroutine(MoveProjectileCoroutine(projectile, startTransform, endTransform, targetRandomOffset, onComplete));
}
/// <summary>
/// 将逻辑战斗物体(Ally/Enemy)映射到 GfxController 中定义的 UI 挂载点
/// </summary>
private GameObject ResolveGfxMountPoint(GameObject logicObj)
{
if (logicObj == null) return null;
// 检查是否是敌人
if (logicObj.name == "thisEnemy" || logicObj.GetComponent<EnemyCombatant>() != null)
{
return enemyObject;
}
// 检查是否是盟友 (ally_01 到 ally_05)
var ally = logicObj.GetComponent<AllyCombatant>();
if (ally != null)
{
return GetAllyObject(ally.slotIndex);
}
// 如果名字符合模式也可以识别
string name = logicObj.name.ToLower();
if (name.Contains("ally_01")) return ally01Object;
if (name.Contains("ally_02")) return ally02Object;
if (name.Contains("ally_03")) return ally03Object;
if (name.Contains("ally_04")) return ally04Object;
if (name.Contains("ally_05")) return ally05Object;
return null;
}
private IEnumerator MoveProjectileCoroutine(GameObject projectile, Transform start, Transform end, Vector3 targetOffset, Action<Vector3> onComplete)
{
float elapsed = 0f;
Vector3 startPos = start.position;
Vector3 lastEndPos = startPos;
while (elapsed < projectileDuration)
{
if (projectile == null)
{
onComplete?.Invoke(lastEndPos);
yield break;
}
elapsed += Time.deltaTime;
float normalizedTime = Mathf.Clamp01(elapsed / projectileDuration);
// 使用动画曲线计算插值比例
float t = projectileSpeedCurve.Evaluate(normalizedTime);
// 如果目标还在,更新目标位置(支持动态移动的目标)
Vector3 currentEndPos = end != null ? end.position + targetOffset : projectile.transform.position;
lastEndPos = currentEndPos;
projectile.transform.position = Vector3.Lerp(startPos, currentEndPos, t);
// 可选:让拖尾朝向移动方向
if (t > 0)
{
Vector3 direction = currentEndPos - startPos;
if (direction != Vector3.zero)
{
projectile.transform.rotation = Quaternion.LookRotation(Vector3.forward, direction);
}
}
yield return null;
}
// 到达终点,触发回调并传回终点坐标
onComplete?.Invoke(lastEndPos);
// 到达后销毁
if (projectile != null)
{
// 如果有粒子系统,先停止发射让残余拖尾自然消失,或者直接销毁
ParticleSystem[] ps = projectile.GetComponentsInChildren<ParticleSystem>();
if (ps.Length > 0)
{
foreach (var p in ps) p.Stop(true, ParticleSystemStopBehavior.StopEmitting);
Destroy(projectile, 1.0f); // 给一点时间让残余粒子消失
}
else
{
Destroy(projectile);
}
}
}
/// <summary>
/// 为特效物体及其子物体中的 TrailRenderer 和 ParticleSystem 应用颜色渐变
/// </summary>
private void ApplyColorToEffect(GameObject effectObj, Gradient gradient)
{
if (effectObj == null || gradient == null) return;
// 检查 Gradient 是否有效(至少有一个 alpha key 或 color key 且不全是透明)
// 如果用户没有设置颜色,则保持原样
if (gradient.colorKeys.Length <= 1 && gradient.alphaKeys.Length <= 1 &&
gradient.colorKeys[0].color == Color.white && gradient.alphaKeys[0].alpha == 0)
{
// 简单的空检查,如果 Gradient 看起来是默认未设置状态则跳过
// 注意:Unity 默认 Gradient 通常是全白不透明,这里可以根据需要调整判断
}
// 应用于 TrailRenderer
var trails = effectObj.GetComponentsInChildren<TrailRenderer>(true);
foreach (var trail in trails)
{
trail.colorGradient = gradient;
}
// 应用于 ParticleSystem (主要影响起始颜色)
var particles = effectObj.GetComponentsInChildren<ParticleSystem>(true);
foreach (var ps in particles)
{
var main = ps.main;
main.startColor = new ParticleSystem.MinMaxGradient(gradient);
}
}
/// <summary>
/// 为特效物体及其子物体中的渲染器应用透明度和层级
/// </summary>
private void ApplyTransparencyAndRender(GameObject fx, float transparency, int sortingOrder)
{
// 1. 处理所有普通渲染器 (MeshRenderer, SpriteRenderer 等)
Renderer[] renderers = fx.GetComponentsInChildren<Renderer>();
foreach (var r in renderers)
{
// 设置排序层级
r.sortingOrder = sortingOrder;
// 处理材质透明度
if (transparency < 1f)
{
foreach (var mat in r.materials)
{
if (mat.HasProperty("_Color"))
{
Color c = mat.color;
c.a *= transparency;
mat.color = c;
}
else if (mat.HasProperty("_BaseColor")) // URP 命名
{
Color c = mat.GetColor("_BaseColor");
c.a *= transparency;
mat.SetColor("_BaseColor", c);
}
}
}
}
// 2. 处理粒子系统颜色 (考虑不同模式)
ParticleSystem[] particles = fx.GetComponentsInChildren<ParticleSystem>();
foreach (var ps in particles)
{
var main = ps.main;
// 处理 Start Color
var startColor = main.startColor;
if (startColor.mode == ParticleSystemGradientMode.Color)
{
Color c = startColor.color;
c.a *= transparency;
main.startColor = c;
}
else if (startColor.mode == ParticleSystemGradientMode.TwoColors)
{
Color c1 = startColor.colorMin;
Color c2 = startColor.colorMax;
c1.a *= transparency;
c2.a *= transparency;
main.startColor = new ParticleSystem.MinMaxGradient(c1, c2);
}
}
// 3. 处理 TrailRenderer (拖尾特效的核心组件)
TrailRenderer[] trails = fx.GetComponentsInChildren<TrailRenderer>();
foreach (var trail in trails)
{
if (transparency < 1f)
{
Gradient gradient = trail.colorGradient;
GradientAlphaKey[] alphaKeys = gradient.alphaKeys;
for (int i = 0; i < alphaKeys.Length; i++)
{
alphaKeys[i].alpha *= transparency;
}
gradient.SetKeys(gradient.colorKeys, alphaKeys);
trail.colorGradient = gradient;
}
}
}
public void PlayKOFX(GameObject target)
{
if (target == null || k_o_fx == null)
{
if (k_o_fx == null) Debug.LogWarning("[GfxController] k_o_fx Prefab 未分配!");
return;
}
GameObject fx;
// 优先使用指定的 KO 父物体
if (koFatherObject != null)
{
// 控制父物体的缩放
koFatherObject.transform.localScale = Vector3.one * koFxScale;
fx = Instantiate(k_o_fx, koFatherObject.transform);
RectTransform rt = fx.GetComponent<RectTransform>();
if (rt != null)
{
rt.anchoredPosition = Vector2.zero;
rt.localRotation = Quaternion.identity;
rt.localScale = Vector3.one; // 预设物体缩放回归 1,由父物体控制
}
else
{
fx.transform.localPosition = Vector3.zero;
fx.transform.localRotation = Quaternion.identity;
fx.transform.localScale = Vector3.one;
}
}
else
{
// 备份逻辑:映射到 UI 挂载点
GameObject visualParent = ResolveGfxMountPoint(target);
if (visualParent != null)
{
fx = Instantiate(k_o_fx, visualParent.transform);
fx.transform.SetAsLastSibling();
RectTransform rt = fx.GetComponent<RectTransform>();
if (rt != null)
{
rt.anchoredPosition = Vector2.zero;
rt.localRotation = Quaternion.identity;
rt.localScale = Vector3.one * koFxScale;
}
else
{
fx.transform.localPosition = Vector3.zero;
fx.transform.localRotation = Quaternion.identity;
fx.transform.localScale = Vector3.one * koFxScale;
}
}
else
{
fx = Instantiate(k_o_fx, target.transform.position, Quaternion.identity);
fx.transform.localScale = Vector3.one * koFxScale;
}
}
// 动画播放完后销毁
Destroy(fx, 3.0f);
}
/// <summary>
/// 当 Miss 发生时,从敌人向指定角色发射弹道,并在到达时触发受击效果及实际扣血
/// </summary>
/// <returns>是否成功启动弹道逻辑。如果返回 false,调用方应执行保底扣血。</returns>
public bool PlayEnemyAttackOnMiss(int allySlotIndex, float damageAmount, AllyCombatant allyInstance)
{
// 尝试自动寻找敌人挂载点,如果未分配
if (enemyObject == null || !enemyObject.activeInHierarchy)
{
var enemy = GameObject.Find("thisEnemy");
if (enemy != null) enemyObject = enemy;
}
if (enemyObject == null || !enemyObject.activeInHierarchy)
{
Debug.LogWarning("[GfxController] enemyObject 为空且未能在场景中找到 'thisEnemy',无法播放 Miss 攻击特效。");
return false;
}
GameObject targetAllyObj = GetAllyObject(allySlotIndex);
if (targetAllyObj == null)
{
Debug.LogWarning($"[GfxController] 找不到槽位 {allySlotIndex} 的盟友物体。");
return false;
}
GameObject projectilePrefab = enemyMissProjectilePrefab != null ? enemyMissProjectilePrefab : projectileFX;
if (projectilePrefab == null)
{
Debug.LogWarning("[GfxController] 未分配任何弹道 Prefab。");
return false;
}
// 生成弹道
GameObject projectile = Instantiate(projectilePrefab, enemyObject.transform.position, Quaternion.identity);
projectile.transform.SetParent(this.transform, true);
projectile.transform.localScale = Vector3.one * enemyAttackProjectileScale;
// 应用层级
ApplyTransparencyAndRender(projectile, projectileTransparency, enemyAttackProjectileSortingOrder);
// 应用颜色
ApplyColorToEffect(projectile, enemyAttackProjectileColor);
// 启动移动协程
StartCoroutine(MoveEnemyAttackProjectileCoroutine(projectile, enemyObject.transform, targetAllyObj.transform, allySlotIndex, damageAmount, allyInstance));
return true;
}
private IEnumerator MoveEnemyAttackProjectileCoroutine(GameObject projectile, Transform start, Transform end, int allySlotIndex, float damageAmount, AllyCombatant allyInstance)
{
float elapsed = 0f;
Vector3 startPos = start.position;
Vector3 lastEndPos = startPos;
while (elapsed < enemyAttackProjectileDuration)
{
if (projectile == null) yield break;
elapsed += Time.deltaTime;
float normalizedTime = Mathf.Clamp01(elapsed / enemyAttackProjectileDuration);
float t = enemyAttackSpeedCurve.Evaluate(normalizedTime);
Vector3 currentEndPos = end != null ? end.position : lastEndPos;
lastEndPos = currentEndPos;
projectile.transform.position = Vector3.Lerp(startPos, currentEndPos, t);
// 朝向目标
Vector3 direction = currentEndPos - projectile.transform.position;
if (direction != Vector3.zero)
{
projectile.transform.rotation = Quaternion.LookRotation(Vector3.forward, direction);
}
yield return null;
}
// 到达终点
if (projectile != null)
{
// 停止粒子系统
ParticleSystem[] ps = projectile.GetComponentsInChildren<ParticleSystem>();
foreach (var p in ps) { var m = p.main; m.loop = false; }
Destroy(projectile, 0.5f);
}
// 播放 Hit 特效、闪红,并执行实际扣血
GameObject targetAllyObj = GetAllyObject(allySlotIndex);
if (targetAllyObj != null)
{
// 1. 播放 Hit FX 并触发闪红(PlayHitFX 内部已处理闪红)
PlayHitFX(targetAllyObj, null, false, lastEndPos, enemyAttackHitFxScale);
// 2. 执行实际扣血逻辑 (使用传入的实例,更准确)
if (allyInstance != null)
{
allyInstance.ReceiveDamage(damageAmount, null);
}
else
{
// 备选方案:尝试从物体获取
var ally = targetAllyObj.GetComponent<AllyCombatant>();
if (ally != null) ally.ReceiveDamage(damageAmount, null);
}
}
}
/// <summary>
/// 根据槽位索引获取盟友特效挂载点
/// </summary>
public GameObject GetAllyObject(int slotIndex)
{
switch (slotIndex)
{
case 0: return ally01Object;
case 1: return ally02Object;
case 2: return ally03Object;
case 3: return ally04Object;
case 4: return ally05Object;
default: return null;
}
}
/// <summary>
/// 获取盟友/敌人的移动父物体(用于震动)
/// </summary>
public GameObject GetMoveObject(GameObject targetObj)
{
if (targetObj == null) return null;
// 1. 检查是否是敌人(逻辑物体或受击挂载点)
if (targetObj.name == "thisEnemy" ||
targetObj == enemyObject ||
targetObj.GetComponent<EnemyCombatant>() != null)
{
return enemyMoveObject;
}
// 2. 检查是否是盟友逻辑物体
var ally = targetObj.GetComponent<AllyCombatant>();
if (ally != null)
{
return GetAllyMoveObject(ally.slotIndex);
}
// 3. 检查是否是盟友受击挂载点 (ally01Object - ally05Object)
if (targetObj == ally01Object) return ally01MoveObject;
if (targetObj == ally02Object) return ally02MoveObject;
if (targetObj == ally03Object) return ally03MoveObject;
if (targetObj == ally04Object) return ally04MoveObject;
if (targetObj == ally05Object) return ally05MoveObject;
// 4. 通过名称模式兜底识别
string n = targetObj.name.ToLower();
if (n.Contains("ally_01") || n.Contains("ally01")) return ally01MoveObject;
if (n.Contains("ally_02") || n.Contains("ally02")) return ally02MoveObject;
if (n.Contains("ally_03") || n.Contains("ally03")) return ally03MoveObject;
if (n.Contains("ally_04") || n.Contains("ally04")) return ally04MoveObject;
if (n.Contains("ally_05") || n.Contains("ally05")) return ally05MoveObject;
return null;
}
private GameObject GetAllyMoveObject(int slotIndex)
{
switch (slotIndex)
{
case 0: return ally01MoveObject;
case 1: return ally02MoveObject;
case 2: return ally03MoveObject;
case 3: return ally04MoveObject;
case 4: return ally05MoveObject;
default: return null;
}
}
/// <summary>
/// 触发指定目标的震动效果
/// </summary>
private void TriggerShake(GameObject target, bool isEnemy)
{
GameObject moveObj = GetMoveObject(target);
if (moveObj == null) return;
SmoothShake ss = moveObj.GetComponent<SmoothShake>();
if (ss == null) ss = moveObj.AddComponent<SmoothShake>();
// 修复:确保 Shaker 实例已初始化,避免 NullReferenceException
if (ss.positionShake == null) ss.positionShake = new Shaker();
if (ss.rotationShake == null) ss.rotationShake = new Shaker();
// 选择震动参数
Vector3 amplitude = isEnemy ? hitShakeAmplitude : allyHitShakeAmplitude;
Vector3 frequency = isEnemy ? hitShakeFrequency : allyHitShakeFrequency;
float duration = isEnemy ? hitShakeDuration : allyHitShakeDuration;
// 配置震动参数
ss.positionShake.noiseType = Shaker.NoiseType.SineWave;
ss.positionShake.amplitude = amplitude;
ss.positionShake.frequency = frequency;
// 旋转震动重置为0,防止干扰
ss.rotationShake.amplitude = Vector3.zero;
// 配置时间参数
ss.timeSettings.constantShake = false;
ss.timeSettings.fadeInDuration = 0.05f;
ss.timeSettings.holdDuration = duration * 0.4f;
ss.timeSettings.fadeOutDuration = duration * 0.55f;
// 确保动画曲线有值(否则震动无法淡出)
if (ss.timeSettings.fadeInCurve == null || ss.timeSettings.fadeInCurve.length == 0)
ss.timeSettings.fadeInCurve = AnimationCurve.Linear(0, 0, 1, 1);
if (ss.timeSettings.fadeOutCurve == null || ss.timeSettings.fadeOutCurve.length == 0)
ss.timeSettings.fadeOutCurve = AnimationCurve.Linear(0, 1, 1, 0);
// 初始化内部数组(插件在 AddComponent 时 Awake 已经运行,由于当时字段为空,需要重新同步数组)
ss.shakers = new Shaker[] { ss.positionShake, ss.rotationShake };
ss.sum = new Vector3[2];
// 启动震动
ss.StartShake();
}
}