Project_WL/AgentScripts/WL924_Probe.cs

249 lines
19 KiB
C#

// WL924_Probe.cs — PD 지시 #924 시험(Play 중 · 섬)
// Cal(classId, clip) : 궤적 교정 — 저장(구운) 값 vs 이번 Play 에서 실제 스윙을 잰 값(평면 법선 · 이등분 · 시작 방향 · 피벗 · 칼끝 반경 · 각도) · 차이
// SwordTip(socketName) : 지금 무기 끝(소켓 원점에서 가장 먼 메시 경계 모서리) — PC 기준 앞 · 옆 · 높이
// Yellow(seconds) : seconds 동안 프레임마다 입자가 있는 파티클 중 노란색(시작색 · 수명색 · 재질색) 계열 — 경로별 프레임 수 · 최대 입자 수
// Wrap(spec, seconds) : 칼날 폭풍 회오리 사본을 PC 둘레에 겹쳐 띄워 본다(spec = "높이:배율|높이:배율…" · 0.5 초마다 다시)
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public static class WL924_Probe
{
const string Key = "wl924log";
static StringBuilder Buf()
{
var sb = System.AppDomain.CurrentDomain.GetData(Key) as StringBuilder;
if (sb == null) { sb = new StringBuilder(); System.AppDomain.CurrentDomain.SetData(Key, sb); }
return sb;
}
public static object Log() { return Buf().ToString(); }
static string V(Vector3 v) { return v.ToString("F2"); }
public static object Cal(int classId, string clip)
{
var baked = WL.Combat.SlashTrailSettings.Instance != null ? WL.Combat.SlashTrailSettings.Instance.FindCalibration(classId, clip) : null;
WL.Combat.SwingCalibration rt; WL.Combat.WeaponTrailDriver.RuntimeCalibrations.TryGetValue(classId + "/" + clip, out rt);
var sb = new StringBuilder();
System.Action<string, WL.Combat.SwingCalibration> D = (tag, c) =>
{
if (c == null) { sb.Append(tag).Append(" 없음\n"); return; }
sb.Append(tag).Append(" ").Append(c.kind).Append(" · 법선 ").Append(V(c.planeNormal)).Append(" · 이등분 ").Append(V(c.bisector)).Append(" · 시작 ").Append(V(c.startDir))
.Append(" · 피벗 ").Append(V(c.pivot)).Append(" · 칼끝 r ").Append(c.tipRadius.ToString("F2")).Append(" · 손잡이 r ").Append(c.hiltRadius.ToString("F2")).Append(" · 각도 ").Append(c.sweepDeg.ToString("F0"))
.Append(" · 품질 ").Append(c.quality.ToString("F2")).Append(" · ").Append(c.source).Append('\n');
};
D("저장", baked); D("실측", rt);
if (baked != null && rt != null)
{
float nA = Vector3.Angle(baked.planeNormal, rt.planeNormal), nA2 = Vector3.Angle(baked.planeNormal, -rt.planeNormal);
sb.Append("차이 · 평면 ").Append(Mathf.Min(nA, nA2).ToString("F0")).Append("°(방향 ").Append(nA <= nA2 ? "같음" : "반대").Append(") · 이등분 ").Append(Vector3.Angle(baked.bisector, rt.bisector).ToString("F0"))
.Append("° · 시작 ").Append(Vector3.Angle(baked.startDir, rt.startDir).ToString("F0")).Append("° · 피벗 ").Append(Vector3.Distance(baked.pivot, rt.pivot).ToString("F2")).Append(" m · 칼끝 r ").Append((rt.tipRadius - baked.tipRadius).ToString("+0.00;-0.00")).Append('\n');
}
var cam = Camera.main; var pc = MyValue.MyPC as PCActor;
if (cam != null && pc != null)
foreach (var c in new[] { baked, rt })
if (c != null) sb.Append(c == baked ? "저장" : "실측").Append(" 면이 카메라를 보는 정도(|법선·카메라| · 1 = 정면 · 0 = 옆면 선) ").Append(Mathf.Abs(Vector3.Dot(pc.transform.TransformDirection(c.planeNormal).normalized, cam.transform.forward)).ToString("F2")).Append('\n');
return sb.ToString();
}
public static object SwordTip(string socketName)
{
var pc = MyValue.MyPC as PCActor; if (pc == null || pc.tfs_weapon == null) return "PC 없음";
Transform sock = null; foreach (var s in pc.tfs_weapon) if (s != null && s.name == socketName && s.gameObject.activeInHierarchy) { sock = s; break; }
if (sock == null) return "소켓 없음 " + socketName;
Vector3 far = Vector3.zero; float best = -1f;
foreach (var r in sock.GetComponentsInChildren<Renderer>(false))
{
if (r == null || !r.enabled || r.name == "[WL] Glow") continue; var mf = r.GetComponent<MeshFilter>(); if (mf == null || mf.sharedMesh == null) continue;
var lb = mf.sharedMesh.bounds;
for (int c = 0; c < 8; c++)
{
var corner = lb.center + new Vector3((c & 1) == 0 ? -lb.extents.x : lb.extents.x, (c & 2) == 0 ? -lb.extents.y : lb.extents.y, (c & 4) == 0 ? -lb.extents.z : lb.extents.z);
var w = r.transform.TransformPoint(corner); float d = (w - sock.position).magnitude;
if (d > best) { best = d; far = w; }
}
}
var rel = far - pc.Get_position(); var f = pc.transform.forward; f.y = 0f; f.Normalize(); var right = Vector3.Cross(Vector3.up, f);
return "칼끝 앞 " + Vector3.Dot(rel, f).ToString("F2") + " · 옆 " + Vector3.Dot(rel, right).ToString("F2") + " · 높이 " + rel.y.ToString("F2") + " · 손에서 " + best.ToString("F2") + " m";
}
static bool Yellowish(Color c) { float mx = Mathf.Max(c.r, Mathf.Max(c.g, c.b)); if (mx < 0.25f || c.a < 0.05f) return false; return c.r >= 0.55f * mx && c.g >= 0.5f * mx && c.b <= 0.55f * Mathf.Min(c.r, c.g); }
static Color MatColor(Material m)
{
if (m == null) return Color.white;
foreach (var p in new[] { "_TintColor", "_BaseColor", "_Color", "_MainColor" }) if (m.HasProperty(p)) return m.GetColor(p);
return Color.white;
}
static string WhyYellow(ParticleSystem ps, Renderer r)
{
var m = ps.main; var sc = m.startColor; var why = new List<string>();
if (sc.mode == ParticleSystemGradientMode.Color && Yellowish(sc.color)) why.Add("시작색 " + sc.color.ToString("F2"));
if (sc.mode == ParticleSystemGradientMode.TwoColors && (Yellowish(sc.colorMin) || Yellowish(sc.colorMax))) why.Add("시작색 둘 " + sc.colorMax.ToString("F2"));
if ((sc.mode == ParticleSystemGradientMode.Gradient || sc.mode == ParticleSystemGradientMode.RandomColor) && sc.gradient != null) foreach (var k in sc.gradient.colorKeys) if (Yellowish(new Color(k.color.r, k.color.g, k.color.b, 1f))) { why.Add("시작 그라디언트 " + k.color.ToString("F2")); break; }
var col = ps.colorOverLifetime; if (col.enabled && col.color.gradient != null) foreach (var k in col.color.gradient.colorKeys) if (Yellowish(new Color(k.color.r, k.color.g, k.color.b, 1f))) { why.Add("수명색 " + k.color.ToString("F2")); break; }
var mc = MatColor(r != null ? r.sharedMaterial : null); if (Yellowish(new Color(mc.r, mc.g, mc.b, 1f))) why.Add("재질 " + (r.sharedMaterial != null ? r.sharedMaterial.name : "") + " " + mc.ToString("F2"));
return why.Count > 0 ? string.Join(" · ", why) : null;
}
static string PathOf(Transform t, int depth) { string p = t.name; var tr = t.parent; for (int k = 0; k < depth && tr != null; k++, tr = tr.parent) p = tr.name + "/" + p; return p; }
public static object Yellow(float seconds)
{
var sb = Buf(); sb.Length = 0; float t0 = Time.time;
var frames = new Dictionary<string, int>(); var maxN = new Dictionary<string, int>(); var why = new Dictionary<string, string>(); var first = new Dictionary<string, float>();
UnityEngine.Events.UnityAction act = null;
act = () =>
{
float t = Time.time - t0;
foreach (var ps in Object.FindObjectsByType<ParticleSystem>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
{
if (ps == null || ps.particleCount == 0) continue; var r = ps.GetComponent<ParticleSystemRenderer>(); if (r == null || !r.enabled) continue;
string w = WhyYellow(ps, r); if (w == null) continue;
string key = PathOf(ps.transform, 3);
int f; frames.TryGetValue(key, out f); frames[key] = f + 1; int m; maxN.TryGetValue(key, out m); if (ps.particleCount > m) maxN[key] = ps.particleCount; why[key] = w; if (!first.ContainsKey(key)) first[key] = t;
}
if (t > seconds)
{
Application.onBeforeRender -= act;
var keys = new List<string>(frames.Keys); keys.Sort((a, b) => frames[b].CompareTo(frames[a]));
foreach (var k in keys) sb.Append(frames[k]).Append(" 프레임 · 처음 ").Append(first[k].ToString("F2")).Append(" 초 · 최대 입자 ").Append(maxN[k]).Append(" · ").Append(k).Append(" · ").Append(why[k]).Append('\n');
sb.Append("끝 · 노란 계열 ").Append(keys.Count).Append(" 개\n");
}
};
Application.onBeforeRender += act;
return "노란 이펙트 찾기 " + seconds + " 초";
}
/// <summary>seconds 동안 새로 켜진(입자 있는) 파티클의 최상위 오브젝트 — 이름 · 처음 시각 · 횟수 · PC/더미까지 거리 · 렌더러 재질(텍스처) 이름. 칼날 폭풍 회오리(WL_Fx_BladeStorm) 제외.</summary>
public static object Spawned(float seconds)
{
var pc = MyValue.MyPC as PCActor; var d = WL.IslandMob.WLTrailTest.Dummy;
var sb = Buf(); sb.Length = 0; float t0 = Time.time;
var seen = new HashSet<int>(); var count = new Dictionary<string, int>(); var info = new Dictionary<string, string>(); var firstT = new Dictionary<string, float>();
foreach (var ps in Object.FindObjectsByType<ParticleSystem>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) if (ps != null && ps.particleCount > 0) seen.Add(ps.transform.root.gameObject.GetInstanceID());
UnityEngine.Events.UnityAction act = null;
act = () =>
{
float t = Time.time - t0;
foreach (var ps in Object.FindObjectsByType<ParticleSystem>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
{
if (ps == null || ps.particleCount == 0) continue;
// 최상위 = 파티클을 가진 가장 위 조상(캐릭터 · 투사체 아래 붙은 것도 이름이 보이게)
Transform top = ps.transform; for (var p = ps.transform.parent; p != null; p = p.parent) if (p.GetComponent<ParticleSystem>() != null) top = p;
if (top.name.StartsWith("WL_Fx_BladeStorm")) continue;
int id = top.gameObject.GetInstanceID(); if (!seen.Add(id)) continue;
string key = top.name.Replace("(Clone)", "") + " ← " + (top.parent != null ? top.parent.name : "-");
int c; count.TryGetValue(key, out c); count[key] = c + 1; if (!firstT.ContainsKey(key)) firstT[key] = t;
var r = top.GetComponentInChildren<ParticleSystemRenderer>(); var m = r != null ? r.sharedMaterial : null; var tex = m != null && m.mainTexture != null ? m.mainTexture.name : "-";
info[key] = "PC " + (pc != null ? Vector3.Distance(top.position, pc.Get_position()).ToString("F1") : "-") + " m · 더미 " + (d != null ? Vector3.Distance(top.position, d.Get_position()).ToString("F1") : "-") + " m · 재질 " + (m != null ? m.name : "-") + " / " + tex;
}
if (t > seconds)
{
Application.onBeforeRender -= act;
var keys = new List<string>(count.Keys); keys.Sort((a, b) => firstT[a].CompareTo(firstT[b]));
foreach (var k in keys) sb.Append(firstT[k].ToString("F2")).Append(" 초 · ").Append(count[k]).Append(" 회 · ").Append(k).Append(" · ").Append(info[k]).Append('\n');
sb.Append("끝 · 새 이펙트 ").Append(keys.Count).Append(" 종\n");
}
};
Application.onBeforeRender += act;
return "새 이펙트 기록 " + seconds + " 초";
}
/// <summary>이름이 name 인 파티클에 입자가 생기면 delay 초 뒤 일시 정지 — 그 위치의 화면 좌표 · 부모 경로.</summary>
public static object PauseOnFx(string name, float delay, float maxSeconds)
{
var sb = Buf(); sb.Length = 0; float t0 = Time.time, at = -1f; ParticleSystem hit = null;
UnityEngine.Events.UnityAction act = null;
act = () =>
{
float t = Time.time - t0;
if (at < 0f)
foreach (var ps in Object.FindObjectsByType<ParticleSystem>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
if (ps != null && ps.name == name && ps.particleCount > 0) { hit = ps; at = Time.time + delay; break; }
if ((at > 0f && Time.time >= at) || t > maxSeconds)
{
Application.onBeforeRender -= act;
if (at > 0f) UnityEditor.EditorApplication.isPaused = true;
var cam = Camera.main; var vp = hit != null && cam != null ? cam.WorldToViewportPoint(hit.transform.position) : Vector3.zero;
sb.AppendFormat("{0} · t {1:F2} · 화면 {2:F3},{3:F3} · {4}", at > 0f ? "■ 일시 정지" : "못 찾음", t, vp.x, 1f - vp.y, hit != null ? PathOf(hit.transform, 4) : "-").Append('\n');
}
};
Application.onBeforeRender += act;
return name + " 대기";
}
/// <summary>프리팹의 자식 하나(이름)만 보이게 해 시각 t 에 섬 바닥 색 배경으로 찍는다 — 게임 카메라 각도 · 각 자식 PNG.</summary>
public static object Solo(string path, string namesCsv, float t, float scale, string outPrefix)
{
var prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path); if (prefab == null) return "없음";
var main = Camera.main; var sb = new StringBuilder();
foreach (var name in namesCsv.Split(','))
{
var root = new Vector3(0f, 1000f, 0f); var go = Object.Instantiate(prefab, root, prefab.transform.rotation); go.transform.localScale = prefab.transform.localScale * scale;
Transform keep = null; foreach (var tr in go.GetComponentsInChildren<Transform>(true)) if (tr.name == name) { keep = tr; break; }
bool only = name.StartsWith("ONLY:"); string nm = only ? name.Substring(5) : name; if (only) foreach (var tr in go.GetComponentsInChildren<Transform>(true)) if (tr.name == nm) { keep = tr; break; }
foreach (var r in go.GetComponentsInChildren<Renderer>(true)) r.enabled = name == "ALL" || (keep != null && (only ? r.transform == keep : r.transform.IsChildOf(keep)));
foreach (var ps in go.GetComponentsInChildren<ParticleSystem>(true)) if (ps.transform.parent == null || ps.transform.parent.GetComponentInParent<ParticleSystem>() == null) ps.Simulate(t, true, true, false);
if (keep != null) { var kr = keep.GetComponent<Renderer>(); var kps = keep.GetComponent<ParticleSystem>(); sb.Append("[").Append(nm).Append(" 재질 ").Append(kr != null && kr.sharedMaterial != null ? kr.sharedMaterial.name + "/" + (kr.sharedMaterial.mainTexture != null ? kr.sharedMaterial.mainTexture.name : "-") + "/" + kr.sharedMaterial.shader.name : "-").Append(" 켜짐 ").Append(kr != null && kr.enabled).Append(" 입자 ").Append(kps != null ? kps.particleCount : -1).Append(kps != null ? " 시작색 " + kps.main.startColor.color.ToString("F2") + " 방출 " + kps.emission.rateOverTime.constant.ToString("F0") + "/s 속도 " + kps.main.startSpeed.constant.ToString("F1") + " 수명 " + kps.main.startLifetime.constant.ToString("F1") + " 길이 " + kps.main.duration.ToString("F1") + " 반복 " + kps.main.loop : "").Append("] "); }
var cg = new GameObject("~WL924_Solo"); var cam = cg.AddComponent<Camera>(); cam.enabled = false;
cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = new Color(0.78f, 0.72f, 0.52f, 1f); cam.fieldOfView = 30f;
cg.transform.rotation = main != null ? main.transform.rotation : Quaternion.Euler(55f, 0f, 0f); cg.transform.position = root - cg.transform.forward * 9f;
var rt = new RenderTexture(420, 420, 24); cam.targetTexture = rt; cam.Render();
var prev = RenderTexture.active; RenderTexture.active = rt; var tex = new Texture2D(420, 420, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(0, 0, 420, 420), 0, 0); tex.Apply(); RenderTexture.active = prev;
System.IO.File.WriteAllBytes(outPrefix + "_" + name.Replace("ONLY:", "only_").Replace(" ", "").Replace("(", "").Replace(")", "") + ".png", tex.EncodeToPNG());
Object.DestroyImmediate(tex); cam.targetTexture = null; Object.DestroyImmediate(rt); Object.DestroyImmediate(cg); Object.DestroyImmediate(go);
sb.Append(name).Append(" · ");
}
return sb.ToString();
}
/// <summary>아무 프리팹(에셋 경로)을 PC 발 + 높이에 배율로 띄워 every 초마다 다시(life 초 뒤 삭제) — PC 를 따라감.</summary>
public static object WrapP(string path, float height, float scale, float every, float life, float seconds)
{
var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음";
var prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path); if (prefab == null) return "없음 " + path;
float t0 = Time.time, next = 0f; var live = new List<GameObject>();
UnityEngine.Events.UnityAction act = null;
act = () =>
{
float t = Time.time - t0;
if (t >= next && t < seconds)
{
next += Mathf.Max(0.05f, every);
var go = Object.Instantiate(prefab, pc.Get_position() + Vector3.up * height, prefab.transform.rotation);
foreach (var ps in go.GetComponentsInChildren<ParticleSystem>(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; }
go.transform.localScale = prefab.transform.localScale * scale; Object.Destroy(go, life); live.Add(go);
}
for (int i = live.Count - 1; i >= 0; i--) { if (live[i] == null) { live.RemoveAt(i); continue; } live[i].transform.position = pc.Get_position() + Vector3.up * height; }
if (t > seconds + life) Application.onBeforeRender -= act;
};
Application.onBeforeRender += act;
return "띄움 " + System.IO.Path.GetFileNameWithoutExtension(path);
}
public static object Wrap(string spec, float seconds)
{
var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음";
var prefab = Resources.Load<GameObject>("WL/Fx/WL_Fx_BladeStorm"); if (prefab == null) return "프리팹 없음";
var layers = new List<Vector2>(); var ci = System.Globalization.CultureInfo.InvariantCulture;
foreach (var part in spec.Split('|')) { var p = part.Split(':'); layers.Add(new Vector2(float.Parse(p[0], ci), float.Parse(p[1], ci))); }
float t0 = Time.time, next = 0f; var live = new List<KeyValuePair<GameObject, float>>();
UnityEngine.Events.UnityAction act = null;
act = () =>
{
float t = Time.time - t0;
if (t >= next && t < seconds)
{
next += 0.5f;
foreach (var L in layers)
{
var go = Object.Instantiate(prefab, pc.Get_position() + Vector3.up * L.x, prefab.transform.rotation);
go.transform.localScale = prefab.transform.localScale * L.y; Object.Destroy(go, 0.9f); live.Add(new KeyValuePair<GameObject, float>(go, L.x));
}
}
for (int i = live.Count - 1; i >= 0; i--) { if (live[i].Key == null) { live.RemoveAt(i); continue; } live[i].Key.transform.position = pc.Get_position() + Vector3.up * live[i].Value; }
if (t > seconds + 1f) Application.onBeforeRender -= act;
};
Application.onBeforeRender += act;
return "겹침 " + layers.Count + " 층 · " + seconds + " 초";
}
}