74 lines
5.1 KiB
C#
74 lines
5.1 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLHitboxFit.cs — 기본 공격 판정 상자를 직업 · 타수별 궤적 길이에 맞춘다 — 표 기반 (#904 · 2026-10-17)
|
||
//
|
||
// PD 원문: 「소드 공격은 궤적 범위에 비해 실제 판정 범위가 너무 넓은 것 같아. 공격 판정 영역(공격 가능 사정거리)을 궤적에 맞게 수정
|
||
// (검 2타 공격의 공격 범위가 모자라는 부분은 2타 공격 시점에 전진하며 맞출 수 있게 · 다른 근접 무기도 타수마다 사정거리가 다르면
|
||
// 전진하며 맞추거나 궤적을 늘리는 등으로 자연스러운 타격)」
|
||
// ■ 원리 — 원본 Actor.Shoot_Projectile 훅 1줄이 판정 상자(Projectile_101xx 등 · 원본 = PC 몸 중심 앞 0.3 m · BoxCollider 깊이 2 m · 중심 z 0.5
|
||
// = PC 정면 −0.2 ~ 1.8 m)를 만든 직후 부른다. 메인 PC 직업 · 콤보 단계가 표에 있으면 상자의 앞 끝을 frontReach(PC 몸 중심에서 정면 m)로,
|
||
// 뒤 끝은 원본 그대로 두고 깊이 · 중심만 바꾼다(너비 · 높이 그대로). 표에 없으면 원본 값으로 되돌린다(풀 인스턴스를 창 등 다른 직업도 같이 쓴다).
|
||
// ■ 값 = Resources/WL/WLHitboxFit.json (C45 · frontReach = Play 실측 궤적 정면 도달) · 끄기 = enabled 0
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
using UnityEngine;
|
||
|
||
namespace WL.Combat.Weapon
|
||
{
|
||
[System.Serializable] public class WLHitboxRule { public int[] classIds = new int[0]; public int stage; public float frontReach; }
|
||
[System.Serializable] public class WLHitboxJson { public int enabled; public float spawnForward = 0.3f; public WLHitboxRule[] rules = new WLHitboxRule[0]; public string note = ""; }
|
||
|
||
/// <summary>풀 인스턴스의 원본 상자 값(처음 본 값)을 기억한다.</summary>
|
||
public sealed class WLHitboxBase : MonoBehaviour { public bool has; public Vector3 size, center; }
|
||
|
||
public static class WLHitboxFit
|
||
{
|
||
public const string ResourcesPath = "WL/WLHitboxFit";
|
||
public static int Fitted, Restored; public static string LastLog = "";
|
||
static WLHitboxJson s_cfg;
|
||
|
||
static WLHitboxJson Cfg
|
||
{
|
||
get
|
||
{
|
||
if (s_cfg != null) return s_cfg;
|
||
s_cfg = new WLHitboxJson();
|
||
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; }
|
||
|
||
/// <summary>원본 Actor.Shoot_Projectile 훅 — 판정 상자가 만들어진 직후.</summary>
|
||
public static void Apply(Actor shooter, ProjectileBase pb)
|
||
{
|
||
try
|
||
{
|
||
if (pb == null) return;
|
||
var bc = pb.GetComponent<BoxCollider>(); if (bc == null) return;
|
||
var b = pb.GetComponent<WLHitboxBase>(); if (b == null) b = pb.gameObject.AddComponent<WLHitboxBase>();
|
||
if (!b.has) { b.has = true; b.size = bc.size; b.center = bc.center; }
|
||
WLHitboxRule hit = null;
|
||
var c = Cfg;
|
||
var pc = shooter as PCActor;
|
||
if (c.enabled != 0 && pc != null && pc.IsMainPC() && c.rules != null)
|
||
{
|
||
var ct = pc.GetComponent<WL.Combat.Core.ComboTracker>();
|
||
int stage = ct != null ? ct.LastStage : 0, cls = pc.Get_ID();
|
||
foreach (var r in c.rules)
|
||
if (r != null && r.frontReach > 0f && r.classIds != null && System.Array.IndexOf(r.classIds, cls) >= 0 && (r.stage == 0 || r.stage == stage)) { hit = r; if (r.stage == stage) break; }
|
||
}
|
||
if (hit == null) { if (bc.size != b.size || bc.center != b.center) { bc.size = b.size; bc.center = b.center; Restored++; } return; }
|
||
// 뒤 끝(원본) 고정 · 앞 끝 = frontReach(PC 몸 중심 기준) − 생성 위치 앞쪽 거리(spawnForward)
|
||
float back = b.center.z - b.size.z * 0.5f;
|
||
float front = Mathf.Max(back + 0.1f, hit.frontReach - c.spawnForward);
|
||
bc.size = new Vector3(b.size.x, b.size.y, front - back);
|
||
bc.center = new Vector3(b.center.x, b.center.y, (front + back) * 0.5f);
|
||
Fitted++; LastLog = pb.name + " cls " + pc.Get_ID() + " 단계 " + hit.stage + " 앞 끝 " + hit.frontReach.ToString("0.##") + " m";
|
||
}
|
||
catch (System.Exception ex) { LastLog = "실패 — " + ex.Message; }
|
||
}
|
||
}
|
||
}
|