using UnityEngine;
using UnityEngine.UI;
using System;
using System.Collections;
using SmoothShakeFree;
using Random = UnityEngine.Random;
///
/// 控制游戏过程中的打击特效及其他图形效果
///
public class GfxController : MonoBehaviour
{
public static GfxController Instance { get; private set; }
// 复用的属性块:命中特效透明度覆写用,避免每次命中访问 r.materials 克隆材质。
private MaterialPropertyBlock _fxPropertyBlock;
[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;
public float koFxScale = 1f;
public int koFxSortingOrder = 31;
[Header("Explosion Effect Settings")]
[Tooltip("当单次伤害超过敌人最大生命值的百分比时触发爆炸 (0.2 = 20%)")]
public float explosionDamageThresholdRatio = 0.2f;
[Tooltip("爆炸特效是否使用独立的缩放设置 (否则跟随Hit特效缩放)")]
public bool useExplosionSeparateScale = false;
public float explosionFxScale = 1.5f;
[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("Ally Attack Projectile")]
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;
[Tooltip("弹道到达终点时的随机偏移范围 - X轴")]
public float projectileRandomOffsetX = 0f;
[Tooltip("弹道到达终点时的随机偏移范围 - Y轴")]
public float projectileRandomOffsetY = 0f;
[Tooltip("弹道到达终点时的随机偏移范围 - Z轴")]
public float projectileRandomOffsetZ = 0f;
public int projectileSortingOrder = 10;
[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);
[Header("Ally Skill Projectile")]
[Tooltip("友军技能释放的弹道特效")]
public GameObject allySkillProjectilePrefab;
[Tooltip("友军技能弹道颜色控制")]
public Gradient allySkillProjectileColor;
[Tooltip("友军技能弹道移动时间")]
public float allySkillProjectileDuration = 0.5f;
[Tooltip("友军技能弹道缩放")]
public float allySkillProjectileScale = 1.0f;
[Tooltip("友军技能弹道层级")]
public int allySkillProjectileSortingOrder = 15;
[Tooltip("友军技能弹道速度曲线")]
public AnimationCurve allySkillSpeedCurve = AnimationCurve.Linear(0, 0, 1, 1);
[Tooltip("友军技能弹道到达时的随机偏移范围 - X轴")]
public float allySkillRandomOffsetX = 0f;
[Tooltip("友军技能弹道到达时的随机偏移范围 - Y轴")]
public float allySkillRandomOffsetY = 0f;
[Tooltip("友军技能弹道到达时的随机偏移范围 - Z轴")]
public float allySkillRandomOffsetZ = 0f;
[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);
}
}
///
/// 在指定目标位置播放受击特效
///
/// 受击者物体
/// 父物体(特效将生成在该物体下)
/// 是否为敌人受击
/// 可选:自定义世界坐标播放(如匹配弹道终点)
/// 可选:强制指定特效缩放倍率
/// 可选:传入伤害信息以判断是否触发爆炸特效 (damage, maxHealth)
public void PlayHitFX(GameObject target, GameObject parent = null, bool isEnemy = false, Vector3? customWorldPos = null, float? scaleOverride = null, (float damage, float maxHealth)? damageInfo = null)
{
// --- Check if the target ally slot is active before playing VFX ---
if (!isEnemy && teamUIController.Instance != null)
{
int slot = -1;
var ally = target.GetComponent();
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)
{
bool isActive = false;
switch (slot)
{
case 0: isActive = teamUIController.Instance.isAlly01_active; break;
case 1: isActive = teamUIController.Instance.isAlly02_active; break;
case 2: isActive = teamUIController.Instance.isAlly03_active; break;
case 3: isActive = teamUIController.Instance.isAlly04_active; break;
case 4: isActive = teamUIController.Instance.isAlly05_active; break;
}
if (!isActive) return; // Skip VFX for inactive ally
}
}
// Determine which FX prefab to use (Hit or Explosion)
GameObject fxPrefab = hitFX;
bool isExplosion = false;
if (isEnemy && damageInfo.HasValue && explosionFX != null)
{
float ratio = damageInfo.Value.damage / damageInfo.Value.maxHealth;
if (ratio >= explosionDamageThresholdRatio)
{
fxPrefab = explosionFX;
isExplosion = true;
}
}
if (target == null || fxPrefab == null)
{
if (fxPrefab == null) Debug.LogWarning($"[GfxController] {(isExplosion ? "ExplosionFX" : "HitFX")} Prefab 未在 Inspector 中分配!");
return;
}
// 触发受击震动效果
TriggerShake(target, isEnemy);
GameObject visualParent = null;
if (isEnemy && enemyObject != null)
{
visualParent = enemyObject;
}
else if (!isEnemy)
{
visualParent = ResolveGfxMountPoint(target);
}
if (visualParent == null)
{
visualParent = parent != null ? parent : ResolveGfxMountPoint(target);
}
// 如果是敌人,触发闪红效果 (使用 teamUIController 已有的逻辑)
if (isEnemy && teamUIController.Instance != null)
{
teamUIController.Instance.TriggerEnemyHurtFlash();
}
else if (!isEnemy && teamUIController.Instance != null)
{
// 尝试从物体名称或组件中识别槽位并触发闪红
int slot = -1;
var ally = target.GetComponent();
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);
}
}
// 根据身份选择参数
float offsetX = isEnemy ? enemyOffsetX : allyOffsetX;
float offsetY = isEnemy ? enemyOffsetY : allyOffsetY;
// 计算缩放逻辑
float fxScale;
if (scaleOverride.HasValue)
{
fxScale = scaleOverride.Value;
}
else if (isEnemy)
{
if (isExplosion && useExplosionSeparateScale)
{
fxScale = explosionFxScale;
}
else
{
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(fxPrefab, visualParent.transform);
// 确保特效在 UI 层级中显示在最前方
fx.transform.SetAsLastSibling();
// 检查是否是 UI 元素 (RectTransform)
RectTransform rt = fx.GetComponent();
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 = fxPrefab.GetComponent().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(fxPrefab, spawnPos, Quaternion.identity);
fx.transform.localScale = Vector3.one * fxScale;
}
// 针对粒子系统的特殊处理
ParticleSystem[] allParticles = fx.GetComponentsInChildren();
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);
}
}
///
/// 从发射源向目标发射一个拖尾特效
///
/// 发射源逻辑物体
/// 目标逻辑物体
/// 到达终点后的回调(可选,返回弹道终点的世界坐标)
public void PlayProjectile(GameObject source, GameObject target, Action 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);
// --- 核心修正:挂载到 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));
}
///
/// 触发友军技能弹道:从施法者飞向目标友军
///
public void PlayAllySkillProjectile(GameObject source, GameObject target, Action onComplete = null)
{
if (source == null || target == null) return;
// --- Check if the source or target ally slot is active before playing VFX ---
if (teamUIController.Instance != null)
{
// Check source
var sourceAlly = source.GetComponent();
if (sourceAlly != null)
{
bool isActive = false;
switch (sourceAlly.slotIndex)
{
case 0: isActive = teamUIController.Instance.isAlly01_active; break;
case 1: isActive = teamUIController.Instance.isAlly02_active; break;
case 2: isActive = teamUIController.Instance.isAlly03_active; break;
case 3: isActive = teamUIController.Instance.isAlly04_active; break;
case 4: isActive = teamUIController.Instance.isAlly05_active; break;
}
if (!isActive) { onComplete?.Invoke(target.transform.position); return; }
}
// Check target
var targetAlly = target.GetComponent();
if (targetAlly != null)
{
bool isActive = false;
switch (targetAlly.slotIndex)
{
case 0: isActive = teamUIController.Instance.isAlly01_active; break;
case 1: isActive = teamUIController.Instance.isAlly02_active; break;
case 2: isActive = teamUIController.Instance.isAlly03_active; break;
case 3: isActive = teamUIController.Instance.isAlly04_active; break;
case 4: isActive = teamUIController.Instance.isAlly05_active; break;
}
if (!isActive) { onComplete?.Invoke(target.transform.position); return; }
}
}
GameObject prefab = allySkillProjectilePrefab != null ? allySkillProjectilePrefab : projectileFX;
if (prefab == null)
{
Debug.LogWarning("[GfxController] allySkillProjectilePrefab 未分配且无默认 projectileFX!");
onComplete?.Invoke(target.transform.position);
return;
}
// 核心修正:将逻辑物体映射到 UI 挂载点
GameObject visualSource = ResolveGfxMountPoint(source);
GameObject visualTarget = ResolveGfxMountPoint(target);
Transform startTransform = visualSource != null ? visualSource.transform : source.transform;
Transform endTransform = visualTarget != null ? visualTarget.transform : target.transform;
GameObject projectile = Instantiate(prefab, startTransform.position, Quaternion.identity);
projectile.transform.SetParent(this.transform, true);
projectile.transform.localScale = Vector3.one * allySkillProjectileScale;
// 应用层级(复用 projectileTransparency)
ApplyTransparencyAndRender(projectile, projectileTransparency, allySkillProjectileSortingOrder);
// 应用颜色
ApplyColorToEffect(projectile, allySkillProjectileColor);
// 计算随机偏移
Vector3 targetRandomOffset = Vector3.zero;
if (allySkillRandomOffsetX > 0f || allySkillRandomOffsetY > 0f || allySkillRandomOffsetZ > 0f)
{
targetRandomOffset = new Vector3(
UnityEngine.Random.Range(-allySkillRandomOffsetX, allySkillRandomOffsetX),
UnityEngine.Random.Range(-allySkillRandomOffsetY, allySkillRandomOffsetY),
UnityEngine.Random.Range(-allySkillRandomOffsetZ, allySkillRandomOffsetZ)
);
}
StartCoroutine(MoveAllySkillProjectileCoroutine(projectile, startTransform, endTransform, targetRandomOffset, onComplete));
}
///
/// 将逻辑战斗物体(Ally/Enemy)映射到 GfxController 中定义的 UI 挂载点
///
private GameObject ResolveGfxMountPoint(GameObject logicObj)
{
if (logicObj == null) return null;
// 检查是否是敌人
if (logicObj.name == "thisEnemy" || logicObj.GetComponent() != null)
{
return enemyObject;
}
// 检查是否是盟友 (ally_01 到 ally_05)
var ally = logicObj.GetComponent();
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 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();
if (ps.Length > 0)
{
foreach (var p in ps) p.Stop(true, ParticleSystemStopBehavior.StopEmitting);
Destroy(projectile, 1.0f); // 给一点时间让残余粒子消失
}
else
{
Destroy(projectile);
}
}
}
private IEnumerator MoveAllySkillProjectileCoroutine(GameObject projectile, Transform start, Transform end, Vector3 targetOffset, Action onComplete)
{
float elapsed = 0f;
Vector3 startPos = start.position;
Vector3 lastEndPos = startPos;
while (elapsed < allySkillProjectileDuration)
{
if (projectile == null)
{
onComplete?.Invoke(lastEndPos);
yield break;
}
elapsed += Time.deltaTime;
float normalizedTime = Mathf.Clamp01(elapsed / allySkillProjectileDuration);
// 使用动画曲线计算插值比例
float t = allySkillSpeedCurve.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();
if (ps.Length > 0)
{
foreach (var p in ps) p.Stop(true, ParticleSystemStopBehavior.StopEmitting);
Destroy(projectile, 1.0f); // 给一点时间让残余粒子消失
}
else
{
Destroy(projectile);
}
}
}
///
/// 为特效物体及其子物体中的 TrailRenderer 和 ParticleSystem 应用颜色渐变
///
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(true);
foreach (var trail in trails)
{
trail.colorGradient = gradient;
}
// 应用于 ParticleSystem (主要影响起始颜色)
var particles = effectObj.GetComponentsInChildren(true);
foreach (var ps in particles)
{
var main = ps.main;
main.startColor = new ParticleSystem.MinMaxGradient(gradient);
}
}
///
/// 为特效物体及其子物体中的渲染器应用透明度和层级
///
private void ApplyTransparencyAndRender(GameObject fx, float transparency, int sortingOrder)
{
// 1. 处理所有普通渲染器 (MeshRenderer, SpriteRenderer 等)
Renderer[] renderers = fx.GetComponentsInChildren();
foreach (var r in renderers)
{
// 设置排序层级
r.sortingOrder = sortingOrder;
// 处理材质透明度:用 MaterialPropertyBlock 覆写 alpha,避免访问 r.materials
// 触发材质实例化(每次命中都会克隆并泄漏材质,产生 GC)。视觉结果等价。
if (transparency < 1f)
{
var sharedMat = r.sharedMaterial;
if (sharedMat != null)
{
if (_fxPropertyBlock == null) _fxPropertyBlock = new MaterialPropertyBlock();
r.GetPropertyBlock(_fxPropertyBlock);
if (sharedMat.HasProperty("_Color"))
{
Color c = sharedMat.color;
c.a *= transparency;
_fxPropertyBlock.SetColor("_Color", c);
}
else if (sharedMat.HasProperty("_BaseColor")) // URP 命名
{
Color c = sharedMat.GetColor("_BaseColor");
c.a *= transparency;
_fxPropertyBlock.SetColor("_BaseColor", c);
}
r.SetPropertyBlock(_fxPropertyBlock);
}
}
}
// 2. 处理粒子系统颜色 (考虑不同模式)
ParticleSystem[] particles = fx.GetComponentsInChildren();
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();
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();
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();
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;
}
}
// 应用层级 Order In Layer
Renderer[] renderers = fx.GetComponentsInChildren(true);
foreach (var r in renderers)
{
r.sortingOrder = koFxSortingOrder;
}
Canvas[] canvases = fx.GetComponentsInChildren