Project_WL/Assets/WL/Combat/Weapon/WLStaffLaser.cs

207 lines
15 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// WLStaffLaser.cs — 현자의 지팡이(10302) 기본 스킬: 전방 직선 레이저(화염 광선) 3 초 유지 · 0.5 초 간격 다단 피해 · 맞을 때마다 0.2 m 밀침 (#986 · 2026-10-26)
//
// PD 원문: 「지팡이 기본 스킬 시전 시 마법 시전 연출 후 지팡이 기본 공격 3타와 동일하게 손을 내지르는 모션과 함께 "RPG Game VFX Collection(URP)" 에셋의
// FX_Dragonfire 이펙트를 사용하게 해줘. 이 스킬은 전방 직선 방향으로 레이저 형태의 마법을 발사해 0.5초간격으로 다단히트 피해를 주며, 0.2m씩 적을
// 밀쳐낼 수 있으며 3초 동안 유지되고, 캐릭터가 방향을 변경할 경우 변경 된 방향으로 지속시간 동안 레이져 공격을 할 수 있어. (단, 구르기를 시도할 경우 마법이 즉시 취소)」
// ■ 그릇 = 원본 Skill_Blizzard(703304 · 시전 skill3 1 s · 지팡이 표 칸 3) — 원본 Set() 훅 1줄(Take)이 원본 눈보라 진행을 건너뛰고 넘겨받는다
// ■ 진행: 이동 막음 + 스킬 상태 → 다음 프레임 3타 자세(holdState · holdNormalized)로 애니메이터 멈춤(다른 상태로 바뀌면 다시 세움) → 손 앞(fxOffset)에 레이저(fx · 반복)를
// 시전자 정면으로 매 프레임 붙임 → interval 마다 정면 직선(length × width + 몸 반지름) 안 적에게 원본 투사체 피해(ProcessCollider) + 레이저 방향으로 push m 밀침 → seconds 뒤 끝
// ■ 방향 전환: 레이저 중엔 원본 CanMove() 가 막혀 조이스틱 이동(Run_byJoystick)이 안 불린다 → 러너가 매 프레임 가상패드 입력(섬 SimpleInput 축 · 테스트 씬 원본 FloatingJoystick · 편집기 방향키)을
// 카메라 정면 기준 방향으로 바꿔 몸만 turnDegPerSec 로 돌린다(이동 없음 · 레이저가 따라감)
// ■ 구르기 = 즉시 취소(시전자 상태가 Dodge 가 되면 레이저를 끄고 애니메이터 · 이동을 되돌린다)
// ■ 값 = Resources/WL/WLStaffLaser.json (C45) · enabled 0 = 원본 눈보라 그대로
// 🔴 Debug.LogError 금지(원본 팝업).
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace WL.Combat.Weapon
{
[System.Serializable]
public class WLStaffLaserJson
{
public int enabled = 0;
public string skill = "Skill_Blizzard"; // 넘겨받을 원본 스킬 클래스 이름
public int classId = 10302; // 시전자 직업(0 = 모두)
public string fx = "WL/Fx/WL_Fx_StaffLaser"; // 레이저 이펙트(Resources · 원본 빔 축 = 로컬 -Y)
public float fxScale = 0.24f;
public float[] fxOffset = { 0f, 0.55f, 0.45f }; // 시전자 정면 기준 (오른쪽, 위, 앞) m — 내민 손 앞
public float[] fxEuler = { -90f, 0f, 0f }; // 시전자 정면 기준 회전(빔 -Y → 정면 +Z)
public float seconds = 3f, interval = 0.5f; // 유지 · 다단 간격(s) — 첫 피해는 레이저가 켜지는 순간
public float length = 8f, width = 1.2f; // 피해 직선(m · 시전자 발에서 정면 · 폭 = 전체 · 적 몸 반지름 더함)
public float push = 0.2f; // 맞을 때마다 레이저 방향 밀침(m)
public string holdState = "attack3"; // 손 내지른 자세(애니메이터 상태 · 지팡이 3타)
public float holdNormalized = 0.5f;
public float turnDegPerSec = 360f; // 가상패드 방향으로 도는 속도
public float steerDeadzone = 0.2f; // 가상패드 축 크기가 이보다 작으면 돌지 않음
public float fadeSeconds = 0.4f; // 끝 · 취소 때 방출을 멈추고 지우기까지
public int noExtra = 0; // #994 1 = 원본 스킬 부가 효과(눈보라 = 감속 디버프) 없이 피해만(WLStaffEarth noExtra 와 같은 방식)
public string note = "";
}
public static class WLStaffLaser
{
public const string ResourcesPath = "WL/WLStaffLaser";
public static int Takes, Ticks, Hits, Pushes, Cancels, Turns, Reasserts, Ends, NoExtra; public static string LastLog = "";
static WLStaffLaserJson s_cfg; static WLStaffLaserRunner s_runner;
internal static PCActor Active; internal static WLStaffLaserJson ActiveCfg;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void Reset() { s_cfg = null; Active = null; ActiveCfg = null; }
internal static WLStaffLaserJson Cfg
{
get
{
if (s_cfg != null) return s_cfg;
s_cfg = new WLStaffLaserJson();
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>원본 Skill_Blizzard.Set 훅 — 규칙에 맞으면 레이저로 넘겨받는다(true = 원본 진행 건너뜀).</summary>
public static bool Take(ProjectileBase pb)
{
try
{
var c = Cfg; if (c.enabled == 0 || pb == null || pb.GetType().Name != c.skill) return false;
var pd = pb.Get_ProjectTileData(); var pc = pd != null ? pd.shooter as PCActor : null; if (pc == null) return false;
if (c.classId > 0 && pc.Get_ID() != c.classId) return false;
if (s_runner == null) { var go = new GameObject("[WL986] StaffLaser"); go.hideFlags = HideFlags.HideInHierarchy; Object.DontDestroyOnLoad(go); s_runner = go.AddComponent<WLStaffLaserRunner>(); }
s_runner.Begin(pb, pd, pc, c); Takes++;
return true;
}
catch (System.Exception ex) { LastLog = "넘겨받기 실패 — " + ex.Message; return false; }
}
/// <summary>레이저 중 그 방향으로 몸만 돌린다(러너가 가상패드 축을 읽어 부름).</summary>
public static bool Steer(PCActor pc, Vector3 dir)
{
if (Active == null || !ReferenceEquals(pc, Active) || ActiveCfg == null) return false;
dir.y = 0f; if (dir.sqrMagnitude < 1e-4f) return true;
var want = Quaternion.LookRotation(dir.normalized);
pc.transform.rotation = Quaternion.RotateTowards(pc.transform.rotation, want, ActiveCfg.turnDegPerSec * Time.deltaTime); Turns++;
return true;
}
}
internal sealed class WLStaffLaserRunner : MonoBehaviour
{
public void Begin(ProjectileBase pb, ProjectileData pd, PCActor pc, WLStaffLaserJson c) { StopAllCoroutines(); StartCoroutine(Run(pb, pd, pc, c)); }
static Vector3 V(float[] a) { return a != null && a.Length >= 3 ? new Vector3(a[0], a[1], a[2]) : Vector3.zero; }
IEnumerator Run(ProjectileBase pb, ProjectileData pd, PCActor pc, WLStaffLaserJson c)
{
// 원본 겉모습 · 판정 상자 · Update 끔(끝나면 되돌림 · 풀 재사용)
var ren = new List<Renderer>(); foreach (var x in pb.GetComponentsInChildren<Renderer>(true)) if (x.enabled) { x.enabled = false; ren.Add(x); }
var col = new List<Collider>(); foreach (var x in pb.GetComponentsInChildren<Collider>(true)) if (x.enabled) { x.enabled = false; col.Add(x); }
bool wasEnabled = pb.enabled; pb.enabled = false;
if (c.noExtra != 0 && pd != null) { pd.e_SkillExtraType = eSkillExtraType.None; pd.m_skillTypeConfigTable = null; pd.SetSkillExtraType = false; WLStaffLaser.NoExtra++; } // #994 PD 「지팡이 기본 스킬에 명중 된 적에게 디버프 이펙트가 발생되지 않도록」
if (OptionInfo.Ins != null) OptionInfo.Ins.Set_PCMove(pc, false);
pc.Set_CurAnim(eAnim.Skill);
WLStaffLaser.Active = pc; WLStaffLaser.ActiveCfg = c;
yield return null; // 원본 시전 코루틴이 같은 프레임에 다음 동작을 틀 수 있어 한 프레임 뒤에 자세를 세운다
var an = pc.m_animation; float sp0 = an != null && an.speed > 0f ? an.speed : 1f;
Hold(an, c);
GameObject fx = null; var p = !string.IsNullOrEmpty(c.fx) ? Resources.Load<GameObject>(c.fx) : null;
if (p != null)
{
fx = Instantiate(p);
foreach (var ps in fx.GetComponentsInChildren<ParticleSystem>(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; m.loop = true; }
fx.transform.localScale = Vector3.one * c.fxScale; Place(fx, pc, c);
}
else WLStaffLaser.LastLog = "레이저 이펙트 없음 " + c.fx;
float t = 0f, next = 0f; bool canceled = false; int hits0 = WLStaffLaser.Hits;
while (t < c.seconds)
{
if (pc == null || pc.IsDead()) { canceled = true; break; }
if (pc.Get_CurAnim() == eAnim.Dodge) { canceled = true; WLStaffLaser.Cancels++; break; } // PD 「구르기를 시도할 경우 마법이 즉시 취소」
if (pc.Get_CurAnim() < eAnim.Skill) pc.Set_CurAnim(eAnim.Skill);
if (an != null && (!an.GetCurrentAnimatorStateInfo(0).IsName(c.holdState) || an.IsInTransition(0))) { Hold(an, c); WLStaffLaser.Reasserts++; }
else if (an != null && an.speed != 0f) an.speed = 0f;
SteerInput(pc, c);
if (fx != null) Place(fx, pc, c);
if (t >= next) { next += Mathf.Max(0.05f, c.interval); WLStaffLaser.Ticks++; Tick(pb, pc, c); }
yield return null; t += Time.deltaTime;
}
// 끝 · 취소 — 레이저 방출을 멈추고 흐려지게 · 애니메이터 · 이동 · 스킬 상태 되돌림 · 원본 투사체 끔
if (fx != null) { foreach (var ps in fx.GetComponentsInChildren<ParticleSystem>(true)) ps.Stop(true, ParticleSystemStopBehavior.StopEmitting); Destroy(fx, Mathf.Max(0.05f, c.fadeSeconds)); }
if (an != null && an.speed == 0f) an.speed = sp0;
WLStaffLaser.Active = null; WLStaffLaser.ActiveCfg = null;
if (OptionInfo.Ins != null && pc != null) OptionInfo.Ins.Set_PCMove(pc, true);
if (!canceled && pc != null && !pc.IsDead()) pc.Play_Idle();
if (pb != null) { pb.enabled = wasEnabled; WLStaffEarth.OffOriginal(pb); }
foreach (var x in ren) if (x != null) x.enabled = true;
foreach (var x in col) if (x != null) x.enabled = true;
WLStaffLaser.Ends++;
WLStaffLaser.LastLog = (canceled ? "취소 " : "끝 ") + t.ToString("F2") + " s · 다단 " + WLStaffLaser.Ticks + " · 이번 명중 " + (WLStaffLaser.Hits - hits0) + " · 밀침 " + WLStaffLaser.Pushes + " · 회전 입력 " + WLStaffLaser.Turns + " · 자세 다시 세움 " + WLStaffLaser.Reasserts;
}
// 가상패드 축(SimpleInput) → 카메라 정면 기준 수평 방향 → Steer(몸만 회전)
static void SteerInput(PCActor pc, WLStaffLaserJson c)
{
var a = PadAxis(); float h = a.x, v = a.y;
if (h * h + v * v < c.steerDeadzone * c.steerDeadzone) return;
var cam = Camera.main; var fwd = cam != null ? cam.transform.forward : Vector3.forward; fwd.y = 0f; if (fwd.sqrMagnitude < 1e-4f) fwd = Vector3.forward; fwd.Normalize();
WLStaffLaser.Steer(pc, fwd * v + Vector3.Cross(Vector3.up, fwd) * h);
}
// 가상패드 입력 — 섬 = SimpleInput 축(WLIslandAvatar) · 테스트 씬 = 원본 가상패드(Joystick Pack FloatingJoystick · global::Joystick.Direction) · 편집기 방향키 — 먼저 잡히는 것
static global::Joystick s_joy; static float s_joyFind;
internal static Vector2 PadAxis()
{
var a = Vector2.zero;
try { a = new Vector2(SimpleInput.GetAxis("Horizontal"), SimpleInput.GetAxis("Vertical")); } catch { }
if (a.sqrMagnitude < 1e-4f)
{
if (s_joy == null && Time.unscaledTime >= s_joyFind) { s_joy = Object.FindAnyObjectByType<global::Joystick>(); s_joyFind = Time.unscaledTime + 1f; }
if (s_joy != null && s_joy.isActiveAndEnabled) a = s_joy.Direction;
}
if (a.sqrMagnitude < 1e-4f) a = new Vector2((Input.GetKey(KeyCode.RightArrow) ? 1f : 0f) - (Input.GetKey(KeyCode.LeftArrow) ? 1f : 0f), (Input.GetKey(KeyCode.UpArrow) ? 1f : 0f) - (Input.GetKey(KeyCode.DownArrow) ? 1f : 0f));
return a;
}
static void Hold(Animator an, WLStaffLaserJson c)
{
if (an == null) return;
an.speed = 0f; an.Play(c.holdState, 0, Mathf.Clamp01(c.holdNormalized)); an.Update(0f);
}
static void Place(GameObject fx, PCActor pc, WLStaffLaserJson c)
{
var f = pc.transform.forward; f.y = 0f; if (f.sqrMagnitude < 1e-4f) f = Vector3.forward; var basis = Quaternion.LookRotation(f.normalized);
fx.transform.SetPositionAndRotation(pc.transform.position + basis * V(c.fxOffset), basis * Quaternion.Euler(V(c.fxEuler)));
}
// 정면 직선(시전자 발에서 length · 폭 width + 적 몸 반지름) 안 적 — 원본 투사체 피해 + 레이저 방향 밀침
static void Tick(ProjectileBase pb, PCActor pc, WLStaffLaserJson c)
{
var info = ActorInfo.Ins; if (info == null || pb == null) return;
var o = pc.Get_position(); var f = pc.transform.forward; f.y = 0f; if (f.sqrMagnitude < 1e-4f) return; f.Normalize();
var list = info.Get_EnemyActors(pc.IsEnemy(), o, c.length + 3f); if (list == null) return;
try
{
for (int k = 0; k < list.Count; k++)
{
var a = list[k]; if (a == null || a.IsDead()) continue;
var v = a.Get_position() - o; v.y = 0f; float along = Vector3.Dot(v, f), side = Mathf.Abs(Vector3.Cross(f, v).y), br = WL.Combat.WLCombatMotionSettings.BodyRadius(a);
if (along < -br || along > c.length + br || side > c.width * 0.5f + br) continue;
var cl = a.GetComponent<Collider>(); if (cl == null || !cl.enabled) continue;
if (!pb.ProcessCollider(cl, false)) continue; WLStaffLaser.Hits++;
if (c.push > 0f)
{
var d = f * c.push; var ag = a.GetComponent<UnityEngine.AI.NavMeshAgent>();
if (ag != null && ag.enabled && ag.isOnNavMesh) ag.Move(d); else a.transform.position += d;
WLStaffLaser.Pushes++;
}
}
}
finally { info.Release_Actors(list); }
}
}
}