diff --git a/Assets/WL/Island/Resources/WL/WLIslandSettings.asset b/Assets/WL/Island/Resources/WL/WLIslandSettings.asset index c057992d5..9edb9e85c 100644 --- a/Assets/WL/Island/Resources/WL/WLIslandSettings.asset +++ b/Assets/WL/Island/Resources/WL/WLIslandSettings.asset @@ -48,6 +48,7 @@ MonoBehaviour: gateFlowEnabled: 1 hideFarms: 1 farmReHideDelaySeconds: 3 + matchNewTileMaterials: 1 dungeonUnlockPrefsKey: WL_Island_DungeonsUnlocked dungeonUnlockPrices: - 10 diff --git a/Assets/WL/Island/WLIslandGateFlow.cs b/Assets/WL/Island/WLIslandGateFlow.cs index 8bf037f0a..b238950e8 100644 --- a/Assets/WL/Island/WLIslandGateFlow.cs +++ b/Assets/WL/Island/WLIslandGateFlow.cs @@ -292,10 +292,10 @@ namespace WL.Island return haveCenter; } - /// 섬이 나중에 열리면 그 섬의 밭도 다시 숨긴다(확장해도 밭이 안 보이게). + /// 섬이 나중에 열리면 ① 그 섬의 밭을 다시 숨기고 ② 타일 재질을 기존 타일과 맞춘다(§1). static void HookIslandActivation(WLIslandSettings cfg, Scene scene) { - if (cfg.hideFarms == 0) return; + if (cfg.hideFarms == 0 && cfg.matchNewTileMaterials == 0) return; var islands = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); for (int i = 0; i < islands.Length; i++) { @@ -311,7 +311,8 @@ namespace WL.Island static void OnIslandActivated(WLIslandSettings cfg, FIIsland isl) { - if (cfg == null || cfg.gateFlowEnabled == 0 || cfg.hideFarms == 0) return; + if (cfg == null || cfg.gateFlowEnabled == 0) return; + if (cfg.hideFarms == 0 && cfg.matchNewTileMaterials == 0) return; var runner = WLIslandBridge.Runner; if (runner == null || isl == null) return; runner.StartCoroutine(Co_ReHide(cfg, isl)); @@ -319,10 +320,14 @@ namespace WL.Island static IEnumerator Co_ReHide(WLIslandSettings cfg, FIIsland isl) { - // 확장 애니메이션(DOScale 1초) + 소품 배치(AnimateSoils 0.05초 × 30칸)가 끝나기를 기다린다 + // 확장 애니메이션(DOScale 1초) + 소품 배치(AnimateSoils 0.05초 × 30칸) + 룩 재적용 + // (`WLIslandLookSettings.rebuildDelaySeconds` = 1.4 실측)이 끝나기를 기다린다. yield return new WaitForSeconds(cfg.farmReHideDelaySeconds); if (isl == null) yield break; + MatchTileMaterials(cfg, isl); + + if (cfg.hideFarms == 0) yield break; var farms = isl.GetComponentsInChildren(true); for (int i = 0; i < farms.Length; i++) { @@ -336,6 +341,82 @@ namespace WL.Island } } + // ───────────────────────────────────────────────────────────────── + // §1 새 타일 경계 색차 — 남은 몫(외곽선 패스) + // + // 실측(2026-09-13 · Play): + // · **바닥 색 자체는 Lead 의 `b1e58d795`(대비 복제 제외)로 해결됐다** — 새로 연 타일도 + // 기존 타일과 **같은 머티리얼 인스턴스** `Farm_IslandTop_Demo#83780` 을 쓴다. + // · 다만 새로 연 타일은 슬롯이 **1개**뿐이다(기존 타일은 2개 = 바닥 + `WL/HullOutline`). + // `WLReferenceLook.Apply` 가 씬 로드 때 **활성 렌더러만** 훑는데 잠긴 타일은 꺼져 있어 + // 외곽선 패스를 못 받는다. 그래서 **경계에서 테두리 유무가 갈린다.** + // 조치: 열린 직후, 이미 열려 있는 타일의 머티리얼 배열을 **그대로** 물려준다(새 머티리얼 0개). + // ───────────────────────────────────────────────────────────────── + public static int TilesMatched, SlotsMatched; + + static void MatchTileMaterials(WLIslandSettings cfg, FIIsland isl) + { + if (cfg.matchNewTileMaterials == 0 || isl == null) return; + + var mine = isl.GetComponentsInChildren(true); + if (mine.Length == 0) return; + + // 기준 = 이미 열려 있고 **슬롯이 더 많은** 타일(= 외곽선을 받은 타일) + FIIsland refIsl = null; + int bestSlots = 0; + var all = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + for (int i = 0; i < all.Length; i++) + { + var o = all[i]; + if (o == null || o == isl || !o.IsUnlocked || !o.gameObject.activeInHierarchy) continue; + if (o.gameObject.scene != isl.gameObject.scene) continue; + var r = o.GetComponent(); + if (r == null) continue; + int n = r.sharedMaterials != null ? r.sharedMaterials.Length : 0; + if (n > bestSlots) { bestSlots = n; refIsl = o; } + } + if (refIsl == null) return; + + // 🔴 이름으로 짝을 지으면 **타일 본체**(GameObject 이름이 `Island (3)` / `Island (6)` 처럼 다 다르다)가 + // 안 맞는다(실측 — 첫 시도에서 본체만 슬롯 1개로 남았다). 루트 기준 **상대 경로**로 짝 짓는다. + var refs = refIsl.GetComponentsInChildren(true); + var byPath = new Dictionary(refs.Length); + for (int j = 0; j < refs.Length; j++) + if (refs[j] != null) byPath[RelPath(refIsl.transform, refs[j].transform)] = refs[j]; + + int changed = 0; + for (int i = 0; i < mine.Length; i++) + { + var m = mine[i]; + if (m == null || m is ParticleSystemRenderer) continue; + var mm = m.sharedMaterials; + if (mm == null || mm.Length == 0 || mm[0] == null) continue; + + Renderer r; + if (!byPath.TryGetValue(RelPath(isl.transform, m.transform), out r) || r == null) continue; + var rm = r.sharedMaterials; + if (rm == null || rm.Length <= mm.Length || rm[0] != mm[0]) continue; + m.sharedMaterials = rm; // 같은 인스턴스 배열 = 픽셀이 같아진다 + changed++; SlotsMatched += rm.Length - mm.Length; + } + if (changed > 0) + { + TilesMatched++; + WLIslandBridge.Log(cfg, "새 타일 재질 맞춤 — " + isl.name + " 렌더러 " + changed + + "개를 기존 타일(" + refIsl.name + ")과 같은 배열로(외곽선 포함)"); + } + } + + /// 타일 루트 기준 상대 경로(프리팹 인스턴스끼리 짝 짓는 열쇠 · 루트 자신은 빈 문자열). + static string RelPath(Transform root, Transform t) + { + if (t == root) return ""; + string s = t.name; + var p = t.parent; + while (p != null && p != root) { s = p.name + "/" + s; p = p.parent; } + return s; + } + /// 우리가 끈 것만 되살린다(C8). public static void RestoreFarms() { diff --git a/Assets/WL/Island/WLIslandSettings.cs b/Assets/WL/Island/WLIslandSettings.cs index 5cf18f2e6..65ab83674 100644 --- a/Assets/WL/Island/WLIslandSettings.cs +++ b/Assets/WL/Island/WLIslandSettings.cs @@ -220,9 +220,15 @@ namespace WL.Island "꺼진 오브젝트에서 코루틴을 시작해 콘솔 에러가 난다.")] public int hideFarms = 1; - [Tooltip("섬이 나중에 열렸을 때 그 밭을 다시 숨기기까지 기다리는 초(확장 연출 + 흙칸 연출이 끝날 시간).")] + [Tooltip("섬이 나중에 열렸을 때 그 밭을 다시 숨기고 타일 재질을 맞추기까지 기다리는 초 " + + "(확장 연출 1초 + 흙칸 연출 + 룩 재적용 1.4초가 끝날 시간).")] public float farmReHideDelaySeconds = 3.0f; + [Tooltip("🔴 §1 — 1 이면 **새로 열린 타일**의 머티리얼 배열을 이미 열려 있는 타일과 **같게** 만든다. " + + "실측: 바닥 색 자체는 이미 같지만(main b1e58d795), 새 타일만 외곽선 패스(WL/HullOutline)를 " + + "못 받아 경계에서 테두리 유무가 갈린다. 새 머티리얼을 만들지 않고 기존 배열을 물려준다.")] + public int matchNewTileMaterials = 1; + [Tooltip("해금된 던전 수를 적어 두는 PlayerPrefs 키(FI 세이브 파일은 건드리지 않는다).")] public string dungeonUnlockPrefsKey = "WL_Island_DungeonsUnlocked";