Project_WL/AgentScripts/WL793_Apply.cs

318 lines
16 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
// WL793_Apply.cs — PD 지시 #793·#794·#795 타이틀 해상도 대응 집행 (에디트 모드 · Assets 수정)
//
// unity command run_script --file AgentScripts/WL793_Apply.cs --entry WL793_Apply.ApplyScene --args '[0.29]'
// unity command run_script --file AgentScripts/WL793_Apply.cs --entry WL793_Apply.ApplyPrefab --args '[0.29]'
// unity command run_script --file AgentScripts/WL793_Apply.cs --entry WL793_Apply.ApplyTexture
// unity command run_script --file AgentScripts/WL793_Apply.cs --entry WL793_Apply.SetBgPivot --args '[0.5]' // 크롭 기준점만 재조정
//
// 진단(실측 근거):
// #794 원인 = CanvasScaler 참조 1920x1080 + 폭기준. 세로 1080x1920 에서 scaleFactor 0.5625 →
// 로그인 버튼 104px 가 화면 58.5px 로 축소. (권장 터치 타깃 88~120px 대비 절반 이하)
// #795 원인 = 배경 RawImage 가 앵커 스트레치라 1920x1080 원본이 1920x3413 캔버스에 세로 3.16배로
// 늘어남(종횡비 파괴) + Default 압축이 CompressedLQ(ASTC_8x8). 둘 다 "뭉개짐"으로 보인다.
//
// 해법:
// 스케일 = 참조 1080x1920 · Expand (인게임 NewGameUI #775 와 동일 기준으로 통일).
// 배경 = 스톡 AspectRatioFitter(HeightControlsWidth) 로 "세로 맞춤 · 종횡비 유지 · 중앙".
// 커스텀 런타임 컴포넌트를 만들지 않는다(오버드로우·코드 추가 최소, C11).
// 비는 영역은 부모 RawImage 를 검정 무지로 바꿔 백드롭으로 재사용한다(레이어 +1).
// 크롭 기준점 = 배경 RectTransform 의 pivot.x 하나로 제어된다(수식: 화면 중앙에 오는
// 원본 이미지의 정규화 x = pivot.x). 아트가 이미지별로 조정 가능한 단일 숫자.
//
// 값의 SOT 는 이 스크립트가 아니라 씬/프리팹이다. 여기서는 "기본값 1회 주입"만 하고 이후는 인스펙터에서 튜닝한다(C45).
using System.Text;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UI;
public static class WL793_Apply
{
const string ScenePath = "Assets/Scenes/Title.unity";
const string PrefabPath = "Assets/ResWork/UIPrefabs/Title/TitleInfo.prefab";
const string BgTexPath = "Assets/Res_Addr/Loading/Loading4.jpg";
const string BgChild = "bg_image"; // 새로 만드는 배경 자식 이름
static readonly Vector2 Ref = new Vector2(1080f, 1920f); // 세로 기준 참조 해상도
// ---------- 공통 유틸 ----------
static void FixScaler(CanvasScaler cs, StringBuilder sb)
{
sb.AppendLine(" CanvasScaler: ref " + cs.referenceResolution + " / " + cs.screenMatchMode
+ " -> ref " + Ref + " / Expand");
cs.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
cs.referenceResolution = Ref;
cs.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
}
/// 전체 스트레치 배경 RawImage 를 [검정 백드롭(부모) + 종횡비 유지 배경(자식)] 구조로 바꾼다.
/// 부모의 Button/raycast 는 그대로 두어 "화면 아무 곳이나 터치" 동작을 보존한다.
static void BuildBackground(RawImage parentRaw, float focusX, StringBuilder sb)
{
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(BgTexPath);
if (tex == null) { sb.AppendLine(" !! 배경 텍스처 로드 실패 " + BgTexPath); return; }
var parentRt = parentRaw.rectTransform;
// 1) 부모 = 검정 백드롭 (좌우/상하 여백을 확실히 검정으로 채운다. 이 씬엔 카메라가 없어
// 카메라 클리어에 기댈 수 없다.)
parentRaw.texture = null;
parentRaw.color = Color.black;
// 부모는 전체 스트레치를 유지
parentRt.anchorMin = Vector2.zero;
parentRt.anchorMax = Vector2.one;
parentRt.pivot = new Vector2(0.5f, 0.5f);
parentRt.anchoredPosition = Vector2.zero;
parentRt.sizeDelta = Vector2.zero;
sb.AppendLine(" " + parentRaw.name + ": RawImage -> 검정 백드롭(texture=null, color=black), 전체 스트레치 유지");
// 2) 자식 = 실제 배경 (세로 맞춤 · 종횡비 유지 · 중앙)
var childTr = parentRt.Find(BgChild) as RectTransform;
GameObject childGo;
if (childTr == null)
{
childGo = new GameObject(BgChild, typeof(RectTransform), typeof(CanvasRenderer), typeof(RawImage), typeof(AspectRatioFitter));
childGo.transform.SetParent(parentRt, false);
childTr = (RectTransform)childGo.transform;
childTr.SetAsFirstSibling(); // 다른 UI 뒤에 깔린다
sb.AppendLine(" " + BgChild + ": 신규 생성");
}
else { childGo = childTr.gameObject; sb.AppendLine(" " + BgChild + ": 기존 재사용"); }
var raw = childGo.GetComponent<RawImage>() ?? childGo.AddComponent<RawImage>();
raw.texture = tex;
raw.color = Color.white;
raw.raycastTarget = false; // 터치는 부모가 받는다 (오버드로우/레이캐스트 비용 최소)
// 세로 스트레치 + 가로 자유 => AspectRatioFitter 가 폭을 계산할 수 있는 유일한 구성
childTr.anchorMin = new Vector2(0.5f, 0f);
childTr.anchorMax = new Vector2(0.5f, 1f);
childTr.pivot = new Vector2(0.5f, 0.5f); // 오프셋은 WLBackgroundFit 이 clamp 해서 잡는다
childTr.anchoredPosition = Vector2.zero;
childTr.sizeDelta = Vector2.zero;
childTr.localScale = Vector3.one;
var arf = childGo.GetComponent<AspectRatioFitter>() ?? childGo.AddComponent<AspectRatioFitter>();
arf.aspectMode = AspectRatioFitter.AspectMode.HeightControlsWidth;
arf.aspectRatio = tex.width / (float)tex.height; // 원본에서 유도 (하드코딩 아님)
// 크롭이 생길 때만 초점을 존중하고, 이미지가 화면보다 좁아지면 자동으로 정중앙 → 여백 대칭
var fit = childGo.GetComponent<WLBackgroundFit>() ?? childGo.AddComponent<WLBackgroundFit>();
fit.focusX = focusX;
fit.applyVertical = false;
fit.Apply();
sb.AppendLine(" tex=" + tex.name + " " + tex.width + "x" + tex.height
+ " ratio=" + arf.aspectRatio.ToString("F4")
+ " mode=HeightControlsWidth focusX=" + focusX.ToString("F2") + " raycast=false");
}
static RectTransform Rt(Transform root, string path, StringBuilder sb)
{
var t = root.Find(path);
if (t == null) { sb.AppendLine(" !! 경로 없음: " + path); return null; }
return t as RectTransform;
}
static void SetRect(RectTransform rt, Vector2 size, Vector2? pos, StringBuilder sb, string label)
{
if (rt == null) return;
var before = rt.sizeDelta; var beforePos = rt.anchoredPosition;
rt.sizeDelta = size;
if (pos.HasValue) rt.anchoredPosition = pos.Value;
sb.AppendLine(" " + label + ": size " + before + " -> " + rt.sizeDelta
+ (pos.HasValue ? (" pos " + beforePos + " -> " + rt.anchoredPosition) : ""));
}
// ---------- 1) Title.unity (로더/팝업 씬) ----------
public static object ApplyScene(float bgPivotX)
{
if (EditorApplication.isPlaying) return "ABORT: Play 중";
var scene = EditorSceneManager.GetActiveScene();
if (scene.path != ScenePath) return "ABORT: 활성 씬이 " + scene.path + " 임 (기대: " + ScenePath + ")";
var sb = new StringBuilder();
sb.AppendLine("=== Title.unity ===");
var canvasGo = GameObject.Find("MainToTitle");
if (canvasGo == null) return "ABORT: MainToTitle 없음";
var root = canvasGo.transform;
FixScaler(canvasGo.GetComponent<CanvasScaler>(), sb);
// 배경 (#795)
var titleRaw = root.Find("Title")?.GetComponent<RawImage>();
if (titleRaw == null) sb.AppendLine(" !! Title RawImage 없음");
else BuildBackground(titleRaw, bgPivotX, sb);
// 팝업 (#794) — btn_ok 를 터치 가능한 크기로 키우고 팝업이 이를 담도록 확장
SetRect(Rt(root, "PopupUI/bg", sb), new Vector2(860f, 520f), null, sb, "PopupUI/bg");
SetRect(Rt(root, "PopupUI/msg", sb), new Vector2(780f, 280f), new Vector2(0f, 70f), sb, "PopupUI/msg");
SetRect(Rt(root, "PopupUI/btn_ok", sb), new Vector2(340f, 130f), new Vector2(0f, -150f), sb, "PopupUI/btn_ok");
// 진행 슬라이더 — 하단 제스처바(약 48px)를 피해 올리고 폭에 좌우 여유를 준다
SetRect(Rt(root, "Slider_proc", sb), new Vector2(880f, 28f), new Vector2(0f, 90f), sb, "Slider_proc");
EditorSceneManager.MarkSceneDirty(scene);
EditorSceneManager.SaveScene(scene);
AssetDatabase.SaveAssets();
sb.AppendLine(" 저장 완료");
return sb.ToString();
}
// ---------- 2) TitleInfo.prefab (실제 타이틀 화면) ----------
public static object ApplyPrefab(float bgPivotX)
{
if (EditorApplication.isPlaying) return "ABORT: Play 중";
var sb = new StringBuilder();
sb.AppendLine("=== TitleInfo.prefab ===");
var root = PrefabUtility.LoadPrefabContents(PrefabPath);
if (root == null) return "ABORT: 프리팹 로드 실패 " + PrefabPath;
try
{
FixScaler(root.GetComponent<CanvasScaler>(), sb);
var titleRaw = root.transform.Find("Title")?.GetComponent<RawImage>();
if (titleRaw == null) sb.AppendLine(" !! Title RawImage 없음");
else BuildBackground(titleRaw, bgPivotX, sb);
// 로그인 버튼 = PD 가 말한 "게임 진입 버튼". 104 -> 140 (권장 120px 이상)
SetRect(Rt(root.transform, "Btns", sb), new Vector2(1000f, 140f), null, sb, "Btns");
foreach (var n in new[] { "btn_apple", "btn_google", "btn_guest" })
SetRect(Rt(root.transform, "Btns/" + n, sb), new Vector2(140f, 140f), null, sb, "Btns/" + n);
// 개발용 로그인 버튼(에디터 전용, 빌드에서는 비활성) — 최소 기준만 충족
SetRect(Rt(root.transform, "InputField (TMP)/btn_login", sb), new Vector2(88f, 88f), null, sb, "btn_login(에디터 전용)");
PrefabUtility.SaveAsPrefabAsset(root, PrefabPath);
sb.AppendLine(" 저장 완료");
}
finally { PrefabUtility.UnloadPrefabContents(root); }
AssetDatabase.SaveAssets();
return sb.ToString();
}
// ---------- 3) 배경 텍스처 임포트 (#795 화질) ----------
public static object ApplyTexture()
{
if (EditorApplication.isPlaying) return "ABORT: Play 중";
var ti = AssetImporter.GetAtPath(BgTexPath) as TextureImporter;
if (ti == null) return "ABORT: TextureImporter 없음 " + BgTexPath;
var sb = new StringBuilder();
int ow, oh; ti.GetSourceTextureWidthAndHeight(out ow, out oh);
sb.AppendLine("=== " + BgTexPath + " (원본 " + ow + "x" + oh + ") ===");
var def = ti.GetDefaultPlatformTextureSettings();
sb.AppendLine(" Default 압축: " + def.textureCompression + " -> Compressed (LQ 해제)");
def.maxTextureSize = 2048; // 원본 1920 이상 · PD 상한 2048
def.textureCompression = TextureImporterCompression.Compressed;
ti.SetPlatformTextureSettings(def);
// Android 명시 오버라이드: ASTC 6x6 (PD 권고 "ASTC 6x6 이하"), 품질 100
var and = ti.GetPlatformTextureSettings("Android");
sb.AppendLine(" Android: override " + and.overridden + "/" + and.format
+ " -> true / ASTC_6x6 / quality 100 / maxSize 2048");
and.overridden = true;
and.maxTextureSize = 2048;
and.format = TextureImporterFormat.ASTC_6x6;
and.compressionQuality = 100;
and.crunchedCompression = false;
ti.SetPlatformTextureSettings(and);
sb.AppendLine(" mipmap " + ti.mipmapEnabled + " (UI 이므로 OFF 유지) / filter " + ti.filterMode);
ti.mipmapEnabled = false;
ti.filterMode = FilterMode.Bilinear;
// 읽기 가능 플래그는 CPU 사본을 하나 더 들고 있어 메모리를 2배로 쓴다. UI 배경엔 불필요.
if (ti.isReadable) { sb.AppendLine(" isReadable true -> false (CPU 사본 제거, 메모리 절감)"); ti.isReadable = false; }
EditorUtility.SetDirty(ti);
ti.SaveAndReimport();
AssetDatabase.SaveAssets();
var t = AssetDatabase.LoadAssetAtPath<Texture2D>(BgTexPath);
sb.AppendLine(" 재임포트 결과: " + t.width + "x" + t.height + " format=" + t.format + " mip=" + t.mipmapCount);
return sb.ToString();
}
// ---------- 4) 크롭 기준점만 재조정 (A/B 비교용) ----------
public static object SetBgPivot(float focusX)
{
if (EditorApplication.isPlaying) return "ABORT: Play 중";
var sb = new StringBuilder();
var scene = EditorSceneManager.GetActiveScene();
var t = GameObject.Find("MainToTitle")?.transform.Find("Title/" + BgChild);
var f = t != null ? t.GetComponent<WLBackgroundFit>() : null;
if (f != null)
{
f.focusX = focusX; f.Apply();
EditorSceneManager.MarkSceneDirty(scene); EditorSceneManager.SaveScene(scene);
sb.AppendLine("Title.unity " + BgChild + " focusX = " + focusX.ToString("F2"));
}
var root = PrefabUtility.LoadPrefabContents(PrefabPath);
try
{
var pf = root.transform.Find("Title/" + BgChild)?.GetComponent<WLBackgroundFit>();
if (pf != null)
{
pf.focusX = focusX; pf.Apply();
PrefabUtility.SaveAsPrefabAsset(root, PrefabPath);
sb.AppendLine("TitleInfo.prefab " + BgChild + " focusX = " + focusX.ToString("F2"));
}
}
finally { PrefabUtility.UnloadPrefabContents(root); }
AssetDatabase.SaveAssets();
return sb.ToString();
}
/// 배경 표시 방식 전환 (PD 결정 옵션 비교용).
/// mode 0 = HeightControlsWidth (세로 맞춤 · 넘치면 크롭)
/// mode 1 = FitInParent (전체 보이기 · 상하 검정 여백)
/// unity command run_script --file AgentScripts/WL793_Apply.cs --entry WL793_Apply.SetBgMode --args '[1]'
public static object SetBgMode(int mode)
{
if (EditorApplication.isPlaying) return "ABORT: Play 중";
var m = mode == 1 ? AspectRatioFitter.AspectMode.FitInParent
: AspectRatioFitter.AspectMode.HeightControlsWidth;
var sb = new StringBuilder();
var scene = EditorSceneManager.GetActiveScene();
var a = GameObject.Find("MainToTitle")?.transform.Find("Title/" + BgChild)?.GetComponent<AspectRatioFitter>();
if (a != null)
{
a.aspectMode = m;
// FitInParent 는 세로도 줄어들 수 있으므로 세로 정렬도 clamp 대상에 넣는다
var fit = a.GetComponent<WLBackgroundFit>(); if (fit != null) { fit.applyVertical = mode == 1; fit.Apply(); }
EditorSceneManager.MarkSceneDirty(scene); EditorSceneManager.SaveScene(scene);
sb.AppendLine("Title.unity aspectMode = " + m);
}
var root = PrefabUtility.LoadPrefabContents(PrefabPath);
try
{
var pa = root.transform.Find("Title/" + BgChild)?.GetComponent<AspectRatioFitter>();
if (pa != null)
{
pa.aspectMode = m;
var fit = pa.GetComponent<WLBackgroundFit>(); if (fit != null) { fit.applyVertical = mode == 1; fit.Apply(); }
PrefabUtility.SaveAsPrefabAsset(root, PrefabPath);
sb.AppendLine("TitleInfo.prefab aspectMode = " + m);
}
}
finally { PrefabUtility.UnloadPrefabContents(root); }
AssetDatabase.SaveAssets();
return sb.ToString();
}
}