Project_WL/Assets/WL/Look/Character/WLWeaponPalette.cs

118 lines
6.2 KiB
C#
Raw Permalink 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.

// ─────────────────────────────────────────────────────────────────────────────
// WLWeaponPalette.cs — 무기를 팔레트 아틀라스 UV(도트)로 갈아끼운다 (WL-816b)
//
// PD 지시 #816 「이 화풍으로 무기와 코스튬으로 넓히자」 · 발주서 WL-816b
//
// ■ 왜 필요한가 (인게임 Play 실측 · 게임 크기 1080×1920 · ortho 10)
// 무기는 캐릭터(2,959 px)보다 훨씬 작다 — `Elven_Sword_01` 304 px 뿐인데 **252 색**이 보였다
// (서로 다른 색 82.9 % = 캐릭터가 814x 이전에 앓던 「흐린 사진」 그대로다).
// PurePoly 9종은 이미 팔레트 텍스처(`PP_Color_Palette`)를 쓰지만 **Bilinear + 밉 + ASTC 압축**이라
// 화면에서 칸이 섞여 27~44 %가 나왔다. 그래서 캐릭터와 같은 처방이 그대로 듣는다.
//
// ■ 무엇을 하나 — 814x 와 동일
// 메시 복사본의 UV 를 팔레트 텍스처의 **칸 한가운데**로 보낸다(면이 영구 단색) +
// 머티리얼을 캐릭터 팔레트 머티리얼(`M05_ToonP`)과 같은 셰이더·같은 값으로 바꾼다.
// 🔴 팔레트 텍스처는 32×32 · 칸 8 px · Point · **밉 OFF** · 무압축 — 밉을 끄는 게 핵심이다.
//
// ■ 언제 부르나 — `WLWeaponFitter.Scan` (LateUpdate 소켓 스캔)
// 무기는 `PCActor.Make_Weapon` 의 **Addressables 비동기 콜백**으로 붙어서 생성 프레임을 알 수 없다.
// 814p 가 같은 이유로 만든 소켓 스캔에 얹는다 = 새 훅·새 컴포넌트 0.
// 🔴 크기 보정(`WLWeaponFitSettings`)과 **독립**이다 — 그쪽을 꺼도 팔레트는 돈다(반대도 같다).
// 메시 복사본은 정점 좌표가 원본과 같아(UV 만 바뀐다) 바운즈가 같으므로 배수 계산에 영향이 없다.
//
// ■ 되돌리기
// `WLCharacterPaletteSettings.weaponEnabled_ = 0` → 무기만 원본 100 %.
// `enabled_ = 0` → 캐릭터·무기 전부 off (= 814s + 814t 상태 100 %).
//
// ■ 성능 / GC
// 무기 인스턴스 1개당 1회. 표에 없는 무기는 이름 비교 몇 번으로 끝난다(대입 0).
//
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(813z 규칙).
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections.Generic;
using UnityEngine;
namespace WL.Look.Character
{
public static class WLWeaponPalette
{
// 진단(프로브가 읽는다)
public static int AppliedWeapons, AppliedMeshes, AppliedRenderers;
public static string LastLog = "";
static readonly List<MeshFilter> s_mf = new List<MeshFilter>(8);
static readonly List<Renderer> s_rd = new List<Renderer>(8);
/// <summary>소켓에 새로 붙은 무기 하나를 팔레트 메시·머티리얼로 갈아끼운다. 표에 없으면 무동작.</summary>
public static bool Apply(Transform weaponRoot)
{
if (weaponRoot == null) return false;
var cfg = WLCharacterPaletteSettings.Instance;
if (cfg == null || cfg.enabled_ == 0 || cfg.weaponEnabled_ == 0) return false;
var names = cfg.weaponNames;
if (names == null || names.Length == 0) return false;
int idx = IndexOf(names, Strip(weaponRoot.name));
if (idx < 0)
{
// 이름이 바뀐 인스턴스 대비 — 메시 이름으로 한 번 더 찾는다(원본 FBX/obj 메시 이름).
var mf0 = weaponRoot.GetComponentInChildren<MeshFilter>(true);
if (mf0 != null && mf0.sharedMesh != null) idx = IndexOf(names, Strip(mf0.sharedMesh.name));
if (idx < 0) return false;
}
Mesh mesh = cfg.weaponMeshes != null && idx < cfg.weaponMeshes.Length ? cfg.weaponMeshes[idx] : null;
Material mat = cfg.weaponMaterials != null && idx < cfg.weaponMaterials.Length ? cfg.weaponMaterials[idx] : null;
if (mesh == null && mat == null) return false;
int meshes = 0, rends = 0;
if (mesh != null)
{
s_mf.Clear(); weaponRoot.GetComponentsInChildren(true, s_mf);
for (int i = 0; i < s_mf.Count; i++)
{
var f = s_mf[i];
if (f == null || f.sharedMesh == mesh) continue;
f.sharedMesh = mesh; meshes++;
}
}
if (mat != null)
{
s_rd.Clear(); weaponRoot.GetComponentsInChildren(true, s_rd);
for (int i = 0; i < s_rd.Count; i++)
{
var r = s_rd[i];
if (r == null || r is ParticleSystemRenderer || r is TrailRenderer || r is LineRenderer) continue;
var ms = r.sharedMaterials;
bool changed = false;
for (int s = 0; s < ms.Length; s++) if (ms[s] != mat) { ms[s] = mat; changed = true; }
if (changed) { r.sharedMaterials = ms; rends++; }
}
}
AppliedWeapons++; AppliedMeshes += meshes; AppliedRenderers += rends;
LastLog = names[idx] + " · 메시 " + meshes + " · 렌더러 " + rends;
if (cfg.verboseLog != 0) Debug.Log("[WL816b WeaponPalette] " + LastLog);
return meshes + rends > 0;
}
/// <summary>인스턴스 접미사 "(Clone)" 을 뗀다.</summary>
static string Strip(string n)
{
if (string.IsNullOrEmpty(n)) return n;
int i = n.IndexOf("(Clone)");
return i >= 0 ? n.Substring(0, i).TrimEnd() : n;
}
static int IndexOf(string[] names, string n)
{
for (int i = 0; i < names.Length; i++) if (names[i] == n) return i;
return -1;
}
/// <summary>진단 카운터 초기화(프로브 전용).</summary>
public static void ResetCounters() { AppliedWeapons = 0; AppliedMeshes = 0; AppliedRenderers = 0; LastLog = ""; }
}
}