Project_WL/Assets/WL/Look/Farm/WLIslandLookSettings.cs

244 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// WLIslandLookSettings.cs — 실제 게임 섬(FarmingIsland Level01)에 **런타임으로** 얹는
// ① 데모 룩(머티리얼·조명) ② 데모 풀밭의 단일 출처 (WL-816e · #816)
//
// PD 지시(2026-09-13) 「섬 배경이 단순 타일로만 되어있어. 기본 타일의 디자인을
// 데모씬에서 사용한 풀밭처럼 꾸미고, 전체적인 비주얼을 맞춰줘」
//
// ■ 왜 런타임인가 — 816a 는 **복사본 씬**(WL_FarmLook)에만 룩을 저장했다.
// 게임이 실제로 로드하는 것은 `Assets/FarmingIsland/Scenes/Level01.unity` 라
// 그 씬은 아직 원본 색이다. 원본 씬을 고치지 않고(FI 0줄) 로드 직후 얹는다.
//
// ■ C8 되돌리기 — `enabled_ = 0` 이면 아무것도 얹지 않는다 = 지금 상태 100 %.
// 씬 파일·FI 에셋을 한 글자도 쓰지 않으므로 되돌리기에 복원 작업이 필요 없다.
//
// ■ C45 — 밀도·크기·제외 여백 같은 값은 전부 이 에셋에 있다(코드 상수 0).
//
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(ErrorLogHookManager 팝업).
// 🔴 어셈블리 주의: Assets/WL/Look/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
// ─────────────────────────────────────────────────────────────────────────────
using System;
using UnityEngine;
namespace WL.Look.Farm
{
/// <summary>원본 머티리얼 → 데모 톤 복사본. 한 줄 = 머티리얼 하나.</summary>
[Serializable]
public sealed class WLMatRemap
{
[Tooltip("0 이면 이 줄은 없는 것으로 친다.")]
public int enabled_ = 1;
[Tooltip("FarmingIsland 원본 머티리얼(한 글자도 안 고친다 · 참조만).")]
public Material from;
[Tooltip("816e/816a 가 만든 데모 톤 복사본.")]
public Material to;
}
/// <summary>
/// 메시 하나의 **윗면(걸어다니는 면)** 을 XZ 격자 비트맵으로 구운 것.
/// FI 메시는 `isReadable = 0` 이라 런타임에 정점을 못 읽는다 → 에디터에서 구워 둔다.
/// (FI 에셋의 임포트 설정을 고치지 않기 위한 선택 — `Assets/FarmingIsland/**` 0줄)
/// </summary>
[Serializable]
public sealed class WLTileMask
{
public string meshName = "";
[Tooltip("격자 해상도(res × res).")] public int res = 64;
[Tooltip("마스크가 덮는 반폭(m). 섬 타일 8 m → 4.")] public float half = 4f;
[Tooltip("res*res 비트를 담은 패킹 배열.")] public byte[] bits;
public bool At(float lx, float lz)
{
if (bits == null || res <= 0) return false;
int cx = (int)((lx + half) / (half * 2f) * res);
int cz = (int)((lz + half) / (half * 2f) * res);
if (cx < 0 || cz < 0 || cx >= res || cz >= res) return false;
int i = cz * res + cx;
int b = i >> 3;
if (b >= bits.Length) return false;
return (bits[b] & (1 << (i & 7))) != 0;
}
public void Set(int cx, int cz, bool v)
{
if (bits == null) bits = new byte[(res * res + 7) / 8];
int i = cz * res + cx;
int b = i >> 3;
if (b >= bits.Length) return;
if (v) bits[b] |= (byte)(1 << (i & 7));
else bits[b] &= (byte)~(1 << (i & 7));
}
}
/// <summary>흩뿌릴 것 하나(풀 · 꽃 · 자갈). 데모 `InstancingSettings` 와 같은 값 구성.</summary>
[Serializable]
public sealed class WLScatterDef
{
public int enabled_ = 1;
[Tooltip("이름표(로그용).")] public string label = "풀";
public Mesh mesh;
public Material material;
[Tooltip("같은 층 안에서의 가중치. 데모 = 풀 100 : 꽃 1.")] public float probability = 100f;
[Tooltip("메시 배율. 데모 = 0.35.")] public float scale = 0.35f;
[Tooltip("법선 방향으로 띄우는 거리(m). 데모 = 0.04.")] public float normalOffset = 0.04f;
[Tooltip("이 층의 밀도(개/m 한 변). 실제 밀도 = 값². 데모 1층 = 2.5(6.25개/㎡).")]
public float density = 2.5f;
}
[CreateAssetMenu(fileName = "WLIslandLookSettings", menuName = "WL/Island Look Settings", order = 39)]
public sealed class WLIslandLookSettings : ScriptableObject
{
public const string ResourcesPath = "WL/WLIslandLookSettings";
static WLIslandLookSettings s_inst;
static bool s_tried;
public static WLIslandLookSettings Instance
{
get
{
if (s_inst == null && !s_tried) { s_tried = true; s_inst = Resources.Load<WLIslandLookSettings>(ResourcesPath); }
return s_inst;
}
}
public static void Invalidate() { s_inst = null; s_tried = false; }
// ───────────────────────────────── 마스터
[Header("되돌리기 (C8)")]
[Tooltip("0 이면 섬에 아무것도 얹지 않는다 = 지금(원본) 상태 100 %.")]
public int enabled_ = 1;
[Tooltip("1 이면 적용 결과를 Console 에 남긴다.")]
public int verboseLog = 1;
// ───────────────────────────────── §0 룩
[Header("§0 — 실게임 섬(Level01)에 데모 룩 적용")]
[Tooltip("1 이면 섬 씬 로드 직후 머티리얼을 데모 톤 복사본으로 바꾼다(씬 파일 무변경 · 런타임만).")]
public int applyLook = 1;
[Tooltip("1 이면 조명·앰비언트·스카이박스도 데모/아레나 값으로 맞춘다.")]
public int applyLighting = 1;
[Tooltip("1 이면 아레나와 같은 `WL_ReferenceLook`(외곽선·대비·무드)을 섬에도 얹는다.")]
public int applyReferenceLook = 1;
[Tooltip("머티리얼 교체 표. 한 줄 = 원본 → 복사본.")]
public WLMatRemap[] materialRemap = new WLMatRemap[0];
[Tooltip("섬 타일 윗면(걸어다니는 면)에 쓸 머티리얼. 비우면 위 표의 Island01 줄을 쓴다. " +
"체크무늬 두 초록을 데모 잔디색 한 가지로 합친 것.")]
public Material islandTopMaterial;
[Tooltip("1 이면 농사 타일(Soil)은 원본 머티리얼 그대로 둔다(색을 코드가 직접 칠하는 곳이라 건드리면 안 된다).")]
public int skipSoilRenderers = 1;
// ── 816f: 밭(Soil) 체크무늬 **기능은 유지**하고 색만 데모 톤으로 (PD 2026-09-13)
[Header("§0-816f — 밭(Soil) 칸 표시 톤 (기능 유지 · 색만)")]
[Tooltip("1 이면 FI `Farm` 의 마른/젖은 흙 색 4개를 런타임에 데모 톤으로 낮춘다. " +
"칸 구분(마름/젖음 표시)은 그대로 남는다 — 색만 바뀐다. 0 이면 FI 원본 색.")]
public int soilToneEnabled = 1;
[Tooltip("흙 색에 곱하는 값(0~1). 낮출수록 주황이 죽는다.")]
public Color soilTint = new Color(0.84f, 0.86f, 0.90f, 1f);
[Tooltip("체크무늬 두 색의 차이를 이 비율로 줄인다(1 = FI 원본 차이 · 0 = 차이 없음=칸 안 보임). " +
"🔴 0 으로 두지 말 것 — 어디를 갈았는지 안 보인다.")]
[Range(0f, 1f)] public float soilCheckerContrast = 0.6f;
[Header("§0 — 조명 값 (816a 실측 = 데모/아레나와 동일)")]
public Color ambientSky = new Color(0.212f, 0.227f, 0.259f, 1f);
public Color ambientEquator = new Color(0.114f, 0.125f, 0.133f, 1f);
public Color ambientGround = new Color(0.047f, 0.043f, 0.035f, 1f);
public float ambientIntensity = 1f;
[Tooltip("빌트인 Default-Skybox. 비우면 스카이박스는 안 건드린다.")]
public Material skybox;
public Color dirLightColor = Color.white;
public float dirLightIntensity = 1f;
[Tooltip("1 = Soft(데모) · 0 = 원본(Hard) 유지.")]
public int dirLightSoftShadows = 1;
[Tooltip("1 이면 안개를 끈다(데모 = off).")]
public int fogOff = 1;
[Tooltip("1 이면 FI `GraphicsManager` 를 끈다. 이 컴포넌트는 Play 중 FI 의 URP 에셋 파일에 " +
"renderScale 을 **직접 써 넣어** `Assets/FarmingIsland/**` 를 더럽힌다(816a 실측). " +
"우리 프로젝트의 활성 URP 에셋이 아니라 화면에는 영향 0.")]
public int disableFiGraphicsManager = 1;
[Tooltip("런타임에 뒤늦게 생기는 오브젝트(상인·작물·아이템)까지 잡기 위해 다시 훑는 시각(초).")]
public float[] rescanAtSeconds = new float[] { 0.5f, 2f, 5f };
// ───────────────────────────────── §1 풀밭
[Header("§1 — 섬 타일 위 풀밭 (데모와 같은 인스턴싱)")]
[Tooltip("0 이면 풀을 깔지 않는다.")]
public int grassEnabled = 1;
[Tooltip("흩뿌릴 것들. 같은 density 를 가진 줄끼리 한 층으로 묶여 확률 분배된다(데모와 같은 방식).")]
public WLScatterDef[] scatter = new WLScatterDef[0];
[Tooltip("격자 흔들기(격자 칸 단위). 데모 = 0.5.")]
[Range(0f, 0.5f)] public float positionVariance = 0.5f;
[Tooltip("크기 흔들기. 데모 = 0.2 → ×0.8~1.2.")]
[Range(0f, 0.9f)] public float scaleVariance = 0.2f;
[Tooltip("1 이면 풀 하나하나를 Y 축으로 랜덤 회전한다(데모는 회전 0 = 빌보드라 불필요). " +
"빌보드 셰이더에는 영향이 없다.")]
public int randomYaw = 0;
[Header("§1 — 풀을 깔지 않는 곳 (제외 규칙)")]
[Tooltip("섬 타일 가장자리에서 이만큼(m) 안쪽까지만 깐다. 물 위로 삐져나오지 않게.")]
public float edgeInset = 0.15f;
[Tooltip("길(Road 메시)에서 이만큼(m) 떨어뜨린다.")]
public float roadMargin = 0.15f;
[Tooltip("농사 타일(Farm 트리거 · Soil 1×1)에서 이만큼(m) 떨어뜨린다.")]
public float farmMargin = 0.35f;
[Tooltip("건물·소품 콜라이더에서 이만큼(m) 떨어뜨린다.")]
public float propMargin = 0.05f;
[Tooltip("이 크기(m) 이상인 콜라이더는 섬 본체로 보고 제외 대상에서 뺀다.")]
public float propMaxSize = 7.5f;
[Header("§1 — 모바일 예산")]
[Tooltip("인스턴스 총개수 상한. 넘으면 밀도를 자동으로 낮춘다(0 = 무제한).")]
public int maxInstances = 40000;
[Tooltip("섬이 확장되면 다시 깐다 — 이 주기(초)로 잠긴 섬이 열렸는지 본다. 0 이면 감시 안 함.")]
public float rebuildPollSeconds = 1f;
[Tooltip("확장 애니메이션(DOTween 1초)이 끝나기를 기다리는 초.")]
public float rebuildDelaySeconds = 1.4f;
// ───────────────────────────────── 구운 마스크
[Header("§1 — 구운 윗면 마스크 (에디터에서 생성 · 손대지 말 것)")]
public WLTileMask[] tileMasks = new WLTileMask[0];
public WLTileMask[] roadMasks = new WLTileMask[0];
// ───────────────────────────────── 섬 씬 판별
[Header("어느 씬에 적용하는가")]
[Tooltip("이 이름의 씬에 적용한다(비워도 IslandManager 가 있으면 섬으로 본다).")]
public string[] islandSceneNames = new string[] { "Level01", "WL_FarmLook" };
public WLTileMask FindTileMask(string meshName) { return Find(tileMasks, meshName); }
public WLTileMask FindRoadMask(string meshName) { return Find(roadMasks, meshName); }
static WLTileMask Find(WLTileMask[] a, string n)
{
if (a == null || string.IsNullOrEmpty(n)) return null;
// `MeshFilter.mesh` 로 읽으면 이름 뒤에 " Instance" 가 붙는다 — 떼고 비교한다.
int k = n.IndexOf(" Instance");
if (k > 0) n = n.Substring(0, k);
for (int i = 0; i < a.Length; i++) if (a[i] != null && a[i].meshName == n) return a[i];
return null;
}
public void Log(string msg) { if (verboseLog != 0) Debug.Log("[WL-816e] " + msg); }
}
}