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

109 lines
5.5 KiB
C#

// ─────────────────────────────────────────────────────────────────────────────
// WLSkillYaw.cs — 스킬 동작 상태 동안 보이는 몸(엉덩이 뼈)을 위 축으로 일정 각만큼 돌려 공격 대상 쪽에 맞춘다 (#974 · 2026-10-23)
//
// PD 원문: 「한손검 주력 스킬 시 회전할 때 공격 대상의 방향과 일치하도록 수정해줘 (현재는 공격 투사체 방향보다 더 회전하고 있어.)」
// ■ 실측(#974 진단 Play · 974_probe Spin): 검기 분출 동작 energyslash(월광 클립 combo09_3)는 몸을 한 바퀴 반 돌리고,
// 검기를 쏘는 순간(정규화 0.41~0.45) 몸이 대상 · 검기 방향과 어긋난다(PC 루트 · 검기는 대상 쪽 · 어긋난 각 = 규칙 yaw 의 근거 · JSON 메모)
// ■ 처리 = 규칙 상태(지금 상태 또는 넘어가는 중인 다음 상태)일 때 LateUpdate(애니메이션 평가 뒤)에서 엉덩이 뼈를 PC 루트 둘레 위 축으로 yaw° —
// 들어갈 때 blendIn · 나갈 때 blendOut 초에 걸쳐 · 애니메이터가 매 프레임 뼈를 다시 쓰므로 누적 없음 · 루트 · 판정 · 투사체 방향 · 카메라는 그대로(보이는 몸만)
// (WLImpactFx 비수 3타 회전 #956 과 같은 방식)
// ■ 값 = Resources/WL/WLSkillYaw.json (C45) · 끄기 = enabled 0
// 🔴 Debug.LogError 금지(원본 ErrorLogHook 팝업).
// ─────────────────────────────────────────────────────────────────────────────
using UnityEngine;
namespace WL.Combat.Weapon
{
[System.Serializable]
public class WLSkillYawRule
{
public string state = ""; // 애니메이터 0층 상태 이름(예: energyslash)
public float yaw; // 보이는 몸을 돌릴 각(°) · + = 위에서 볼 때 시계 방향
public float blendIn = 0.08f, blendOut = 0.15f;
public string note = "";
}
[System.Serializable]
public class WLSkillYawJson
{
public int enabled = 1;
public WLSkillYawRule[] rules = new WLSkillYawRule[0];
public string note = "";
}
public static class WLSkillYaw
{
public const string ResourcesPath = "WL/WLSkillYaw";
public static int Frames;
public static float LastYaw;
public static string LastLog = "";
static WLSkillYawJson s_cfg;
static GameObject s_runner;
internal static WLSkillYawJson Cfg
{
get
{
if (s_cfg != null) return s_cfg;
s_cfg = new WLSkillYawJson();
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;
}
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void Boot()
{
s_cfg = null;
if (Cfg.enabled == 0 || s_runner != null) return;
s_runner = new GameObject("[WL974] SkillYaw");
s_runner.hideFlags = HideFlags.HideAndDontSave;
Object.DontDestroyOnLoad(s_runner);
s_runner.AddComponent<WLSkillYawRunner>();
}
internal static WLSkillYawRule Match(AnimatorStateInfo st)
{
var rules = Cfg.rules;
if (rules == null) return null;
for (int i = 0; i < rules.Length; i++) { var r = rules[i]; if (r != null && !string.IsNullOrEmpty(r.state) && st.IsName(r.state)) return r; }
return null;
}
}
[DefaultExecutionOrder(-100)] // 손 · 무기를 따라가는 궤적 · 이펙트(LateUpdate)보다 먼저 몸을 돌린다
internal sealed class WLSkillYawRunner : MonoBehaviour
{
float _w;
WLSkillYawRule _rule;
void LateUpdate()
{
try
{
if (WLSkillYaw.Cfg.enabled == 0) return;
var pc = MyValue.MyPC as PCActor;
if (pc == null || pc.m_animation == null || !pc.m_animation.isHuman) { _w = 0f; _rule = null; return; }
var an = pc.m_animation;
var hit = WLSkillYaw.Match(an.GetCurrentAnimatorStateInfo(0));
if (hit == null && an.IsInTransition(0)) hit = WLSkillYaw.Match(an.GetNextAnimatorStateInfo(0));
if (hit != null) _rule = hit;
if (_rule == null) return;
float target = hit != null ? 1f : 0f;
float dur = target > _w ? _rule.blendIn : _rule.blendOut;
_w = Mathf.MoveTowards(_w, target, Time.deltaTime / Mathf.Max(0.01f, dur));
if (_w <= 0f) { _rule = null; WLSkillYaw.LastYaw = 0f; return; }
var hips = an.GetBoneTransform(HumanBodyBones.Hips);
if (hips == null) return;
float d = _rule.yaw * _w;
hips.RotateAround(pc.transform.position, pc.transform.up, d);
WLSkillYaw.Frames++; WLSkillYaw.LastYaw = d;
}
catch (System.Exception ex) { WLSkillYaw.LastLog = "회전 실패 — " + ex.Message; _w = 0f; _rule = null; }
}
}
}