Project_WL/AgentScripts/WL772_Repick.cs

141 lines
7.2 KiB
C#
Raw Permalink 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
// WL772_Repick.cs — PD 지시 #772 보정 : 베이크된 NavMesh 의 「최대 연결 성분」으로 시작 지점·플레이 영역을 다시 고른다.
// ... --entry WL772_Repick.Run --args '["Assets/WL/Settings/WL_Nature07_NavMesh.asset", "Assets/Res_Addr/Map/WL_Nature07.prefab", -9999.0]' (minY = 수면 위 하한)
//
// 1단계 자동 선정은 「평탄 + 수면 근접」만 봤기 때문에 고립된 대지/섬을 고르는 경우가 있었다(07·08·10).
// 여기서는 NavMesh 삼각분할을 union-find 로 묶어 가장 큰 연결 성분을 찾고, 그 성분 안에서
// 중심에 가깝고 법선이 위를 향하는 삼각형을 시작 지점으로 삼는다. 읽기 전용(에셋 미변경).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.AI;
#if UNITY_EDITOR
using UnityEditor;
#endif
public static class WL772_Repick
{
#if UNITY_EDITOR
class UF
{
int[] p;
public UF(int n) { p = new int[n]; for (int i = 0; i < n; i++) p[i] = i; }
public int F(int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }
public void U(int a, int b) { a = F(a); b = F(b); if (a != b) p[a] = b; }
}
public static object Run(string navAsset, string prefabPath, float minY)
{
var data = AssetDatabase.LoadAssetAtPath<NavMeshData>(navAsset);
if (data == null) return "NavMeshData 로드 실패: " + navAsset;
var inst = NavMesh.AddNavMeshData(data);
try
{
var tri = NavMesh.CalculateTriangulation();
int nt = tri.indices.Length / 3;
if (nt == 0) return "삼각형 0";
// 정점 좌표를 양자화해서 공유 정점을 찾는다 (float 오차 흡수)
var map = new Dictionary<long, int>();
var vid = new int[tri.vertices.Length];
for (int i = 0; i < tri.vertices.Length; i++)
{
var v = tri.vertices[i];
long k = ((long)Mathf.RoundToInt(v.x * 100f) * 73856093) ^ ((long)Mathf.RoundToInt(v.y * 100f) * 19349663) ^ ((long)Mathf.RoundToInt(v.z * 100f) * 83492791);
int id;
if (!map.TryGetValue(k, out id)) { id = map.Count; map[k] = id; }
vid[i] = id;
}
// 정점 → 삼각형, union-find
var uf = new UF(nt);
var byVert = new Dictionary<int, List<int>>();
for (int t = 0; t < nt; t++)
for (int j = 0; j < 3; j++)
{
int v = vid[tri.indices[t * 3 + j]];
List<int> l;
if (!byVert.TryGetValue(v, out l)) { l = new List<int>(); byVert[v] = l; }
l.Add(t);
}
foreach (var kv in byVert) for (int i = 1; i < kv.Value.Count; i++) uf.U(kv.Value[0], kv.Value[i]);
// 성분별 면적
var area = new Dictionary<int, float>();
var cent = new Dictionary<int, Vector3>();
var wsum = new Dictionary<int, float>();
for (int t = 0; t < nt; t++)
{
var a = tri.vertices[tri.indices[t * 3]];
var b = tri.vertices[tri.indices[t * 3 + 1]];
var c = tri.vertices[tri.indices[t * 3 + 2]];
float ar = Vector3.Cross(b - a, c - a).magnitude * 0.5f;
int r = uf.F(t);
area[r] = (area.TryGetValue(r, out var v0) ? v0 : 0f) + ar;
cent[r] = (cent.TryGetValue(r, out var v1) ? v1 : Vector3.zero) + (a + b + c) / 3f * ar;
wsum[r] = (wsum.TryGetValue(r, out var v2) ? v2 : 0f) + ar;
}
var top = area.OrderByDescending(k => k.Value).First();
var centroid = cent[top.Key] / Mathf.Max(0.0001f, wsum[top.Key]);
float totalArea = area.Values.Sum();
// 최대 성분의 XZ 범위
var bmin = new Vector3(9e9f, 9e9f, 9e9f); var bmax = -bmin;
var pool = new List<Vector3>();
for (int t = 0; t < nt; t++)
{
if (uf.F(t) != top.Key) continue;
var a = tri.vertices[tri.indices[t * 3]];
var b = tri.vertices[tri.indices[t * 3 + 1]];
var c = tri.vertices[tri.indices[t * 3 + 2]];
var ctr = (a + b + c) / 3f;
var nrm = Vector3.Cross(b - a, c - a).normalized;
bmin = Vector3.Min(bmin, ctr); bmax = Vector3.Max(bmax, ctr);
if (Vector3.Angle(nrm, Vector3.up) < 12f && ctr.y >= minY) pool.Add(ctr);
}
if (pool.Count == 0) pool.Add(centroid);
// 성분 중심에 가장 가까운 평탄 삼각형
var start = pool.OrderBy(v => Vector2.Distance(new Vector2(v.x, v.z), new Vector2(centroid.x, centroid.z))).First();
NavMeshHit hit;
// 스냅은 같은 층으로만 (아래 층 NavMesh 로 끌려가는 것을 막는다)
if (NavMesh.SamplePosition(start, out hit, 2f, NavMesh.AllAreas) && Mathf.Abs(hit.position.y - start.y) < 1f) start = hit.position;
float halfX = (bmax.x - bmin.x) * 0.5f, halfZ = (bmax.z - bmin.z) * 0.5f;
float half = Mathf.Clamp(Mathf.Min(halfX, halfZ), 40f, 100f);
float cx = Mathf.Clamp(start.x, bmin.x + half * 0.5f, bmax.x - half * 0.5f);
float cz = Mathf.Clamp(start.z, bmin.z + half * 0.5f, bmax.z - half * 0.5f);
if (bmax.x - bmin.x < half) cx = (bmin.x + bmax.x) * 0.5f;
if (bmax.z - bmin.z < half) cz = (bmin.z + bmax.z) * 0.5f;
// camYaw = 프리팹의 수면 방향(있으면), 없으면 영역 중심 방향
float yaw = 0f;
var root = PrefabUtility.LoadPrefabContents(prefabPath);
try
{
var waters = root.GetComponentsInChildren<MeshRenderer>(true)
.Where(r => r.name.IndexOf("water", StringComparison.OrdinalIgnoreCase) >= 0
|| (r.sharedMaterial != null && r.sharedMaterial.name.IndexOf("water", StringComparison.OrdinalIgnoreCase) >= 0)).ToArray();
Vector3 look;
if (waters.Length > 0)
{
var nearest = waters.OrderBy(w => Vector2.Distance(new Vector2(start.x, start.z), new Vector2(w.bounds.center.x, w.bounds.center.z))).First();
look = new Vector3(nearest.bounds.center.x - start.x, 0f, nearest.bounds.center.z - start.z);
}
else look = new Vector3(cx - start.x, 0f, cz - start.z);
if (look.sqrMagnitude < 1f) look = Vector3.forward;
yaw = Mathf.Repeat(Quaternion.LookRotation(look).eulerAngles.y, 360f);
}
finally { PrefabUtility.UnloadPrefabContents(root); }
return string.Format(
"CSV,{0:F3},{1:F3},{2:F3},{3:F1},{4:F1},{5:F1},{6:F1} | 성분 {7}개 · 최대성분 면적 {8:F0}/{9:F0} ({10:F0}%) · XZ {11:F0}~{12:F0} x {13:F0}~{14:F0} · 평탄삼각형 {15}",
start.x, start.y, start.z, yaw, cx, cz, half,
area.Count, top.Value, totalArea, 100f * top.Value / totalArea,
bmin.x, bmax.x, bmin.z, bmax.z, pool.Count);
}
finally { NavMesh.RemoveNavMeshData(inst); }
}
#endif
}