Project_WL/AgentScripts/WL816t_Cap.cs

157 lines
10 KiB
C#

// WL816t_Cap.cs — 8방향 격자 · 3타 연속 캡처 (에디트 모드 · Play 없음)
// unity command run_script --file AgentScripts/WL816t_Cap.cs --entry WL816t_Cap.Grid8 --args "[10101, 2, \"Screenshots_WL/WL816t_before_8dir.png\"]"
// unity command run_script --file AgentScripts/WL816t_Cap.cs --entry WL816t_Cap.Combo3 --args "[10101, \"Screenshots_WL/WL816t_after_combo.png\"]"
// PlaceArc 와 같은 수식으로 실제 Effect_WLSwingArc 프리팹을 배치하고 ParticleSystem.Simulate 로 굽는다.
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using WL.Combat;
public static class WL816t_Cap
{
const string SettingsPath = "Assets/WL/Settings/Resources/WL/SlashTrailSettings.asset";
const string ClassConfigJson = "Assets/ResWork/Table/Export/ClassConfig.json";
const string ArcPrefab = "Assets/Res_Addr/Effect/Effect_WLSwingArc.prefab";
public static object Grid8(int classId, int stage, string outPath) { return Build(classId, new[] { stage }, true, outPath); }
public static object Combo3(int classId, string outPath) { return Build(classId, new[] { 1, 2, 3 }, false, outPath); }
static string Build(int classId, int[] stages, bool grid8, string outPath)
{
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
var settings = AssetDatabase.LoadAssetAtPath<SlashTrailSettings>(SettingsPath);
var arcGo = AssetDatabase.LoadAssetAtPath<GameObject>(ArcPrefab);
if (settings == null || arcGo == null) return "load fail";
string weaponName = null; int socketIndex = -1; float fScale = 1f, atkRange = 0f;
var arr = Newtonsoft.Json.Linq.JArray.Parse(System.IO.File.ReadAllText(ClassConfigJson));
foreach (var row in arr)
{
if ((string)row["n_ClassID"] != classId.ToString()) continue;
weaponName = (string)row["s_WeaponRHPrefab"]; socketIndex = int.Parse((string)row["n_WeaponRHIndex"]);
if (string.IsNullOrEmpty(weaponName)) { weaponName = (string)row["s_WeaponLHPrefab"]; socketIndex = int.Parse((string)row["n_WeaponLHIndex"]); }
float.TryParse((string)row["f_Scale"], out fScale); float.TryParse((string)row["f_AttackRange"], out atkRange);
break;
}
string pcName = WL.Character.WLCharacterSwapSettings.Resolve("Ai01");
float rootScale = fScale * WL.Character.WLCharacterSwapSettings.HeightCompensation;
var model = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Res_Addr/PC/" + pcName + ".prefab");
var weaponPrefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Res_Addr/Weapon/" + weaponName + ".prefab");
if (model == null || weaponPrefab == null) return "pc/weapon load fail";
float reach = settings.arcFitAttackRange ? (atkRange + WL.Settings.WLTargetingSettings.ImmediateAttackMargin) * settings.arcRangeScale : 0f;
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
var log = new System.Text.StringBuilder();
try
{
var lightGo = new GameObject("Sun"); var lt = lightGo.AddComponent<Light>();
lt.type = LightType.Directional; lt.intensity = 1.2f; lightGo.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
var ground = GameObject.CreatePrimitive(PrimitiveType.Plane);
ground.transform.localScale = Vector3.one * 5f; ground.name = "Ground";
int cols = grid8 ? 4 : stages.Length;
int cells = grid8 ? 8 : stages.Length;
float pitch = 3.2f;
var pss = new List<ParticleSystem>();
for (int i = 0; i < cells; i++)
{
float yaw = grid8 ? i * 45f : 0f;
int stage = grid8 ? stages[0] : stages[i];
var cal = settings.FindCalibration(classId, "Effect_Slash_" + classId + "_" + stage);
if (cal == null) { log.AppendLine("cal 없음 stage " + stage); continue; }
Vector3 cell = new Vector3((i % cols) * pitch - (cols - 1) * pitch * 0.5f, 0f, -(i / cols) * pitch);
var go = (GameObject)PrefabUtility.InstantiatePrefab(model);
go.transform.position = cell; go.transform.rotation = Quaternion.Euler(0f, yaw, 0f);
go.transform.localScale = Vector3.one * rootScale;
var pc = go.GetComponentInChildren<PCActor>(true);
var socket = pc.tfs_weapon[socketIndex];
var w = (GameObject)PrefabUtility.InstantiatePrefab(weaponPrefab);
w.transform.SetParent(socket, false);
w.transform.localPosition = Vector3.zero; w.transform.localRotation = Quaternion.identity; w.transform.localScale = Vector3.one;
var fitter = go.GetComponent<WL.Character.WLWeaponFitter>() ?? go.AddComponent<WL.Character.WLWeaponFitter>();
fitter.Fit(w.transform, socket);
FreezeAtSwingMid(go, classId, stage);
// ── PlaceArc 와 같은 수식 ────────────────────────────────────
var geo = settings.FindGeometry("Effect_WLSwingArc");
if (geo == null || !geo.arcValid) { log.AppendLine("geo 없음"); continue; }
var fx = (GameObject)PrefabUtility.InstantiatePrefab(arcGo);
Quaternion rootRot = go.transform.rotation; Vector3 rootPos = go.transform.position;
Vector3 nW = (rootRot * cal.planeNormal).normalized;
Vector3 bW = rootRot * cal.bisector; bW -= nW * Vector3.Dot(bW, nW); bW.Normalize();
Vector3 pivotW = rootPos + rootRot * cal.pivot;
float s = cal.tipRadius / geo.arcRadiusOuter;
if (reach > 0.01f) s = reach / geo.arcRadiusOuter;
s *= Mathf.Max(settings.arcSizeScale, 0.01f);
Quaternion frame = Quaternion.LookRotation(geo.arcNormal, geo.arcBisector);
Quaternion rot = Quaternion.LookRotation(nW, bW) * Quaternion.Inverse(frame);
fx.transform.rotation = rot;
fx.transform.localScale = Vector3.one * s;
fx.transform.position = pivotW + nW * settings.arcPlaneOffset + bW * settings.arcRadialOffset - rot * (geo.arcCenter * s);
fx.SetActive(true);
foreach (var tr in fx.GetComponentsInChildren<Transform>(true)) tr.gameObject.SetActive(true);
foreach (var r in fx.GetComponentsInChildren<Renderer>(true)) r.enabled = true;
foreach (var ps in fx.GetComponentsInChildren<ParticleSystem>(true)) pss.Add(ps);
if (i == 0) log.AppendLine(" fx: ps=" + fx.GetComponentsInChildren<ParticleSystem>(true).Length
+ " rend=" + fx.GetComponentsInChildren<Renderer>(true).Length
+ " mesh=" + fx.GetComponentsInChildren<MeshRenderer>(true).Length);
log.AppendLine(string.Format(" yaw {0,3:F0} stage {1} · s={2:F3} r={3:F3} pivotW={4}", yaw, stage, s, geo.arcRadiusOuter * s, pivotW.ToString("F2")));
}
int alive = 0;
foreach (var ps in pss) { ps.Simulate(0.12f, true, true, false); alive += ps.particleCount; }
log.AppendLine(" particles alive = " + alive + " (ps " + pss.Count + "개)");
// ── 카메라 (PD 구도 · 비스듬히 내려보기) ────────────────────────
var camGo = new GameObject("CapCam"); var cam = camGo.AddComponent<Camera>();
cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = new Color(0.12f, 0.13f, 0.16f);
float span = grid8 ? 12f : 6f;
camGo.transform.rotation = Quaternion.Euler(38f, 0f, 0f);
Vector3 focus = new Vector3(0f, 0.9f, grid8 ? -pitch * 0.5f : 0f);
camGo.transform.position = focus - camGo.transform.forward * span;
cam.fieldOfView = 45f;
string full = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), outPath);
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(full));
var rt = new RenderTexture(1600, 900, 24, RenderTextureFormat.ARGB32);
cam.targetTexture = rt; cam.Render();
RenderTexture.active = rt;
var tex = new Texture2D(1600, 900, TextureFormat.RGB24, false);
tex.ReadPixels(new Rect(0, 0, 1600, 900), 0, 0); tex.Apply();
RenderTexture.active = null; cam.targetTexture = null;
System.IO.File.WriteAllBytes(full, tex.EncodeToPNG());
Object.DestroyImmediate(tex); rt.Release(); Object.DestroyImmediate(rt);
log.AppendLine("SAVED " + full + " · reach=" + reach.ToString("F2") + " · arcFitAttackRange=" + settings.arcFitAttackRange);
}
finally { }
return log.ToString();
}
static void FreezeAtSwingMid(GameObject go, int classId, int stage)
{
var fbx = string.Format("Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack{0}_S.FBX", stage);
var clips = AssetDatabase.LoadAllAssetsAtPath(fbx).OfType<AnimationClip>().Where(c => !c.name.StartsWith("__preview")).ToArray();
var clip = clips.FirstOrDefault(c => c.name == "Attack" + stage + "_S_" + classId) ?? clips.FirstOrDefault(c => c.name.StartsWith("Attack" + stage));
if (clip == null) return;
var anim = go.GetComponentInChildren<Animator>();
if (anim == null) return;
anim.applyRootMotion = false;
var bones = go.GetComponentsInChildren<Transform>(true);
var trs = new Vector3[bones.Length]; var rots = new Quaternion[bones.Length]; var scl = new Vector3[bones.Length];
AnimationMode.StartAnimationMode();
AnimationMode.BeginSampling();
AnimationMode.SampleAnimationClip(anim.gameObject, clip, clip.length * 0.42f);
AnimationMode.EndSampling();
for (int i = 0; i < bones.Length; i++) { trs[i] = bones[i].localPosition; rots[i] = bones[i].localRotation; scl[i] = bones[i].localScale; }
AnimationMode.StopAnimationMode();
anim.enabled = false;
for (int i = 0; i < bones.Length; i++) { bones[i].localPosition = trs[i]; bones[i].localRotation = rots[i]; bones[i].localScale = scl[i]; }
}
}