diff --git a/AgentScripts/WL816t_Cap.cs b/AgentScripts/WL816t_Cap.cs new file mode 100644 index 000000000..203550649 --- /dev/null +++ b/AgentScripts/WL816t_Cap.cs @@ -0,0 +1,156 @@ +// 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(SettingsPath); + var arcGo = AssetDatabase.LoadAssetAtPath(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("Assets/Res_Addr/PC/" + pcName + ".prefab"); + var weaponPrefab = AssetDatabase.LoadAssetAtPath("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(); + 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(); + 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(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() ?? go.AddComponent(); + 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(true)) tr.gameObject.SetActive(true); + foreach (var r in fx.GetComponentsInChildren(true)) r.enabled = true; + foreach (var ps in fx.GetComponentsInChildren(true)) pss.Add(ps); + if (i == 0) log.AppendLine(" fx: ps=" + fx.GetComponentsInChildren(true).Length + + " rend=" + fx.GetComponentsInChildren(true).Length + + " mesh=" + fx.GetComponentsInChildren(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(); + 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().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(); + if (anim == null) return; + anim.applyRootMotion = false; + var bones = go.GetComponentsInChildren(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]; } + } +}