393 lines
25 KiB
C#
393 lines
25 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLSkillVfx.cs — 스킬 이펙트 교체(휘두를 때 · 맞은 대상) — 표 기반
|
||
//
|
||
// PD 원문(#887 · 2026-10-17): 「방패 치기 모션일 때 이펙트를 교체하고 싶어. (Anime Stylized VFX URP 에셋의 Slash를
|
||
// 방패를 휘두를 때 발생시키고, 히트 된 대상에게 Aroow Shot Impact를 발생시켜줘)」
|
||
//
|
||
// ■ 원리 — 원본 SkillEvent 훅(CombatEvents.SkillFired = 스킬 투사체가 나오는 순간)에서 표의 스킬이면 swing 이펙트를 시전자 앞에,
|
||
// 타격 확정 훅(CombatEvents.HitConfirmed · 피해 정보 Beater_pd 의 스킬)에서 hit 이펙트를 맞은 대상 중심에 띄운다(시전자 → 대상 방향).
|
||
// 원본 스킬 프리팹의 기존 이펙트(hideChildren)는 그 투사체가 켜진 같은 프레임(타격 확정)에 끈다 — 원본 프리팹 무수정.
|
||
// ■ 이펙트 = Resources/WL/Fx/ 래퍼(자식 = 팩 프리팹 인스턴스) · 팩 파티클은 크기 모드가 Local 이라 scale ≠ 1 이면 Hierarchy 로 바꿔 키운다.
|
||
// ■ 값 = Resources/WL/WLSkillVfx.json (C45) · 끄기 = enabled 0(구독 0) · 원본 수정 0줄
|
||
// 🔴 Debug.LogError 금지(원본 ErrorLogHook 이 팝업을 띄운다) — 구독자 예외도 LastLog 로만 남긴다.
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using WL.Combat.Core;
|
||
|
||
namespace WL.Combat.Weapon
|
||
{
|
||
[System.Serializable]
|
||
public class WLSkillVfxFx
|
||
{
|
||
public string prefab = "";
|
||
public float[] offset = new float[3]; // 기준 회전 기준 (오른쪽, 위, 앞) m
|
||
public float[] euler = new float[3];
|
||
public float scale = 1f;
|
||
public float life = 1f;
|
||
public float fade = 0f; // #921 0 보다 크면 life 의 마지막 fade 초 동안 투명해지며 사라진다(방출 멈춤 + 입자 알파 → 0) — 0 = life 에 바로 지움
|
||
}
|
||
|
||
/// <summary>#921 PD 「플레이어 위치에 생성 된 이펙트가 갑자기 사라지지 않고 … 투명해지며 자연스럽게 소멸」 — hold 초 뒤 fade 초 동안 방출을 멈추고 입자 알파를 0 으로 · 끝나면 지운다(셰이더 무관 · 입자 색).</summary>
|
||
public sealed class WLFxFade : MonoBehaviour
|
||
{
|
||
float _start, _fade; bool _began; ParticleSystem[] _ps; ParticleSystem.Particle[] _buf = new ParticleSystem.Particle[64];
|
||
readonly Dictionary<ParticleSystem, float> _a0 = new Dictionary<ParticleSystem, float>();
|
||
// #934 PD 「(회오리가) 소멸할 때 뭉쳐진 회오리만 남아있어서 어색해 … 스킬 종료 타이밍에 맞게 자연스럽게 소멸」 — 가산(더하기) 재질은 알파만 낮추면 밝기가 남는다:
|
||
// dimAdditive 면 가산 재질 입자는 처음 색(입자별 · randomSeed) × 남은 비율로 색 · 알파를 함께 낮춘다(알파 블렌드 입자는 그대로 알파만)
|
||
bool _dimAdd; bool[] _add; readonly Dictionary<ParticleSystem, Dictionary<uint, Color32>> _c0 = new Dictionary<ParticleSystem, Dictionary<uint, Color32>>();
|
||
public static int DimmedSystems;
|
||
public static void Attach(GameObject go, float hold, float fade) { Attach(go, hold, fade, false); }
|
||
public static void Attach(GameObject go, float hold, float fade, bool dimAdditive)
|
||
{
|
||
var f = go.AddComponent<WLFxFade>(); f._start = Time.time + Mathf.Max(0f, hold); f._fade = Mathf.Max(0.05f, fade); f._dimAdd = dimAdditive;
|
||
}
|
||
static bool IsAdditive(ParticleSystem ps)
|
||
{
|
||
var r = ps.GetComponent<ParticleSystemRenderer>(); var m = r != null ? r.sharedMaterial : null; if (m == null) return false;
|
||
if (m.HasProperty("_DstBlend")) return Mathf.RoundToInt(m.GetFloat("_DstBlend")) == (int)UnityEngine.Rendering.BlendMode.One;
|
||
return m.shader != null && m.shader.name.IndexOf("Add", System.StringComparison.OrdinalIgnoreCase) >= 0;
|
||
}
|
||
void LateUpdate()
|
||
{
|
||
if (Time.time < _start) return;
|
||
if (!_began)
|
||
{
|
||
_began = true; _ps = GetComponentsInChildren<ParticleSystem>(true); _add = new bool[_ps.Length];
|
||
for (int j = 0; j < _ps.Length; j++) { _ps[j].Stop(false, ParticleSystemStopBehavior.StopEmitting); _add[j] = _dimAdd && IsAdditive(_ps[j]); if (_add[j]) DimmedSystems++; }
|
||
}
|
||
float k = Mathf.Clamp01((Time.time - _start) / _fade), keep = 1f - k;
|
||
for (int j = 0; j < _ps.Length; j++)
|
||
{
|
||
var ps = _ps[j];
|
||
if (ps == null) continue;
|
||
int n = ps.particleCount; if (n == 0) continue;
|
||
if (_buf.Length < n) _buf = new ParticleSystem.Particle[n];
|
||
n = ps.GetParticles(_buf, n);
|
||
if (_add[j])
|
||
{
|
||
Dictionary<uint, Color32> c0; if (!_c0.TryGetValue(ps, out c0)) { c0 = new Dictionary<uint, Color32>(); _c0[ps] = c0; }
|
||
for (int i = 0; i < n; i++) { Color32 o; uint key = _buf[i].randomSeed; if (!c0.TryGetValue(key, out o)) { o = _buf[i].startColor; c0[key] = o; } _buf[i].startColor = new Color32((byte)(o.r * keep), (byte)(o.g * keep), (byte)(o.b * keep), (byte)(o.a * keep)); }
|
||
}
|
||
else
|
||
for (int i = 0; i < n; i++) { var c = _buf[i].startColor; float a0; if (!_a0.TryGetValue(ps, out a0)) { a0 = c.a; _a0[ps] = a0; } c.a = (byte)Mathf.RoundToInt(Mathf.Min(c.a, a0 * keep)); _buf[i].startColor = c; }
|
||
ps.SetParticles(_buf, n);
|
||
}
|
||
if (k >= 1f) Destroy(gameObject);
|
||
}
|
||
}
|
||
|
||
/// <summary>#914 원본 스킬 프리팹 안 파티클 색 · 투명도 바꾸기(이름별) — 풀 인스턴스마다 1 회(표시 컴포넌트 WLSkillVfxTinted).</summary>
|
||
[System.Serializable]
|
||
public class WLSkillVfxTint
|
||
{
|
||
public string[] names = new string[0]; // 파티클 오브젝트 이름
|
||
public float[] rgb = new float[0]; // 시작색 RGB(비우면 그대로)
|
||
public float alpha = -1f; // 시작색 알파(-1 = 그대로)
|
||
public float alphaMul = 1f; // 시작색 알파 배율
|
||
public float[] colKeys = new float[0]; // 수명색 색 키 t,r,g,b…(비우면 그대로)
|
||
public float[] colAlpha = new float[0]; // 수명색 알파 키 t,a…(비우면 그대로)
|
||
}
|
||
|
||
[System.Serializable]
|
||
public class WLSkillVfxRule
|
||
{
|
||
public string skill = ""; // eEffect 이름 = 원본 스킬 클래스 이름
|
||
public string[] hideChildren = new string[0]; // 원본 스킬 프리팹에서 끌 자식 이름
|
||
public WLSkillVfxFx swing = new WLSkillVfxFx();
|
||
public WLSkillVfxFx hit = new WLSkillVfxFx();
|
||
public float effectScale = 1f; // #899 원본 스킬 프리팹의 보이는 이펙트(크기 노드) 배율 — 피해 판정(콜라이더)은 그대로
|
||
// #902 PD 「해머 점프 내려치기 — 내려친 위치에 스크린샷 이펙트 · 충격 지점 중심 8방향 충격파 · 맞은 적 1m 띄웠다 떨어뜨린 뒤 스턴 1.5초」
|
||
public float hitBoxScale = 1f; // 원본 판정 상자 가로·세로 배율(8방향 충격파까지 맞게 · 각성 성장분 유지)
|
||
public WLSkillVfxFx centerFx = new WLSkillVfxFx(); // 원본 Set 때 스킬 자리(내려친 위치)에 띄움
|
||
public WLSkillVfxFx radialFx = new WLSkillVfxFx(); // 스킬 자리 둘레 radialCount 방향(시전자 정면부터 등간격)
|
||
public int radialCount;
|
||
public float radialRadius = 1f;
|
||
// #904 PD 「충격 지점부터 빠르게 퍼지며 폭파(중간 범위 광역)」 — radialSteps ≥ 2 면 방향마다 radialStartRadius → radialRadius 사이를 radialSteps 번에 나눠
|
||
// radialStepDelay 초 간격으로 radialFx 를 잇달아 띄우고(퍼짐), 끝 자리에 radialEndFx 를 띄운다(폭발). 1 이하 = 끝 자리 한 번에(#902).
|
||
public int radialSteps;
|
||
public float radialStartRadius = 0.4f, radialStepDelay = 0.04f;
|
||
public WLSkillVfxFx radialEndFx = new WLSkillVfxFx();
|
||
public float launchHeight, launchSeconds = 0.5f, stunSeconds; // 이 스킬에 맞은 적: 띄우기 높이(m) · 걸리는 시간(초) · 내려온 뒤 기절(초)
|
||
public WLSkillVfxTint[] tint = new WLSkillVfxTint[0]; // #914 원본 스킬 프리팹 파티클 색 · 투명도(검은 갈라짐 · 연기 → 반투명)
|
||
// #917 원본 Set 때 바로 숨김(hideChildren 을 첫 타격까지 기다리지 않음) · 멈춰 서서 판정 상자를 켜는 스킬(Projectile_StopAndColliderControl)의 다단 히트 시각
|
||
public int hideOnSet;
|
||
public float multiHitDelay = -1f; // 첫 판정까지(초 · ColliderActiveDealyTime) — 음수 = 원본 그대로
|
||
public int multiHitCount; // 판정 횟수(Loop) — 0 = 원본 그대로
|
||
public float multiHitInterval = -1f; // 판정 간격(초 · DelayTime) — 음수 = 원본 그대로
|
||
public float lifeSeconds = -1f; // 화살 비(Skill_ArrowRain.lifetime) 유지 시간 — 음수 = 원본 그대로
|
||
public WLSkillVfxFx casterFx = new WLSkillVfxFx(); // 원본 Set 때 시전자 발밑(몸)에 띄움
|
||
}
|
||
|
||
[System.Serializable]
|
||
public class WLSkillVfxJson
|
||
{
|
||
public int enabled = 0;
|
||
public WLSkillVfxRule[] rules = new WLSkillVfxRule[0];
|
||
public string note = "";
|
||
}
|
||
|
||
public static class WLSkillVfx
|
||
{
|
||
public const string ResourcesPath = "WL/WLSkillVfx";
|
||
|
||
public static int Swings, Hits, Hidden, Errors;
|
||
public static string LastLog = "";
|
||
|
||
static WLSkillVfxJson s_cfg;
|
||
static readonly Dictionary<string, WLSkillVfxRule> s_rules = new Dictionary<string, WLSkillVfxRule>();
|
||
static readonly Dictionary<string, GameObject> s_prefabs = new Dictionary<string, GameObject>();
|
||
static readonly Dictionary<string, System.Type> s_types = new Dictionary<string, System.Type>();
|
||
|
||
static WLSkillVfxJson Cfg
|
||
{
|
||
get
|
||
{
|
||
if (s_cfg != null) return s_cfg;
|
||
s_cfg = new WLSkillVfxJson();
|
||
s_rules.Clear();
|
||
var ta = Resources.Load<TextAsset>(ResourcesPath);
|
||
if (ta == null) { LastLog = "설정 없음 " + ResourcesPath; return s_cfg; }
|
||
try { JsonUtility.FromJsonOverwrite(ta.text, s_cfg); } catch (System.Exception ex) { LastLog = "설정 파싱 실패 — " + ex.Message; }
|
||
if (s_cfg.rules != null) foreach (var r in s_cfg.rules) if (r != null && !string.IsNullOrEmpty(r.skill)) s_rules[r.skill] = r;
|
||
return s_cfg;
|
||
}
|
||
}
|
||
|
||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||
static void Boot()
|
||
{
|
||
s_cfg = null; s_prefabs.Clear();
|
||
if (Cfg.enabled == 0) { CombatEvents.SkillFired.Remove(OnSkillFired); CombatEvents.HitConfirmed.Remove(OnHitConfirmed); return; }
|
||
CombatEvents.SkillFired.Add(OnSkillFired);
|
||
CombatEvents.HitConfirmed.Add(OnHitConfirmed);
|
||
}
|
||
|
||
/// <summary>#899 PD 「강력한 일격 이펙트가 너무 과도 — 캐릭터 크기·비율을 고려해 이펙트 크기 조절」 — 원본 스킬 Set() 끝 훅 1줄이 부른다.
|
||
/// 표(skill = 클래스 이름)의 effectScale 을 보이는 크기 노드(sizeNode)에만 곱한다(원본이 매번 1 로 되돌린 뒤라 반복 시전에도 누적되지 않음). 꺼져 있으면(enabled 0) 아무것도 안 한다.</summary>
|
||
public static void ScaleOriginal(ProjectileBase skill, Transform sizeNode)
|
||
{
|
||
try
|
||
{
|
||
if (skill == null || Cfg.enabled == 0) return;
|
||
WLSkillVfxRule r;
|
||
if (!s_rules.TryGetValue(skill.GetType().Name, out r)) return;
|
||
if (sizeNode != null && r.effectScale > 0f && !Mathf.Approximately(r.effectScale, 1f)) { sizeNode.localScale *= r.effectScale; Scaled++; }
|
||
if (r.tint != null && r.tint.Length > 0 && skill.GetComponent<WLSkillVfxTinted>() == null) { Tinted += Tint(skill, r.tint); skill.gameObject.AddComponent<WLSkillVfxTinted>(); }
|
||
// #917 바로 숨김 · 다단 히트 시각 · 유지 시간 · 시전자 이펙트
|
||
if (r.hideOnSet != 0 && r.hideChildren != null)
|
||
foreach (var n in r.hideChildren) { var c = skill.transform.Find(n); if (c != null && c.gameObject.activeSelf) { c.gameObject.SetActive(false); Hidden++; } }
|
||
var sc = skill as Projectile_StopAndColliderControl;
|
||
if (sc != null)
|
||
{
|
||
if (r.multiHitDelay >= 0f) sc.ColliderActiveDealyTime = r.multiHitDelay;
|
||
if (r.multiHitCount > 0) { sc.Loop = r.multiHitCount; sc.LoopTime = 0f; }
|
||
if (r.multiHitInterval >= 0f) sc.DelayTime = r.multiHitInterval;
|
||
}
|
||
var rain = skill as Skill_ArrowRain;
|
||
if (rain != null && r.lifeSeconds > 0f) rain.lifetime = r.lifeSeconds;
|
||
var pdc = skill.Get_ProjectTileData(); var caster = pdc != null ? pdc.shooter : null;
|
||
if (r.casterFx != null && !string.IsNullOrEmpty(r.casterFx.prefab) && !DSUtil.CheckNull(caster))
|
||
{
|
||
var cf = caster.transform.forward; cf.y = 0f; if (cf.sqrMagnitude < 1e-4f) cf = Vector3.forward;
|
||
if (Spawn(r.casterFx, caster.Get_position(), Quaternion.LookRotation(cf.normalized))) Casters++;
|
||
}
|
||
// #902 판정 상자 · 내려친 위치 이펙트 · 8방향 충격파
|
||
if (r.hitBoxScale > 0f && !Mathf.Approximately(r.hitBoxScale, 1f))
|
||
{
|
||
var bc = skill.GetComponent<BoxCollider>();
|
||
if (bc != null)
|
||
{
|
||
// #917 풀 재사용 때 배율이 누적되지 않게 — 원본 크기를 기억(원본이 Set 에서 크기를 다시 정했으면 그 값이 새 기준)
|
||
var bs = skill.GetComponent<WLSkillVfxBase>();
|
||
if (bs == null) { bs = skill.gameObject.AddComponent<WLSkillVfxBase>(); bs.box = bc.size; bs.written = new Vector3(-1f, -1f, -1f); }
|
||
if (bc.size != bs.written) bs.box = bc.size;
|
||
bc.size = new Vector3(bs.box.x * r.hitBoxScale, bs.box.y, bs.box.z * r.hitBoxScale);
|
||
bs.written = bc.size;
|
||
}
|
||
}
|
||
var pd = skill.Get_ProjectTileData();
|
||
var shooter = pd != null ? pd.shooter : null;
|
||
Vector3 fwd = !DSUtil.CheckNull(shooter) ? shooter.transform.forward : skill.transform.forward; fwd.y = 0f;
|
||
if (fwd.sqrMagnitude < 1e-4f) fwd = Vector3.forward; fwd.Normalize();
|
||
Vector3 at = skill.transform.position;
|
||
if (Spawn(r.centerFx, at, Quaternion.LookRotation(fwd))) Centers++;
|
||
for (int k = 0; k < r.radialCount; k++)
|
||
{
|
||
var dir = Quaternion.Euler(0f, k * 360f / r.radialCount, 0f) * fwd;
|
||
var rot = Quaternion.LookRotation(dir);
|
||
if (r.radialSteps <= 1) { if (Spawn(r.radialFx, at + dir * r.radialRadius, rot)) Radials++; continue; }
|
||
for (int i = 0; i < r.radialSteps; i++) // #904 중심에서 바깥으로 퍼짐 → 끝에서 폭발
|
||
{
|
||
float rr = Mathf.Lerp(r.radialStartRadius, r.radialRadius, i / (float)(r.radialSteps - 1));
|
||
Later(i * r.radialStepDelay, r.radialFx, at + dir * rr, rot);
|
||
}
|
||
Later(r.radialSteps * r.radialStepDelay, string.IsNullOrEmpty(r.radialEndFx.prefab) ? r.radialFx : r.radialEndFx, at + dir * r.radialRadius, rot);
|
||
}
|
||
}
|
||
catch (System.Exception ex) { Errors++; LastLog = "크기 조절 실패 — " + ex.Message; }
|
||
}
|
||
public static int Scaled, Centers, Radials, Launched, Tinted, Casters;
|
||
|
||
/// <summary>#914 이름이 맞는 파티클의 시작색(RGB · 알파 · 알파 배율) · 수명색(색 키 · 알파 키)을 바꾼다 — 바꾼 파티클 수.</summary>
|
||
static int Tint(ProjectileBase skill, WLSkillVfxTint[] tints)
|
||
{
|
||
int n = 0;
|
||
foreach (var ps in skill.GetComponentsInChildren<ParticleSystem>(true))
|
||
foreach (var t in tints)
|
||
{
|
||
if (t == null || t.names == null || System.Array.IndexOf(t.names, ps.name) < 0) continue;
|
||
var m = ps.main; var c = m.startColor;
|
||
if (c.mode == ParticleSystemGradientMode.Color) c.color = TintColor(c.color, t);
|
||
else if (c.mode == ParticleSystemGradientMode.TwoColors) { c.colorMin = TintColor(c.colorMin, t); c.colorMax = TintColor(c.colorMax, t); }
|
||
m.startColor = c;
|
||
if ((t.colKeys != null && t.colKeys.Length >= 4) || (t.colAlpha != null && t.colAlpha.Length >= 2))
|
||
{
|
||
var col = ps.colorOverLifetime; col.enabled = true;
|
||
var old = col.color.mode == ParticleSystemGradientMode.Gradient ? col.color.gradient : new Gradient();
|
||
var ck = old.colorKeys; var ak = old.alphaKeys;
|
||
if (t.colKeys != null && t.colKeys.Length >= 4)
|
||
{
|
||
ck = new GradientColorKey[t.colKeys.Length / 4];
|
||
for (int i = 0; i < ck.Length; i++) ck[i] = new GradientColorKey(new Color(t.colKeys[i * 4 + 1], t.colKeys[i * 4 + 2], t.colKeys[i * 4 + 3]), t.colKeys[i * 4]);
|
||
}
|
||
if (t.colAlpha != null && t.colAlpha.Length >= 2)
|
||
{
|
||
ak = new GradientAlphaKey[t.colAlpha.Length / 2];
|
||
for (int i = 0; i < ak.Length; i++) ak[i] = new GradientAlphaKey(t.colAlpha[i * 2 + 1], t.colAlpha[i * 2]);
|
||
}
|
||
var g = new Gradient(); g.SetKeys(ck, ak); col.color = new ParticleSystem.MinMaxGradient(g);
|
||
}
|
||
n++;
|
||
break;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
static Color TintColor(Color k, WLSkillVfxTint t)
|
||
{
|
||
if (t.rgb != null && t.rgb.Length >= 3) { k.r = t.rgb[0]; k.g = t.rgb[1]; k.b = t.rgb[2]; }
|
||
if (t.alpha >= 0f) k.a = t.alpha;
|
||
k.a *= t.alphaMul;
|
||
return k;
|
||
}
|
||
|
||
static WLSkillVfxRule RuleOf(SkillListTableData s)
|
||
{
|
||
if (s == null) return null;
|
||
WLSkillVfxRule r;
|
||
return s_rules.TryGetValue(s.e_Skill.ToString(), out r) ? r : null;
|
||
}
|
||
|
||
static void OnSkillFired(in SkillFiredEvent e)
|
||
{
|
||
try
|
||
{
|
||
var r = RuleOf(e.skill);
|
||
if (r == null || DSUtil.CheckNull(e.actor)) return;
|
||
var t = e.actor.transform;
|
||
if (Spawn(r.swing, t.position, t.rotation)) Swings++;
|
||
}
|
||
catch (System.Exception ex) { Errors++; LastLog = "swing 실패 — " + ex.Message; }
|
||
}
|
||
|
||
static void OnHitConfirmed(in HitConfirmedEvent e)
|
||
{
|
||
try
|
||
{
|
||
var pd = e.projectile;
|
||
var r = pd != null ? RuleOf(pd.m_SkillListTableData) : null;
|
||
if (r == null || DSUtil.CheckNull(e.victim)) return;
|
||
HideOriginal(r, pd);
|
||
var at = e.victim.Get_Center_position();
|
||
var dir = !DSUtil.CheckNull(e.attacker) ? at - e.attacker.Get_Center_position() : e.victim.transform.forward;
|
||
dir.y = 0f;
|
||
var rot = dir.sqrMagnitude > 1e-4f ? Quaternion.LookRotation(dir) : Quaternion.identity;
|
||
if (Spawn(r.hit, at + WL.Combat.Impact.WLImpactFx.TowardCamera(e.victim), rot)) Hits++; // #941 몸 앞(카메라 쪽)
|
||
if ((r.launchHeight > 0f || r.stunSeconds > 0f) && WLLaunch.Apply(e.victim, r.launchHeight, r.launchSeconds, r.stunSeconds)) Launched++; // #902
|
||
}
|
||
catch (System.Exception ex) { Errors++; LastLog = "hit 실패 — " + ex.Message; }
|
||
}
|
||
|
||
/// <summary>그 스킬의 켜진 원본 투사체(이번 피해의 투사체 데이터)에서 hideChildren 자식을 끈다.</summary>
|
||
static void HideOriginal(WLSkillVfxRule r, ProjectileData pd)
|
||
{
|
||
if (r.hideChildren == null || r.hideChildren.Length == 0) return;
|
||
System.Type type;
|
||
if (!s_types.TryGetValue(r.skill, out type)) { type = System.Type.GetType(r.skill + ", Assembly-CSharp"); s_types[r.skill] = type; }
|
||
if (type == null) return;
|
||
foreach (var o in Object.FindObjectsByType(type, FindObjectsInactive.Exclude, FindObjectsSortMode.None))
|
||
{
|
||
var pb = o as ProjectileBase;
|
||
if (pb == null || pb.Get_ProjectTileData() != pd) continue;
|
||
foreach (var n in r.hideChildren)
|
||
{
|
||
var c = pb.transform.Find(n);
|
||
if (c != null && c.gameObject.activeSelf) { c.gameObject.SetActive(false); Hidden++; }
|
||
}
|
||
}
|
||
}
|
||
|
||
static bool Spawn(WLSkillVfxFx fx, Vector3 pos, Quaternion rot)
|
||
{
|
||
if (fx == null || string.IsNullOrEmpty(fx.prefab)) return false;
|
||
var p = Prefab(fx.prefab);
|
||
if (p == null) return false;
|
||
var go = Object.Instantiate(p, pos + rot * V(fx.offset), rot * Quaternion.Euler(V(fx.euler)));
|
||
if (!Mathf.Approximately(fx.scale, 1f))
|
||
{
|
||
foreach (var ps in go.GetComponentsInChildren<ParticleSystem>(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; }
|
||
go.transform.localScale = Vector3.one * fx.scale;
|
||
}
|
||
if (fx.fade > 0f) WLFxFade.Attach(go, Mathf.Max(0f, fx.life - fx.fade), fx.fade); // #921 투명해지며 소멸
|
||
else Object.Destroy(go, Mathf.Max(0.1f, fx.life));
|
||
LastLog = "생성 " + fx.prefab + " @" + pos.ToString("F1");
|
||
return true;
|
||
}
|
||
|
||
// ── #904 지연 생성(퍼지는 충격파) — 숨김 러너 1개가 시각이 된 것을 띄운다 ──
|
||
struct Pending { public float at; public WLSkillVfxFx fx; public Vector3 pos; public Quaternion rot; }
|
||
static readonly List<Pending> s_pending = new List<Pending>(64);
|
||
static void Later(float delay, WLSkillVfxFx fx, Vector3 pos, Quaternion rot)
|
||
{
|
||
if (fx == null || string.IsNullOrEmpty(fx.prefab)) return;
|
||
if (delay <= 0f) { if (Spawn(fx, pos, rot)) Radials++; return; }
|
||
s_pending.Add(new Pending { at = Time.time + delay, fx = fx, pos = pos, rot = rot });
|
||
if (s_runner == null) { var go = new GameObject("[WL904] SkillVfxLater"); go.hideFlags = HideFlags.HideAndDontSave; Object.DontDestroyOnLoad(go); s_runner = go.AddComponent<WLSkillVfxLater>(); }
|
||
}
|
||
static WLSkillVfxLater s_runner;
|
||
internal static void TickLater()
|
||
{
|
||
float now = Time.time;
|
||
for (int i = s_pending.Count - 1; i >= 0; i--)
|
||
{
|
||
var p = s_pending[i];
|
||
if (now < p.at) continue;
|
||
s_pending.RemoveAt(i);
|
||
try { if (Spawn(p.fx, p.pos, p.rot)) Radials++; } catch (System.Exception ex) { Errors++; LastLog = "지연 생성 실패 — " + ex.Message; }
|
||
}
|
||
}
|
||
|
||
static GameObject Prefab(string path)
|
||
{
|
||
GameObject p;
|
||
if (s_prefabs.TryGetValue(path, out p)) return p;
|
||
p = Resources.Load<GameObject>(path);
|
||
if (p == null) LastLog = "이펙트 없음 " + path;
|
||
s_prefabs[path] = p;
|
||
return p;
|
||
}
|
||
|
||
static Vector3 V(float[] a) { return a != null && a.Length >= 3 ? new Vector3(a[0], a[1], a[2]) : Vector3.zero; }
|
||
}
|
||
|
||
/// <summary>#904 지연 생성 러너(숨김 GameObject 1개).</summary>
|
||
public sealed class WLSkillVfxLater : MonoBehaviour { void Update() { WLSkillVfx.TickLater(); } }
|
||
|
||
/// <summary>#914 이 원본 스킬 인스턴스(풀)는 tint 를 이미 적용함 — 다시 쓰여도 알파 배율이 누적되지 않게.</summary>
|
||
public sealed class WLSkillVfxTinted : MonoBehaviour { }
|
||
|
||
/// <summary>#917 이 원본 스킬 인스턴스(풀)의 판정 상자 원래 크기 · 마지막으로 쓴 크기(hitBoxScale 누적 방지).</summary>
|
||
public sealed class WLSkillVfxBase : MonoBehaviour { public Vector3 box, written; }
|
||
}
|