Project_WL/Assets/WL/SkillTest/WLSkillTestHarness.cs

559 lines
41 KiB
C#
Raw Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLSkillTestHarness.cs — 독립 스킬 테스트 씬(WLSkillTest) · 캐릭터 + 스킬 시스템만 따로 세운 하네스 (PD #947 재지시 · 2026-10-20)
//
// PD 원문: 「이 프로젝트를 기반으로 만들라는 의미가 아니라, 스킬 애니메이션 및 연출을 위해 캐릭터 및 스킬 시스템만 별도로 구축해서
// 테스트 목적으로 쓸 씬을 구축해달라는 뜻」
//
// ■ 방식 = 원본 개발자의 StatTest 하네스(MainToStatTest)와 같은 줄기: **로그인 0 · HUD(NewGameUI) 0 · 섬·로비 0 · Addressables 부트 0**.
// 씬에 직접 둔 것만으로 돈다 — 테이블 프리팹 · ActorInfo · InGameInfo(이펙트·투사체 풀) · ProjectileInfo · 원본 Main Camera · 치비 PC 프리팹 · 더미 몹 프리팹 · 평지.
// 순서: 평지 NavMesh 베이크 → 테이블 로딩 대기 → 오프라인 ServerData(신규 유저 꼴 · 자동 전투 1) → PC.Set(직업 = 낀 WL 무기) → 더미 Set(무적 · 제자리) → 카메라 리그 → ready.
// ■ 원본 코드와의 접점 = StatTest 전용 분기 13곳을 `WLTestScene.Active`(StatTest 또는 WLSkillTest)로 넓힌 것뿐(HUD·포털·서버 일시정지·대상 목록 분기 · 전부 #if UNITY_EDITOR 안).
// 이동·넉백·소환 AI 분기(Actor 1665·1710·2198 · MobActor 75 · SummonActor 60 · WizardActor 53·61)는 StatTest 전용으로 남겨 **실제 게임 동작**을 유지한다.
// ■ 이 클래스가 MainToStatTest 를 상속하는 이유 = 원본 분기가 `MainToStatTest.Ins.actors[1]`(대상)·`Set_Dmglabel`(피해 글자)를 부른다 → 같은 자리를 채워 준다.
// ■ 조작: **원본 가상패드**(NewGameUI 의 Floating Joystick 중첩 프리팹을 빌더가 같은 값으로 복제 · 화면 왼쪽 드래그 · PD 2026-10-20 #947 「원본과 동일한 가상패드 + 키 입력」)
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
// + WASD/방향키 이동(같은 호출 · Set_AutoBraking(false) → JoystickStatus 1 → Run_byJoystick) · Space 기본 공격 1타 · Q 주력 · E 각성 · 숫자 = 그 번호 무기(#958 · 등급 치트 대체)
// · #958 모든 무기 각성 상태(해금 등급 무시) · 자동 스킬 끔(Q · E 치트키로만 스킬 · 그 밖엔 기본 공격만) — WLTestScene.AllAwakened · ManualSkills · NumberKeysWeapon
// · O 무기 이전(왼쪽 순환) · P 무기 다음(오른쪽 순환) — PD 2026-10-20 #947 지시 · 종류(GroupNo)마다 1개 · R 더미 제자리 · T 슬로 모션 · Pause = 설정값(기본 Backslash)
// ■ 값 = Resources/WL/WLSkillTest.json (C45) · 상태 파일 = Library/WLSkillTest_status.txt(외부 스크립트는 Play 중 eval 대신 이 파일) · 🔴 Debug.LogError 금지(원본 ErrorLogHook 팝업)
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections;
using System.Collections.Generic;
using Unity.AI.Navigation;
using UnityEngine;
using UnityEngine.SceneManagement;
using WL.Combat.Weapon;
using WL.Equip;
using WL.Island;
using WL.IslandMob;
namespace WL.SkillTest
{
/// <summary>원본 코드의 StatTest 전용 분기가 이 씬에서도 서도록 넓힌 판정(에디터 전용 분기 안에서만 쓰인다).</summary>
public static class WLTestScene
{
public const string Name = "WLSkillTest";
public static bool Active { get { var n = SceneManager.GetActiveScene().name; return n == Name || n == "StatTest"; } }
/// <summary>테스트 씬에서 원본 InGameInfo.Start 의 이펙트·스킬 예열(인스턴스 514 · 한 프레임 18 s · #947 실측)을 돌릴지 — JSON preloadEffects(기본 0 = 생략 · 첫 사용 때 로드).</summary>
public static bool PreloadEffects { get { return WLSkillTestHarness.Cfg.preloadEffects != 0; } }
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
// #958 PD 「모든 무기는 각성 상태로 동작 · 테스트 씬에선 치트키 Q(주력) · E(각성)로 내가 쓰는 경우가 아니면 기본 공격만 · 숫자 키 = 그 번호 무기로 즉시 교체(등급 변경 대체)」
// — 하네스 Awake 가 JSON 값으로 켜고 Play 시작마다 끈다(도메인 리로드 꺼짐 · 다른 씬에 남지 않게).
/// <summary>무기 기술 해금 등급 무시(주력 · 각성 둘 다 열림) — WLWeaponSkillSlots · WLSkillChain · WLPartSkillState 가 본다.</summary>
public static bool AllAwakened;
/// <summary>자동 스킬 시전 끔 — 원본 PCActor 자동 시전 · WL_TryAutoSkillNow(우선 시전 · 대쉬 도착 시전) · 파츠 스킬 자동 시전. Q/E 치트키(WLSkillHotkey)만 스킬을 쓴다.</summary>
public static bool ManualSkills;
/// <summary>숫자 키 = 무기 번호(등급 치트 WLWeaponGradeCheat 대신).</summary>
public static bool NumberKeysWeapon;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetFlags() { AllAwakened = ManualSkills = NumberKeysWeapon = false; }
}
[System.Serializable]
public class WLSkillTestJson
{
public string note = "";
public int baseClassId = 10101; // 낀 WL 무기가 없을 때 PC 직업(묵광 검+방패)
public int autoBattle = 1; // 1 = 섬처럼 자동 전투(대상 있으면 기본 공격 콤보)
public float[] spawnPos = { 0f, 0f, 0f };
public int cameraRig = 1; // 1 = 섬 구도 카메라 리그(WLDungeonCameraRig)
public int dummyMobId = 0; // 0 = WLIslandMobSettings.hitTestDummyMobId (몬스터 표 행 · 능력치)
public float[] dummyOffset = { 0f, 0f, 0f }; // 전부 0 = WLIslandMobSettings.hitTestDummyOffset
public int dummyStill = 1; // 1 = 더미 제자리
public int preloadEffects = 0; // 1 = 원본 InGameInfo 이펙트·스킬 예열(514 인스턴스 · 약 18 s) · 0 = 생략(첫 사용 때 로드)
public string gameMode = "Lobby"; // InGameInfo 게임 모드 — 섬과 같은 Lobby(자동 전투·대상 찾기가 로비 규칙). Stage 로 두면 PC 가 대상 없이 달린다(#948 실측). 로비 컨트롤러(pcanim_lobby) 문제는 Set_Obj 동안만 Stage 로 바꾸는 전투 형태 적용(섬 WLIslandAvatar.ApplyCombatForm 과 같은 꼴)으로 푼다
public float combatFormCheckSeconds = 0.5f; // 이 간격으로 PC 가 로비 컨트롤러면 전투 형태 다시 적용 · 대상이 없으면 더미를 다시 겨눔
public float autoResetDummySeconds = 2.0f; // 더미가 공격 사거리 밖(넉백 누적)에 이만큼(초) 머물면 PC 앞 기본 자리로(R 키와 같음) → 콤보가 끊기지 않고 반복된다(#948 · 0 = 끔)
public int moveKeys = 1; // 1 = WASD/방향키도 이동(가상패드와 함께 · PD #947 「가상패드 + 키 입력」)
public string attackKey = "Space";
public string weaponPrevKey = "O"; // PD #947: O = 무기 왼쪽 순환
public string weaponNextKey = "P"; // PD #947: P = 무기 오른쪽 순환
public string dummyResetKey = "R";
public string slowKey = "T";
public float slowScale = 0.2f;
public string pauseKey = "Backslash"; // P 를 무기에 내주고 일시정지는 \ 로
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
public int numberKeysWeapon = 1; // #958 1 = 숫자 키 1~9 · 0(=10) 이 그 번호 무기로 즉시 교체(번호 = O/P 순환과 같은 종류 순 · 화면 왼쪽 위 목록) · 등급 치트는 이 씬에서 끔
public int allAwakened = 1; // #958 1 = 모든 무기 각성 상태(등급과 무관하게 주력 · 각성 해금)
public int manualSkills = 1; // #958 1 = 자동 스킬 시전 끔(Q · E 치트키로만 스킬 · 그 밖엔 기본 공격만)
WL #960 PD 지시 4건(비수 · 테스트 씬) — 잔상 검정 · 각성 착지 옆 진입 양손 베기 · 주력 10회 판정 · TestMap Play = 스킬 테스트 씬 - ① 잔상 색: 주력 난사 잔상(#959 teal) · 각성 진입 그림자 → 검정(0,0,0,0.5) · 4타 순간이동 · 시전 · 착지 잔상은 원래 거의 검정(0.07,0.03,0.12) - ② 비수 각성 착지: 낙하로 보인 원인 = 모델이 드러난 뒤 ghost_land(Platform Sub_HeroLanding = 공중에서 내려앉는 클립) 재생 + 옆 진입 동안 실제 모델 숨김 → 착지 상태 = 4타 양손 낮게 가르기(DualBlades_DodgeAttack · 이벤트 끔) · 실제 모델이 보인 채 옆 1.2 m 에서 0.2 s 진입 · 0.04 s 마다 검은 지금 자세 잔상 · 끝 잔상 3 = 양손 베기 자세 · 착지 동안 공격 전진 표 막음(AttackRootMotion.Gate · 실측 이동 0.75 → 0.11 m) - ③ 비수 주력: 원본 판정(시전 0.5 s 뒤 0.1 s 간격 6회 · 난사와 무관) 끄고(go_effect Loop 0) 난사 틱 0.24 → 0.2 s · 휘두름마다 0.1 s 뒤 원본 한 대 피해 1회 × 10(Skill_ShadowSlash.ProcessCollider · 1회 피해 그대로 → 총량 원본의 10/6) · 범위 PC 발 3.5 m + 몸 반지름 · 대상 쪽 ±110° · 판정 시각 대기열(끊김에도 휘두름마다) · 실측 3회 모두 10/10 · 0.2 s 간격 - ④ 테스트 씬 미실행: PD 가 Play 한 씬 = Assets/Scenes/TestMap.unity(원본 맵 조각 · LoadMapMgr.Awake NRE · #957 과 같음) → WLSkillTestPlayRedirect(에디터) = WLSkillTest.json redirectScenes 의 씬을 연 채 Play 하면 Play 시작 씬을 WLSkillTest.unity 로(playModeStartScene · 끝나면 원래 씬) · 실측 TestMap 에서 Play → ready · 콘솔 오류 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:29:56 +00:00
public string[] redirectScenes = new string[0]; // #960 이 씬을 연 채 Play 하면 스킬 테스트 씬으로 시작(WLSkillTestPlayRedirect · 원본 맵 조각 TestMap 등)
public int label = 1;
public float labelFontScale = 0.018f;
public float groundSize = 100f;
public float[] groundColor = { 0.45f, 0.62f, 0.35f };
public float[] skyColor = { 0.55f, 0.75f, 0.95f };
}
public sealed class WLSkillTestHarness : MainToStatTest
{
public const string ResourcesPath = "WL/WLSkillTest";
[Header("씬 빌더가 채운다")]
public Actor pcActor; // 치비 PC 프리팹 인스턴스(씬에 직접)
public MobActor dummyActor; // 더미 몹 프리팹 인스턴스(씬에 직접)
public static string Stage = "none", LastLog = "";
public static bool Ready;
public static Actor Dummy;
public static readonly List<string> Laps = new List<string>();
public static readonly List<string> Hangs = new List<string>();
public static float T0;
public static string MoveLog = "";
static WLSkillTestJson s_cfg;
static System.Reflection.FieldInfo s_fEffects, s_fMagic, s_fMagicArr;
float _lastRt, _simH, _simV, _formTimer, _outOfRange;
bool _moving, _slow;
KeyCode _kAttack = KeyCode.Space, _kPrev = KeyCode.O, _kNext = KeyCode.P, _kReset = KeyCode.R, _kSlow = KeyCode.T, _kPause = KeyCode.Backslash;
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
static readonly KeyCode[] s_numKeys = { KeyCode.Alpha1, KeyCode.Alpha2, KeyCode.Alpha3, KeyCode.Alpha4, KeyCode.Alpha5, KeyCode.Alpha6, KeyCode.Alpha7, KeyCode.Alpha8, KeyCode.Alpha9, KeyCode.Alpha0 };
static readonly KeyCode[] s_padKeys = { KeyCode.Keypad1, KeyCode.Keypad2, KeyCode.Keypad3, KeyCode.Keypad4, KeyCode.Keypad5, KeyCode.Keypad6, KeyCode.Keypad7, KeyCode.Keypad8, KeyCode.Keypad9, KeyCode.Keypad0 };
string _msg = "", _weaponLine = "";
GUIStyle _style;
public static WLSkillTestJson Cfg
{
get
{
if (s_cfg != null) return s_cfg;
s_cfg = new WLSkillTestJson();
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;
}
}
public static string StatusPath { get { return System.IO.Path.GetFullPath(System.IO.Path.Combine(Application.dataPath, "../Library/WLSkillTest_status.txt")); } }
static Vector3 V3(float[] a) { return a != null && a.Length >= 3 ? new Vector3(a[0], a[1], a[2]) : Vector3.zero; }
static KeyCode Key(string s, KeyCode d) { KeyCode k; return !string.IsNullOrEmpty(s) && System.Enum.TryParse(s, true, out k) ? k : d; }
static void WriteStatus() { try { System.IO.File.WriteAllText(StatusPath, Describe()); } catch { } }
static void Lap(string name) { Stage = name; Laps.Add(name + " " + (Time.realtimeSinceStartup - T0).ToString("F1") + "s"); WriteStatus(); }
// MainToStatTest.Awake 는 go_tables 를 복제한다 — 여기선 테이블이 씬에 직접 있으므로 싱글턴 자리만 잡는다
protected override void Awake()
{
Ins = this; isIns = true;
T0 = Time.realtimeSinceStartup;
Laps.Clear(); Hangs.Clear(); Ready = false; Dummy = null; s_cfg = null; LastLog = "";
var cfg = Cfg;
_kAttack = Key(cfg.attackKey, KeyCode.Space); _kPrev = Key(cfg.weaponPrevKey, KeyCode.O); _kNext = Key(cfg.weaponNextKey, KeyCode.P);
_kReset = Key(cfg.dummyResetKey, KeyCode.R); _kSlow = Key(cfg.slowKey, KeyCode.T); _kPause = Key(cfg.pauseKey, KeyCode.Backslash);
MoveLog = ""; _simH = _simV = 0f;
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
WLTestScene.AllAwakened = cfg.allAwakened != 0; WLTestScene.ManualSkills = cfg.manualSkills != 0; WLTestScene.NumberKeysWeapon = cfg.numberKeysWeapon != 0; // #958
if (actors == null || actors.Length < 2) actors = new Actor[] { pcActor, dummyActor };
Lap("awake");
}
// 단계마다 try — 예외는 상태 파일(stage=<단계>-fail · 예외 종류 · 메시지 · 호출 위치 8줄)에 남긴다. 코루틴이 조용히 죽으면 콘솔 스팸에 묻혀 찾기 어려웠다(#947 1차 Play · PC.Set NRE 가 Freeze 1000건 아래)
IEnumerator Start()
{
var cfg = Cfg;
if (!Try("navmesh", () => { BakeGround(); ApplySky(cfg); })) yield break;
Lap("navmesh");
while (TableChecker.Ins == null || !TableChecker.Ins.CheckAllLoad()) yield return null;
Lap("tables");
// 예열을 켠 경우만: InGameInfo.Start 의 이펙트 예열 루프(테이블 0.1 s 폴링 뒤 1프레임)가 끝난 다음에 PC 를 만든다(먼저 만들면 PC 피격 이펙트 키가 먼저 등록돼 그 루프의 Add 가 중복 · #947 2차 Play 실측 · 원본에 가드도 넣음)
if (cfg.preloadEffects != 0) for (float t = 0f; t < 3f && !EffectsPreloaded(); t += Time.unscaledDeltaTime) yield return null;
if (!Try("gamemode", () => SetGameMode(cfg.gameMode))) yield break;
Lap("ingameinfo");
if (pcActor == null || dummyActor == null) { Fail("refs-missing", "pcActor/dummyActor 참조 없음 — Build Scene 다시"); yield break; }
// PC — 직업 = 낀 WL 무기(없으면 baseClassId). 원본 Set 안에서 WL 무기 적용기·스타터 킷이 돈다(섬과 같은 경로)
int cls = 0;
if (!Try("pc", () =>
{
// 오프라인 서버 데이터(신규 유저 꼴 · 테이블 로딩 뒤에만 생성 가능) + 자동 전투
var sdata = new ServerData();
try { if (cfg.autoBattle != 0) sdata.Add_Common(eCommon.Auto, 1); } catch (System.Exception ex) { LastLog = "Auto 설정 실패 " + ex.Message; }
cls = ResolveClass(cfg);
pcActor.transform.position = NavPos(V3(cfg.spawnPos));
pcActor.gameObject.SetActive(true); // 빌더가 꺼 둔 것을 Set 직전에 켠다(원본 Set 도 켜지만 그 전에 콜라이더 bounds 를 읽는다)
WithStageMode(() => pcActor.Set(false, null, sdata, cls, true)); // Set 안의 Set_Obj 가 Lobby 면 로비 컨트롤러를 입힌다(#948 실측: 지팡이가 pcanim_lobby 로 서서 투사체 0) → 섬처럼 그 동안만 Stage
FillMagicSlots(pcActor, sdata);
ActorInfo.Ins.Add_Actor(pcActor, eRole.PC, 0, pcActor.name);
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
if (cfg.allAwakened != 0) { try { WL.PartSkill.WLPartSkillState.Rebuild("skilltest958"); } catch (System.Exception ex) { LastLog = "파츠 스킬 다시 짜기 실패 " + ex.Message; } } // #958 각성 해금을 파츠 스킬 칸(해머 대지 분쇄)에도 바로
})) yield break;
Lap("pc " + cls);
// 더미 — 몬스터 표 행으로 능력치 · 무적(WLTrailTest 체력 되채움) · 제자리
int mobId = 0;
if (!Try("dummy", () =>
{
var ms = WLIslandMobSettings.Instance;
mobId = cfg.dummyMobId > 0 ? cfg.dummyMobId : (ms != null ? ms.hitTestDummyMobId : 101);
var data = table_monsterlist.Ins.Get_Data_orNull(mobId);
if (data == null) throw new System.Exception("몬스터 표 없음 " + mobId);
dummyActor.gameObject.SetActive(true); // 빌더가 꺼 둔 것(Set 전 Update 의 IsCC 예외 방지) — Set 이 자식 SkinnedMeshRenderer bounds 를 읽으므로 먼저 켠다
ActorInfo.Ins.Add_Actor(dummyActor, eRole.Mob, 1, dummyActor.name);
dummyActor.Set(true, null, null, 1, false);
dummyActor.Set(data, DummyPos(cfg, ms), null, 0);
if (cfg.dummyStill != 0) dummyActor.Stop(true);
WLTrailTest.Adopt(dummyActor); Dummy = dummyActor;
if (dummyActor.GetComponent<WLTrailTestDummy>() == null) dummyActor.gameObject.AddComponent<WLTrailTestDummy>().Begin(dummyActor);
})) yield break;
Lap("dummy " + mobId);
// 카메라 — 섬 구도 리그 + 원본 RealCamera 대상
if (!Try("camera", () =>
{
var icfg = WLIslandSettings.Instance;
if (icfg != null && cfg.cameraRig != 0 && Object.FindFirstObjectByType<WLDungeonCameraRig>() == null) WLDungeonCameraRig.Create(icfg, gameObject.scene);
var rc = MyValue.m_RealCamera;
if (rc != null) rc.Set_Target(pcActor.transform, icfg != null ? WLDungeonCameraRig.SolvedYaw(icfg) : 0f);
})) yield break;
yield return null;
if (!Try("ready", () => { pcActor.Stop(false); pcActor.Change_Target(false, dummyActor); })) yield break;
Ready = true;
Lap("ready");
}
bool Try(string stage, System.Action step)
{
try { step(); return true; }
catch (System.Exception ex) { Fail(stage + "-fail", ex.GetType().Name + ": " + ex.Message + " @ " + Where(ex)); return false; }
}
/// <summary>예외 호출 위치 8줄 — 인자 목록·IL 오프셋·절대 경로를 지워 「at 클래스.메서드 파일.cs:줄」 만 남긴다(#957: 4줄이면 Unity 내부 3줄에 묻혀 호출자가 안 보였다).</summary>
static string Where(System.Exception ex)
{
var lines = (ex.StackTrace ?? "").Split('\n'); var sb = new System.Text.StringBuilder();
for (int i = 0; i < lines.Length && i < 8; i++)
sb.Append(System.Text.RegularExpressions.Regex.Replace(lines[i].Trim(), @"\s*\(.*?\)\s*\[0x[0-9a-fA-F]+\]\s*in\s*.*[\\/]", " ")).Append(" | ");
return sb.ToString();
}
void Fail(string stage, string msg) { Stage = stage; LastLog = msg; WriteStatus(); Debug.LogWarning("[WLSkillTest] " + stage + " — " + msg); } // LogError 금지(원본 ErrorLogHook 팝업)
static System.Reflection.FieldInfo s_fMode;
public static int CombatForms;
static System.Reflection.FieldInfo ModeField()
{
if (s_fMode == null) s_fMode = typeof(InGameInfo).GetField("m_GameMode", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
return s_fMode;
}
/// <summary>InGameInfo 의 비공개 m_GameMode 를 JSON 값(기본 Lobby = 섬과 같음)으로.</summary>
static void SetGameMode(string mode)
{
if (string.IsNullOrEmpty(mode)) return;
eGameMode m; if (!System.Enum.TryParse(mode, true, out m)) { LastLog = "게임 모드 이름 오류 " + mode; return; }
var ig = InGameInfo.Ins; var f = ModeField();
if (ig == null || f == null) { LastLog = "InGameInfo/m_GameMode 없음"; return; }
f.SetValue(ig, m);
}
/// <summary>act 를 Stage 모드로 돌린다(Lobby 였으면 끝나고 되돌림) — 원본 PCActor.Set_Obj 가 Lobby 면 pcanim_lobby 를 입히는 것을 피한다(섬 WLIslandAvatar.ApplyCombatForm · WLWeaponClasses.RefreshModelIfNeeded 와 같은 꼴).</summary>
static void WithStageMode(System.Action act)
{
var ig = InGameInfo.Ins; var f = ModeField();
object prev = ig != null && f != null ? f.GetValue(ig) : null; bool flipped = false;
try
{
if (f != null && prev is eGameMode && (eGameMode)prev == eGameMode.Lobby) { f.SetValue(ig, eGameMode.Stage); flipped = true; }
act();
}
finally { if (flipped) f.SetValue(ig, prev); }
}
static bool PcInLobbyForm(PCActor pc)
{
var an = pc != null ? pc.GetComponentInChildren<Animator>() : null;
var c = an != null ? an.runtimeAnimatorController : null;
return c != null && c.name.IndexOf("lobby", System.StringComparison.OrdinalIgnoreCase) >= 0;
}
/// <summary>무기 교체(O/P · 숫자 키 등급) 뒤 원본 Set_Obj 가 로비 컨트롤러를 다시 입혔으면 전투 형태로 되돌리고, 대상을 잃었으면 더미를 다시 겨눈다(0.5 s 마다 · JSON combatFormCheckSeconds).</summary>
void CheckCombatForm(PCActor pc)
{
if (PcInLobbyForm(pc)) { WithStageMode(() => pc.Set_Obj()); CombatForms++; _msg = "전투 형태 다시 적용 " + CombatForms; }
if (Dummy != null && pc.Get_Target() == null && !_moving) pc.Change_Target(false, Dummy);
// 더미가 사거리 밖에 머물면(넉백 누적) 자동 R — 콤보 반복 관찰용
float auto = Cfg.autoResetDummySeconds;
if (auto > 0f && Dummy != null)
{
var d = Dummy.Get_position() - pc.Get_position(); d.y = 0f;
if (d.magnitude > pc.WL_AttackRange + 0.3f) { _outOfRange += Mathf.Max(0.1f, Cfg.combatFormCheckSeconds); if (_outOfRange >= auto) { _outOfRange = 0f; _msg = "더미 사거리 밖 " + auto + " s → " + ResetDummy(); } }
else _outOfRange = 0f;
}
}
/// <summary>InGameInfo.Start 의 예열 루프가 돌았는가 — 비공개 dic_str_Effect 의 키 수 ≥ 이펙트 표(목록 1) 행 수.</summary>
static bool EffectsPreloaded()
{
var ig = InGameInfo.Ins; if (ig == null) return false;
if (s_fEffects == null) s_fEffects = typeof(InGameInfo).GetField("dic_str_Effect", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
var d = s_fEffects != null ? s_fEffects.GetValue(ig) as System.Collections.IDictionary : null;
if (d == null) return true; // 필드가 바뀌었으면 기다리지 않는다
var lst = table_effectlist.Ins != null ? table_effectlist.Ins.Get_DataList(1) : null;
return lst == null || d.Count >= lst.Count;
}
/// <summary>원본 PCActor.Set 은 HUD 스킬 카드(NewGameUI)가 있을 때만 dic_MagicData 를 채운다 → HUD 없는 이 씬에선 Use_Skill 의 dic_MagicData[index] 가 KeyNotFound(#947 2차 Play · Q/E 발동 실패). 같은 꼴로 스킬 칸 수만큼 채운다.</summary>
static void FillMagicSlots(Actor pc, ServerData sdata)
{
var bf = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public;
if (s_fMagic == null) s_fMagic = typeof(Actor).GetField("dic_MagicData", bf);
if (s_fMagicArr == null) s_fMagicArr = typeof(PCActor).GetField("arr_magicData", bf);
var dic = s_fMagic != null ? s_fMagic.GetValue(pc) as Dictionary<int, UseMagicInfo> : null;
if (dic == null) { LastLog = "dic_MagicData 없음"; return; }
var arr = s_fMagicArr != null ? s_fMagicArr.GetValue(pc) as System.Array : null;
int n = arr != null ? arr.Length : 6;
for (int i = 0; i < n; i++)
if (!dic.ContainsKey(i)) dic.Add(i, new UseMagicInfo { m_uiIndex = i, m_SkillTableData = table_skilllist.Ins.Get_Data_orNull(sdata.Get_EquipSkillID(i)) });
}
/// <summary>검증용 — 이동 키를 seconds 동안 누른 것과 같은 호출(카메라 기준 h · v · Update 의 같은 경로)을 돌리고 이동 거리를 MoveLog 에 남긴다.</summary>
public static string MoveFor(float seconds, float h, float v)
{
var ins = Ins as WLSkillTestHarness; if (ins == null) return "하네스 없음";
if (!Ready || MyValue.MyPC == null) return "ready 아님";
MoveLog = "이동 중"; ins.StartCoroutine(ins.CoMove(seconds, h, v));
return "이동 시작 " + seconds + " s (h " + h + " · v " + v + ")";
}
/// <summary>검증용 — 원본 가상패드(FloatingJoystick)를 화면 왼쪽 아래에서 누르고 (dx, dy) 방향으로 끈 채 seconds 유지한 뒤 뗀다(실제 터치와 같은 OnPointerDown → OnDrag → OnPointerUp). 결과 = MoveLog.</summary>
public static string PadFor(float seconds, float dx, float dy)
{
var ins = Ins as WLSkillTestHarness; if (ins == null) return "하네스 없음";
if (!Ready || MyValue.MyPC == null) return "ready 아님";
MoveLog = "패드 이동 중"; ins.StartCoroutine(ins.CoPad(seconds, dx, dy));
return "패드 시작 " + seconds + " s (" + dx + ", " + dy + ")";
}
IEnumerator CoPad(float seconds, float dx, float dy)
{
var joy = Object.FindAnyObjectByType<FloatingJoystick>(); var pc = MyValue.MyPC as PCActor;
if (joy == null || pc == null) { MoveLog = joy == null ? "FloatingJoystick 없음" : "PC 없음"; yield break; }
var ev = new UnityEngine.EventSystems.PointerEventData(UnityEngine.EventSystems.EventSystem.current);
Vector2 p0 = new Vector2(Screen.width * 0.25f, Screen.height * 0.3f), dir = new Vector2(dx, dy).normalized;
ev.position = p0; joy.OnPointerDown(ev);
Vector3 w0 = pc.Get_position(); float t0 = Time.time; string animMid = "-";
while (Time.time - t0 < seconds) { ev.position = p0 + dir * (Screen.height * 0.12f); joy.OnDrag(ev); animMid = pc.Get_CurAnim().ToString(); yield return null; }
string padDir = joy.Direction.ToString("F2");
joy.OnPointerUp(ev);
yield return null;
MoveLog = "패드 이동 " + Vector3.Distance(w0, pc.Get_position()).ToString("F2") + " m / " + seconds + " s · 패드 입력 " + padDir + " · 이동 중 anim " + animMid + " → 뗀 뒤 " + pc.Get_CurAnim() + " · joystick " + pc.JoystickStatus + " · RealCamera " + (MyValue.m_RealCamera != null);
}
IEnumerator CoMove(float seconds, float h, float v)
{
var pc = MyValue.MyPC as PCActor; if (pc == null) yield break;
Vector3 p0 = pc.Get_position(); float t0 = Time.time; string animMid = "-";
while (Time.time - t0 < seconds) { _simH = h; _simV = v; animMid = pc.Get_CurAnim().ToString(); yield return null; }
_simH = _simV = 0f;
yield return null;
MoveLog = "이동 " + Vector3.Distance(p0, pc.Get_position()).ToString("F2") + " m / " + seconds + " s · 이동 중 anim " + animMid + " → 뗀 뒤 " + pc.Get_CurAnim() + " · joystick " + pc.JoystickStatus;
}
void OnDestroy() { try { if (System.IO.File.Exists(StatusPath)) System.IO.File.Delete(StatusPath); } catch { } Time.timeScale = 1f; }
// ─────────────────────────────────────────────────────────────────
static int ResolveClass(WLSkillTestJson cfg)
{
try
{
WLWeaponClasses.Ensure(); // WL 추가 직업(대검 10801 등)을 ClassConfig 사전에 먼저 — 섬은 PCActor.Set 의 ResolveSpawnClass 훅이 하지만 이 씬의 ServerData 는 새 것이라 그 훅을 타지 않는다(#947 2회차 Play 실측: 대검 낀 세이브로 시작 → KeyNotFound 10801)
if (WLInventory.EnsureLoaded())
{
var w = WLInventory.EquippedInSlot(eFREquipmentType.Weapon);
if (w != null && w.itemJson != null) { int c = WLWeaponAttackTable.ClassIdFor(w.itemJson.GroupNo); if (c > 0) return c; }
}
}
catch (System.Exception ex) { LastLog = "직업 판정 실패 " + ex.Message; }
return cfg.baseClassId > 0 ? cfg.baseClassId : 10101;
}
static Vector3 NavPos(Vector3 want)
{
UnityEngine.AI.NavMeshHit hit;
return UnityEngine.AI.NavMesh.SamplePosition(want, out hit, 5f, UnityEngine.AI.NavMesh.AllAreas) ? hit.position : want;
}
static Vector3 DummyPos(WLSkillTestJson cfg, WLIslandMobSettings ms)
{
Vector3 off = V3(cfg.dummyOffset); if (off == Vector3.zero && ms != null) off = ms.hitTestDummyOffset;
var pc = MyValue.MyPC;
Vector3 basePos = pc != null ? pc.Get_position() : V3(cfg.spawnPos);
Quaternion rot = pc != null ? Quaternion.Euler(0f, pc.transform.eulerAngles.y, 0f) : Quaternion.identity;
return NavPos(basePos + rot * off);
}
static void ApplySky(WLSkillTestJson cfg)
{
var cam = Camera.main;
if (cam == null || cfg.skyColor == null || cfg.skyColor.Length < 3) return;
if (RenderSettings.skybox == null) { cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = new Color(cfg.skyColor[0], cfg.skyColor[1], cfg.skyColor[2]); }
}
void BakeGround()
{
var surf = Object.FindFirstObjectByType<NavMeshSurface>();
if (surf != null && surf.navMeshData != null) return; // 빌더가 구워 둔 데이터(씬 옆 에셋 · OnEnable 이 등록) — 런타임 베이크 5 s 생략
if (surf == null) { var ground = GameObject.Find("Ground"); if (ground == null) return; surf = ground.AddComponent<NavMeshSurface>(); }
surf.useGeometry = UnityEngine.AI.NavMeshCollectGeometry.PhysicsColliders;
surf.BuildNavMesh();
}
/// <summary>더미를 PC 앞 기본 자리로(R 키 · 프로브).</summary>
public static string ResetDummy()
{
var d = Dummy; if (d == null || MyValue.MyPC == null) return "PC/더미 없음";
var pos = DummyPos(Cfg, WLIslandMobSettings.Instance);
d.Stop(true); d.Set_Warp(pos);
return "더미 " + pos.ToString("F1");
}
/// <summary>낀 무기를 무기 종류(GroupNo) 순으로 앞뒤로(dir ±1 · O = -1 · P = +1). 종류마다 1개 — SortedItems 는 장착 중인 것을 맨 앞에 두므로 그대로 쓰면 같은 종류 2개 사이를 왕복했다(#947 2차 Play 실측 · 묵광 2개).</summary>
public static string CycleWeapon(int dir)
{
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
var grp = new List<int>(); var ids = new List<int>(); var names = new List<string>();
int curIdx; string err = WeaponList(grp, ids, names, out curIdx);
if (err != null) return err;
int pos = curIdx < 0 ? 0 : (curIdx + dir + grp.Count) % grp.Count;
return EquipAt(pos, ids, names);
}
/// <summary>#958 PD 「숫자 키를 눌러 해당하는 넘버의 무기로 즉시 바뀌게(기존 등급 변경 대체)」 — number(1부터) = WeaponList 순서(O/P 순환과 같음 · 화면 목록 번호).</summary>
public static string SelectWeapon(int number)
{
var grp = new List<int>(); var ids = new List<int>(); var names = new List<string>();
int curIdx; string err = WeaponList(grp, ids, names, out curIdx);
if (err != null) return err;
if (number < 1 || number > grp.Count) return "무기 " + number + "번 없음(1~" + grp.Count + ")";
if (number - 1 == curIdx) return "이미 " + number + " " + names[curIdx];
return EquipAt(number - 1, ids, names);
}
static string EquipAt(int pos, List<int> ids, List<string> names)
{
bool ok = WLInventory.Equip(ids[pos]);
return (ok ? "장착 " : "실패 ") + (pos + 1) + " " + names[pos] + " · " + (pos + 1) + "/" + ids.Count + (ok ? "" : " · " + WLInventory.LastMessage);
}
/// <summary>낀 수 있는 무기 — 종류(GroupNo) 오름차순 · 종류마다 1개(SortedItems 는 장착 중인 것을 맨 앞에 두므로 같은 종류는 장착 중인 것) · curIdx = 장착 중인 종류의 번호(0부터 · 없으면 -1). 실패면 이유 문장.</summary>
static string WeaponList(List<int> grp, List<int> ids, List<string> names, out int curIdx)
{
curIdx = -1;
if (!WLInventory.EnsureLoaded()) return "인벤토리 미로드";
var cur = WLInventory.EquippedInSlot(eFREquipmentType.Weapon);
int curGroup = cur != null && cur.itemJson != null ? cur.itemJson.GroupNo : -1;
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
var tmp = new List<KeyValuePair<int, Item>>();
foreach (var it in WLInventory.SortedItems())
{
if (it == null || it.itemJson == null || !it.itemJson.IsEquipment || it.itemJson.EquipmentType != eFREquipmentType.Weapon) continue;
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
int g = it.itemJson.GroupNo; bool dup = false;
foreach (var kv in tmp) if (kv.Key == g) { dup = true; break; }
if (!dup) tmp.Add(new KeyValuePair<int, Item>(g, it)); // 같은 종류는 첫 것(장착 중이면 그것)만
}
if (tmp.Count == 0) return "무기 없음";
tmp.Sort((a, b) => a.Key.CompareTo(b.Key));
for (int i = 0; i < tmp.Count; i++)
{
grp.Add(tmp[i].Key); ids.Add(tmp[i].Value.rawItem._id); names.Add(tmp[i].Value.itemJson.Item_name);
if (tmp[i].Key == curGroup) curIdx = i;
}
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
return null;
}
/// <summary>#958 화면 목록 한 줄 — 「1 묵광 · 2 파괴자 · …」(장착 중 = [ ]).</summary>
static string WeaponLine()
{
var grp = new List<int>(); var ids = new List<int>(); var names = new List<string>();
int curIdx; if (WeaponList(grp, ids, names, out curIdx) != null) return "";
var sb = new System.Text.StringBuilder();
for (int i = 0; i < names.Count; i++)
{
if (i > 0) sb.Append(" ");
string key = i < 9 ? (i + 1).ToString() : i == 9 ? "0" : "-";
sb.Append(i == curIdx ? "[" : "").Append(key).Append(' ').Append(names[i]).Append(i == curIdx ? "]" : "");
}
return sb.ToString();
}
public static string Describe()
{
var pc = MyValue.MyPC; string w = "-";
if (WLInventory.Loaded) { var cur = WLInventory.EquippedInSlot(eFREquipmentType.Weapon); if (cur != null && cur.itemJson != null) w = cur.itemJson.Item_name + "(" + cur.itemJson.GroupNo + ")"; }
return "stage=" + Stage + " ready=" + Ready + " pc=" + (pc != null ? pc.name + pc.Get_position().ToString("F1") : "-") + " weapon=" + w +
" dummy=" + (Dummy != null ? Dummy.name + Dummy.Get_position().ToString("F1") + " hp " + Dummy.Get_HP().ToString("F0") : "-") +
" laps=[" + string.Join(" · ", Laps.ToArray()) + "] hangs=[" + string.Join(" ", Hangs.ToArray()) + "] " + LastLog;
}
// ─────────────────────────────────────────────────────────────────
void LateUpdate()
{
float now = Time.realtimeSinceStartup;
if (_lastRt > 0f && now - _lastRt > 0.5f && Hangs.Count < 40) Hangs.Add((now - T0).ToString("F1") + "@" + (now - _lastRt).ToString("F1") + "s");
_lastRt = now;
}
void Update()
{
if (!Ready) return;
var pc = MyValue.MyPC as PCActor;
if (pc == null) return;
var cfg = Cfg;
_formTimer += Time.unscaledDeltaTime;
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
if (_formTimer >= Mathf.Max(0.1f, cfg.combatFormCheckSeconds)) { _formTimer = 0f; CheckCombatForm(pc); if (cfg.numberKeysWeapon != 0) _weaponLine = WeaponLine(); }
// 이동 — 섬 조이스틱(WLIslandAvatar.DrivePc · 원본 FloatingJoystick)과 같은 호출 순서. 누름 = Set_AutoBraking(false) → JoystickStatus 1 · 매 프레임 Run_byJoystick(카메라 기준) · 뗌 = JoystickStatus 0 → Set_AutoBraking(true)(Play_Idle · 자동 전투 재개)
float h = 0f, v = 0f;
if (cfg.moveKeys != 0)
{
h = (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow) ? 1f : 0f) - (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow) ? 1f : 0f);
v = (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow) ? 1f : 0f) - (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow) ? 1f : 0f);
}
if (_simH != 0f || _simV != 0f) { h = _simH; v = _simV; } // 검증 프로브(MoveFor) — 키와 같은 경로
if (h != 0f || v != 0f)
{
if (!_moving) { pc.Set_AutoBraking(false); pc.Set_JoystickStatus(1); _moving = true; }
var cam = Camera.main;
Vector3 fwd = cam != null ? Vector3.ProjectOnPlane(cam.transform.forward, Vector3.up) : Vector3.forward;
if (fwd.sqrMagnitude < 1e-4f) fwd = Vector3.forward;
fwd.Normalize();
pc.Run_byJoystick((fwd * v + Vector3.Cross(Vector3.up, fwd) * h).normalized);
}
else if (_moving) { pc.Set_JoystickStatus(0); pc.Set_AutoBraking(true); _moving = false; }
// 평지 가장자리 — 공격 전진·피격 넉백으로 PC·더미가 1분에 약 20 m 흘러간다(#947 2차 Play 실측) → 끝에 닿기 전에 둘 다 원점으로
float half = cfg.groundSize * 0.5f - 3f; var pp = pc.Get_position();
if (Mathf.Abs(pp.x) > half || Mathf.Abs(pp.z) > half) { pc.Set_Warp(NavPos(V3(cfg.spawnPos))); _msg = "평지 가장자리 — PC·더미 원점 " + ResetDummy(); }
if (Input.GetKeyDown(_kAttack)) { if (WL.Combat.Weapon.WLGhostStrike.IsLanding(pc)) _msg = "착지 중 — 공격 불가"; else { if (Dummy != null) pc.Change_Target(false, Dummy); pc.Play_Attack(0, 1f); _msg = "기본 공격"; } } // #954 랜딩 중 공격 금지
if (Input.GetKeyDown(_kPrev)) _msg = CycleWeapon(-1);
if (Input.GetKeyDown(_kNext)) _msg = CycleWeapon(+1);
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
if (cfg.numberKeysWeapon != 0 && Input.anyKeyDown) // #958 숫자 키 = 그 번호 무기(1~9 · 0 = 10번 · 키패드 포함)
for (int i = 0; i < s_numKeys.Length; i++)
if (Input.GetKeyDown(s_numKeys[i]) || Input.GetKeyDown(s_padKeys[i])) { _msg = SelectWeapon(i + 1); break; }
if (Input.GetKeyDown(_kReset)) _msg = ResetDummy();
if (Input.GetKeyDown(_kSlow)) { _slow = !_slow; Time.timeScale = _slow ? Mathf.Clamp(cfg.slowScale, 0.05f, 1f) : 1f; _msg = _slow ? "슬로 모션 ×" + Time.timeScale : "정상 속도"; }
#if UNITY_EDITOR
if (Input.GetKeyDown(_kPause)) UnityEditor.EditorApplication.isPaused = !UnityEditor.EditorApplication.isPaused;
#endif
}
void OnGUI()
{
if (Cfg.label == 0) return;
if (_style == null) { _style = new GUIStyle(GUI.skin.label); _style.alignment = TextAnchor.UpperLeft; _style.wordWrap = true; }
_style.fontSize = Mathf.Max(10, Mathf.RoundToInt(Screen.height * Cfg.labelFontScale));
_style.normal.textColor = Ready ? Color.white : new Color(1f, 0.9f, 0.5f);
string w = "-";
if (WLInventory.Loaded) { var cur = WLInventory.EquippedInSlot(eFREquipmentType.Weapon); if (cur != null && cur.itemJson != null) w = cur.itemJson.Item_name + "(" + cur.itemJson.GroupNo + ")"; }
string s = "WL 스킬 테스트 · " + Stage + (Ready ? "" : " …") +
"\n무기 " + w + " 더미 HP " + (Dummy != null ? Dummy.Get_HP().ToString("F0") : "-") + (_slow ? " [슬로 ×" + Time.timeScale + "]" : "") +
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
"\n가상패드(왼쪽 화면 드래그 · 원본과 동일) 또는 WASD 이동 · " + _kAttack + " 공격 · " + WLSkillHotkey.Key + " 주력 · " + (WLSkillHotkey.Key2 != KeyCode.None ? WLSkillHotkey.Key2 + " 각성 · " : "") + (Cfg.numberKeysWeapon != 0 ? "숫자 무기 · " : "숫자 등급 · ") +
_kPrev + " 무기 이전 · " + _kNext + " 무기 다음 · " + _kReset + " 더미 · " + _kSlow + " 슬로 · " + _kPause + " 일시정지" +
WL #958 PD 지시 6건(테스트 씬 편의 · 대검 · 비수) + 궤적 이펙트 Play 간 막힘 수정 - ① 숫자 키 1~9 · 0 = 그 번호 무기로 즉시 교체(WLSkillTestHarness.SelectWeapon · 번호 = O/P 순환과 같은 종류 순 · 화면 목록) · 이 씬에서 등급 치트 끔 - ② 모든 무기 각성 상태(WLTestScene.AllAwakened → 무기 기술 · 연계 · 파츠 스킬 칸 해금 등급 무시) · 자동 스킬 끔(ManualSkills → PCActor 자동 시전 · WL_TryAutoSkillNow · 파츠 스킬 자동 시전) — Q/E 치트키로만 · 실측 해머 8 s 공격 427 프레임 · 스킬 진입 0 · E = 대지 분쇄 발동 - ③ 대검 주력 모으기 = 검 빛 손잡이 금색 → 칼끝 노랑 축 그라데이션(WL/WeaponGlow _GlowColor2 · _GradAxis · _GradLen · WLWeaponGlowFx 5인자 Set) + 모으기 입자 수명색 노랑 계열(TintGradient · 어두운 층 제외) · 파란 호 = 모으기 사본 루트 ChargePath 렌더러 끔(chargeFxHide) - ④ 대검 각성 회오리 흰 뭉개짐 = WL_Fx_BladeStormTornado 의 HS 재질 _Emission 5(블룸 없는 화면에서 흰색 포화 · 층별 끄기로 확정) → wraps layers 로 몸통 _Emission 0.7 + 푸른 회색 · 회전 줄무늬 2.5 · 바닥 고리 1.8(프로퍼티 블록) - ⑤ 비수 3타 = 이펙트와 같은 반시계 1080°/s 한 바퀴(−360°/0.333 s) · 팔라딘 클립 자체 시계 300°/0.18 s 회전을 덮어씀(WLSwingExtras yaw 덮기 · 끝 0.12 s 섞기) · 실측 엉덩이 각이 코드 각을 그대로 따라감 - ⑥ 비수 주력 = 양손 작은 사본 끔 · 대상 쪽 공격 범위 × 0.5 자리에 1개(폭 = 범위 × 1.6) · 실측 거리 0.50 · 범위 1.00 · 배율 0.304 - 발견 수정: WLImpactFx.s_swingAt(같은 PC·단계 궤적 0.3 s 묶음)가 도메인 리로드 꺼짐으로 Play 사이에 남아 새 Play 의 궤적 이펙트 · 비수 3타 회전이 막힘(실측 swing +0 · spins +0) → Play 시작마다 비움 · 연속 Play 2회 확인(swing +6 · spins +3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:05:18 +00:00
(Cfg.manualSkills != 0 ? "\n스킬 = " + WLSkillHotkey.Key + " · " + WLSkillHotkey.Key2 + " 로만(자동 스킬 끔)" + (Cfg.allAwakened != 0 ? " · 모든 무기 각성" : "") : "") +
(string.IsNullOrEmpty(_weaponLine) ? "" : "\n" + _weaponLine) +
(string.IsNullOrEmpty(_msg) ? "" : "\n" + _msg) + (Ready ? "\n" + string.Join(" · ", Laps.ToArray()) : "");
var r = new Rect(Screen.width * 0.01f, Screen.height * 0.01f, Screen.width * 0.6f, Screen.height * 0.3f);
GUI.color = new Color(0f, 0f, 0f, 0.6f); GUI.Label(new Rect(r.x + 1, r.y + 1, r.width, r.height), s, _style);
GUI.color = Color.white; GUI.Label(r, s, _style);
}
}
}