diff --git a/Assets/WL/Combat/Weapon/Resources/WL/Fx/WL_Fx_DaggerShurikenMSP.prefab b/Assets/WL/Combat/Weapon/Resources/WL/Fx/WL_Fx_DaggerShurikenMSP.prefab index 8e8e722db..638a93452 100644 --- a/Assets/WL/Combat/Weapon/Resources/WL/Fx/WL_Fx_DaggerShurikenMSP.prefab +++ b/Assets/WL/Combat/Weapon/Resources/WL/Fx/WL_Fx_DaggerShurikenMSP.prefab @@ -5071,7 +5071,7 @@ ParticleSystem: serializedVersion: 2 minMaxState: 0 minColor: {r: 1, g: 1, b: 1, a: 1} - maxColor: {r: 0.12616038, g: 1, b: 0, a: 0.53333336} + maxColor: {r: 0.55, g: 1, b: 0.15, a: 0.6} maxGradient: serializedVersion: 2 key0: {r: 1, g: 1, b: 1, a: 1} @@ -6215,16 +6215,16 @@ ParticleSystem: maxGradient: serializedVersion: 2 key0: {r: 1, g: 1, b: 1, a: 0.97933906} - key1: {r: 0.8301887, g: 0, b: 0.43195304, a: 1} - key2: {r: 0, g: 0, b: 0, a: 0} + key1: {r: 0.95, g: 1, b: 0.85, a: 1} + key2: {r: 0.9, g: 1, b: 0.8, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} key4: {r: 0, g: 0, b: 0, a: 0} key5: {r: 0, g: 0, b: 0, a: 0} key6: {r: 0, g: 0, b: 0, a: 0} key7: {r: 0, g: 0, b: 0, a: 0} ctime0: 0 - ctime1: 65535 - ctime2: 0 + ctime1: 39321 + ctime2: 65535 ctime3: 0 ctime4: 0 ctime5: 0 @@ -6239,8 +6239,8 @@ ParticleSystem: atime6: 0 atime7: 0 m_Mode: 0 - m_ColorSpace: 0 - m_NumColorKeys: 2 + m_ColorSpace: -1 + m_NumColorKeys: 3 m_NumAlphaKeys: 3 minGradient: serializedVersion: 2 diff --git a/Assets/WL/Combat/Weapon/Resources/WL/WLShurikenRider.json b/Assets/WL/Combat/Weapon/Resources/WL/WLShurikenRider.json index 72f3dfe2a..fb5359555 100644 --- a/Assets/WL/Combat/Weapon/Resources/WL/WLShurikenRider.json +++ b/Assets/WL/Combat/Weapon/Resources/WL/WLShurikenRider.json @@ -10,11 +10,27 @@ "prefab": "WL/Fx/WL_Fx_DaggerShurikenMSP", "every": 8, "maxRiders": 12, - "riderScale": 0.25, + "riderScale": 0.3, "rimFraction": 1.0, "maxSeconds": 2.5, "hidePath": 1, "minHeight": 0.6, + "mode": "spiral", + "spiralCount": 10, + "spiralArms": 2, + "spiralStartRadius": 0.6, + "spiralEndRadius": 4.0, + "spiralSeconds": 1.0, + "spiralDegPerSec": 540.0, + "spiralDir": 1.0, + "spiralHeight": 0.8, + "spiralRise": 0.2, + "spiralStagger": 0.07, + "spiralEase": 2.0, + "spiralFadeIn": 0.08, + "spiralFadeOut": 0.2, + "faceCamera": 1, + "note_980": "#980 PD 「지금 수리검은 어색하게 움직이다가 사라지는데 회전하는 수리검이 플레이어 주위를 나선형으로 휘몰아치며 퍼져나는 연출로 구성되길 바래」 + 「수리검의 이펙트 방향은 카메라 시점을 기준으로 가장 자연스럽게 보이도록 구성해줘」 — mode spiral(경로 입자 무관 · 시전자 둘레 10개 = 반대편 팔 2개 × 5 · 팔마다 0.07 s 차례(1팔 8개는 한쪽에 줄줄이 몰려 뱀처럼 보여 = Play 캡처) · 1.0 s 동안 540°/s 공전 · 반지름 0.6 → 4.0 m ease-out · 높이 0.8 → 1.0 m · 처음 0.08 s · 끝 0.2 s 크기 페이드) · faceCamera 1(수리검 면이 카메라를 봄 · 제 축 회전) · 크기 0.3 배 · 녹색 고리 수명 색 = 흰 → 연두 → 녹색(분홍 단계 없음 · 스크린샷 녹색 · 연두)(PM 값)", "note_979": "#979 PD 「비수 이펙트는 내가 요청한 이펙트가 아닌 것으로 확인되었어. \"Master Stylized Projectile\" 에셋의 수리검 형태의 이펙트를 요청했는데 엉뚱한 이펙트로 보여. 다시 수정하고, 지면에 남는 검은색 흔적은 보이지 않게 해줘 (공중을 부양하는 수리검 컨셉이야)」 + 「Master Stylized Projectile 에셋 방금 받아서 임포트 해두었어. 내가 지시한 수리검을 다시 체크해봐」 — 수리검 = Assets/MasterStylizedProjectiles/Projectiles/Shurikens/Prefabs/ShurikenBullet 사본 WL_Fx_DaggerShurikenMSP(검은 풍차형 수리검 · 흰 테두리 · 꼬리 메시 · 흰 반짝임 · AutoRotate 회전 · 979_fx.cs Copy2 · 팩이 재생하는 층 전부 재생 — PD 「해당 수리검의 이펙트는 끄지 말고 재생시켜 (내가 끄라고 한것은 궤적 이펙트를 말한거야.)」 · 루트 입자 층은 팩 원본에서 방출이 꺼진 템플릿 층(재질이 팩에 없음 · 켜면 흰 사각형)이라 원본대로 둠 · 빠진 재질 자리만 TemplateBulletHead) · 크기 0.25 배(PM 값) · 공중 부양 = 시전자 발 위 최소 0.6 m(minHeight · PM 값) · 지면 흔적 = Skill_BrutalSlash 의 Crater · Slash Cracks · Flying 렌더러 끔(프리팹 덮어쓰기)", "note_978": "#978 PD 「비수 기본 스킬에 요청한 이펙트가 전혀 보이지 않아. (내가 요청한 이펙트를 확인할 수 있도록 궤적 처럼 보이는 이펙트를 잠시 숨겨줘)」 — 경로 호(Multiple Slashes) 그리기만 끔(hidePath 1 · 입자는 돌아 수리검이 따라감 · 되살리기 = 0) · 수리검 0.3 → 0.6 배(PM 값)", "note": "#977 PD 「비수의 기본 스킬 시전 시 Master Stylized Projectile 에셋의 첨부 된 스크린샷과 같은 수리검을 경로 이펙트에 다수 보이게 연출해줘. (이때 이펙트 궤적의 색상을 기본 공격처럼 녹색 계열로 바꾸고, 투명도를 조금 부여해서 자연스럽게 구성해줘)」 — 수리검 = MasterStylizedFX Projectiles/GreenProjectile 사본(녹색 초승달 소용돌이 + 흰 별 = 스크린샷 · 진행 방향으로 돌림 · 처음 만든 ShurikensHit 층 사본은 녹색 베기 띠가 사각으로 보여 바꿈) · 경로 = 무자비한 일격 Multiple Slashes 호(시작색 (0.17,1.00,0.23) 투명도 0.75 · 기본 공격 녹색) · 호 입자 8개 중 1개에 수리검(최대 12) · 호 가장자리에 붙어 호와 함께 돈다 · 크기 0.3(PM 값)", diff --git a/Assets/WL/Combat/Weapon/WLShurikenRider.cs b/Assets/WL/Combat/Weapon/WLShurikenRider.cs index 252a79991..9f80017a6 100644 --- a/Assets/WL/Combat/Weapon/WLShurikenRider.cs +++ b/Assets/WL/Combat/Weapon/WLShurikenRider.cs @@ -6,6 +6,7 @@ // ■ 수리검 = JSON prefab(#979 = Resources/WL/Fx/WL_Fx_DaggerShurikenMSP = PD 지정 「Master Stylized Projectile」 Shurikens/ShurikenBullet 사본 · 검은 풍차형 수리검 · AutoRotate 회전 · 흰 반짝임) // ■ 원본 Skill_BrutalSlash.Co_Update 가 베기 이펙트를 켜는 줄 다음 훅 1줄(Begin) → 그 투사체의 경로 층(layer) 입자 중 every 번째(randomSeed)마다 // 수리검 1개를 붙여 매 LateUpdate 호의 가장자리(입자 위치 + 3D 회전 × 오른쪽 × 메시 반지름 × 크기 × rimFraction)로 — 호가 돌면 수리검이 호를 따라 돈다 · 입자가 사라지면 숨김 +// ■ #980 mode "spiral" = 시전자 둘레 나선(회전 · 휘몰아치며 퍼져 나감 · 경로 입자 무관) · faceCamera = 수리검 면이 카메라를 봄 // ■ 값 = Resources/WL/WLShurikenRider.json (C45) · 끄기 = enabled 0 // 🔴 Debug.LogError 금지(원본 팝업). // ───────────────────────────────────────────────────────────────────────────── @@ -29,6 +30,17 @@ namespace WL.Combat.Weapon public float maxSeconds = 2.5f; public int hidePath = 0; // #978 1 = 경로 층 그리기만 끔(입자는 그대로 돌아 수리검이 따라감 · 끝나면 되살림) — PD 「요청한 이펙트를 확인할 수 있도록 궤적처럼 보이는 이펙트를 잠시 숨겨줘」 public float minHeight = 0f; // #979 시전자 발 높이 위 최소 높이(m) — PD 「공중을 부양하는 수리검 컨셉」(호가 땅 가까이 돌아도 수리검은 이 높이 위로) + // #980 PD 「회전하는 수리검이 플레이어 주위를 나선형으로 휘몰아치며 퍼져나는 연출」 — mode "spiral" = 경로 입자와 무관하게 시전자 둘레 나선 + public string mode = "path"; + public int spiralCount = 8; // 수리검 수 + public int spiralArms = 1; // 나선 팔 수 — 팔마다 같은 자리에서 stagger 간격으로 차례로 나와 같은 나선을 따라감(팔끼리 360/팔 간격) + public float spiralStartRadius = 0.6f, spiralEndRadius = 4f; // 반지름(m) 시작 → 끝(ease-out) + public float spiralSeconds = 1f; // 한 수리검이 시작 → 끝까지(s) + public float spiralDegPerSec = 540f, spiralDir = 1f; // 공전 속도(°/s) · 방향(1 = 위에서 볼 때 반시계) + public float spiralHeight = 0.8f, spiralRise = 0.2f; // 발 위 높이(m) · 퍼지며 더 오르는 높이(m) + public float spiralStagger = 0.05f, spiralEase = 2f; // 다음 수리검까지(s) · 반지름 ease-out 지수 + public float spiralFadeIn = 0.08f, spiralFadeOut = 0.2f; // 처음 · 끝에 크기 0 ↔ 1(s) + public int faceCamera = 0; // #980 PD 「수리검 이펙트 방향은 카메라 시점 기준 가장 자연스럽게」 — 1 = 수리검 면이 카메라를 봄(제 축으로 회전) public string note = ""; } @@ -66,7 +78,8 @@ namespace WL.Combat.Weapon foreach (var ps in pb.GetComponentsInChildren(true)) if (ps.name == c.layer) { path = ps; break; } if (path == null) { LastLog = "경로 층 없음 " + c.layer; return; } var run = pb.GetComponent(); if (run == null) run = pb.gameObject.AddComponent(); - run.Begin(path, c, pd != null && !DSUtil.CheckNull(pd.shooter) ? pd.shooter.Get_position().y : pb.transform.position.y); Begun++; + bool has = pd != null && !DSUtil.CheckNull(pd.shooter); + run.Begin(path, c, has ? pd.shooter.Get_position().y : pb.transform.position.y, has ? pd.shooter.transform : pb.transform); Begun++; } catch (System.Exception ex) { LastLog = "실패 — " + ex.Message; } } @@ -76,16 +89,18 @@ namespace WL.Combat.Weapon { ParticleSystem _ps; WLShurikenRiderJson _c; ParticleSystem.Particle[] _buf = new ParticleSystem.Particle[256]; readonly List _riders = new List(); readonly Dictionary _bySeed = new Dictionary(); - float _t0, _rim = 0.5f, _ground; bool _on; + float _t0, _rim = 0.5f, _ground; bool _on; Transform _center; - public void Begin(ParticleSystem ps, WLShurikenRiderJson c, float ground) + public void Begin(ParticleSystem ps, WLShurikenRiderJson c, float ground, Transform center) { - _ps = ps; _c = c; _t0 = Time.time; _on = true; _bySeed.Clear(); _ground = ground; + _ps = ps; _c = c; _t0 = Time.time; _on = true; _bySeed.Clear(); _ground = ground; _center = center; var r = ps.GetComponent(); var m = r != null ? r.mesh : null; _pathRenderer = c.hidePath != 0 && r != null && r.enabled ? r : null; if (_pathRenderer != null) _pathRenderer.enabled = false; _rim = m != null ? Mathf.Max(m.bounds.extents.x, m.bounds.extents.y) : 0.5f; // 호 메시 반지름(입자 크기 1 기준) var p = Resources.Load(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); } + int need = c.mode == "spiral" ? Mathf.Max(c.maxRiders, c.spiralCount) : c.maxRiders; + while (p != null && _riders.Count < need) { var g = Instantiate(p); g.SetActive(false); _riders.Add(g); } + foreach (var g in _riders) if (g != null) { g.SetActive(false); g.transform.localScale = Vector3.one * c.riderScale; } if (p == null) WLShurikenRider.LastLog = "수리검 프리팹 없음 " + c.prefab; } @@ -93,6 +108,7 @@ namespace WL.Combat.Weapon { if (!_on) return; if (_ps == null || !_ps.gameObject.activeInHierarchy || Time.time - _t0 > _c.maxSeconds) { Stop(); return; } + if (_c.mode == "spiral") { Spiral(); return; } int n = _ps.GetParticles(_buf); var alive = new HashSet(); int shown = 0; for (int i = 0; i < n; i++) { @@ -105,7 +121,7 @@ namespace WL.Combat.Weapon if (_c.minHeight > 0f) pos.y = Mathf.Max(pos.y, _ground + _c.minHeight); // 공중 부양 WLShurikenRider.MinY = Mathf.Min(WLShurikenRider.MinY, pos.y - _ground); WLShurikenRider.MaxY = Mathf.Max(WLShurikenRider.MaxY, pos.y - _ground); var mv = pos - g.transform.position; // 투사체 이펙트라 진행 방향(앞 = +Z)으로 돌린다 — 꼬리가 지나온 쪽 - if (!fresh && mv.sqrMagnitude > 1e-6f) g.transform.rotation = Quaternion.LookRotation(mv); + if (_c.faceCamera != 0) g.transform.rotation = Face(); else if (!fresh && mv.sqrMagnitude > 1e-6f) g.transform.rotation = Quaternion.LookRotation(mv); g.transform.position = pos; shown++; } @@ -115,6 +131,40 @@ namespace WL.Combat.Weapon if (n == 0 && Time.time - _t0 > 0.3f) Stop(); } + // 나선 — 팔이 1 이면 i 번째 수리검이 i × stagger 뒤 각도 360/n × i 에서 · 팔이 여럿이면 팔마다 같은 각도(360/팔 × 팔)에서 차례로 나와 + // spiralSeconds 동안 공전하며 반지름 시작 → 끝(ease-out)으로 퍼지고 · 처음 · 끝은 크기로 페이드 + void Spiral() + { + float t = Time.time - _t0; int n = Mathf.Min(_c.spiralCount, _riders.Count); bool pending = false; int shown = 0; + var c0 = _center != null ? _center.position : transform.position; c0.y = _ground; var rot = _c.faceCamera != 0 ? Face() : Quaternion.identity; + for (int i = 0; i < n; i++) + { + int arms = Mathf.Max(1, _c.spiralArms), arm = i % arms, idx = i / arms; + var g = _riders[i]; if (g == null) continue; float ti = t - idx * _c.spiralStagger; + if (ti < 0f) { pending = true; continue; } + if (ti > _c.spiralSeconds) { if (g.activeSelf) g.SetActive(false); continue; } + pending = true; if (!g.activeSelf) g.SetActive(true); + float u = ti / Mathf.Max(0.01f, _c.spiralSeconds), e = 1f - Mathf.Pow(1f - u, Mathf.Max(1f, _c.spiralEase)); + float r = Mathf.Lerp(_c.spiralStartRadius, _c.spiralEndRadius, e), ang = ((arms > 1 ? 360f / arms * arm : 360f / Mathf.Max(1, n) * i) + _c.spiralDir * _c.spiralDegPerSec * ti) * Mathf.Deg2Rad; + var dir = new Vector3(Mathf.Cos(ang), 0f, Mathf.Sin(ang)); + var pos = c0 + dir * r + Vector3.up * (_c.spiralHeight + _c.spiralRise * e); + float k = Mathf.Min(1f, ti / Mathf.Max(0.01f, _c.spiralFadeIn)) * Mathf.Clamp01((_c.spiralSeconds - ti) / Mathf.Max(0.01f, _c.spiralFadeOut)); + g.transform.position = pos; g.transform.localScale = Vector3.one * (_c.riderScale * k); + g.transform.rotation = _c.faceCamera != 0 ? rot : Quaternion.LookRotation(Vector3.Cross(Vector3.up, dir) * _c.spiralDir); // 카메라를 보거나 · 공전 진행 방향 + WLShurikenRider.MinY = Mathf.Min(WLShurikenRider.MinY, pos.y - _ground); WLShurikenRider.MaxY = Mathf.Max(WLShurikenRider.MaxY, pos.y - _ground); + shown++; + } + if (shown > WLShurikenRider.MaxShown) WLShurikenRider.MaxShown = shown; + if (!pending) Stop(); + } + + // 수리검 면(사본 루트 +Y)을 카메라 쪽으로 · 앞(+Z) = 카메라 위쪽 — 위에서 내려다보는 카메라에서 납작한 원판이 정면으로 보이고 제 축(AutoRotate)으로 돈다 + static Quaternion Face() + { + var cam = Camera.main; if (cam == null) return Quaternion.identity; + return Quaternion.LookRotation(cam.transform.up, -cam.transform.forward); + } + int FreeSlot() { for (int i = 0; i < _riders.Count; i++) if (!_bySeed.ContainsValue(i)) return i; return -1; } ParticleSystemRenderer _pathRenderer;