205 lines
10 KiB
C#
205 lines
10 KiB
C#
// PD #755 2단계 — 이동 방향 카메라 리드/피치 실측 프로브
|
|
// Play 중에 EditorApplication.update 에 붙어 프레임당 1회 Run_byJoystick 을 주입하고
|
|
// 30 Hz 로 CSV 를 남긴다. 시퀀스가 끝나면 스스로 등록을 해제하고 파일을 쓴다.
|
|
// 사용: unity command run_script --file AgentScripts/WL_CamLeadProbe.cs --entry WL_CamLeadProbe.Run --args '["on"]'
|
|
using System;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Text;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
public static class WL_CamLeadProbe
|
|
{
|
|
const string PrefKey = "WL_CamLeadProbeGen";
|
|
|
|
static string RootPath(string file)
|
|
{
|
|
return Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Screenshots_WL", file));
|
|
}
|
|
|
|
static string F(float v) { return v.ToString("F4", CultureInfo.InvariantCulture); }
|
|
|
|
/// <summary>현재 카메라·플레이어 상태 확인(주입 전 사전 점검).</summary>
|
|
public static object Info()
|
|
{
|
|
if (!Application.isPlaying) return "not playing";
|
|
var pc = MyValue.MyPC;
|
|
var rc = MyValue.m_RealCamera;
|
|
if (pc == null) return "MyPC null (아직 인게임 아님)";
|
|
if (rc == null) return "RealCamera null";
|
|
var st = CameraMoveLead.Settings;
|
|
var cam = Camera.main;
|
|
var sb = new StringBuilder();
|
|
sb.Append("pc=").Append(pc.name).Append(" pos=").Append(pc.transform.position.ToString("F2"))
|
|
.Append(" moveSpeed=").Append(F(pc.Get_MoveSpeed()))
|
|
.Append(" | cam=").Append(cam ? cam.name : "null")
|
|
.Append(" pos=").Append(rc.transform.position.ToString("F2"))
|
|
.Append(" euler=").Append(rc.transform.eulerAngles.ToString("F2"))
|
|
.Append(" fov=").Append(cam ? F(cam.fieldOfView) : "?")
|
|
.Append(" cameraHeight=").Append(F(rc.cameraHeight))
|
|
.Append(" rotateAround=").Append(F(rc.rotateAround))
|
|
.Append(" m_LookAt=").Append(rc.m_LookAt.ToString("F2"))
|
|
.Append(" smooth=").Append(F(rc.smooth))
|
|
.Append(" | settings=").Append(st == null ? "NULL(기능 비활성)" : ("enabled=" + st.enabled + " lead=" + F(st.leadDistance) + " backLead=" + F(st.backwardLeadDistance) + " pitch=" + F(st.backwardPitchAdd)))
|
|
.Append(" | lead=").Append(rc.Get_MoveLead().LeadOffset.ToString("F3"))
|
|
.Append(" pitchAdd=").Append(F(rc.Get_MoveLead().PitchAdd));
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>누가 캐릭터를 움직이는가 — 사람(조이스틱) vs 게임 AI(NavMesh 경로) 판별.</summary>
|
|
public static object Who()
|
|
{
|
|
if (!Application.isPlaying) return "not playing";
|
|
var pc = MyValue.MyPC;
|
|
if (pc == null) return "MyPC null";
|
|
var ag = pc.GetComponent<UnityEngine.AI.NavMeshAgent>();
|
|
return "joystick=" + pc.JoystickStatus
|
|
+ " anim=" + pc.Get_CurAnim()
|
|
+ " pos=" + pc.transform.position.ToString("F2")
|
|
+ (ag == null ? " agent=null"
|
|
: " agentVel=" + ag.velocity.magnitude.ToString("F2")
|
|
+ " hasPath=" + ag.hasPath
|
|
+ " remain=" + (ag.hasPath ? ag.remainingDistance.ToString("F2") : "-")
|
|
+ " stopDist=" + ag.stoppingDistance.ToString("F2"))
|
|
+ " timeScale=" + Time.timeScale.ToString("F2");
|
|
}
|
|
|
|
/// <summary>설정 에셋의 enabled 토글 (ⓖ OFF 대조군용). Play 중에도 즉시 반영된다.</summary>
|
|
public static object SetEnabled(bool v)
|
|
{
|
|
var st = CameraMoveLead.Settings;
|
|
if (st == null) return "settings asset null";
|
|
st.enabled = v;
|
|
return "enabled=" + st.enabled;
|
|
}
|
|
|
|
// 시퀀스 — 사전 정지(기준선) 2s → 전진 5 → 정지 3 → 후진 5 → 정지 3 → 측면 3 → 정지 3
|
|
static readonly string[] s_phase = { "pre", "fwd", "stop1", "back", "stop2", "side", "stop3" };
|
|
static readonly float[] s_dur = { 2f, 5f, 3f, 5f, 3f, 3f, 3f };
|
|
|
|
/// <summary>22초(+사전 정지 2초) 시퀀스를 시작한다. 즉시 반환하며, 끝나면 CSV 가 생성된다.</summary>
|
|
public static object Run(string tag) { return RunCore(tag, true, 0f); }
|
|
|
|
/// <summary>입력을 주입하지 않고 순수 관찰만 한다(다른 사람이 조작 중인 세션에서도 안전).</summary>
|
|
public static object Observe(string tag, float seconds) { return RunCore(tag, false, seconds); }
|
|
|
|
static object RunCore(string tag, bool inject, float obsSeconds)
|
|
{
|
|
if (!Application.isPlaying) return "refused: not playing";
|
|
var pc = MyValue.MyPC;
|
|
var rc = MyValue.m_RealCamera;
|
|
if (pc == null || rc == null) return "refused: MyPC or RealCamera null";
|
|
|
|
long gen = DateTime.UtcNow.Ticks;
|
|
EditorPrefs.SetString(PrefKey, gen.ToString()); // 이전 세대 델리게이트 자동 종료용
|
|
|
|
string csv = RootPath("camlead_" + tag + ".csv");
|
|
Directory.CreateDirectory(Path.GetDirectoryName(csv));
|
|
|
|
var sb = new StringBuilder();
|
|
sb.Append("t,phase,px,py,pz,spd,cx,cy,cz,cpitch,cyaw,leadLen,leadX,leadY,leadZ,pitchAdd,lookY,vyRoot,vyChest,camDist,maxAngSpd,maxPosSpd,fwdDot\n");
|
|
|
|
float t0 = Time.time;
|
|
int lastFrame = -1;
|
|
float nextSample = 0f;
|
|
Vector3 prevPlayer = pc.transform.position;
|
|
float prevPlayerT = Time.time;
|
|
Vector3 prevCamPos = rc.transform.position;
|
|
Vector3 prevCamEuler = rc.transform.eulerAngles;
|
|
float prevTickT = Time.time;
|
|
float maxAng = 0f, maxPos = 0f;
|
|
|
|
// 방향 기준은 시퀀스 시작 시점의 카메라 수평 전방으로 고정한다(카메라 회전 피드백 차단).
|
|
Vector3 camFwd = rc.transform.forward; camFwd.y = 0f; camFwd.Normalize();
|
|
Vector3 camRight = rc.transform.right; camRight.y = 0f; camRight.Normalize();
|
|
|
|
float total = 0f; for (int i = 0; i < s_dur.Length; i++) total += s_dur[i];
|
|
if (!inject) total = obsSeconds > 0f ? obsSeconds : 60f;
|
|
|
|
EditorApplication.CallbackFunction cb = null;
|
|
cb = () =>
|
|
{
|
|
bool die = !Application.isPlaying || EditorPrefs.GetString(PrefKey, "") != gen.ToString();
|
|
float t = Time.time - t0;
|
|
if (die || t > total)
|
|
{
|
|
EditorApplication.update -= cb;
|
|
try { File.WriteAllText(csv, sb.ToString()); } catch { }
|
|
return;
|
|
}
|
|
if (Time.frameCount == lastFrame) return; // 프레임당 1회만
|
|
lastFrame = Time.frameCount;
|
|
|
|
var pcc = MyValue.MyPC; var rcc = MyValue.m_RealCamera;
|
|
if (pcc == null || rcc == null) return;
|
|
|
|
// 현재 페이즈
|
|
string phase = "obs"; float acc = 0f;
|
|
if (inject)
|
|
{
|
|
phase = "?";
|
|
for (int i = 0; i < s_dur.Length; i++) { acc += s_dur[i]; if (t < acc) { phase = s_phase[i]; break; } }
|
|
|
|
Vector3 dir = Vector3.zero;
|
|
if (phase == "fwd") dir = camFwd;
|
|
else if (phase == "back") dir = -camFwd;
|
|
else if (phase == "side") dir = camRight;
|
|
if (dir != Vector3.zero) pcc.Run_byJoystick(dir);
|
|
}
|
|
|
|
// 프레임 단위 스파이크 추적
|
|
float dtTick = Time.time - prevTickT;
|
|
if (dtTick > 1e-5f)
|
|
{
|
|
Vector3 e = rcc.transform.eulerAngles;
|
|
float dy = Mathf.DeltaAngle(prevCamEuler.y, e.y);
|
|
float dx = Mathf.DeltaAngle(prevCamEuler.x, e.x);
|
|
float ang = Mathf.Sqrt(dy * dy + dx * dx) / dtTick;
|
|
float pos = Vector3.Distance(prevCamPos, rcc.transform.position) / dtTick;
|
|
if (ang > maxAng) maxAng = ang;
|
|
if (pos > maxPos) maxPos = pos;
|
|
prevCamEuler = e; prevCamPos = rcc.transform.position; prevTickT = Time.time;
|
|
}
|
|
|
|
if (t < nextSample) return;
|
|
nextSample = t + (1f / 30f);
|
|
|
|
Vector3 p = pcc.transform.position;
|
|
float dtp = Time.time - prevPlayerT;
|
|
Vector3 dp = p - prevPlayer; dp.y = 0f;
|
|
float spd = dtp > 1e-5f ? dp.magnitude / dtp : 0f;
|
|
prevPlayer = p; prevPlayerT = Time.time;
|
|
|
|
var ml = rcc.Get_MoveLead();
|
|
Vector3 lead = ml.LeadOffset;
|
|
Vector3 cp = rcc.transform.position;
|
|
Vector3 ce = rcc.transform.eulerAngles;
|
|
var cam = Camera.main;
|
|
Vector3 vRoot = cam ? cam.WorldToViewportPoint(p) : Vector3.zero;
|
|
Vector3 vChest = cam ? cam.WorldToViewportPoint(p + rcc.m_LookAt) : Vector3.zero;
|
|
|
|
sb.Append(F(t)).Append(',').Append(phase).Append(',')
|
|
.Append(F(p.x)).Append(',').Append(F(p.y)).Append(',').Append(F(p.z)).Append(',')
|
|
.Append(F(spd)).Append(',')
|
|
.Append(F(cp.x)).Append(',').Append(F(cp.y)).Append(',').Append(F(cp.z)).Append(',')
|
|
.Append(F(ce.x > 180f ? ce.x - 360f : ce.x)).Append(',').Append(F(ce.y)).Append(',')
|
|
.Append(F(lead.magnitude)).Append(',').Append(F(lead.x)).Append(',').Append(F(lead.y)).Append(',').Append(F(lead.z)).Append(',')
|
|
.Append(F(ml.PitchAdd)).Append(',')
|
|
.Append(F(p.y + lead.y + rcc.m_LookAt.y)).Append(',')
|
|
.Append(F(vRoot.y)).Append(',').Append(F(vChest.y)).Append(',')
|
|
.Append(F(Vector3.Distance(cp, p + rcc.m_LookAt))).Append(',')
|
|
.Append(F(maxAng)).Append(',').Append(F(maxPos)).Append(',')
|
|
.Append(F(dp.sqrMagnitude > 1e-8f ? Vector3.Dot(dp.normalized, new Vector3(rcc.transform.forward.x, 0f, rcc.transform.forward.z).normalized) : 0f)).Append('\n');
|
|
maxAng = 0f; maxPos = 0f;
|
|
|
|
// 중간 저장(진행 상황을 디스크에서 볼 수 있게)
|
|
if (sb.Length % 4096 < 220) { try { File.WriteAllText(csv, sb.ToString()); } catch { } }
|
|
};
|
|
EditorApplication.update += cb;
|
|
|
|
return "started tag=" + tag + " total=" + F(total) + "s csv=" + csv
|
|
+ " camFwd=" + camFwd.ToString("F3") + " moveSpeed=" + F(pc.Get_MoveSpeed());
|
|
}
|
|
}
|