Project_WL/Assets/WL/Look/Env/WLEnvLookSettings.cs

151 lines
10 KiB
C#
Raw Permalink Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLEnvLookSettings.cs — Critter Environment 룩(툰 램프 + 1px 픽셀 외곽선 + 구름 그림자
// + 인스턴싱 잔디 + 거품 물)의 값 SOT. PD 지시 #814 · 발주서 WL-814c (2026-09-10)
//
// 🔴 코드 상수 0(C45) — 여기 있는 값만 바꾸면 룩이 바뀐다.
// 🔴 originals / toons 는 에디터 스크립트(AgentScripts/WL814c_Convert.cs)가 채우는 스왑 표다.
// 런타임은 이 표에 **있는 머티리얼만** 갈아끼운다 → 파티클·UI·이펙트는 절대 안 바뀐다.
// 🔴 원본 .mat 파일은 어떤 경로로도 수정되지 않는다(참조만 보관).
// ─────────────────────────────────────────────────────────────────────────────
using UnityEngine;
namespace WL.Look.Env
{
public sealed class WLEnvLookSettings : ScriptableObject
{
public const string kResourcePath = "WL/WLEnvLookSettings";
// ── C8 롤백 ──────────────────────────────────────────────────────────
[Tooltip("0 = 이 기능 전체 정지(러너 오브젝트 0 · 스왑 0 · 피처 0 · 잔디 0)")]
public bool enabled = true;
[Tooltip("적용/복원 1줄 로그")]
public bool verboseLog = false;
[Tooltip("🔴 WL-814e — 1 이면 814d 룩 토글 허브(LookModeHub.EnvLookOn)를 따른다: 맵 진입 자동 적용도 허브가 ON 일 때만 · [배경] /. 0 814c ( )")]
public bool followLookModeHub = true;
// ── 맵 경계 ──────────────────────────────────────────────────────────
[Tooltip("인게임 맵에서만 적용(로비/타이틀 제외)")]
public bool applyOnlyInBattleMap = true;
[Tooltip("맵 이탈·종료에서 원복")]
public bool restoreOnExit = true;
[Tooltip("맵 경계 폴링 주기(초)")]
public float pollSeconds = 0.5f;
[Tooltip("새로 스폰된 몹을 훑는 주기(초) · 0 이면 새 스폰 적용 안 함")]
public float respawnSweepSeconds = 0.5f;
// ── 스왑 대상 ────────────────────────────────────────────────────────
[Tooltip("맵 렌더러를 Toon 으로 스왑")]
public bool swapMap = true;
[Tooltip("캐릭터(PC·몹·보스) 렌더러를 Toon 으로 스왑 — PD 결정용(캡처 ⓔ/ⓕ 비교)")]
public bool swapCharacters = true;
// ── 스왑 표(에디터 스크립트가 채운다 · 손으로 만지지 말 것) ───────────
public Material[] originals;
public Material[] toons;
// ── 머티리얼 생성 값(에디터 스크립트가 읽는다) ────────────────────────
[Tooltip("캐릭터 머티리얼도 Toon 으로 만들 것인가(생성 단계)")]
public bool convertCharacters = true;
[Tooltip("툰 램프 단계 수 4~8 (데모 7 · 제안 5)")]
public int shades = 5;
public float brightness = 0.25f;
public float minimumDarkness = 0.2f;
public float ambientStrength = 0.1f;
[Tooltip("그림자 색 = 원본 색 × 이 계수(데모 머티리얼 관계 근사)")]
public Color shadowTint = new Color(0.42f, 0.40f, 0.55f, 1f);
// ── 1px 픽셀 외곽선(오브젝트 셰이더 내부 계산) ────────────────────────
public bool outlinesEnabled = true;
[Tooltip("🔴 캐릭터에도 외곽선을 줄 것인가. 실측: 스킨드 메시는 폴리곤이 촘촘해 270px 에서 노이즈가 된다 → 기본 off")]
public bool characterOutlines = false;
[Tooltip("깊이 외곽선 임계 = (외곽선을 낼 최소 단차 m) ÷ (직교 카메라 far near). 데모 0.01 은 원근 카메라 기준")]
public float depthThreshold = 0.004f;
[Tooltip("노멀 외곽선 임계. 데모 1 · 낮출수록 면 안쪽까지 선이 생겨 노이즈가 된다")]
public float normalThreshold = 1f;
public Vector4 normalBias = new Vector4(1f, 1f, 1f, 0f);
public float depthEdgeStrength = 0.5f;
public float normalEdgeStrength = 0.3f;
public Color outlineColor = new Color(0f, 0f, 0f, 0f);
// ── 구름 그림자 ──────────────────────────────────────────────────────
// 🔴 실측: Critter Toon 셰이더에 `_CLOUDSENABLED` 키워드는 **없다**(데모 .mat 의 그 키워드는 구버전 잔재).
// on/off 는 `_Cloud_Strength` 로 한다(0 = 끔).
public bool cloudShadows = true;
public float cloudDensity = 0.01f;
public Vector4 cloudMovement = new Vector4(1f, 1f, 0f, 0f);
public float cloudStrength = 1f;
public float cloudCover = 0.5f;
public float cloudChange = 0.005f;
public Vector4 cloudStep = new Vector4(13f, 17f, 0f, 0f);
// ── 렌더러 피처(깊이·노멀 텍스처) ─────────────────────────────────────
[Tooltip("PixelOutlineSetupFeature 를 런타임에 켠다")]
public bool outlineFeature = true;
[Tooltip("인게임 맵에서만 피처 활성(타이틀·로비 비용 0)")]
public bool outlineFeatureOnlyInBattleMap = true;
// ── 잔디 인스턴싱 ────────────────────────────────────────────────────
public bool grassEnabled = true;
[Tooltip("데모 대비 밀도 비율. 1 = 데모(Critter Density 2.5) · 0.5 = 인스턴스 절반")]
public float grassDensity = 0.5f;
[Tooltip("데모 실측 Density(면적/인스턴스). 만지지 말 것")]
public float demoDensity = 2.5f;
[Tooltip("모바일 상한 — 이 수를 넘으면 밀도를 자동으로 낮춘다")]
public int maxInstances = 20000;
[Tooltip("지면 후보 판정: 수평 투영 면적(m²) 하한")]
public float minGroundFootprint = 100f;
[Tooltip("지면 후보 판정: 바운드 높이(m) 상한 — 배경 산·절벽을 뺀다(WL_Nature 실측 산 58~200 m · 지면 2~16 m)")]
public float maxGroundBoundsHeight = 30f;
[Tooltip("지면 후보 판정: 평평함 = 높이 ÷ 짧은 변. 실측 지면 0.02~0.15 · 나무/바위 0.65~0.90")]
public float groundFlatness = 0.35f;
[Tooltip("지면 후보 판정: 메시 면적 ÷ 수평 투영 면적. 실측 지면 ≈1.0 · 나무 수관 ≈0.3 (성긴 면은 지면이 아니다)")]
public float groundAreaRatio = 0.5f;
[Tooltip("지면 후보 최대 개수(플레이어에 가까운 순)")]
public int maxGroundMeshes = 4;
[Tooltip("잔디 한 포기의 목표 높이(m) — 메시 크기로 Scale 을 자동 계산한다(0 이면 아래 grassScale 을 그대로 쓴다)")]
public float grassHeightMeters = 0.3f;
[Tooltip("grassHeightMeters 가 0 일 때 쓰는 원시 Scale(데모 0.35)")]
public float grassScale = 0.35f;
public float grassNormalOffset = 0.04f;
[Tooltip("잔디 : 꽃 가중치(데모 100 : 1)")]
public float grassProbability = 100f;
public float flowerProbability = 1f;
[Tooltip("지면 메시가 읽기 불가일 때 레이캐스트로 만드는 대체 격자 한 변 분할 수")]
public int groundProbeGrid = 48;
// ── 물 ───────────────────────────────────────────────────────────────
[Tooltip("물 렌더러를 Critter Water 로 스왑(반사 카메라는 붙이지 않는다)")]
public bool waterEnabled = true;
public Material waterMaterial;
// ── 에셋 참조(에디터 스크립트가 채움) ─────────────────────────────────
public Mesh grassMesh;
public Material grassMaterial;
public Mesh flowerMesh;
public Material flowerMaterial;
// ─────────────────────────────────────────────────────────────────────
static WLEnvLookSettings s_ins;
static bool s_tried;
/// <summary>프로브·C8 용 런타임 강제 정지(에셋 파일은 건드리지 않는다).</summary>
public static bool RuntimeDisabled;
public static WLEnvLookSettings Instance
{
get
{
if (s_ins == null && !s_tried) { s_tried = true; s_ins = Resources.Load<WLEnvLookSettings>(kResourcePath); }
return s_ins;
}
}
/// <summary>기능이 살아 있는가 = 에셋 존재 · enabled · RuntimeDisabled off.</summary>
public static bool Enabled { get { var c = Instance; return c != null && c.enabled && !RuntimeDisabled; } }
/// <summary>프로브 전용 — 캐시를 비운다(SO 를 코드로 바꿔치기할 때).</summary>
public static void ClearCacheForProbe() { s_ins = null; s_tried = false; }
}
}