119 lines
7.6 KiB
C#
119 lines
7.6 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLShurikenRider.cs — 비수 기본 스킬(무자비한 일격) 경로 이펙트(Multiple Slashes 호) 위에 수리검을 여러 개 태운다 (#977 · 2026-10-24)
|
||
//
|
||
// PD 원문: 「비수의 "기본 스킬" 시전 시 Master Stylized Projectile 에셋의 첨부 된 스크린샷과 같은 수리검을 경로 이펙트에 다수 보이게 연출해줘.
|
||
// (이때 이펙트 궤적의 색상을 기본 공격처럼 녹색 계열로 바꾸고, 투명도를 조금 부여해서 자연스럽게 구성해줘)」
|
||
// ■ 수리검 = Resources/WL/Fx/WL_Fx_DaggerShuriken(MasterStylizedFX ShurikensHit 사본 · 녹색 소용돌이 ShurikenSlash + 흰 별 CoreWhite 만 · 반복 · 회전)
|
||
// ■ 원본 Skill_BrutalSlash.Co_Update 가 베기 이펙트를 켜는 줄 다음 훅 1줄(Begin) → 그 투사체의 경로 층(layer) 입자 중 every 번째(randomSeed)마다
|
||
// 수리검 1개를 붙여 매 LateUpdate 호의 가장자리(입자 위치 + 3D 회전 × 오른쪽 × 메시 반지름 × 크기 × rimFraction)로 — 호가 돌면 수리검이 호를 따라 돈다 · 입자가 사라지면 숨김
|
||
// ■ 값 = Resources/WL/WLShurikenRider.json (C45) · 끄기 = enabled 0
|
||
// 🔴 Debug.LogError 금지(원본 팝업).
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace WL.Combat.Weapon
|
||
{
|
||
[System.Serializable]
|
||
public class WLShurikenRiderJson
|
||
{
|
||
public int enabled = 0;
|
||
public string[] skills = new string[0];
|
||
public int[] classes = new int[0];
|
||
public string layer = "Multiple Slashes";
|
||
public string prefab = "WL/Fx/WL_Fx_DaggerShuriken";
|
||
public int every = 8; // 경로 입자 randomSeed % every == 0 인 것에만
|
||
public int maxRiders = 12;
|
||
public float riderScale = 0.12f;
|
||
public float rimFraction = 1f; // 호 반지름 대비 자리(1 = 가장자리)
|
||
public float maxSeconds = 2.5f;
|
||
public string note = "";
|
||
}
|
||
|
||
public static class WLShurikenRider
|
||
{
|
||
public const string ResourcesPath = "WL/WLShurikenRider";
|
||
public static int Begun, MaxShown; public static string LastLog = "";
|
||
static WLShurikenRiderJson s_cfg;
|
||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void Reset() { s_cfg = null; }
|
||
|
||
internal static WLShurikenRiderJson Cfg
|
||
{
|
||
get
|
||
{
|
||
if (s_cfg != null) return s_cfg;
|
||
s_cfg = new WLShurikenRiderJson();
|
||
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; }
|
||
return s_cfg;
|
||
}
|
||
}
|
||
|
||
/// <summary>원본 스킬이 베기(경로) 이펙트를 켠 직후 훅 — 규칙에 맞으면 그 투사체에 수리검 러너를 붙인다.</summary>
|
||
public static void Begin(ProjectileBase pb)
|
||
{
|
||
try
|
||
{
|
||
var c = Cfg; if (c.enabled == 0 || pb == null) return;
|
||
if (c.skills != null && c.skills.Length > 0 && System.Array.IndexOf(c.skills, pb.GetType().Name) < 0) return;
|
||
var pd = pb.Get_ProjectTileData(); int cls = pd != null && !DSUtil.CheckNull(pd.shooter) ? pd.shooter.Get_ID() : 0;
|
||
if (c.classes != null && c.classes.Length > 0 && System.Array.IndexOf(c.classes, cls) < 0) return;
|
||
ParticleSystem path = null;
|
||
foreach (var ps in pb.GetComponentsInChildren<ParticleSystem>(true)) if (ps.name == c.layer) { path = ps; break; }
|
||
if (path == null) { LastLog = "경로 층 없음 " + c.layer; return; }
|
||
var run = pb.GetComponent<WLShurikenRiderRunner>(); if (run == null) run = pb.gameObject.AddComponent<WLShurikenRiderRunner>();
|
||
run.Begin(path, c); Begun++;
|
||
}
|
||
catch (System.Exception ex) { LastLog = "실패 — " + ex.Message; }
|
||
}
|
||
}
|
||
|
||
internal sealed class WLShurikenRiderRunner : MonoBehaviour
|
||
{
|
||
ParticleSystem _ps; WLShurikenRiderJson _c; ParticleSystem.Particle[] _buf = new ParticleSystem.Particle[256];
|
||
readonly List<GameObject> _riders = new List<GameObject>(); readonly Dictionary<uint, int> _bySeed = new Dictionary<uint, int>();
|
||
float _t0, _rim = 0.5f; bool _on;
|
||
|
||
public void Begin(ParticleSystem ps, WLShurikenRiderJson c)
|
||
{
|
||
_ps = ps; _c = c; _t0 = Time.time; _on = true; _bySeed.Clear();
|
||
var r = ps.GetComponent<ParticleSystemRenderer>(); var m = r != null ? r.mesh : null;
|
||
_rim = m != null ? Mathf.Max(m.bounds.extents.x, m.bounds.extents.y) : 0.5f; // 호 메시 반지름(입자 크기 1 기준)
|
||
var p = Resources.Load<GameObject>(c.prefab);
|
||
while (p != null && _riders.Count < c.maxRiders) { var g = Instantiate(p); g.SetActive(false); g.transform.localScale = Vector3.one * c.riderScale; _riders.Add(g); }
|
||
if (p == null) WLShurikenRider.LastLog = "수리검 프리팹 없음 " + c.prefab;
|
||
}
|
||
|
||
void LateUpdate()
|
||
{
|
||
if (!_on) return;
|
||
if (_ps == null || !_ps.gameObject.activeInHierarchy || Time.time - _t0 > _c.maxSeconds) { Stop(); return; }
|
||
int n = _ps.GetParticles(_buf); var alive = new HashSet<uint>(); int shown = 0;
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var p = _buf[i]; uint seed = p.randomSeed; if (_c.every > 1 && seed % (uint)_c.every != 0) continue;
|
||
alive.Add(seed); int idx;
|
||
if (!_bySeed.TryGetValue(seed, out idx)) { idx = FreeSlot(); if (idx < 0) continue; _bySeed[seed] = idx; }
|
||
var g = _riders[idx]; bool fresh = !g.activeSelf; if (fresh) g.SetActive(true);
|
||
var size = p.GetCurrentSize3D(_ps);
|
||
var pos = p.position + Quaternion.Euler(p.rotation3D) * (Vector3.right * _rim * size.x * _c.rimFraction);
|
||
var mv = pos - g.transform.position; // 투사체 이펙트라 진행 방향(앞 = +Z)으로 돌린다 — 꼬리가 지나온 쪽
|
||
if (!fresh && mv.sqrMagnitude > 1e-6f) g.transform.rotation = Quaternion.LookRotation(mv);
|
||
g.transform.position = pos;
|
||
shown++;
|
||
}
|
||
var gone = new List<uint>(); foreach (var kv in _bySeed) if (!alive.Contains(kv.Key)) gone.Add(kv.Key);
|
||
foreach (var s in gone) { _riders[_bySeed[s]].SetActive(false); _bySeed.Remove(s); }
|
||
if (shown > WLShurikenRider.MaxShown) WLShurikenRider.MaxShown = shown;
|
||
if (n == 0 && Time.time - _t0 > 0.3f) Stop();
|
||
}
|
||
|
||
int FreeSlot() { for (int i = 0; i < _riders.Count; i++) if (!_bySeed.ContainsValue(i)) return i; return -1; }
|
||
|
||
void Stop() { _on = false; _bySeed.Clear(); foreach (var g in _riders) if (g != null) g.SetActive(false); WLShurikenRider.LastLog = "끝 · 최대 동시 " + WLShurikenRider.MaxShown; }
|
||
void OnDisable() { Stop(); }
|
||
void OnDestroy() { foreach (var g in _riders) if (g != null) Destroy(g); }
|
||
}
|
||
}
|