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

142 lines
9.2 KiB
C#
Raw 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.

// ─────────────────────────────────────────────────────────────────────────────
// 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 과 같은 방식)
// ■ #975 PD 「회전 방향은 공격 대상을 보고 있는데 상체만 움직이고 다리 위치가 제대로 바뀌지 않아 몸이 너무 꼬인 느낌」 — 실측(편집 모드 975_legs):
// 클립은 한 바퀴 돈 뒤 골반 · 상체만 90~108° 더 돌고 발은 처음 자세 그대로(골반 − 발 = 대기 자세 −17° → 쏘는 순간 +73.5° · 0.68 에 +91°)
// → legFollow > 0 이면 정규화 legFrom 부터(legRamp 동안 서서히) 매 프레임 「골반 정면 − 두 발 정면 평균 − legNeutral」 만큼 두 다리(허벅지)를 제 관절 둘레 위 축으로 돌려
// 발이 골반을 따라가게 한다(상체 · 판정 · 투사체는 그대로 · 돌기 중(legFrom 전)은 발이 꼬여 엇갈려 계산하지 않음)
// ■ 값 = 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;
// #975 다리 따라 돌리기 — legFollow(0 = 끔 · 1 = 대기 자세만큼만 남기고 다 풂) · legFrom(정규화 시각) · legRamp(정규화 길이) · legNeutral(대기 자세 골반 − 발 각 °) · legMax(한 프레임 상한 °)
public float legFollow, legFrom, legRamp = 0.06f, legNeutral, legMax = 130f;
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, LegFrames;
public static float LastYaw, LastLeg, LastLegExcess; // #975 마지막 다리 회전(°) · 그때 골반 − 발 − 대기 자세(°)
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, _legW;
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 cur = an.GetCurrentAnimatorStateInfo(0);
var hit = WLSkillYaw.Match(cur); float n = cur.normalizedTime;
if (hit == null && an.IsInTransition(0)) { var nx = an.GetNextAnimatorStateInfo(0); hit = WLSkillYaw.Match(nx); n = nx.normalizedTime; }
if (hit != null) { _rule = hit; _legW = hit.legFollow > 0f ? Mathf.Clamp01((n - hit.legFrom) / Mathf.Max(0.001f, hit.legRamp)) : 0f; } // 나가는 중(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; WLSkillYaw.LastLeg = 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;
if (_rule.legFollow > 0f && _legW > 0f) Legs(an, pc.transform.up, _rule, _legW * _w);
}
catch (System.Exception ex) { WLSkillYaw.LastLog = "회전 실패 — " + ex.Message; _w = 0f; _rule = null; }
}
/// <summary>#975 두 허벅지를 제 관절 둘레 위 축으로 돌려 두 발 정면 평균이 「골반 정면 − legNeutral」 쪽을 보게(k = 진행 가중).</summary>
static void Legs(Animator an, Vector3 up, WLSkillYawRule r, float k)
{
var lul = an.GetBoneTransform(HumanBodyBones.LeftUpperLeg); var rul = an.GetBoneTransform(HumanBodyBones.RightUpperLeg);
var lf = an.GetBoneTransform(HumanBodyBones.LeftFoot); var rf = an.GetBoneTransform(HumanBodyBones.RightFoot);
if (lul == null || rul == null || lf == null || rf == null) return;
Vector3 pelvis = Vector3.ProjectOnPlane(Vector3.Cross(rul.position - lul.position, up), up);
Vector3 fl = Vector3.ProjectOnPlane(FootDir(an, HumanBodyBones.LeftToes, HumanBodyBones.LeftLowerLeg, lul, lf), up);
Vector3 fr = Vector3.ProjectOnPlane(FootDir(an, HumanBodyBones.RightToes, HumanBodyBones.RightLowerLeg, rul, rf), up);
if (pelvis.sqrMagnitude < 1e-8f || fl.sqrMagnitude < 1e-8f || fr.sqrMagnitude < 1e-8f) return;
Vector3 feet = fl.normalized + fr.normalized;
if (feet.sqrMagnitude < 1e-4f) return; // 두 발이 반대쪽(엇갈림) — 평균 없음
float excess = Mathf.Clamp(Mathf.DeltaAngle(0f, Vector3.SignedAngle(feet, pelvis, up) - r.legNeutral), -r.legMax, r.legMax);
float a = excess * r.legFollow * k;
lul.RotateAround(lul.position, up, a); rul.RotateAround(rul.position, up, a);
WLSkillYaw.LegFrames++; WLSkillYaw.LastLeg = a; WLSkillYaw.LastLegExcess = excess;
}
/// <summary>발 정면 = 발 → 발가락 · 발가락 뼈가 없으면 무릎이 향한 쪽(무릎 − 허벅지 · 발 가운데).</summary>
static Vector3 FootDir(Animator an, HumanBodyBones toes, HumanBodyBones knee, Transform hip, Transform foot)
{
var t = an.GetBoneTransform(toes); if (t != null) return t.position - foot.position;
var kn = an.GetBoneTransform(knee); return kn != null ? kn.position - (hip.position + foot.position) * 0.5f : Vector3.zero;
}
}
}