Project_WL/AgentScripts/WL796_Apply.cs

252 lines
13 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
// WL796_Apply.cs — PD 지시 #796 적용 (에디트 모드 전용 · 프리팹/설정 에셋 수정)
// unity command run_script --file AgentScripts/WL796_Apply.cs --entry WL796_Apply.ApplyAll
// unity command run_script --file AgentScripts/WL796_Apply.cs --entry WL796_Apply.Report
//
// 1) NewGameUI.prefab / IngameUIs/WL_HUD/HUD_BottomMenu
// - 바 폭 = 캔버스 폭(1920 단위) / 스케일 1.55 = 1238.71 → 좌우를 가득 채운다
// - 바 바닥 y = 10 단위(≈5.6px) — 가운데 전투 버튼의 ProgressText 가 -5.9 단위까지 내려가 잘리지 않게 하는 최소 여유
// - 버튼 5개: 셀 중심 앵커(0.1/0.3/0.5/0.7/0.9 — 원본 그대로) · 셀 폭 = 바폭/5 - 간격 2
// - 일반 버튼 높이 140 → 200 (아이콘이 rect 위로 42 단위 튀어나와 터치가 안 되던 것을 rect 안으로 넣는다)
// - 자식은 "부모 피벗(하단 중앙) 기준 localPosition" 을 기록/복원해 시각적 위치를 그대로 유지한다
// - BottomBarFitter 부착(해상도·Safe Area 변화 시 런타임 재계산)
// 2) WLGameplaySettings.asset : hideIngameChat = true · ingameHideCanvasGroupPaths = [MessageInfo/Chat]
//
// 🔴 Play 중 실행 금지(에셋을 디스크에 쓴다). 백업 = 공유/개발팀_백업/WL/Hud2_20260907_0143/
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEngine;
// WL.Settings / WL.UI 를 using 하지 않는다 — run_script 어셈블리가 Assembly-CSharp 최신 스냅샷을 못 볼 수 있어
// 새로 추가한 타입·필드는 전부 SerializedObject/리플렉션으로 접근한다.
public static class WL796_Apply
{
const string PrefabPath = "Assets/Res_Addr/MainUI/NewGameUI.prefab";
const string MenuPath = "IngameUIs/WL_HUD/HUD_BottomMenu";
// 캔버스 폭(세로 화면에서 항상 1920 단위) / HUD_BottomMenu localScale.x(1.55)
const float CanvasWidthUnits = 1920f;
const float BarBottomY = 10f; // Safe Area 바닥에서 띄우는 최소 여유(단위)
const float BarHeight = 280f; // 244 px @1080x1920 — 사양 230~260 px 창 안
const float CellGap = 2f; // ≈1.1 px — 인접 사각형 겹침 0 보장
const float NormalButtonHeight = 200f;
/// <summary>WL.UI.BottomBarFitter 타입을 로드된 어셈블리에서 찾는다(컴파일 타임 참조 회피).</summary>
static System.Type FitterType()
{
var t = System.Type.GetType("WL.UI.BottomBarFitter, Assembly-CSharp");
if (t != null) return t;
foreach (var asm in System.AppDomain.CurrentDomain.GetAssemblies())
{
t = asm.GetType("WL.UI.BottomBarFitter");
if (t != null) return t;
}
return null;
}
static Transform FindByPath(Transform root, string path)
{
var cur = root;
foreach (var raw in path.Split('/'))
{
var name = raw.Trim();
if (name.Length == 0) continue;
Transform next = null;
for (int c = 0; c < cur.childCount; c++)
if (cur.GetChild(c).name == name) { next = cur.GetChild(c); break; }
if (next == null) return null;
cur = next;
}
return cur;
}
public static object ApplyAll()
{
if (EditorApplication.isPlaying) return "ABORT: Play 중에는 실행하지 않는다(에셋 디스크 쓰기).";
var sb = new StringBuilder();
sb.AppendLine(ApplyPrefabInternal());
sb.AppendLine(ApplySettingsInternal());
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
return sb.ToString();
}
static string ApplyPrefabInternal()
{
var sb = new StringBuilder();
var root = PrefabUtility.LoadPrefabContents(PrefabPath);
if (root == null) return "prefab load 실패: " + PrefabPath;
try
{
var menuT = FindByPath(root.transform, MenuPath);
if (menuT == null) { return "HUD_BottomMenu 없음: " + MenuPath; }
var menu = menuT as RectTransform;
float scale = menu.localScale.x;
float barWidth = CanvasWidthUnits / scale;
int n = menu.childCount;
if (n <= 0) return "버튼 없음";
int center = n / 2;
float cellW = barWidth / n;
sb.AppendLine("[before] bar sizeDelta=" + menu.sizeDelta + " anchoredPos=" + menu.anchoredPosition + " scale=" + scale);
// ── 1) 바 ────────────────────────────────────────────────────────
menu.sizeDelta = new Vector2(barWidth, BarHeight);
menu.anchoredPosition = new Vector2(0f, BarBottomY);
// ── 2) 버튼 5개 ──────────────────────────────────────────────────
for (int i = 0; i < n; i++)
{
var b = menu.GetChild(i) as RectTransform;
if (b == null) continue;
// 자식들의 "버튼 피벗(하단 중앙) 기준" 위치를 기록 → rect 를 키운 뒤 그대로 복원한다.
var keep = new List<KeyValuePair<Transform, Vector3>>();
for (int c = 0; c < b.childCount; c++)
keep.Add(new KeyValuePair<Transform, Vector3>(b.GetChild(c), b.GetChild(c).localPosition));
var before = b.sizeDelta;
float mid = (i + 0.5f) / n;
var aMin = b.anchorMin; var aMax = b.anchorMax;
aMin.x = mid; aMax.x = mid;
b.anchorMin = aMin; b.anchorMax = aMax;
var ap = b.anchoredPosition; ap.x = 0f; b.anchoredPosition = ap;
float h = (i == center) ? b.sizeDelta.y : NormalButtonHeight;
b.sizeDelta = new Vector2(cellW - CellGap, h);
foreach (var kv in keep) kv.Key.localPosition = kv.Value; // 시각 위치 그대로
sb.AppendLine(" " + b.name + " : size " + before + " -> " + b.sizeDelta +
" anchorX=" + mid.ToString("F2") + " children=" + keep.Count);
}
// ── 3) 런타임 보정 컴포넌트 ──────────────────────────────────────
// run_script 어셈블리가 Assembly-CSharp 의 최신 스냅샷을 못 볼 수 있어 타입 참조 대신 리플렉션으로 붙인다.
var fitterType = FitterType();
if (fitterType == null) sb.AppendLine(" ⚠ BottomBarFitter 타입 없음 — 프리팹 값만 적용(런타임 보정 미부착)");
else
{
var fitter = menu.GetComponent(fitterType);
if (fitter == null) { fitter = menu.gameObject.AddComponent(fitterType); sb.AppendLine(" BottomBarFitter 추가"); }
else sb.AppendLine(" BottomBarFitter 이미 있음");
var so = new SerializedObject(fitter);
so.FindProperty("fitWidthToParent").boolValue = true;
so.FindProperty("centerCellMultiplier").floatValue = 1f; // 옵션 A(균등 5등분) 기본
so.FindProperty("cellGap").floatValue = CellGap;
so.FindProperty("uniformButtonHeight").floatValue = 0f; // 프리팹 높이 유지
so.ApplyModifiedPropertiesWithoutUndo();
}
sb.AppendLine("[after] bar sizeDelta=" + menu.sizeDelta + " anchoredPos=" + menu.anchoredPosition +
" cellW=" + cellW.ToString("F2") + " (px@1080=" + (cellW * scale * 0.5625f).ToString("F1") + ")");
PrefabUtility.SaveAsPrefabAsset(root, PrefabPath);
sb.AppendLine("saved prefab");
}
finally { PrefabUtility.UnloadPrefabContents(root); }
return sb.ToString();
}
const string SettingsPath = "Assets/WL/Settings/Resources/WL/WLGameplaySettings.asset";
static string ApplySettingsInternal()
{
var s = AssetDatabase.LoadAssetAtPath<ScriptableObject>(SettingsPath);
if (s == null) return "WLGameplaySettings.asset 없음";
var so = new SerializedObject(s);
var p = so.FindProperty("hideIngameChat");
var arr = so.FindProperty("ingameHideCanvasGroupPaths");
if (p == null || arr == null)
return "⚠ 설정 필드 미인식(WLGameplaySettings.cs 컴파일 반영 대기) — hideIngameChat/ingameHideCanvasGroupPaths 미기록";
p.boolValue = true;
arr.arraySize = 1;
arr.GetArrayElementAtIndex(0).stringValue = "MessageInfo/Chat";
so.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(s);
return "settings: hideIngameChat=true paths=[MessageInfo/Chat]";
}
/// <summary>적용 결과 확인(프리팹 값 · 설정 에셋 값).</summary>
public static object Report()
{
var sb = new StringBuilder();
var go = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
var menu = go != null ? FindByPath(go.transform, MenuPath) as RectTransform : null;
if (menu == null) return "HUD_BottomMenu 없음";
float scale = menu.localScale.x, k = scale * 0.5625f; // 1080x1920 기준 px 환산
sb.AppendLine("bar sizeDelta=" + menu.sizeDelta + " anchoredPos=" + menu.anchoredPosition + " scale=" + scale +
" => px w=" + (menu.sizeDelta.x * k).ToString("F0") + " h=" + (menu.sizeDelta.y * k).ToString("F0") +
" bottom=" + (menu.anchoredPosition.y * 0.5625f).ToString("F0"));
var ft = FitterType();
sb.AppendLine("fitter=" + (ft != null && menu.GetComponent(ft) != null));
for (int i = 0; i < menu.childCount; i++)
{
var b = menu.GetChild(i) as RectTransform;
sb.AppendLine(" " + b.name + " anchorX=" + b.anchorMin.x.ToString("F2") + " size=" + b.sizeDelta +
" => px w=" + (b.sizeDelta.x * k).ToString("F0") + " h=" + (b.sizeDelta.y * k).ToString("F0"));
}
var s = AssetDatabase.LoadAssetAtPath<ScriptableObject>(SettingsPath);
if (s != null)
{
var so2 = new SerializedObject(s);
var p = so2.FindProperty("hideIngameChat");
var arr = so2.FindProperty("ingameHideCanvasGroupPaths");
var pad = so2.FindProperty("hideBattlePad");
var paths = new List<string>();
if (arr != null) for (int i = 0; i < arr.arraySize; i++) paths.Add(arr.GetArrayElementAtIndex(i).stringValue);
sb.AppendLine("settings hideIngameChat=" + (p != null ? p.boolValue.ToString() : "(필드없음)") +
" paths=[" + string.Join(",", paths.ToArray()) + "]" +
" hideBattlePad=" + (pad != null ? pad.boolValue.ToString() : "?"));
}
return sb.ToString();
}
/// <summary>옵션 B(가운데 셀 1.3배)로 전환. 프리팹의 앵커·폭을 다시 계산한다.</summary>
public static object SetCenterMultiplier(float mul)
{
if (EditorApplication.isPlaying) return "ABORT: Play 중";
var root = PrefabUtility.LoadPrefabContents(PrefabPath);
if (root == null) return "prefab load 실패";
var sb = new StringBuilder();
try
{
var menu = FindByPath(root.transform, MenuPath) as RectTransform;
if (menu == null) return "HUD_BottomMenu 없음";
int n = menu.childCount, center = n / 2;
float barWidth = menu.sizeDelta.x;
float total = 0f;
for (int i = 0; i < n; i++) total += (i == center ? mul : 1f);
float cursor = 0f;
for (int i = 0; i < n; i++)
{
var b = menu.GetChild(i) as RectTransform;
float w = (i == center ? mul : 1f) / total;
float mid = cursor + w * 0.5f; cursor += w;
var keep = new List<KeyValuePair<Transform, Vector3>>();
for (int c = 0; c < b.childCount; c++) keep.Add(new KeyValuePair<Transform, Vector3>(b.GetChild(c), b.GetChild(c).localPosition));
var aMin = b.anchorMin; var aMax = b.anchorMax; aMin.x = mid; aMax.x = mid;
b.anchorMin = aMin; b.anchorMax = aMax;
var ap = b.anchoredPosition; ap.x = 0f; b.anchoredPosition = ap;
b.sizeDelta = new Vector2(barWidth * w - CellGap, b.sizeDelta.y);
foreach (var kv in keep) kv.Key.localPosition = kv.Value;
sb.AppendLine(" " + b.name + " anchorX=" + mid.ToString("F3") + " w=" + b.sizeDelta.x.ToString("F1"));
}
var ft = FitterType();
var fitter = ft != null ? menu.GetComponent(ft) : null;
if (fitter != null)
{
var so = new SerializedObject(fitter);
so.FindProperty("centerCellMultiplier").floatValue = mul;
so.ApplyModifiedPropertiesWithoutUndo();
}
PrefabUtility.SaveAsPrefabAsset(root, PrefabPath);
}
finally { PrefabUtility.UnloadPrefabContents(root); }
AssetDatabase.SaveAssets();
return "centerCellMultiplier=" + mul + "\n" + sb;
}
}