130 lines
8.9 KiB
C#
130 lines
8.9 KiB
C#
|
|
// 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();
|
|||
|
|
}
|
|||
|
|
}
|