699 lines
41 KiB
C#
699 lines
41 KiB
C#
// WL-815f 프로브 — 적 공격 예고(텔레그래프) 1차 · 에디트 모드 전용(Play 0 · 로그인 0 · 커밋 0)
|
||
//
|
||
// MakeAsset — WLTelegraphSettings.asset 생성/갱신(손으로 YAML 쓰지 않는다)
|
||
// Run — A 클립 히트 시각 표(몹 12종) · B 콜라이더 실측 · C 가짜 왕복(3종+보스) · D 동시 상한
|
||
// E 보스 무변경 · F GC 0 · G C8 · H 애니메이터 실측(데미지도 늦어짐 증명) · I 캡처 3장
|
||
//
|
||
// 산출물 = AgentScripts/staging/WL815f/EDIT_PROBE.txt · 캡처 = Screenshots_WL/WL815f/
|
||
|
||
using UnityEngine;
|
||
using UnityEditor;
|
||
using System.Collections.Generic;
|
||
using WL.Combat.Telegraph;
|
||
|
||
public static class WL815f_Probe
|
||
{
|
||
const string kOutDir = @"E:\NerdNavis\nn_himminji\Screenshots_WL\WL815f";
|
||
const string kSnap = "AgentScripts/staging/WL815f/EDIT_PROBE.txt";
|
||
const string kSo = "Assets/WL/Combat/Settings/Resources/WL/WLTelegraphSettings.asset";
|
||
const string kMobRoot = "Assets/Res_Addr/Mobs/";
|
||
const string kProjRoot = "Assets/Res_Addr/Projectile/";
|
||
const int W = 1080, H = 1920;
|
||
|
||
static System.Text.StringBuilder sb;
|
||
static int pass, fail;
|
||
static readonly List<string> s_errors = new List<string>();
|
||
|
||
static void L(string s) { sb.Append(s).Append('\n'); }
|
||
static void Chk(bool ok, string label, string detail)
|
||
{
|
||
if (ok) pass++; else fail++;
|
||
sb.Append(ok ? " PASS " : " FAIL ").Append(label);
|
||
if (!string.IsNullOrEmpty(detail)) sb.Append(" ").Append(detail);
|
||
sb.Append('\n');
|
||
}
|
||
static string F(float v, int d) { return v.ToString(d == 3 ? "F3" : d == 2 ? "F2" : "F4"); }
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────
|
||
public static string MakeAsset()
|
||
{
|
||
sb = new System.Text.StringBuilder();
|
||
var dir = System.IO.Path.GetDirectoryName(kSo).Replace('\\', '/');
|
||
if (!System.IO.Directory.Exists(dir)) { L("FAIL 폴더 없음 " + dir); return sb.ToString(); }
|
||
|
||
var so = AssetDatabase.LoadAssetAtPath<WLTelegraphSettings>(kSo);
|
||
bool created = false;
|
||
if (so == null)
|
||
{
|
||
so = ScriptableObject.CreateInstance<WLTelegraphSettings>();
|
||
AssetDatabase.CreateAsset(so, kSo);
|
||
created = true;
|
||
}
|
||
// 캡처 실측으로 조정한 값(몹이 통째로 노랗게 보여 에미션을 낮췄다) — 코드 기본값과 같게 유지한다
|
||
so.tintAlbedoAvoidable = new Color(1.20f, 1.05f, 0.60f, 1f);
|
||
so.tintEmissionAvoidable = new Color(0.55f, 0.34f, 0.02f, 1f);
|
||
so.tintAlbedoUnavoidable = new Color(1.30f, 0.60f, 0.55f, 1f);
|
||
so.tintEmissionUnavoidable = new Color(0.75f, 0.08f, 0.04f, 1f);
|
||
so.defaultProjectileSpeed = 5f;
|
||
EditorUtility.SetDirty(so);
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
L((created ? "CREATED " : "EXISTS ") + kSo);
|
||
L(" enabled=" + so.enabled + " 잡몹 " + so.telegraphSecondsNormal + "s · 원거리 " + so.telegraphSecondsRanged
|
||
+ "s · 엘리트 " + so.telegraphSecondsElite + "s · 하한 k " + so.minSpeedMultiplier
|
||
+ " · 상한 " + so.maxConcurrent + " · 펄스 " + so.pulseScale + " · SFX idx " + so.sfxIndex);
|
||
return sb.ToString();
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────
|
||
public static string Run()
|
||
{
|
||
sb = new System.Text.StringBuilder();
|
||
pass = fail = 0; s_errors.Clear();
|
||
Application.logMessageReceived += OnLog;
|
||
|
||
// 배치 에디터의 현재(제목 없는·저장 안 된) 씬을 그대로 작업장으로 쓴다.
|
||
// 만드는 오브젝트는 전부 HideFlags.DontSave 이고 끝에서 파기한다 → 저장 0 · 커밋 0.
|
||
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
||
|
||
try
|
||
{
|
||
L("# WL-815f 프로브 — 적 공격 예고 1차 (에디트 모드 · Play 0)");
|
||
L("생성 " + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||
L("");
|
||
|
||
WLTelegraphSettings.ClearCache();
|
||
var st = WLTelegraphSettings.Instance;
|
||
if (st == null) { L("FAIL — SO 를 못 읽었다: " + kSo); return Finish(); }
|
||
|
||
Telegraph.EnsureSubscribed();
|
||
Telegraph.ResetDiagnostics();
|
||
Telegraph.ClearClipCache();
|
||
TelegraphShape.ClearCache(); TelegraphShape.ResetDiagnostics();
|
||
TelegraphDecal.ResetDiagnostics();
|
||
WL.Combat.Reaction.ImpactTier.ResetDiagnostics();
|
||
TelegraphRunner.BeginForceClock();
|
||
|
||
SectionA(st);
|
||
SectionB(st);
|
||
SectionC(st, scene);
|
||
SectionD(st, scene);
|
||
SectionE(st, scene);
|
||
SectionF(st, scene);
|
||
SectionG(st, scene);
|
||
SectionH(scene);
|
||
SectionJ(st, scene);
|
||
SectionI(st, scene);
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
L("EXCEPTION " + ex.GetType().Name + " : " + ex.Message);
|
||
L(ex.StackTrace);
|
||
fail++;
|
||
}
|
||
finally
|
||
{
|
||
Telegraph.ClearAll();
|
||
TelegraphDecal.DestroyAll();
|
||
TelegraphRunner.ResetForceClock();
|
||
WLTelegraphSettings.RuntimeDisabled = false;
|
||
CleanupStrays();
|
||
Application.logMessageReceived -= OnLog;
|
||
}
|
||
return Finish();
|
||
}
|
||
|
||
static string Finish()
|
||
{
|
||
L("");
|
||
L("## 콘솔 Error/Exception " + s_errors.Count + "건");
|
||
for (int i = 0; i < s_errors.Count && i < 12; i++) L(" " + s_errors[i]);
|
||
L("");
|
||
L("RESULT PASS " + pass + " / FAIL " + fail);
|
||
var dir = System.IO.Path.GetDirectoryName(kSnap);
|
||
if (!System.IO.Directory.Exists(dir)) System.IO.Directory.CreateDirectory(dir);
|
||
System.IO.File.WriteAllText(kSnap, sb.ToString());
|
||
return sb.ToString();
|
||
}
|
||
|
||
/// <summary>프로브가 만든 오브젝트가 씬에 남지 않게 한다(이름 접두 PROBE_ · 저장 0).</summary>
|
||
static void CleanupStrays()
|
||
{
|
||
var all = Object.FindObjectsByType<GameObject>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||
int n = 0;
|
||
for (int i = 0; i < all.Length; i++)
|
||
{
|
||
var g = all[i];
|
||
if (g == null || g.transform.parent != null) continue;
|
||
if (g.name.StartsWith("PROBE_") || g.name.StartsWith("__WLTelegraph")) { Object.DestroyImmediate(g); n++; }
|
||
}
|
||
if (n > 0) L(" (정리) 프로브 오브젝트 " + n + "개 파기");
|
||
}
|
||
|
||
static void OnLog(string cond, string stack, LogType t)
|
||
{
|
||
if (t == LogType.Error || t == LogType.Exception || t == LogType.Assert)
|
||
if (s_errors.Count < 40) s_errors.Add(t + " : " + cond);
|
||
}
|
||
|
||
// ── 몹 표(기준서 §G-0 실측표와 대조할 12종) ────────────────────────────
|
||
struct MobRow { public string label, prefab, proj; public float lifetime; public bool elite, boss; }
|
||
static readonly MobRow[] s_mobs =
|
||
{
|
||
new MobRow{ label="Batty_A(101) 존1 잡몹", prefab="Mob/Batty_A", proj="Mob/P_Batty_Meele", lifetime=0.1f },
|
||
new MobRow{ label="Batty_B(102) 존1 원거리", prefab="Mob/Batty_B", proj="Mob/P_Batty_Range", lifetime=1.5f },
|
||
new MobRow{ label="Porin_A(168) 존2 잡몹", prefab="Mob/Porin_A", proj="Mob/P_Porin_Meele", lifetime=0.1f },
|
||
new MobRow{ label="Racco_A(173) 존3 잡몹", prefab="Mob/Racco_A", proj="Mob/P_Racco_Meele", lifetime=0.1f },
|
||
new MobRow{ label="Rabby_Brown(146) 존3 잡몹", prefab="Mob/Rabby_Brown", proj="Mob/P_Rabby_Meele", lifetime=0.1f },
|
||
new MobRow{ label="Devilu_A(118) 존4 잡몹", prefab="Mob/Devilu_A", proj="Mob/P_Devilu_Meele", lifetime=0.1f },
|
||
new MobRow{ label="Golem_A(1014) 엘리트", prefab="Elite/Golem_A", proj="Elite/P_Golem_A", lifetime=0.1f, elite=true },
|
||
new MobRow{ label="Aspethis_D(1023) 엘리트", prefab="Elite/Aspethis_D", proj="Elite/P_Aspethis_D", lifetime=0.1f, elite=true },
|
||
new MobRow{ label="Beeto_A(1009) 엘리트", prefab="Elite/Beeto_A", proj="Elite/P_Beeto_A", lifetime=0.1f, elite=true },
|
||
new MobRow{ label="Cute_Crab_C(1028) 엘리트", prefab="Elite/Cute_Crab_C_Blue", proj="Elite/P_Cute_Crab_C_Blue",lifetime=0.1f, elite=true },
|
||
new MobRow{ label="Mummy_King(1024) 엘리트", prefab="Elite/Mummy_King", proj="Elite/P_Mummy_King", lifetime=1.5f, elite=true },
|
||
new MobRow{ label="Anubis(10006) 보스", prefab="FieldBoss/Anubis", proj="FieldBoss/Anubis_Attack", lifetime=0.1f, boss=true },
|
||
};
|
||
|
||
// ─────────────────────────────────────────────────────────── A. 클립 히트 시각 표
|
||
static void SectionA(WLTelegraphSettings st)
|
||
{
|
||
L("## A. 예고 시간 전/후 표 (몹 12종 · 클립 히트 이벤트 실측 · 목표 = SO 값)");
|
||
L("| 몹 | 컨트롤러 | 히트 이벤트 s(전) | 등급 목표 s | k(= 적용 속도) | 하한 걸림 | 예고 s(후) | 배수 |");
|
||
L("|---|---|---|---|---|---|---|---|");
|
||
|
||
int measured = 0;
|
||
for (int i = 0; i < s_mobs.Length; i++)
|
||
{
|
||
var m = s_mobs[i];
|
||
var go = AssetDatabase.LoadAssetAtPath<GameObject>(kMobRoot + m.prefab + ".prefab");
|
||
if (go == null) { L("| " + m.label + " | (프리팹 없음) | | | | | | |"); continue; }
|
||
var anim = go.GetComponentInChildren<Animator>(true);
|
||
var rac = anim != null ? anim.runtimeAnimatorController : null;
|
||
string racName = rac != null ? rac.name : "(없음)";
|
||
|
||
var tbl = Telegraph.HitSecondsTable(rac);
|
||
float before = tbl != null && tbl.Length > 0 ? tbl[0] : -1f;
|
||
if (before > 0f) measured++;
|
||
|
||
bool ranged = m.lifetime >= st.rangedLifetimeThreshold;
|
||
float target = m.boss ? -1f : (m.elite ? st.telegraphSecondsElite : (ranged ? st.telegraphSecondsRanged : st.telegraphSecondsNormal));
|
||
|
||
if (m.boss)
|
||
{
|
||
L("| " + m.label + " | " + racName + " | " + (before > 0f ? F(before, 3) : "-")
|
||
+ " | **무변경**(코드 3.0 s 차징) | 1.000 | - | " + (before > 0f ? F(before, 3) : "-") + " | ×1.00 |");
|
||
continue;
|
||
}
|
||
if (before <= 0f) { L("| " + m.label + " | " + racName + " | (히트 이벤트 못 찾음) | " + F(target, 2) + " | | | | |"); continue; }
|
||
|
||
float desired = before / target;
|
||
float floorSpeed = 1f * Mathf.Clamp(st.minSpeedMultiplier, 0.01f, 1f);
|
||
float applied = Mathf.Clamp(desired, floorSpeed, 1f);
|
||
bool clamped = desired < floorSpeed - 0.0001f;
|
||
float after = before / applied;
|
||
L("| " + m.label + " | " + racName + " | " + F(before, 3) + " | " + F(target, 2) + " | " + F(applied, 3)
|
||
+ " | " + (clamped ? "🔴 O" : "-") + " | **" + F(after, 3) + "** | ×" + F(after / before, 2) + " |");
|
||
}
|
||
L("");
|
||
Chk(measured >= 10, "12종 중 클립 히트 이벤트 실측 성공", measured + " / " + s_mobs.Length);
|
||
L("→ 🔴 속도 곡선만으로는 목표 0.5/0.6/0.8 s 에 도달하지 못한다(하한 k=" + st.minSpeedMultiplier + ").");
|
||
L(" 나머지 시간은 **바닥 판이 채워지는 " + F(st.telegraphSecondsNormal, 2) + " s** 와 몹 크기/색/소리가 채운다(기준서 §G-2 ⓐ 한계 그대로).");
|
||
L("");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────── B. 콜라이더 실측
|
||
static void SectionB(WLTelegraphSettings st)
|
||
{
|
||
L("## B. 데칼 크기의 출처 = 투사체 프리팹 BoxCollider (신규 데이터 0)");
|
||
L("| 투사체 | 읽은 size | 읽은 center | 속도 | 출처 | 기준서 §G-0 실측 |");
|
||
L("|---|---|---|---|---|---|");
|
||
|
||
string[] expect = { "3×3×3 c(0,0,0)", "1×3×1 c(0,0,0)", "3×3×3 c(0,0,0)", "3×3×3 c(0,0,1.5)", "2×2×4 c(0,0,2)" };
|
||
string[] names = { "Mob/P_Batty_Meele", "Mob/P_Batty_Range", "Mob/P_Porin_Meele", "Elite/P_Golem_A", "FieldBoss/Anubis_Attack" };
|
||
Vector3[] wantSize = { new Vector3(3, 3, 3), new Vector3(1, 3, 1), new Vector3(3, 3, 3), new Vector3(3, 3, 3), new Vector3(2, 2, 4) };
|
||
Vector3[] wantCenter = { Vector3.zero, Vector3.zero, Vector3.zero, new Vector3(0, 0, 1.5f), new Vector3(0, 0, 2f) };
|
||
|
||
int ok = 0;
|
||
for (int i = 0; i < names.Length; i++)
|
||
{
|
||
var b = TelegraphShape.Get(names[i]);
|
||
bool match = b.resolved && (b.size - wantSize[i]).sqrMagnitude < 0.0001f && (b.center - wantCenter[i]).sqrMagnitude < 0.0001f;
|
||
if (match) ok++;
|
||
L("| " + names[i] + " | " + b.size + " | " + b.center + " | " + F(b.speed, 2) + " | " + b.source + " | " + expect[i] + (match ? " ✅" : " ❌") + " |");
|
||
}
|
||
Chk(ok == names.Length, "콜라이더 실측 = 기준서 §G-0 표와 완전 일치", ok + " / " + names.Length);
|
||
L("");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────── C. 가짜 왕복
|
||
static GameObject MakeMob(MobRow m, Vector3 pos, UnityEngine.SceneManagement.Scene scene, out MobActor actor)
|
||
{
|
||
actor = null;
|
||
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(kMobRoot + m.prefab + ".prefab");
|
||
if (prefab == null) return null;
|
||
var go = Object.Instantiate(prefab);
|
||
go.name = "PROBE_" + m.prefab.Replace('/', '_');
|
||
go.hideFlags = HideFlags.DontSave;
|
||
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(go, scene);
|
||
go.transform.position = pos;
|
||
go.transform.rotation = Quaternion.identity;
|
||
|
||
actor = go.GetComponent<MobActor>();
|
||
if (actor == null) { Object.DestroyImmediate(go); return null; }
|
||
actor.m_Role = eRole.Mob;
|
||
actor.m_SubRole = m.boss ? eSubRol.Boss : (m.elite ? eSubRol.Elite : eSubRol.None);
|
||
actor.m_animation = go.GetComponentInChildren<Animator>(true);
|
||
|
||
var td = new MonsterTableData();
|
||
td.e_MonsterType = actor.m_SubRole;
|
||
td.s_Porjectile1 = m.proj;
|
||
td.f_ProjectileLifeTime1 = m.lifetime;
|
||
td.e_AttackType = eAttackType.Physics;
|
||
td.f_DefaultScale = go.transform.localScale.x;
|
||
var fi = typeof(MobActor).GetField("m_MonsterTableData", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
||
if (fi != null) fi.SetValue(actor, td);
|
||
return go;
|
||
}
|
||
|
||
static void SectionC(WLTelegraphSettings st, UnityEngine.SceneManagement.Scene scene)
|
||
{
|
||
L("## C. 가짜 AttackStarted → HitboxSpawned 왕복 (몹 3종 + 보스 · 실측)");
|
||
int[] pick = { 0, 2, 6, 11 }; // Batty_A · Porin_A · Golem_A(엘리트) · Anubis(보스)
|
||
|
||
for (int p = 0; p < pick.Length; p++)
|
||
{
|
||
var m = s_mobs[pick[p]];
|
||
MobActor actor;
|
||
var go = MakeMob(m, new Vector3(p * 20f, 0f, 0f), scene, out actor);
|
||
if (go == null) { Chk(false, "C 몹 인스턴스 " + m.label, "프리팹/MobActor 없음"); continue; }
|
||
|
||
float scale0 = go.transform.localScale.x;
|
||
float speed0 = actor.m_animation != null ? 1f : -1f;
|
||
if (actor.m_animation != null) actor.m_animation.speed = 1f;
|
||
|
||
Telegraph.ResetDiagnostics();
|
||
TelegraphDecal.ResetDiagnostics();
|
||
WL.Combat.Core.CombatEvents.RaiseAttackStarted(actor, 0, 1f, null);
|
||
|
||
L("");
|
||
L("### " + m.label);
|
||
if (m.boss)
|
||
{
|
||
bool untouched = Telegraph.AppliedSpeedOf(actor) < 0f && (actor.m_animation == null || Mathf.Abs(actor.m_animation.speed - 1f) < 0.0001f)
|
||
&& Mathf.Abs(go.transform.localScale.x - scale0) < 0.0001f && Telegraph.DecalIndexOf(actor) < 0;
|
||
Chk(untouched, "보스 무변경(속도·크기·판 전부 원본)",
|
||
"speed=" + (actor.m_animation != null ? F(actor.m_animation.speed, 3) : "-") + " scale=" + F(go.transform.localScale.x, 3)
|
||
+ " 판=" + Telegraph.DecalIndexOf(actor) + " SkippedBoss=" + Telegraph.SkippedBoss);
|
||
Object.DestroyImmediate(go);
|
||
continue;
|
||
}
|
||
|
||
float planned = Telegraph.PlannedSecondsOf(actor);
|
||
float applied = Telegraph.AppliedSpeedOf(actor);
|
||
int decal = Telegraph.DecalIndexOf(actor);
|
||
L(" 히트 시각 " + F(Telegraph.LastHitSeconds, 3) + " s → 목표 " + F(Telegraph.LastTargetSeconds, 2)
|
||
+ " s · 속도 " + F(Telegraph.LastOrigSpeed, 3) + "→" + F(applied, 3)
|
||
+ (Telegraph.LastWasClamped ? " (🔴 하한 " + st.minSpeedMultiplier + " 로 잘림)" : "")
|
||
+ " · 실제 예고 " + F(planned, 3) + " s");
|
||
Chk(applied > 0f && actor.m_animation != null && Mathf.Abs(actor.m_animation.speed - applied) < 0.0001f,
|
||
"animator.speed 가 실제로 낮아졌다", "anim.speed=" + (actor.m_animation != null ? F(actor.m_animation.speed, 3) : "-"));
|
||
Chk(Mathf.Abs(applied - Mathf.Max(Telegraph.LastHitSeconds / Telegraph.LastTargetSeconds, st.minSpeedMultiplier)) < 0.001f,
|
||
"속도 = max(히트시각 ÷ 목표, 하한)", "기대 " + F(Mathf.Max(Telegraph.LastHitSeconds / Telegraph.LastTargetSeconds, st.minSpeedMultiplier), 3));
|
||
|
||
// 판 크기 = 콜라이더 크기인가
|
||
Vector3 psize, pcenter; float yaw;
|
||
var box = TelegraphShape.Get(m.proj);
|
||
bool ranged = m.lifetime >= st.rangedLifetimeThreshold;
|
||
if (TelegraphDecal.TryGetPlate(decal, out psize, out pcenter, out yaw))
|
||
{
|
||
float wantW = box.size.x, wantL = ranged ? Mathf.Clamp(box.speed * m.lifetime, box.size.z, st.rangedMaxLength) : box.size.z;
|
||
bool sizeOk = Mathf.Abs(psize.x - wantW) < 0.01f && Mathf.Abs(psize.z - wantL) < 0.01f;
|
||
Chk(sizeOk, "판 크기 = 투사체 콜라이더 크기", "판 " + F(psize.x, 3) + "×" + F(psize.z, 3) + " · 콜라이더 " + F(wantW, 3) + "×" + F(wantL, 3));
|
||
float wantZ = st.forwardOffset + box.center.z + (ranged ? wantL * 0.5f : 0f);
|
||
bool cenOk = Mathf.Abs((pcenter.z - go.transform.position.z) - wantZ) < 0.01f;
|
||
Chk(cenOk, "판 중심 = 발사 위치 + collider.center", "판 z=" + F(pcenter.z - go.transform.position.z, 3) + " 기대 " + F(wantZ, 3));
|
||
}
|
||
else Chk(false, "판이 켜졌다", "decalIdx=" + decal);
|
||
|
||
// 회전 추종
|
||
go.transform.rotation = Quaternion.Euler(0f, 90f, 0f);
|
||
TelegraphRunner.ForceTick(0.02f);
|
||
if (TelegraphDecal.TryGetPlate(decal, out psize, out pcenter, out yaw))
|
||
Chk(Mathf.Abs(Mathf.DeltaAngle(yaw, 90f)) < 0.5f, "몹이 돌면 판도 따라 돈다", "판 yaw=" + F(yaw, 2));
|
||
|
||
// 채워지는 진행 + 펄스
|
||
float half = Mathf.Max(0.02f, planned * 0.5f - 0.02f);
|
||
TelegraphRunner.ForceTick(half);
|
||
float midScale = go.transform.localScale.x;
|
||
Chk(midScale > scale0 * 1.0005f && midScale < scale0 * st.pulseScale, "예고 중 크기가 커진다(펄스)",
|
||
"기본 " + F(scale0, 3) + " → 중간 " + F(midScale, 3) + " (상한 " + F(scale0 * st.pulseScale, 3) + ")");
|
||
Chk(WL.Combat.Reaction.MobHitFlash.HasEliteTint(actor) || !st.tintEnabled, "예고 중 몹 색(MPB)이 걸렸다",
|
||
"TintApplied=" + Telegraph.TintApplied);
|
||
|
||
// 811gh 히트스톱 스킵 조건
|
||
WL.Combat.Reaction.ImpactTier.ClearStamp();
|
||
var tier = WL.Combat.Reaction.ImpactTier.Resolve(actor, null, false);
|
||
float hs = WL.Combat.Reaction.ImpactTier.HitStopSeconds(tier);
|
||
Chk(!WL.Combat.Reaction.ImpactTier.Active || hs <= 0.0001f, "예고 중 히트스톱 = 0 (기준서 §G-4)",
|
||
"tier=" + tier + " hitStop=" + F(hs, 4) + "s skip=" + WL.Combat.Reaction.ImpactTier.LastSkipHitStop
|
||
+ " (티어 활성=" + WL.Combat.Reaction.ImpactTier.Active + ")");
|
||
|
||
// 히트 → 원복
|
||
WL.Combat.Core.CombatEvents.RaiseHitboxSpawned(actor, m.proj, 1f, m.lifetime, go.transform.position);
|
||
Chk(actor.m_animation != null && Mathf.Abs(actor.m_animation.speed - 1f) < 0.0001f, "히트에서 animator.speed 원복",
|
||
"speed=" + (actor.m_animation != null ? F(actor.m_animation.speed, 3) : "-") + " (원본 " + F(speed0, 3) + ")");
|
||
Chk(Mathf.Abs(go.transform.localScale.x - scale0) < 0.0001f, "히트에서 크기 원복",
|
||
"scale=" + F(go.transform.localScale.x, 4) + " 기본 " + F(scale0, 4));
|
||
Chk(TelegraphDecal.InUse == 0, "판 반납", "InUse=" + TelegraphDecal.InUse);
|
||
|
||
// 흰 섬광 만료 후 색 원복
|
||
TelegraphRunner.ForceTick(st.flashSeconds + 0.02f);
|
||
TelegraphRunner.ForceTick(st.pulseRestoreGuardSeconds + 0.02f);
|
||
Chk(!WL.Combat.Reaction.MobHitFlash.HasEliteTint(actor), "섬광 뒤 몹 색 원복(MPB 잔류 0)",
|
||
"HasTint=" + WL.Combat.Reaction.MobHitFlash.HasEliteTint(actor) + " HasBlock=" + WL.Combat.Reaction.MobHitFlash.HasAnyBlock(actor));
|
||
Chk(Telegraph.SpeedRestored >= 1 && Telegraph.PulseRestored >= 1, "원복 카운터",
|
||
"SpeedRestored=" + Telegraph.SpeedRestored + " PulseRestored=" + Telegraph.PulseRestored);
|
||
|
||
Object.DestroyImmediate(go);
|
||
}
|
||
L("");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────── D. 동시 상한
|
||
static void SectionD(WLTelegraphSettings st, UnityEngine.SceneManagement.Scene scene)
|
||
{
|
||
L("## D. 동시 상한 " + st.maxConcurrent + " (초과 = 판 생략 · 811s 예산기 방식)");
|
||
Telegraph.ResetDiagnostics(); TelegraphDecal.ResetDiagnostics(); Telegraph.ClearAll();
|
||
|
||
int n = st.maxConcurrent + 3;
|
||
var gos = new GameObject[n];
|
||
var actors = new MobActor[n];
|
||
for (int i = 0; i < n; i++) gos[i] = MakeMob(s_mobs[0], new Vector3(i * 6f, 0f, 0f), scene, out actors[i]);
|
||
for (int i = 0; i < n; i++)
|
||
if (actors[i] != null) WL.Combat.Core.CombatEvents.RaiseAttackStarted(actors[i], 0, 1f, null);
|
||
|
||
Chk(TelegraphDecal.InUse == st.maxConcurrent, "동시에 켜진 판 = 상한", "InUse=" + TelegraphDecal.InUse + " / 상한 " + st.maxConcurrent);
|
||
Chk(TelegraphDecal.BudgetSkipCount == n - st.maxConcurrent, "초과분은 판 생략", "생략 " + TelegraphDecal.BudgetSkipCount + " / 기대 " + (n - st.maxConcurrent));
|
||
int granted = Telegraph.SfxPlayed + Telegraph.SfxNoManager;
|
||
Chk(granted == st.sfxMaxConcurrent && Telegraph.SfxThrottled == n - st.sfxMaxConcurrent,
|
||
"예고 SFX 동시 상한 " + st.sfxMaxConcurrent + " (기준서 §G-4)",
|
||
"토큰 " + granted + " · 스킵 " + Telegraph.SfxThrottled + " / " + n + "회 요청 · 실재생 " + Telegraph.SfxPlayed
|
||
+ " (에디트 모드라 SoundInfo 인스턴스=" + SoundInfo.isIns + " → 실재생은 「미확인」)");
|
||
L(" 풀 크기 " + TelegraphDecal.PoolSize + " · 풀 성장 " + TelegraphDecal.PoolGrowCount + "회 · 셰이더 " + TelegraphDecal.ShaderName);
|
||
|
||
// 전부 반납
|
||
for (int i = 0; i < n; i++) if (actors[i] != null) WL.Combat.Core.CombatEvents.RaiseHitboxSpawned(actors[i], s_mobs[0].proj, 1f, 0.1f, Vector3.zero);
|
||
Chk(TelegraphDecal.InUse == 0, "전부 반납(풀 누수 0)", "InUse=" + TelegraphDecal.InUse);
|
||
for (int i = 0; i < n; i++) if (gos[i] != null) Object.DestroyImmediate(gos[i]);
|
||
Telegraph.ClearAll();
|
||
L("");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────── E. 보스 + 타임아웃
|
||
static void SectionE(WLTelegraphSettings st, UnityEngine.SceneManagement.Scene scene)
|
||
{
|
||
L("## E. 보스 무변경 · 히트가 안 올 때 강제 원복");
|
||
Telegraph.ResetDiagnostics(); Telegraph.ClearAll();
|
||
|
||
MobActor boss;
|
||
var bgo = MakeMob(s_mobs[11], new Vector3(-30f, 0f, 0f), scene, out boss);
|
||
if (bgo != null)
|
||
{
|
||
float s0 = bgo.transform.localScale.x;
|
||
if (boss.m_animation != null) boss.m_animation.speed = 1f;
|
||
for (int i = 0; i < 3; i++) WL.Combat.Core.CombatEvents.RaiseAttackStarted(boss, 0, 1f, null);
|
||
Chk(Telegraph.SkippedBoss == 3 && Telegraph.StartCount == 0, "보스 AttackStarted 3회 → 예고 0",
|
||
"SkippedBoss=" + Telegraph.SkippedBoss + " Start=" + Telegraph.StartCount);
|
||
Chk(boss.m_animation == null || Mathf.Abs(boss.m_animation.speed - 1f) < 0.0001f, "보스 animator.speed 무변경",
|
||
"speed=" + (boss.m_animation != null ? F(boss.m_animation.speed, 3) : "-"));
|
||
Chk(Mathf.Abs(bgo.transform.localScale.x - s0) < 0.0001f, "보스 크기 무변경", "scale=" + F(bgo.transform.localScale.x, 4));
|
||
Object.DestroyImmediate(bgo);
|
||
}
|
||
|
||
Telegraph.ResetDiagnostics();
|
||
MobActor a;
|
||
var go = MakeMob(s_mobs[0], new Vector3(-60f, 0f, 0f), scene, out a);
|
||
if (go != null)
|
||
{
|
||
float s0 = go.transform.localScale.x;
|
||
if (a.m_animation != null) a.m_animation.speed = 1f;
|
||
WL.Combat.Core.CombatEvents.RaiseAttackStarted(a, 0, 1f, null);
|
||
TelegraphRunner.ForceTick(st.maxTelegraphSeconds + 0.1f); // 히트가 안 온다(피격·사망)
|
||
Chk(Telegraph.TimeoutCount == 1, "타임아웃 감지", "Timeout=" + Telegraph.TimeoutCount);
|
||
Chk(a.m_animation != null && Mathf.Abs(a.m_animation.speed - 1f) < 0.0001f, "타임아웃 → 속도 원복", "speed=" + F(a.m_animation.speed, 3));
|
||
TelegraphRunner.ForceTick(st.pulseRestoreGuardSeconds + 0.05f);
|
||
Chk(Mathf.Abs(go.transform.localScale.x - s0) < 0.0001f, "타임아웃 → 크기 원복", "scale=" + F(go.transform.localScale.x, 4));
|
||
Chk(TelegraphDecal.InUse == 0, "타임아웃 → 판 반납", "InUse=" + TelegraphDecal.InUse);
|
||
Object.DestroyImmediate(go);
|
||
}
|
||
Telegraph.ClearAll();
|
||
L("");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────── F. GC
|
||
static void SectionF(WLTelegraphSettings st, UnityEngine.SceneManagement.Scene scene)
|
||
{
|
||
L("## F. GC (워밍 후 차분 · GC.GetAllocatedBytesForCurrentThread)");
|
||
Telegraph.ClearAll(); Telegraph.ResetDiagnostics();
|
||
|
||
MobActor a;
|
||
var go = MakeMob(s_mobs[0], new Vector3(-90f, 0f, 0f), scene, out a);
|
||
if (go == null) { Chk(false, "F 몹 인스턴스", "없음"); return; }
|
||
if (a.m_animation != null) a.m_animation.speed = 1f;
|
||
|
||
for (int i = 0; i < 30; i++) // 워밍(캐시·풀·머티리얼)
|
||
{
|
||
WL.Combat.Core.CombatEvents.RaiseAttackStarted(a, 0, 1f, null);
|
||
TelegraphRunner.ForceTick(0.02f);
|
||
WL.Combat.Core.CombatEvents.RaiseHitboxSpawned(a, s_mobs[0].proj, 1f, 0.1f, Vector3.zero);
|
||
TelegraphRunner.ForceTick(0.3f);
|
||
}
|
||
|
||
long b0 = System.GC.GetAllocatedBytesForCurrentThread();
|
||
for (int i = 0; i < 200; i++)
|
||
{
|
||
WL.Combat.Core.CombatEvents.RaiseAttackStarted(a, 0, 1f, null);
|
||
TelegraphRunner.ForceTick(0.02f);
|
||
WL.Combat.Core.CombatEvents.RaiseHitboxSpawned(a, s_mobs[0].proj, 1f, 0.1f, Vector3.zero);
|
||
TelegraphRunner.ForceTick(0.3f);
|
||
}
|
||
long d1 = System.GC.GetAllocatedBytesForCurrentThread() - b0;
|
||
|
||
long b1 = System.GC.GetAllocatedBytesForCurrentThread();
|
||
WL.Combat.Core.CombatEvents.RaiseAttackStarted(a, 0, 1f, null);
|
||
for (int i = 0; i < 5000; i++) TelegraphRunner.ForceTick(0.0001f);
|
||
long d2 = System.GC.GetAllocatedBytesForCurrentThread() - b1;
|
||
WL.Combat.Core.CombatEvents.RaiseHitboxSpawned(a, s_mobs[0].proj, 1f, 0.1f, Vector3.zero);
|
||
|
||
Chk(d1 == 0, "왕복 ×200 할당 0 B", d1 + " B");
|
||
Chk(d2 == 0, "틱 ×5000 할당 0 B", d2 + " B");
|
||
Object.DestroyImmediate(go);
|
||
Telegraph.ClearAll();
|
||
L("");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────── G. C8
|
||
static void SectionG(WLTelegraphSettings st, UnityEngine.SceneManagement.Scene scene)
|
||
{
|
||
L("## G. C8 — enabled = 0 이면 전부 원본");
|
||
Telegraph.ClearAll(); Telegraph.ResetDiagnostics(); TelegraphDecal.ResetDiagnostics();
|
||
WL.Combat.Reaction.ImpactTier.ResetDiagnostics();
|
||
|
||
WLTelegraphSettings.RuntimeDisabled = true; // 에셋 enabled = 0 과 같은 게이트
|
||
MobActor a;
|
||
var go = MakeMob(s_mobs[0], new Vector3(-120f, 0f, 0f), scene, out a);
|
||
if (go != null)
|
||
{
|
||
float s0 = go.transform.localScale.x;
|
||
if (a.m_animation != null) a.m_animation.speed = 1f;
|
||
WL.Combat.Core.CombatEvents.RaiseAttackStarted(a, 0, 1f, null);
|
||
TelegraphRunner.ForceTick(0.2f);
|
||
Chk(Telegraph.StartCount == 0 && Telegraph.SkippedDisabled >= 1, "예고 0", "Start=" + Telegraph.StartCount + " SkippedDisabled=" + Telegraph.SkippedDisabled);
|
||
Chk(a.m_animation != null && Mathf.Abs(a.m_animation.speed - 1f) < 0.0001f, "animator.speed 원본", "speed=" + F(a.m_animation.speed, 3));
|
||
Chk(Mathf.Abs(go.transform.localScale.x - s0) < 0.0001f, "크기 원본", "scale=" + F(go.transform.localScale.x, 4));
|
||
Chk(TelegraphDecal.InUse == 0 && !WL.Combat.Reaction.MobHitFlash.HasEliteTint(a), "판 0 · MPB 0", "InUse=" + TelegraphDecal.InUse);
|
||
Chk(!WLTelegraphSettings.SkipHitStop, "히트스톱 스킵 조건도 꺼진다(811gh 기존 동작)", "SkipHitStop=" + WLTelegraphSettings.SkipHitStop);
|
||
Object.DestroyImmediate(go);
|
||
}
|
||
WLTelegraphSettings.RuntimeDisabled = false;
|
||
Telegraph.ClearAll();
|
||
L("");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────── H. 애니메이터 실측
|
||
static void SectionH(UnityEngine.SceneManagement.Scene scene)
|
||
{
|
||
L("## H. 🔴 「데미지도 같이 늦어진다」 실측 — animator.speed 를 낮추면 클립 진행이 정확히 반비례한다");
|
||
L(" (히트 = 클립 안의 애니메이션 이벤트 `Projectile` 이므로 클립이 늦어지면 데미지도 늦어진다)");
|
||
L("| 몹 | 속도 | 0.2 s 동안 normalizedTime 진행 | 기대(= 0.2 ÷ 길이 × 속도) | 오차 |");
|
||
L("|---|---|---|---|---|");
|
||
|
||
int[] pick = { 0, 2, 6 };
|
||
int okCount = 0;
|
||
for (int p = 0; p < pick.Length; p++)
|
||
{
|
||
var m = s_mobs[pick[p]];
|
||
MobActor actor;
|
||
var go = MakeMob(m, new Vector3(p * 20f, 0f, 40f), scene, out actor);
|
||
if (go == null || actor.m_animation == null) { if (go != null) Object.DestroyImmediate(go); continue; }
|
||
var anim = actor.m_animation;
|
||
anim.enabled = true;
|
||
anim.cullingMode = AnimatorCullingMode.AlwaysAnimate;
|
||
|
||
float len = ClipLength(anim, "attack");
|
||
for (int k = 0; k < 2; k++)
|
||
{
|
||
float sp = k == 0 ? 1f : 0.35f;
|
||
anim.speed = 1f;
|
||
anim.Play("attack", 0, 0f);
|
||
anim.Update(0f);
|
||
anim.speed = sp;
|
||
for (int i = 0; i < 20; i++) anim.Update(0.01f); // 0.2 s
|
||
float nt = anim.GetCurrentAnimatorStateInfo(0).normalizedTime;
|
||
float want = len > 0f ? 0.2f / len * sp : -1f;
|
||
float err = want > 0f ? Mathf.Abs(nt - want) : -1f;
|
||
bool ok = want > 0f && err < 0.02f;
|
||
if (ok) okCount++;
|
||
L("| " + m.label + " | " + F(sp, 2) + " | " + F(nt, 4) + " | " + F(want, 4) + " | " + (err >= 0f ? F(err, 4) : "-") + (ok ? " ✅" : " ❌") + " |");
|
||
}
|
||
anim.speed = 1f;
|
||
Object.DestroyImmediate(go);
|
||
}
|
||
Chk(okCount >= 4, "속도 ↔ 클립 진행 반비례 실측", okCount + " / " + (pick.Length * 2));
|
||
L("");
|
||
}
|
||
|
||
static float ClipLength(Animator anim, string state)
|
||
{
|
||
var rac = anim.runtimeAnimatorController;
|
||
if (rac == null) return -1f;
|
||
anim.Play(state, 0, 0f);
|
||
anim.Update(0f);
|
||
var infos = anim.GetCurrentAnimatorClipInfo(0);
|
||
if (infos != null && infos.Length > 0 && infos[0].clip != null) return infos[0].clip.length;
|
||
return -1f;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────── J. 보유 에셋 실측(SFX 클립 · 인디케이터 20종)
|
||
static void SectionJ(WLTelegraphSettings st, UnityEngine.SceneManagement.Scene scene)
|
||
{
|
||
L("## J. ④ 예고 SFX 클립 · ① IndicatorInfo 재사용 경로 (보유 에셋 실측)");
|
||
|
||
var sound = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/ResWork/UIPrefabs/Title/SoundInfo.prefab");
|
||
var si = sound != null ? sound.GetComponent<SoundInfo>() : null;
|
||
if (si != null && si.arr_clip != null)
|
||
{
|
||
bool inRange = st.sfxIndex >= 0 && st.sfxIndex < si.arr_clip.Length;
|
||
var clip = inRange ? si.arr_clip[st.sfxIndex] : null;
|
||
L(" SoundInfo.arr_clip 길이 " + si.arr_clip.Length + " · eSound.Max = " + (int)eSound.Max);
|
||
Chk(clip != null, "예고 SFX 클립 실존 (index " + st.sfxIndex + " = " + (eSound)st.sfxIndex + ")",
|
||
clip != null ? clip.name + " · " + F(clip.length, 3) + " s" : "null");
|
||
}
|
||
else Chk(false, "SoundInfo 프리팹/클립 배열", sound == null ? "프리팹 없음" : "arr_clip null");
|
||
|
||
// ① Show_Indicator 재사용 — 원본 공개 API(Make_Indicator) 경로가 살아 있는지
|
||
int indicators = 0;
|
||
var guids = AssetDatabase.FindAssets("_Indicator t:GameObject", new[] { "Assets/Res_Addr/Obj" });
|
||
if (guids != null) indicators = guids.Length;
|
||
L(" Res_Addr/Obj 인디케이터 프리팹 " + indicators + "종 (기준서 §G-1 = 20종)");
|
||
Chk(indicators >= 20, "보유 인디케이터 20종 실존", indicators + "종");
|
||
|
||
string save = st.indicatorPrefabName;
|
||
st.indicatorPrefabName = "Golem_Child_Skill1_Indicator";
|
||
int before = TelegraphDecal.IndicatorMissCount;
|
||
MobActor ia;
|
||
var igo = MakeMob(s_mobs[0], new Vector3(-150f, 0f, 0f), scene, out ia);
|
||
var box = TelegraphShape.Get("Mob/P_Batty_Meele");
|
||
int idx = igo != null ? TelegraphDecal.Acquire(ia, in box, false, 0.1f, TelegraphDanger.Avoidable) : -1;
|
||
st.indicatorPrefabName = save;
|
||
Chk(TelegraphDecal.IndicatorMissCount > before || TelegraphDecal.IndicatorBorrowCount > 0,
|
||
"IndicatorInfo 재사용 경로가 안전하게 분기한다(에디트 모드 = 싱글턴 없음)",
|
||
"isIns=" + IndicatorInfo.isIns + " miss=" + TelegraphDecal.IndicatorMissCount + " borrow=" + TelegraphDecal.IndicatorBorrowCount
|
||
+ " → 실제 표시는 Play 전용 「미확인」");
|
||
if (idx >= 0) TelegraphDecal.Release(idx);
|
||
if (igo != null) Object.DestroyImmediate(igo);
|
||
L(" 🔴 기본값은 빈 문자열 = 인디케이터 덧칠 off. 보유 20종은 전부 **원형 파티클**이라 사각 콜라이더와 100 % 일치가 안 된다 →");
|
||
L(" 판정 일치는 런타임 쿼드가 담당하고, 덧칠이 필요하면 SO 한 줄(indicatorPrefabName)로 켠다.");
|
||
L("");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────── I. 캡처
|
||
static void SectionI(WLTelegraphSettings st, UnityEngine.SceneManagement.Scene scene)
|
||
{
|
||
L("## I. 캡처 3장 (1080×1920)");
|
||
if (!System.IO.Directory.Exists(kOutDir)) System.IO.Directory.CreateDirectory(kOutDir);
|
||
Telegraph.ClearAll(); Telegraph.ResetDiagnostics();
|
||
|
||
// 조명 + 지면(판이 보이게)
|
||
var lightGo = new GameObject("PROBE_Light"); lightGo.hideFlags = HideFlags.DontSave;
|
||
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(lightGo, scene);
|
||
var light = lightGo.AddComponent<Light>(); light.type = LightType.Directional; light.intensity = 1.1f;
|
||
lightGo.transform.rotation = Quaternion.Euler(45f, -30f, 0f);
|
||
|
||
var ground = GameObject.CreatePrimitive(PrimitiveType.Plane);
|
||
ground.name = "PROBE_Ground"; ground.hideFlags = HideFlags.DontSave;
|
||
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(ground, scene);
|
||
ground.transform.localScale = new Vector3(6f, 1f, 6f);
|
||
|
||
var camGo = new GameObject("PROBE_Cam"); camGo.hideFlags = HideFlags.DontSave;
|
||
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(camGo, scene);
|
||
var cam = camGo.AddComponent<Camera>();
|
||
cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = new Color(0.18f, 0.2f, 0.24f);
|
||
cam.fieldOfView = 60f; cam.nearClipPlane = 0.05f; cam.farClipPlane = 200f;
|
||
|
||
int[] pick = { 0, 6, 11 };
|
||
string[] tags = { "1_잡몹_예고중", "2_엘리트_예고중", "3_보스_무변경" };
|
||
for (int p = 0; p < pick.Length; p++)
|
||
{
|
||
var m = s_mobs[pick[p]];
|
||
MobActor actor;
|
||
var go = MakeMob(m, Vector3.zero, scene, out actor);
|
||
if (go == null) { L(" (프리팹 없음) " + m.label); continue; }
|
||
if (actor.m_animation != null) { actor.m_animation.speed = 1f; actor.m_animation.cullingMode = AnimatorCullingMode.AlwaysAnimate; }
|
||
|
||
WL.Combat.Core.CombatEvents.RaiseAttackStarted(actor, 0, 1f, null);
|
||
float planned = Telegraph.PlannedSecondsOf(actor);
|
||
if (planned > 0f) TelegraphRunner.ForceTick(planned * 0.6f); // 60 % 채워진 순간
|
||
else TelegraphRunner.ForceTick(0.02f);
|
||
|
||
// 판(3×3 또는 3×3+center 1.5)과 몹이 화면에 꽉 차게 — 세로 1080×1920 구도
|
||
Vector3 focus = new Vector3(0f, 0f, m.boss ? 0.6f : 1.0f);
|
||
float span = Mathf.Max(3.2f, go.transform.localScale.x * 1.8f);
|
||
cam.transform.position = focus + new Vector3(0f, span * 1.15f, -span * 1.45f);
|
||
cam.transform.LookAt(focus + new Vector3(0f, span * 0.18f, 0f));
|
||
|
||
string path = Shot(cam, tags[p]);
|
||
L(" " + tags[p] + " — " + m.label + " · 예고 " + F(planned, 3) + " s · 판 " + (Telegraph.DecalIndexOf(actor) >= 0 ? "O" : "X") + " → " + path);
|
||
|
||
WL.Combat.Core.CombatEvents.RaiseHitboxSpawned(actor, m.proj, 1f, m.lifetime, Vector3.zero);
|
||
TelegraphRunner.ForceTick(0.5f);
|
||
Object.DestroyImmediate(go);
|
||
}
|
||
|
||
Object.DestroyImmediate(ground); Object.DestroyImmediate(camGo); Object.DestroyImmediate(lightGo);
|
||
int n = System.IO.Directory.GetFiles(kOutDir, "*.png").Length;
|
||
Chk(n >= 3, "캡처 3장", n + "장 · " + kOutDir);
|
||
L("");
|
||
}
|
||
|
||
static string Shot(Camera cam, string tag)
|
||
{
|
||
var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32);
|
||
cam.aspect = (float)W / H;
|
||
cam.targetTexture = rt;
|
||
cam.Render();
|
||
var prev = RenderTexture.active;
|
||
RenderTexture.active = rt;
|
||
var tex = new Texture2D(W, H, TextureFormat.RGB24, false);
|
||
tex.ReadPixels(new Rect(0, 0, W, H), 0, 0);
|
||
tex.Apply();
|
||
RenderTexture.active = prev;
|
||
cam.targetTexture = null;
|
||
var path = System.IO.Path.Combine(kOutDir, tag + ".png");
|
||
System.IO.File.WriteAllBytes(path, ImageConversion.EncodeToPNG(tex));
|
||
Object.DestroyImmediate(tex);
|
||
rt.Release(); Object.DestroyImmediate(rt);
|
||
return path;
|
||
}
|
||
}
|