179 lines
10 KiB
C#
179 lines
10 KiB
C#
|
|
// WL811a_Trace.cs — #811a 전투 파이프라인 런타임 이벤트 순서·타이밍 트레이스 (Play 전용 · 읽기 전용 · 에셋/게임 상태 변경 0)
|
||
|
|
//
|
||
|
|
// unity command run_script --file AgentScripts/WL811a_Trace.cs --entry WL811a_Trace.Run --args '[8]' // 8 s 동안 관찰 (자동 교전이 콤보를 시작하면 기록)
|
||
|
|
// unity command run_script --file AgentScripts/WL811a_Trace.cs --entry WL811a_Trace.RunForce --args '[8]' // 3 s 안에 공격이 없으면 Play_Attack(0) 1회 호출
|
||
|
|
// 결과: Screenshots_WL/WL811a/trace_<class>_<HHmmss>.txt + Console
|
||
|
|
//
|
||
|
|
// 재는 것 (완료 기준 ⓒ · 프레임 폴링이라 해상도 = 1 프레임):
|
||
|
|
// · Animator 상태(클립명 Attack1_S/2_S/3_S) 진입·이탈 시각(unscaled ms)·normalizedTime → 콤보 단계
|
||
|
|
// · WeaponTrailDriver.IsSwinging 상승 에지 → ShowEffect 이벤트(검기 창 시작)
|
||
|
|
// · WeaponTrailDriver.MappedSpawnCount 증가 → 검기 매핑 이펙트 스폰
|
||
|
|
// · 활성 ProjectileBase 수 증가 → Projectile 이벤트 → Shoot_Projectile(히트박스 스폰)
|
||
|
|
// · PC m_DamageRate 변화(리플렉션) → Hit(float) 이벤트
|
||
|
|
// · 몹 HP 감소 / IsDead 전이 → Get_Damage → Cal_Damage / Set_Die
|
||
|
|
// · WLHitFeel.HitCount / FiredCount 증가 · Time.timeScale 전이 → Actor.cs:535 훅 → 히트스톱
|
||
|
|
// · DashDriver.DashCount 증가 → 대시
|
||
|
|
// 원본·WL 코드를 수정하지 않는다. 몹 HP 는 public Get_HP() · 나머지는 public 정적 카운터 · m_DamageRate 만 리플렉션 읽기.
|
||
|
|
|
||
|
|
using System;
|
||
|
|
using System.Collections;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Reflection;
|
||
|
|
using System.Text;
|
||
|
|
using UnityEngine;
|
||
|
|
using WL.Combat;
|
||
|
|
|
||
|
|
public static class WL811a_Trace
|
||
|
|
{
|
||
|
|
const string OutDir = "Screenshots_WL/WL811a";
|
||
|
|
|
||
|
|
public static void Run(float seconds) { Launch(seconds, false); }
|
||
|
|
public static void RunForce(float seconds) { Launch(seconds, true); }
|
||
|
|
|
||
|
|
static void Launch(float seconds, bool force)
|
||
|
|
{
|
||
|
|
var old = GameObject.Find("__wl811a_trace");
|
||
|
|
if (old != null) UnityEngine.Object.DestroyImmediate(old);
|
||
|
|
var go = new GameObject("__wl811a_trace");
|
||
|
|
var h = go.AddComponent<Host>();
|
||
|
|
h.StartCoroutine(h.Co_Trace(seconds <= 0f ? 8f : seconds, force));
|
||
|
|
}
|
||
|
|
|
||
|
|
public class Host : MonoBehaviour
|
||
|
|
{
|
||
|
|
static readonly BindingFlags BF = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||
|
|
|
||
|
|
static string ClipName(Animator a)
|
||
|
|
{
|
||
|
|
if (a == null) return "";
|
||
|
|
var infos = a.GetCurrentAnimatorClipInfo(0);
|
||
|
|
return infos.Length > 0 && infos[0].clip != null ? infos[0].clip.name : "";
|
||
|
|
}
|
||
|
|
|
||
|
|
public IEnumerator Co_Trace(float seconds, bool force)
|
||
|
|
{
|
||
|
|
var sb = new StringBuilder();
|
||
|
|
sb.AppendLine("# WL811a 전투 파이프라인 트레이스 " + DateTime.Now.ToString("HH:mm:ss") + " 관찰 " + seconds.ToString("F1") + " s force=" + force);
|
||
|
|
|
||
|
|
var me = MyValue.MyPC;
|
||
|
|
if (me == null) { sb.AppendLine("!! MyValue.MyPC 없음 — Title→인게임 진입 후 실행"); Done(sb); yield break; }
|
||
|
|
|
||
|
|
var anim = me.m_animation;
|
||
|
|
var fiRate = typeof(Actor).GetField("m_DamageRate", BF);
|
||
|
|
var feelSt = WL.Feel.WLHitFeel.Settings;
|
||
|
|
sb.AppendLine("class=" + me.Get_ID() + " feel=" + (feelSt != null ? (feelSt.enabled ? "on" : "off") : "null")
|
||
|
|
+ " hitStop=" + (feelSt != null ? feelSt.hitStopDuration.ToString("F3") + "/" + feelSt.hitStopKillDuration.ToString("F3") : "-")
|
||
|
|
+ " timeScale0=" + Time.timeScale.ToString("F2"));
|
||
|
|
sb.AppendLine("# 열: t(ms unscaled) | clip | nt | 사건");
|
||
|
|
|
||
|
|
float t0 = Time.unscaledTime;
|
||
|
|
string lastClip = ClipName(anim);
|
||
|
|
bool lastSwing = false;
|
||
|
|
int lastSpawn = WeaponTrailDriver.MappedSpawnCount;
|
||
|
|
int lastHit = WL.Feel.WLHitFeel.HitCount, lastFired = WL.Feel.WLHitFeel.FiredCount;
|
||
|
|
int lastDash = DashDriver.DashCount;
|
||
|
|
float lastScale = Time.timeScale;
|
||
|
|
float lastRate = fiRate != null ? (float)fiRate.GetValue(me) : -1f;
|
||
|
|
int lastProj = CountProjectiles();
|
||
|
|
var mobHp = new Dictionary<int, double>();
|
||
|
|
var mobDead = new Dictionary<int, bool>();
|
||
|
|
RefreshMobs(mobHp, mobDead);
|
||
|
|
|
||
|
|
int frame = 0, attackFrames = 0; bool sawAttack = false; float attackEndAt = -1f;
|
||
|
|
while (Time.unscaledTime - t0 < seconds)
|
||
|
|
{
|
||
|
|
yield return null;
|
||
|
|
frame++;
|
||
|
|
float ms = (Time.unscaledTime - t0) * 1000f;
|
||
|
|
string clip = ClipName(anim);
|
||
|
|
float nt = anim != null ? anim.GetCurrentAnimatorStateInfo(0).normalizedTime % 1f : 0f;
|
||
|
|
string pre = string.Format("{0,7:F0} | {1,-22} | {2,5:F3} | ", ms, clip, nt);
|
||
|
|
|
||
|
|
if (clip != lastClip) { sb.AppendLine(pre + "상태 전이 " + lastClip + " → " + clip + (anim != null && anim.IsInTransition(0) ? " (transition)" : "")); lastClip = clip; }
|
||
|
|
bool isAttack = clip.Contains("Attack");
|
||
|
|
if (isAttack) { sawAttack = true; attackFrames++; attackEndAt = -1f; }
|
||
|
|
else if (sawAttack && attackEndAt < 0f) attackEndAt = Time.unscaledTime;
|
||
|
|
|
||
|
|
var drv = me.GetComponent<WeaponTrailDriver>();
|
||
|
|
bool swing = drv != null && drv.IsSwinging;
|
||
|
|
if (swing != lastSwing) { sb.AppendLine(pre + (swing ? "ShowEffect → 검기 창 시작 (IsSwinging=1)" : "검기 창 종료")); lastSwing = swing; }
|
||
|
|
|
||
|
|
int spawn = WeaponTrailDriver.MappedSpawnCount;
|
||
|
|
if (spawn != lastSpawn) { sb.AppendLine(pre + "검기 매핑 이펙트 스폰 +" + (spawn - lastSpawn)); lastSpawn = spawn; }
|
||
|
|
|
||
|
|
if (fiRate != null)
|
||
|
|
{
|
||
|
|
float rate = (float)fiRate.GetValue(me);
|
||
|
|
if (!Mathf.Approximately(rate, lastRate)) { sb.AppendLine(pre + "Hit(float) 이벤트 → m_DamageRate " + lastRate.ToString("F2") + " → " + rate.ToString("F2")); lastRate = rate; }
|
||
|
|
}
|
||
|
|
|
||
|
|
int proj = CountProjectiles();
|
||
|
|
if (proj > lastProj) sb.AppendLine(pre + "Projectile 이벤트 → 활성 ProjectileBase +" + (proj - lastProj) + " (총 " + proj + ")");
|
||
|
|
lastProj = proj;
|
||
|
|
|
||
|
|
if (frame % 30 == 0) RefreshMobs(mobHp, mobDead);
|
||
|
|
foreach (var m in UnityEngine.Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
|
||
|
|
{
|
||
|
|
int id = m.GetInstanceID();
|
||
|
|
double hp; if (!mobHp.TryGetValue(id, out hp)) { mobHp[id] = m.Get_HP(); mobDead[id] = m.IsDead(); continue; }
|
||
|
|
double cur = m.Get_HP();
|
||
|
|
if (cur < hp) { sb.AppendLine(pre + "Get_Damage/Cal_Damage → " + m.name + " HP " + hp.ToString("F0") + " → " + cur.ToString("F0")); }
|
||
|
|
mobHp[id] = cur;
|
||
|
|
bool dead = m.IsDead();
|
||
|
|
if (dead && !mobDead[id]) sb.AppendLine(pre + "Set_Die → " + m.name + " 사망");
|
||
|
|
mobDead[id] = dead;
|
||
|
|
}
|
||
|
|
|
||
|
|
int hit = WL.Feel.WLHitFeel.HitCount, fired = WL.Feel.WLHitFeel.FiredCount;
|
||
|
|
if (hit != lastHit || fired != lastFired) { sb.AppendLine(pre + "WLHitFeel.OnHit (Actor.cs:535) hit+" + (hit - lastHit) + " fired+" + (fired - lastFired)); lastHit = hit; lastFired = fired; }
|
||
|
|
|
||
|
|
if (!Mathf.Approximately(Time.timeScale, lastScale)) { sb.AppendLine(pre + "timeScale " + lastScale.ToString("F2") + " → " + Time.timeScale.ToString("F2") + (Time.timeScale < 0.1f ? " (히트스톱 시작)" : " (복원)")); lastScale = Time.timeScale; }
|
||
|
|
|
||
|
|
int dash = DashDriver.DashCount;
|
||
|
|
if (dash != lastDash) { sb.AppendLine(pre + "DashDriver 대시 +" + (dash - lastDash)); lastDash = dash; }
|
||
|
|
|
||
|
|
if (force && !sawAttack && Time.unscaledTime - t0 > 3f)
|
||
|
|
{
|
||
|
|
force = false;
|
||
|
|
float aspd = (float)me.Get_StatInfo().Get_Stat(eStat.FinalAttackSpeed);
|
||
|
|
sb.AppendLine(pre + "!! 3 s 동안 공격 없음 → Play_Attack(0, " + aspd.ToString("F2") + ") 강제 1회");
|
||
|
|
me.Play_Attack(0, aspd);
|
||
|
|
}
|
||
|
|
// 콤보가 끝난 뒤 0.6 s 여유를 두고 종료 (히트스톱 복원까지 포함)
|
||
|
|
if (sawAttack && attackEndAt > 0f && Time.unscaledTime - attackEndAt > 0.6f) break;
|
||
|
|
}
|
||
|
|
sb.AppendLine("# 프레임 " + frame + " · 공격 상태 프레임 " + attackFrames + " · 공격 관측=" + sawAttack + " · timeScale 종료값 " + Time.timeScale.ToString("F2"));
|
||
|
|
Done(sb);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int CountProjectiles()
|
||
|
|
{
|
||
|
|
return UnityEngine.Object.FindObjectsByType<ProjectileBase>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).Length;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void RefreshMobs(Dictionary<int, double> hp, Dictionary<int, bool> dead)
|
||
|
|
{
|
||
|
|
foreach (var m in UnityEngine.Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
|
||
|
|
{
|
||
|
|
int id = m.GetInstanceID();
|
||
|
|
if (!hp.ContainsKey(id)) { hp[id] = m.Get_HP(); dead[id] = m.IsDead(); }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void Done(StringBuilder sb)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
System.IO.Directory.CreateDirectory(OutDir);
|
||
|
|
var me = MyValue.MyPC;
|
||
|
|
string path = System.IO.Path.Combine(OutDir, "trace_" + (me != null ? me.Get_ID().ToString() : "none") + "_" + DateTime.Now.ToString("HHmmss") + ".txt");
|
||
|
|
System.IO.File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false));
|
||
|
|
Debug.Log("[WL811a] 저장 " + path);
|
||
|
|
}
|
||
|
|
catch (Exception e) { Debug.LogWarning("[WL811a] 저장 실패 " + e.Message); }
|
||
|
|
Debug.Log(sb.ToString());
|
||
|
|
UnityEngine.Object.Destroy(gameObject);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|