Project_WL/AgentScripts/WL849_WeaponModels.cs

130 lines
8.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// WL849_WeaponModels.cs — 무기 래퍼 프리팹 생성(손잡이 기준 확대) + Addressables 등록 · 월드 크기 측정 (에디터 · 멱등)
// PD 2026-10-12 #849 「해머·창·대검을 훨씬 크게 · 900 Low Poly Fantasy RPG Weapons 팩으로 모델링 개량」
// 값 = AgentScripts/WL849_WeaponModels.json (C45 · 모델 경로·배율은 여기 없음)
// unity command run_script --file AgentScripts/WL849_WeaponModels.cs --entry WL849_WeaponModels.Build --timeout 180
// unity command run_script --file AgentScripts/WL849_WeaponModels.cs --entry WL849_WeaponModels.Measure --args '["PP_Theme_01_Hammer_001|WL_W_Hammer_T05_003", 1]' --timeout 120
// 🔴 원본 PCActor.Make_Weapon 은 인스턴스 루트에 (위치 0 · 크기 1)을 덮어쓴다 → 배율은 루트가 아니라 자식(팩 프리팹 인스턴스)에 둔다.
// 팩 모델의 기준점 = 손잡이 → 자식을 원점에 두고 키우면 손에 쥔 자리는 그대로다.
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.SceneManagement;
using UnityEngine;
public static class WL849_WeaponModels
{
const string SpecPath = "AgentScripts/WL849_WeaponModels.json";
const string ClassConfigJson = "Assets/ResWork/Table/Export/ClassConfig.json";
class Spec { public string outDir; public List<Model> models; }
class Model { public string name; public int[] classes; public string source; public float[] scale; public float fitRatio; }
const string FitSettingsPath = "Assets/WL/Settings/Resources/WL/WLWeaponFitSettings.asset";
/// <summary>스펙의 fitRatio 를 WLWeaponFitSettings.perWeapon 에 넣는다(같은 이름이면 갱신 · 다른 무기 행은 그대로).</summary>
public static string ApplyFitRatios()
{
var spec = Load(); var sb = new StringBuilder();
var st = AssetDatabase.LoadAssetAtPath<WL.Character.WLWeaponFitSettings>(FitSettingsPath);
if (st == null) return "fit settings 없음";
if (st.perWeapon == null) st.perWeapon = new List<WL.Character.WLWeaponFitSettings.WeaponRatio>();
foreach (var m in spec.models)
{
if (m.fitRatio <= 0f) continue;
var row = st.perWeapon.Find(r => r != null && string.Equals(r.weaponName, m.name, System.StringComparison.OrdinalIgnoreCase));
if (row == null) { row = new WL.Character.WLWeaponFitSettings.WeaponRatio { weaponName = m.name }; st.perWeapon.Add(row); }
row.ratio = m.fitRatio;
sb.Append(m.name).Append(" ratio ").Append(m.fitRatio).Append('\n');
}
EditorUtility.SetDirty(st); AssetDatabase.SaveAssets();
WL.Character.WLWeaponFitSettings.ClearCache();
return sb.ToString() + "perWeapon " + st.perWeapon.Count + "행 · targetRatio " + st.targetRatio + " · maxScale " + st.maxScale;
}
static Spec Load() { return Newtonsoft.Json.JsonConvert.DeserializeObject<Spec>(System.IO.File.ReadAllText(SpecPath)); }
public static string Build()
{
var spec = Load(); var sb = new StringBuilder();
var settings = AddressableAssetSettingsDefaultObject.Settings;
foreach (var m in spec.models)
{
var src = AssetDatabase.LoadAssetAtPath<GameObject>(m.source);
if (src == null) { sb.Append("원본 없음 ").Append(m.source).Append('\n'); continue; }
string path = spec.outDir + "/" + m.name + ".prefab";
var root = new GameObject(m.name);
try
{
var child = (GameObject)PrefabUtility.InstantiatePrefab(src);
child.transform.SetParent(root.transform, false);
child.transform.localPosition = Vector3.zero; child.transform.localRotation = Quaternion.identity;
child.transform.localScale = new Vector3(m.scale[0], m.scale[1], m.scale[2]);
PrefabUtility.SaveAsPrefabAsset(root, path);
}
finally { Object.DestroyImmediate(root); }
if (settings != null)
{
var e = settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(path), settings.DefaultGroup, false, false);
e.address = path;
}
sb.Append("프리팹 ").Append(path).Append(" ← ").Append(System.IO.Path.GetFileNameWithoutExtension(m.source)).Append(" × (").Append(m.scale[0]).Append(", ").Append(m.scale[1]).Append(", ").Append(m.scale[2]).Append(") · 주소 등록\n");
}
if (settings != null) settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, null, true, true);
AssetDatabase.SaveAssets();
return sb.ToString();
}
/// <summary>런타임과 같은 PC(이름·키 보정)를 미리보기 씬에 세워 소켓에 무기를 붙이고 월드 크기를 잰다. names = "a|b|c" · socket = tfs_weapon 번호.</summary>
public static string Measure(string names, int socket)
{
var sb = new StringBuilder();
float fScale = 1f;
try { foreach (var row in Newtonsoft.Json.Linq.JArray.Parse(System.IO.File.ReadAllText(ClassConfigJson))) if ((string)row["n_ClassID"] == "10101") { float.TryParse((string)row["f_Scale"], out fScale); break; } } catch (System.Exception) { }
string pcName = WL.Character.WLCharacterSwapSettings.Resolve("Ai01");
float rootScale = Mathf.Max(fScale, 0.01f) * Mathf.Max(WL.Character.WLCharacterSwapSettings.HeightCompensation, 0.01f);
var model = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Res_Addr/PC/" + pcName + ".prefab");
if (model == null) return "pc 없음 " + pcName;
var scene = EditorSceneManager.NewPreviewScene();
try
{
var go = (GameObject)PrefabUtility.InstantiatePrefab(model, scene);
go.transform.position = Vector3.zero; go.transform.rotation = Quaternion.identity; go.transform.localScale = Vector3.one * rootScale;
var pc = go.GetComponentInChildren<PCActor>(true);
float h = 0f; foreach (var r in go.GetComponentsInChildren<SkinnedMeshRenderer>(true)) h = Mathf.Max(h, r.bounds.max.y);
var tf = pc != null && pc.tfs_weapon != null && socket < pc.tfs_weapon.Length ? pc.tfs_weapon[socket] : null;
if (tf == null) return "socket 없음";
sb.Append("PC ").Append(pcName).Append(" rootScale=").Append(rootScale.ToString("F3")).Append(" 키(렌더러 최고점)=").Append(h.ToString("F2")).Append(" · socket ").Append(tf.name).Append(" lossy=").Append(tf.lossyScale.ToString("F3")).Append('\n');
foreach (var n in names.Split('|'))
{
var wp = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Res_Addr/Weapon/" + n + ".prefab");
if (wp == null) { sb.Append(n).Append(" 없음\n"); continue; }
var w = (GameObject)PrefabUtility.InstantiatePrefab(wp, scene);
w.transform.SetParent(tf, false); w.transform.localPosition = Vector3.zero; w.transform.localRotation = Quaternion.identity; w.transform.localScale = Vector3.one;
// 런타임과 같은 크기 보정(WLWeaponFitter · 무기별 비율) 적용 뒤 잰다
var fitter = go.GetComponent<WL.Character.WLWeaponFitter>(); if (fitter == null) fitter = go.AddComponent<WL.Character.WLWeaponFitter>();
float k = fitter.Fit(w.transform, tf);
sb.Append(" [보정 ×").Append(k.ToString("F3")).Append(" · 비율 ").Append(WL.Character.WLWeaponFitSettings.ResolveRatio(n).ToString("F2")).Append("] ");
// 무기 길이 축 = 소켓 로컬 Y(팩 모델 규약) — 소켓 공간 bounds 로 길이·폭·두께
Vector3 mn = Vector3.one * 1e9f, mx = -Vector3.one * 1e9f;
foreach (var mf in w.GetComponentsInChildren<MeshFilter>(true))
{
if (mf.sharedMesh == null) continue; var b = mf.sharedMesh.bounds;
for (int c = 0; c < 8; c++)
{
var corner = b.center + new Vector3((c & 1) == 0 ? -b.extents.x : b.extents.x, (c & 2) == 0 ? -b.extents.y : b.extents.y, (c & 4) == 0 ? -b.extents.z : b.extents.z);
var p = tf.InverseTransformPoint(mf.transform.TransformPoint(corner));
mn = Vector3.Min(mn, p); mx = Vector3.Max(mx, p);
}
}
var size = Vector3.Scale(mx - mn, tf.lossyScale);
sb.Append(n).Append(" 월드 [폭 ").Append(size.x.ToString("F2")).Append(" · 길이 ").Append(size.y.ToString("F2")).Append(" · 두께 ").Append(size.z.ToString("F2")).Append("] m · 손잡이 아래 ").Append((-mn.y * tf.lossyScale.y).ToString("F2")).Append(" m · 키 대비 길이 ").Append(h > 0.01f ? (size.y / h).ToString("F2") : "-").Append('\n');
Object.DestroyImmediate(w);
}
}
finally { EditorSceneManager.ClosePreviewScene(scene); }
return sb.ToString();
}
}