110 lines
6.3 KiB
C#
110 lines
6.3 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WLIslandTileFenceStrip.cs — FI 섬 타일 메시에 **구워진 난간(울타리)** 을 잘라낸다.
|
|
//
|
|
// 2026-09-15 Lead 가 실행 중인 화면을 직접 실측:
|
|
// · 별도 오브젝트 `Island (n)/Fence01*` 은 렌더러·콜라이더가 이미 꺼져 있고 SetActive(false) 해도 화면의 울타리는 그대로.
|
|
// · 풀 인스턴싱을 꺼도 그대로. 화면 안 렌더러 전수 = 타일(04·07·26·34)·물·캠프·플레이어뿐.
|
|
// → 남은 울타리는 **타일 메시 자체에 포함된 삼각형**(재질 1개 · 서브메시 1개 · 브라운 = 텍스처 흙 사분면)이다.
|
|
// 조치 = 타일 메시를 복제해 윗면(y=0) 위로 `fenceStripMinY` 이상 솟은 삼각형만 버리고 그 복제본을 물린다.
|
|
// 원본 메시·FI 코드 0줄 · 되돌리기 `stripBakedFences 0`(다음 로드부터 원본 메시).
|
|
// 🔴 모바일 빌드에서는 FI 타일 FBX 의 Read/Write 가 꺼져 있으면 정점을 못 읽는다(에디터는 읽힌다) — 그때는 임포트 설정 1개 변경 필요.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using FIIsland = CryingSnow.FarmingIsland.Island;
|
|
using FIIslandManager = CryingSnow.FarmingIsland.IslandManager;
|
|
|
|
namespace WL.Island
|
|
{
|
|
public static class WLIslandTileFenceStrip
|
|
{
|
|
static readonly Dictionary<Mesh, Mesh> s_cache = new Dictionary<Mesh, Mesh>();
|
|
public static int TilesStripped, TrianglesRemoved, Unreadable, SourceReplaced;
|
|
public static string LastLog = "";
|
|
|
|
/// <summary>섬 씬의 타일(FI Island)마다 메시를 검사해 난간 삼각형이 있으면 잘라낸 복제본으로 바꾼다(멱등).</summary>
|
|
public static void Apply(WLIslandSettings cfg, Scene scene)
|
|
{
|
|
if (cfg == null || cfg.stripBakedFences == 0) return;
|
|
ReplaceSourceList(cfg, scene);
|
|
var isls = Object.FindObjectsByType<FIIsland>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
int changed = 0;
|
|
for (int i = 0; i < isls.Length; i++)
|
|
{
|
|
var isl = isls[i];
|
|
if (isl == null || (scene.IsValid() && isl.gameObject.scene != scene)) continue;
|
|
var mf = isl.GetComponent<MeshFilter>();
|
|
if (mf == null || mf.sharedMesh == null) continue;
|
|
var src = mf.sharedMesh;
|
|
if (src.name.EndsWith("_nofence")) continue;
|
|
|
|
Mesh dst;
|
|
if (!s_cache.TryGetValue(src, out dst)) { dst = Strip(src, cfg.fenceStripMinY); s_cache[src] = dst; }
|
|
if (dst != null && dst != src) { mf.sharedMesh = dst; TilesStripped++; changed++; }
|
|
}
|
|
if (changed > 0)
|
|
{
|
|
LastLog = "타일 난간 제거 — 타일 " + changed + "개 · 누적 삼각형 " + TrianglesRemoved;
|
|
WLIslandBridge.Log(cfg, LastLog);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 2026-09-15 Lead 실측 — 원본 `Island.ConfigureIsland` 가 확장·재구성 때마다 `IslandManager.islandMeshes[i]`(원본 메시)를
|
|
/// 타일에 **다시 물려** 잘라낸 복제본이 원본으로 되돌아간다(화면의 타일 메시명이 `04`·`07` 등 원본 그대로였다).
|
|
/// 그래서 원천인 그 목록의 항목 자체를 잘라낸 복제본으로 바꿔 둔다(리플렉션 · 원본 0줄) — 이후 어떤 시점에 물려도 난간 없음.
|
|
/// </summary>
|
|
static void ReplaceSourceList(WLIslandSettings cfg, Scene scene)
|
|
{
|
|
var mgrs = Object.FindObjectsByType<FIIslandManager>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
if (mgrs == null || mgrs.Length == 0) return;
|
|
var field = typeof(FIIslandManager).GetField("islandMeshes", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
|
|
if (field == null) return;
|
|
for (int k = 0; k < mgrs.Length; k++)
|
|
{
|
|
var mgr = mgrs[k];
|
|
if (mgr == null || (scene.IsValid() && mgr.gameObject.scene != scene)) continue;
|
|
var list = field.GetValue(mgr) as List<Mesh>;
|
|
if (list == null) continue;
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
var src = list[i];
|
|
if (src == null || src.name.EndsWith("_nofence")) continue;
|
|
Mesh dst;
|
|
if (!s_cache.TryGetValue(src, out dst)) { dst = Strip(src, cfg.fenceStripMinY); s_cache[src] = dst; }
|
|
if (dst != null && dst != src) { list[i] = dst; SourceReplaced++; }
|
|
}
|
|
}
|
|
}
|
|
|
|
static Mesh Strip(Mesh src, float minY)
|
|
{
|
|
// 🔴 Read/Write 가 꺼진 메시는 접근 자체가 Unity 에러 로그(→ 게임 팝업)를 낸다 — 예외가 아니라 로그라 try/catch 로 못 막는다.
|
|
if (!src.isReadable) { Unreadable++; return src; }
|
|
Vector3[] verts; int[] tris;
|
|
try { verts = src.vertices; tris = src.triangles; }
|
|
catch (System.Exception) { Unreadable++; return src; }
|
|
if (verts == null || tris == null || tris.Length < 3) return src;
|
|
|
|
var keep = new List<int>(tris.Length);
|
|
int removed = 0;
|
|
for (int t = 0; t + 2 < tris.Length; t += 3)
|
|
{
|
|
int a = tris[t], b = tris[t + 1], c = tris[t + 2];
|
|
if (verts[a].y > minY || verts[b].y > minY || verts[c].y > minY) { removed++; continue; }
|
|
keep.Add(a); keep.Add(b); keep.Add(c);
|
|
}
|
|
if (removed == 0) return src;
|
|
|
|
var m = Object.Instantiate(src);
|
|
m.name = src.name + "_nofence";
|
|
m.triangles = keep.ToArray();
|
|
m.RecalculateBounds();
|
|
TrianglesRemoved += removed;
|
|
return m;
|
|
}
|
|
}
|
|
}
|