Project_WL/AgentScripts/WL791_FootProbe.cs

92 lines
5.8 KiB
C#
Raw Normal View History

WL 리뉴얼 세션 2026-09-06~07 (#760~#800) — 전투·배경·UI·타격감 (조직 PD 로그 #746~#800 · 대화로그 §14~§24) - 전투: Knight@Attack1~3_S 3콤보(4 컨트롤러·클래스별 서브클립) · 타겟팅 4규칙(정면 우선·재타겟·즉시 공격·어그로 금지 · WLTargetingSettings) · 대쉬 후 공격(Stander@Chase_Start · DashDriver · 5 m · 사정거리+0.8 · 근거리 적 우선) · 공격 이동 FrameTable(발 접지 실측 표 · AttackRootMotion) · 충돌 반경/투사체 0.3 배(WLCollisionTuning) · 무적(임시)·펫 금지(WLGameplaySettings) - 검기: NamuFX Slash_B 배리언트 Effect_WLSwingArc 원 피팅 정합 배치 + 캘리브레이션(SlashArcMeasure · SlashTrailSettings) · 찌르기 Effect_WLStab 대기 · 램프 리본(BladeTrail · WL_BladeRibbon.shader · T_WL_BladeRibbonRamp) 보존(drawRibbon 0) - 타격감: Assets/WL/Feel(WLHitFeel · 히트스톱 0.03 · 셰이크 0.10 m · 몹 펀치 1.12 · Actor.Get_Damage 훅 1줄 · 원본 RealCamera 셰이크 결함 대체) - 배경/맵: LMHPOLY Demo_01~10 → WL_Nature01~10(프리팹·씬·NavMesh·스포너·BattleMapConfig) · 물(ToonWaterU) · 포스트 블룸 0.9/0.3 · 잔디(BruteForce·드레싱) 제거 · 마젠타 머티리얼 URP 변환 - UI: 세로 HUD(WL_HUD · 하단 5메뉴 폭 전체 · 채팅/전투 패드 숨김 · WLIngameUiOverride) · Title/TitleInfo 1080×1920 Expand + 배경 높이 fit(WLBackgroundFit) · 로딩 SortOrder_5(WLRawImageAspectSync · 초점표 8장) · Loading1~8 ASTC 6×6 - 도구: AgentScripts/*(LightProbe · WL_MapSwitch · WL760~WL800 프로브/집행/검증 · 상단 사용법 주석) · 에디터 락 프로토콜 파일(staging) - 제외(별도 커밋 예정): Assets/LMHPOLY(703 MB) · Assets/Feel(422 MB) · Assets/Shinabro(300 MB) — 에셋 스토어/구 WL 팩 원본 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 23:24:25 +00:00
// WL791_FootProbe.cs — #791 공격 클립의 발 움직임 실측 → 자연스러운 프레임당 이동값 도출 (에디트 모드 · 읽기 전용 · 프리뷰 씬)
// unity command run_script --file AgentScripts/WL791_FootProbe.cs --entry WL791_FootProbe.Run --args '["Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack1_S.FBX", "Attack1"]'
// unity command run_script --file AgentScripts/WL791_FootProbe.cs --entry WL791_FootProbe.All
// 방법: 실제 플레이어 모델(Assets/Res_Addr/PC/Ai01.prefab · Humanoid)을 프리뷰 씬에 띄우고 AnimationMode 로 클립을 30fps 프레임마다 샘플(루트모션 미적용)
// → 좌/우 발(LeftFoot/RightFoot)의 루트 기준 XZ 를 기록. "접지" = 발 높이 ≤ 최저+5 cm. 접지한 발이 루트 기준으로 뒤로 흐르는 양(dz) = 그 프레임에
// 캐릭터가 앞으로 가야 발이 땅에 붙어 보이는 양(제자리 애니메이션의 발 슬라이딩 보정). 접지 발이 앞으로 가는 프레임(딛는 순간)은 0.
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
public static class WL791_FootProbe
{
const string ModelPath = "Assets/Res_Addr/PC/Ai01.prefab";
static readonly string[][] Targets =
{
new[] { "Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack1_S.FBX", "Attack1" },
new[] { "Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack2_S.FBX", "Attack2" },
new[] { "Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack3_S.FBX", "Attack3" },
};
public static object All()
{
var sb = new StringBuilder();
foreach (var t in Targets) sb.Append(Run(t[0], t[1])).AppendLine();
return sb.ToString();
}
public static object Run(string fbxPath, string clipName)
{
var model = AssetDatabase.LoadAssetAtPath<GameObject>(ModelPath);
var clip = AssetDatabase.LoadAllAssetsAtPath(fbxPath).OfType<AnimationClip>().FirstOrDefault(c => c.name == clipName);
if (model == null || clip == null) return "load fail model=" + (model != null) + " clip=" + (clip != null) + " " + fbxPath;
var scene = EditorSceneManager.NewPreviewScene();
var sb = new StringBuilder();
bool animMode = false;
try
{
var go = (GameObject)PrefabUtility.InstantiatePrefab(model, scene);
go.transform.position = Vector3.zero; go.transform.rotation = Quaternion.identity;
var anim = go.GetComponentInChildren<Animator>();
if (anim == null || anim.avatar == null) return "animator/avatar null on " + ModelPath;
var animGo = anim.gameObject;
anim.applyRootMotion = false;
var lf = anim.GetBoneTransform(HumanBodyBones.LeftFoot);
var rf = anim.GetBoneTransform(HumanBodyBones.RightFoot);
var hips = anim.GetBoneTransform(HumanBodyBones.Hips);
if (lf == null || rf == null || hips == null) return "foot/hips bones null avatar=" + anim.avatar.name;
float fps = clip.frameRate; int n = Mathf.RoundToInt(clip.length * fps);
var L = new Vector3[n + 1]; var R = new Vector3[n + 1]; var H = new Vector3[n + 1];
AnimationMode.StartAnimationMode(); animMode = true;
for (int i = 0; i <= n; i++)
{
AnimationMode.BeginSampling();
AnimationMode.SampleAnimationClip(animGo, clip, i / fps);
AnimationMode.EndSampling();
L[i] = animGo.transform.InverseTransformPoint(lf.position);
R[i] = animGo.transform.InverseTransformPoint(rf.position);
H[i] = animGo.transform.InverseTransformPoint(hips.position);
}
float minY = Mathf.Min(L.Min(p => p.y), R.Min(p => p.y));
float groundThr = minY + 0.05f;
sb.AppendLine("== " + clipName + " len=" + clip.length.ToString("F3") + "s frames=" + n + " fps=" + fps + " model=" + anim.avatar.name + " minFootY=" + minY.ToString("F3") + " ==");
sb.AppendLine(" f | Lz Ly | Rz Ry | 접지 | 루트전진(cm) | 누적(cm) | hipsZ(cm)");
float accum = 0f; var perFrame = new float[n + 1];
for (int i = 1; i <= n; i++)
{
bool lG = L[i].y <= groundThr, rG = R[i].y <= groundThr;
float lDz = L[i].z - L[i - 1].z, rDz = R[i].z - R[i - 1].z;
float step = 0f; string g = "-";
if (lG && rG) { step = Mathf.Max(-lDz, -rDz); g = "LR"; }
else if (lG) { step = -lDz; g = "L"; }
else if (rG) { step = -rDz; g = "R"; }
if (step < 0f) step = 0f;
perFrame[i] = step; accum += step;
sb.AppendLine(string.Format(" {0,2} | {1,5:F2} {2,5:F2} | {3,5:F2} {4,5:F2} | {5,-3} | {6,7:F1} | {7,7:F1} | {8,6:F1}",
i, L[i].z, L[i].y, R[i].z, R[i].y, g, step * 100f, accum * 100f, H[i].z * 100f));
}
sb.AppendLine("합계 전진 = " + (accum * 100f).ToString("F1") + " cm · 프레임당(cm): " + string.Join(",", perFrame.Skip(1).Select(v => (v * 100f).ToString("F1"))));
float maxStride = Enumerable.Range(0, n + 1).Max(i => Mathf.Abs(L[i].z - R[i].z));
sb.AppendLine("최대 보폭(두 발 z 차) = " + (maxStride * 100f).ToString("F1") + " cm · hips z 범위 " + (H.Min(p => p.z) * 100f).ToString("F0") + "~" + (H.Max(p => p.z) * 100f).ToString("F0") + " cm · 발 z 범위 L " + (L.Min(p => p.z) * 100f).ToString("F0") + "~" + (L.Max(p => p.z) * 100f).ToString("F0") + " R " + (R.Min(p => p.z) * 100f).ToString("F0") + "~" + (R.Max(p => p.z) * 100f).ToString("F0"));
}
finally
{
if (animMode) AnimationMode.StopAnimationMode();
EditorSceneManager.ClosePreviewScene(scene);
}
return sb.ToString();
}
}