408 lines
22 KiB
C#
408 lines
22 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// MobHitFlash.cs — 몹 피격 점멸(MaterialPropertyBlock · 머티리얼 인스턴스 0 · GC 0) + 처치 디졸브 엣지 세팅
|
|
//
|
|
// PD 지시 #813 · 발주서 WL-811def §1-3 · 구 발주서 WL-802d(주석 처리된 Actor.HitEffect_Color 의 MPB 복원) · 설계안 §C 811f 행 (2026-09-09)
|
|
//
|
|
// ■ 무엇을 하나 (HitConfirmed / Killed / Spawned 구독 · 원본 훅 0 · Actor.cs/MobActor.cs 무수정)
|
|
// HitConfirmed(몹) → 렌더러별 MPB 에 알베도 색(HDR 흰색) + 에미션을 써서 blinkOnSeconds 뒤 비운다.
|
|
// 같은 프레임 다중 히트(스윙 1회 5~7타)는 켜진 동안 연장 · 꺼진 뒤 blinkOffSeconds 안은 재점멸 안 함.
|
|
// Killed(몹) → 같은 MPB 에 디졸브 엣지 세기/범위(등급별)를 얹는다. 0.5 s 뒤 원본 Co_Dissolve 가 렌더러 머티리얼을
|
|
// 디졸브 머티리얼로 바꾸는 순간 그 프로퍼티가 적용된다(MPB 는 렌더러에 붙어 머티리얼 교체와 무관).
|
|
// Spawned(몹) → 풀 재사용 몹의 MPB 를 비운다(잔류 0).
|
|
// ■ 왜 원본 방식(머티리얼 Add/Remove)이 아니라 MPB 인가
|
|
// 원본 HitEffect_Color 는 sharedMaterials.ToList()+materials= 대입으로 매 타격 2.3 KB GC + 머티리얼 인스턴스가 생겨 주석 처리됐다.
|
|
// MPB 는 렌더러당 1개를 캐시해 재사용하므로 타격당 할당 0 · 머티리얼 인스턴스 0 · 드로우 +0(프로퍼티만).
|
|
// 대신 MPB 가 붙은 렌더러는 그 동안 SRP Batcher 에서 빠진다(≤ 0.03 s · 비운 뒤 복귀 · 디졸브 중 사망체 ≤ 6).
|
|
// ■ 렌더러 캐시 = Actor.arr_Renderer(protected · Awake 에서 ParticleSystemRenderer 를 뺀 캐시)를 리플렉션으로 1회 읽는다(원본 무수정 ·
|
|
// 같은 필터 결과). 필드를 못 찾으면 GetComponentsInChildren 로 같은 필터를 1회 적용. 액터당 상태는 처음 볼 때 1회만 할당(풀이라 유한).
|
|
// ■ 프로퍼티는 셰이더별로 1회 Shader.FindPropertyIndex 로 실측해 캐시 · 없으면 그 렌더러는 건너뛴다(예외 0).
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using UnityEngine;
|
|
using WL.Combat.Core;
|
|
|
|
namespace WL.Combat.Reaction
|
|
{
|
|
public static class MobHitFlash
|
|
{
|
|
struct Resolved { public bool hasColor, hasEmission; public int colorId, emissionId; }
|
|
|
|
sealed class State
|
|
{
|
|
public Actor actor;
|
|
public Renderer[] renderers;
|
|
public MaterialPropertyBlock[] blocks;
|
|
public Resolved[] resolved;
|
|
public bool blinkOn, tint;
|
|
public float blinkOffAt, blinkReadyAt, tintHdr, tintRange;
|
|
// WL #816zb — 이번 점멸이 처음 켜진 시각. 연장(ExtendCount)이 blinkMaxSeconds 를 넘지 못하게 하는 기준점.
|
|
public float blinkStartAt;
|
|
public int activeIndex = -1;
|
|
// WL #811gh — 임팩트 티어별 점멸 강도. 1 = 기존 동작(티어 시스템 off 이면 항상 1).
|
|
public float blinkColorMul = 1f, blinkTimeMul = 1f;
|
|
// WL #813s — 엘리트 상시 색(EliteMarker 가 등록). false = 기존 동작 그대로.
|
|
public bool elite;
|
|
public Color eliteAlbedo, eliteEmission;
|
|
}
|
|
|
|
static readonly Dictionary<Actor, State> s_states = new Dictionary<Actor, State>(64);
|
|
static readonly Dictionary<Shader, Resolved> s_byShader = new Dictionary<Shader, Resolved>(8);
|
|
static State[] s_active = new State[32];
|
|
static int s_activeCount;
|
|
|
|
static FieldInfo s_rendererField;
|
|
static bool s_rendererFieldLooked;
|
|
|
|
static bool s_dissolveResolved, s_dissolveHasHdr, s_dissolveHasRange;
|
|
static int s_edgeHdrId, s_edgeRangeId;
|
|
static readonly List<Renderer> s_tmpRenderers = new List<Renderer>(16);
|
|
|
|
// ── 진단(프로브가 읽는다)
|
|
public static bool Subscribed;
|
|
public static int OnCount, OffCount, ExtendCount, ThrottledCount, TintCount, ClearCount, SkippedFilter, SkippedNoProperty, StatesCreated, RendererFieldViaReflection;
|
|
public static string LastInfo = "";
|
|
public static int ActiveCount { get { return s_activeCount; } }
|
|
public static int StateCount { get { return s_states.Count; } }
|
|
public static bool DissolveResolved { get { return s_dissolveResolved; } }
|
|
public static bool DissolveHasHdr { get { return s_dissolveHasHdr; } }
|
|
public static bool DissolveHasRange { get { return s_dissolveHasRange; } }
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
|
static void Register()
|
|
{
|
|
CombatEvents.HitConfirmed.Add(OnHitConfirmed);
|
|
CombatEvents.Killed.Add(OnKilled);
|
|
CombatEvents.Spawned.Add(OnSpawned);
|
|
Subscribed = true;
|
|
}
|
|
|
|
static WLHitBlinkSettings St { get { return WLHitBlinkSettings.Instance; } }
|
|
|
|
static bool Verbose { get { var st = St; return st != null && st.verboseLog; } }
|
|
|
|
static void Log(string msg)
|
|
{
|
|
var st = St;
|
|
if (st != null && st.verboseLog) Debug.Log("[MobHitFlash] " + msg);
|
|
}
|
|
|
|
// ───────────────────────────────────────── 이벤트
|
|
static void OnHitConfirmed(in HitConfirmedEvent e)
|
|
{
|
|
if (!WLHitBlinkSettings.Enabled) return;
|
|
var st = St;
|
|
var victim = e.victim;
|
|
if (victim == null) return;
|
|
if (st.targetMobsOnly ? !(victim is MobActor) : victim.IsMainPC()) { SkippedFilter++; return; }
|
|
if (st.onlyByMainPC && !e.byMainPC) { SkippedFilter++; return; }
|
|
if (st.skipWhenDamageZero && e.damage <= 0d) { SkippedFilter++; return; }
|
|
|
|
var s = GetState(victim, st);
|
|
if (s == null) return;
|
|
// WL #811gh — 임팩트 티어별 점멸 색/시간 배수. 티어 시스템이 꺼져 있으면 (1, 1) = 기존 동작 그대로.
|
|
float colorMul, timeMul;
|
|
ImpactTier.GetBlinkModifiers(victim, out colorMul, out timeMul);
|
|
float now = Time.unscaledTime;
|
|
// WL #816zb — 연장도 '처음 켜진 시각 + blinkMaxSeconds' 를 넘지 못한다(PD: 몹이 계속 하얗게 보이는 현상).
|
|
if (s.blinkOn) { s.blinkOffAt = CapOffAt(st, s.blinkStartAt, now + st.blinkOnSeconds * timeMul); ExtendCount++; return; }
|
|
if (now < s.blinkReadyAt) { ThrottledCount++; return; }
|
|
if (s.tint && !victim.IsDead()) s.tint = false; // 부활·재사용 뒤 남은 엣지 세팅 정리
|
|
|
|
WLReactionRunner.Ensure();
|
|
s.blinkColorMul = colorMul; s.blinkTimeMul = timeMul;
|
|
s.blinkOn = true; s.blinkStartAt = now;
|
|
s.blinkOffAt = CapOffAt(st, now, now + st.blinkOnSeconds * timeMul);
|
|
Apply(s, st);
|
|
Activate(s);
|
|
OnCount++;
|
|
LastInfo = "on";
|
|
if (Verbose) Log("점멸 on " + victim.name + " renderers=" + s.renderers.Length);
|
|
}
|
|
|
|
static void OnKilled(in KilledEvent e)
|
|
{
|
|
if (!WLHitBlinkSettings.Enabled) return;
|
|
var st = St;
|
|
if (!st.dissolveEdgeEnabled) return;
|
|
var victim = e.victim;
|
|
if (victim == null || !(victim is MobActor)) return;
|
|
ResolveDissolve(st);
|
|
if (!s_dissolveHasHdr && !s_dissolveHasRange) { SkippedNoProperty++; return; }
|
|
var s = GetState(victim, st);
|
|
if (s == null) return;
|
|
WLReactionRunner.Ensure(); // 리뷰 Minor-4: 첫 이벤트가 Killed 여도 씬 전환 훅이 붙어 s_states(파괴 액터 참조)가 씬을 넘지 않게
|
|
float hdr = e.subRole == eSubRol.Boss ? st.dissolveEdgeHdrBoss : e.subRole == eSubRol.Elite ? st.dissolveEdgeHdrElite : st.dissolveEdgeHdrNormal;
|
|
float range = e.subRole == eSubRol.Boss ? st.dissolveEdgeRangeBoss : e.subRole == eSubRol.Elite ? st.dissolveEdgeRangeElite : st.dissolveEdgeRangeNormal;
|
|
if (hdr < 0f && range < 0f) return;
|
|
s.tint = true; s.tintHdr = hdr; s.tintRange = range;
|
|
Apply(s, st);
|
|
TintCount++;
|
|
if (Verbose) Log("디졸브 엣지 " + victim.name + " sub=" + e.subRole + " hdr=" + hdr + " range=" + range);
|
|
}
|
|
|
|
static void OnSpawned(in SpawnedEvent e)
|
|
{
|
|
if (e.actor == null) return;
|
|
State s;
|
|
if (!s_states.TryGetValue(e.actor, out s)) return;
|
|
if (!s.blinkOn && !s.tint) return;
|
|
s.blinkOn = false; s.tint = false; s.blinkReadyAt = 0f;
|
|
var st = St;
|
|
// 리뷰 Minor-8ⓒ: st == null(에셋 자체가 없음)이면 Apply 를 못 부르지만, 그 상태에서는 Enabled 가 false 라 애초에 MPB 를 쓴 적이 없다
|
|
// (블록이 있다면 RestoreAll 의 st == null 경로가 SetPropertyBlock(null) 로 뗀다) → 잔류 0. ⓑ s_states 는 씬 전환(ClearAll)까지 유지 = 풀 크기 상한.
|
|
if (st != null) Apply(s, st);
|
|
Deactivate(s);
|
|
ClearCount++;
|
|
}
|
|
|
|
// ───────────────────────────────────────── 상태
|
|
static State GetState(Actor actor, WLHitBlinkSettings st)
|
|
{
|
|
State s;
|
|
if (s_states.TryGetValue(actor, out s)) return s;
|
|
var renderers = FindRenderers(actor);
|
|
if (renderers == null || renderers.Length == 0) return null;
|
|
s = new State { actor = actor, renderers = renderers, blocks = new MaterialPropertyBlock[renderers.Length], resolved = new Resolved[renderers.Length] };
|
|
for (int i = 0; i < renderers.Length; i++)
|
|
{
|
|
s.blocks[i] = new MaterialPropertyBlock();
|
|
var r = renderers[i];
|
|
var mat = r != null ? r.sharedMaterial : null;
|
|
s.resolved[i] = Resolve(mat != null ? mat.shader : null, st);
|
|
}
|
|
s_states.Add(actor, s);
|
|
StatesCreated++;
|
|
return s;
|
|
}
|
|
|
|
/// <summary>Actor.arr_Renderer(protected 캐시)를 리플렉션으로 1회 읽는다. 실패하면 같은 필터로 직접 수집.</summary>
|
|
static Renderer[] FindRenderers(Actor actor)
|
|
{
|
|
if (!s_rendererFieldLooked)
|
|
{
|
|
s_rendererFieldLooked = true;
|
|
s_rendererField = typeof(Actor).GetField("arr_Renderer", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
|
}
|
|
if (s_rendererField != null)
|
|
{
|
|
var arr = s_rendererField.GetValue(actor) as Renderer[];
|
|
if (arr != null && arr.Length > 0) { RendererFieldViaReflection++; return arr; }
|
|
}
|
|
s_tmpRenderers.Clear();
|
|
actor.GetComponentsInChildren<Renderer>(true, s_tmpRenderers);
|
|
int n = 0;
|
|
for (int i = 0; i < s_tmpRenderers.Count; i++) if (!(s_tmpRenderers[i] is ParticleSystemRenderer)) n++;
|
|
var result = new Renderer[n];
|
|
n = 0;
|
|
for (int i = 0; i < s_tmpRenderers.Count; i++) if (!(s_tmpRenderers[i] is ParticleSystemRenderer)) result[n++] = s_tmpRenderers[i];
|
|
s_tmpRenderers.Clear();
|
|
return result;
|
|
}
|
|
|
|
static Resolved Resolve(Shader shader, WLHitBlinkSettings st)
|
|
{
|
|
Resolved r;
|
|
if (shader == null) return default(Resolved);
|
|
if (s_byShader.TryGetValue(shader, out r)) return r;
|
|
r = default(Resolved);
|
|
var names = st.colorProperties;
|
|
if (names != null)
|
|
for (int i = 0; i < names.Length; i++)
|
|
if (!string.IsNullOrEmpty(names[i]) && shader.FindPropertyIndex(names[i]) >= 0) { r.hasColor = true; r.colorId = Shader.PropertyToID(names[i]); break; }
|
|
names = st.emissionProperties;
|
|
if (names != null)
|
|
for (int i = 0; i < names.Length; i++)
|
|
if (!string.IsNullOrEmpty(names[i]) && shader.FindPropertyIndex(names[i]) >= 0) { r.hasEmission = true; r.emissionId = Shader.PropertyToID(names[i]); break; }
|
|
s_byShader.Add(shader, r);
|
|
if (!r.hasColor && !r.hasEmission) SkippedNoProperty++;
|
|
if (Verbose) Log("셰이더 실측 " + shader.name + " color=" + r.hasColor + " emission=" + r.hasEmission);
|
|
return r;
|
|
}
|
|
|
|
static void ResolveDissolve(WLHitBlinkSettings st)
|
|
{
|
|
if (s_dissolveResolved) return;
|
|
Shader sh = null;
|
|
var info = InGameInfo.Ins;
|
|
if (info != null && info.mat_Dissolve != null) sh = info.mat_Dissolve.shader;
|
|
if (sh == null && !string.IsNullOrEmpty(st.dissolveShaderName)) sh = Shader.Find(st.dissolveShaderName);
|
|
if (sh == null) return; // 아직 못 찾음(타이틀 등) — 다음 Killed 때 재시도
|
|
s_dissolveResolved = true;
|
|
s_dissolveHasHdr = !string.IsNullOrEmpty(st.dissolveEdgeHdrProperty) && sh.FindPropertyIndex(st.dissolveEdgeHdrProperty) >= 0;
|
|
s_dissolveHasRange = !string.IsNullOrEmpty(st.dissolveEdgeRangeProperty) && sh.FindPropertyIndex(st.dissolveEdgeRangeProperty) >= 0;
|
|
if (s_dissolveHasHdr) s_edgeHdrId = Shader.PropertyToID(st.dissolveEdgeHdrProperty);
|
|
if (s_dissolveHasRange) s_edgeRangeId = Shader.PropertyToID(st.dissolveEdgeRangeProperty);
|
|
if (Verbose) Log("디졸브 셰이더 실측 " + sh.name + " hdr=" + s_dissolveHasHdr + " range=" + s_dissolveHasRange);
|
|
}
|
|
|
|
/// <summary>상태(blinkOn · tint)로 렌더러별 MPB 를 다시 구성해 쓴다. 둘 다 꺼져 있으면 블록을 떼어(null) 오버라이드 0 · SRP Batcher 복귀.</summary>
|
|
static void Apply(State s, WLHitBlinkSettings st)
|
|
{
|
|
for (int i = 0; i < s.renderers.Length; i++)
|
|
{
|
|
var r = s.renderers[i];
|
|
if (r == null) continue;
|
|
var mpb = s.blocks[i];
|
|
mpb.Clear();
|
|
// WL #813s — 엘리트 상시 색을 먼저 깐다. 아래 점멸이 같은 프로퍼티를 덮으므로 우선순위 = 점멸 > 상시.
|
|
if (s.elite)
|
|
{
|
|
var eres = s.resolved[i];
|
|
if (eres.hasColor) mpb.SetColor(eres.colorId, s.eliteAlbedo);
|
|
if (eres.hasEmission) mpb.SetColor(eres.emissionId, s.eliteEmission);
|
|
}
|
|
if (s.blinkOn)
|
|
{
|
|
var res = s.resolved[i];
|
|
if (res.hasColor) mpb.SetColor(res.colorId, ScaleRgb(st.blinkColor, s.blinkColorMul));
|
|
if (st.useEmission && res.hasEmission) mpb.SetColor(res.emissionId, ScaleRgb(st.blinkEmission, s.blinkColorMul));
|
|
}
|
|
if (s.tint)
|
|
{
|
|
if (s_dissolveHasHdr && s.tintHdr >= 0f) mpb.SetFloat(s_edgeHdrId, s.tintHdr);
|
|
if (s_dissolveHasRange && s.tintRange >= 0f) mpb.SetFloat(s_edgeRangeId, s.tintRange);
|
|
}
|
|
// 빈 블록을 써도 HasPropertyBlock 은 true 로 남는다(2026-09-09 Title Play 실측) → 오버라이드가 없으면 null 로 블록을 떼어 SRP Batcher 로 복귀
|
|
if (mpb.isEmpty) r.SetPropertyBlock(null); else r.SetPropertyBlock(mpb);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// WL #816zb — 점멸 종료 시각의 상한. startAt + blinkMaxSeconds 를 넘지 못한다.
|
|
/// blinkMaxSeconds ≤ 0 이면 상한 없음 = 기존 동작 그대로(C8 롤백 경로).
|
|
/// </summary>
|
|
static float CapOffAt(WLHitBlinkSettings st, float startAt, float wantOffAt)
|
|
{
|
|
float max = st.blinkMaxSeconds;
|
|
if (max <= 0f) return wantOffAt;
|
|
float limit = startAt + max;
|
|
return wantOffAt < limit ? wantOffAt : limit;
|
|
}
|
|
|
|
/// <summary>WL #811gh — RGB 만 배수. 알파는 건드리지 않는다(배수 1 이면 원본 Color 를 그대로 돌려준다 = 오차 0).</summary>
|
|
static Color ScaleRgb(Color c, float m)
|
|
{
|
|
if (m == 1f) return c;
|
|
return new Color(c.r * m, c.g * m, c.b * m, c.a);
|
|
}
|
|
|
|
static void Activate(State s)
|
|
{
|
|
if (s.activeIndex >= 0) return;
|
|
if (s_activeCount == s_active.Length) System.Array.Resize(ref s_active, s_active.Length * 2);
|
|
s.activeIndex = s_activeCount;
|
|
s_active[s_activeCount++] = s;
|
|
}
|
|
|
|
static void Deactivate(State s)
|
|
{
|
|
int i = s.activeIndex;
|
|
if (i < 0) return;
|
|
int last = --s_activeCount;
|
|
s_active[i] = s_active[last];
|
|
if (s_active[i] != null) s_active[i].activeIndex = i;
|
|
s_active[last] = null;
|
|
s.activeIndex = -1;
|
|
}
|
|
|
|
internal static void Tick(float now)
|
|
{
|
|
if (s_activeCount == 0) return;
|
|
var st = St;
|
|
for (int i = s_activeCount - 1; i >= 0; i--)
|
|
{
|
|
var s = s_active[i];
|
|
if (s.blinkOn && (now >= s.blinkOffAt || st == null || !WLHitBlinkSettings.Enabled))
|
|
{
|
|
s.blinkOn = false;
|
|
s.blinkReadyAt = now + (st != null ? st.blinkOffSeconds : 0f);
|
|
if (st != null) Apply(s, st);
|
|
OffCount++;
|
|
LastInfo = "off";
|
|
}
|
|
if (!s.blinkOn) Deactivate(s);
|
|
}
|
|
}
|
|
|
|
/// <summary>모든 몹의 MPB 를 비운다(러너 파괴 · 앱 종료 · 프로브 정리). 상태 캐시는 유지.</summary>
|
|
public static void RestoreAll()
|
|
{
|
|
var st = St;
|
|
foreach (var kv in s_states)
|
|
{
|
|
var s = kv.Value;
|
|
if (!s.blinkOn && !s.tint && !s.elite) continue;
|
|
s.blinkOn = false; s.tint = false; s.elite = false;
|
|
if (st != null) Apply(s, st);
|
|
else for (int i = 0; i < s.renderers.Length; i++) if (s.renderers[i] != null) { s.blocks[i].Clear(); s.renderers[i].SetPropertyBlock(null); }
|
|
}
|
|
for (int i = 0; i < s_activeCount; i++) { s_active[i].activeIndex = -1; s_active[i] = null; }
|
|
s_activeCount = 0;
|
|
}
|
|
|
|
/// <summary>씬 전환: MPB 를 비우고 상태 캐시(파괴된 Actor 참조)를 버린다.</summary>
|
|
public static void ClearAll()
|
|
{
|
|
RestoreAll();
|
|
s_states.Clear();
|
|
}
|
|
|
|
// ───────────────────────────────────────── WL #813s — 엘리트 상시 색 (EliteMarker 가 부른다)
|
|
// 렌더러 MPB 의 소유자를 하나로 유지하기 위한 진입점이다. 별도 MPB 를 쓰면 이 클래스의 Apply(=Clear 후 재구성)가
|
|
// 첫 피격 점멸에서 통째로 지운다. 상태만 넣고 합성은 기존 Apply 가 한다(우선순위 = 점멸 > 상시).
|
|
// WLHitBlinkSettings 의 enabled 와는 무관하다 — 이 SO 는 프로퍼티명 목록(셰이더 실측)의 출처로만 쓴다.
|
|
|
|
/// <summary>엘리트 상시 색을 건다. 렌더러·프로퍼티를 못 찾으면 false(색 없음).</summary>
|
|
public static bool SetEliteTint(Actor actor, Color albedo, Color emission)
|
|
{
|
|
var st = St;
|
|
if (actor == null || st == null) return false;
|
|
var s = GetState(actor, st);
|
|
if (s == null) return false;
|
|
bool any = false;
|
|
for (int i = 0; i < s.resolved.Length; i++) if (s.resolved[i].hasColor || s.resolved[i].hasEmission) { any = true; break; }
|
|
if (!any) { SkippedNoProperty++; return false; }
|
|
s.elite = true; s.eliteAlbedo = albedo; s.eliteEmission = emission;
|
|
Apply(s, st);
|
|
if (Verbose) Log("엘리트 상시 색 on " + actor.name + " renderers=" + s.renderers.Length);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>엘리트 상시 색을 뗀다(처치·정리). 점멸/디졸브 상태는 건드리지 않는다.</summary>
|
|
public static void ClearEliteTint(Actor actor)
|
|
{
|
|
State s;
|
|
if (actor == null || !s_states.TryGetValue(actor, out s) || !s.elite) return;
|
|
s.elite = false;
|
|
var st = St;
|
|
if (st != null) Apply(s, st);
|
|
else for (int i = 0; i < s.renderers.Length; i++) if (s.renderers[i] != null) { s.blocks[i].Clear(); s.renderers[i].SetPropertyBlock(null); }
|
|
}
|
|
|
|
/// <summary>프로브용: 이 액터에 엘리트 상시 색이 걸려 있는가.</summary>
|
|
public static bool HasEliteTint(Actor actor)
|
|
{
|
|
State s;
|
|
return actor != null && s_states.TryGetValue(actor, out s) && s.elite;
|
|
}
|
|
|
|
/// <summary>프로브용: 특정 액터의 렌더러가 지금 프로퍼티 블록을 갖고 있는가(잔류 검사).</summary>
|
|
public static bool HasAnyBlock(Actor actor)
|
|
{
|
|
State s;
|
|
if (actor == null || !s_states.TryGetValue(actor, out s)) return false;
|
|
for (int i = 0; i < s.renderers.Length; i++) if (s.renderers[i] != null && s.renderers[i].HasPropertyBlock()) return true;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>프로브용 카운터 초기화.</summary>
|
|
public static void ResetDiagnostics()
|
|
{
|
|
OnCount = OffCount = ExtendCount = ThrottledCount = TintCount = ClearCount = SkippedFilter = SkippedNoProperty = StatesCreated = RendererFieldViaReflection = 0;
|
|
LastInfo = "";
|
|
}
|
|
}
|
|
}
|