118 lines
6.2 KiB
C#
118 lines
6.2 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 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 = ""; }
|
||
}
|
||
}
|