/* * CharacterVATConverter * * 一键把现有 SpriteRenderer 主角 (Wizard) 转成 VAT 3D 主角: * 1) 实例化 Wizard.prefab 作为模板 * 2) 在根节点挂上 R02_VAT.prefab 作为 "View" 子物体 * 3) 添加 RendererAdapter (Mode=Mesh, fetchRoot=View) * 4) 把 RendererAdapter 拖到 CharacterBehavior.rendererAdapter 槽 * 5) 保存为 Wizard_R02.prefab * 6) (可选) 把 CharactersDatabase 的 WIZARD 的 prefab 指向新 Wizard_R02.prefab * * 用途:验证 RendererAdapter 在 OctoberStudio 主角生命周期里能正常 wire 起来。 */ using OctoberStudio.Rendering; using UnityEditor; using UnityEngine; namespace OctoberStudio.EditorTools { public static class CharacterVATConverter { private const string TemplatePath = "Assets/Common/Prefabs/Characters/Wizard.prefab"; private const string VATSourcePath = "Assets/_VAT_Baking/Output/R02_S_body01_world/R02_S_body01_world_VAT.prefab"; private const string OutputPath = "Assets/Common/Prefabs/Characters/Wizard_R02.prefab"; private const string DatabasePath = "Assets/Common/Scriptables/Characters Database.asset"; [MenuItem("Tools/OctoberStudio/VAT/1. Make Wizard_R02 Variant", priority = 1)] public static void MakeVariant() { var ok = MakeWizardR02(); // 不弹 modal dialog (会阻塞 MCP 主线程通信),改用 console log。 if (ok) Debug.Log($"[VAT Convert] Created: {OutputPath}\n下一步: Tools/OctoberStudio/VAT/2. Apply Wizard_R02 to Database"); } [MenuItem("Tools/OctoberStudio/VAT/2. Apply Wizard_R02 to Database", priority = 2)] public static void ApplyToDatabase() { var ok = ApplyR02ToDatabase(); if (ok) Debug.Log("[VAT Convert] CharactersDatabase: WIZARD prefab → Wizard_R02.prefab. 跑场景查看效果。"); } // Legacy: 用于校正旧版"单层"R02_VAT.prefab(无 Root+Mesh 结构)。 // 新版 baker 已把 Y=180 烤入 Mesh 子节点,无需调用此菜单。 [MenuItem("Tools/OctoberStudio/VAT/[Legacy] Calibrate Wizard_R02 (Y=180 + auto pivot)", priority = 100)] public static void QuickCalibrateWizardR02() { CalibratePrefab(OutputPath, new Vector3(0f, 180f, 0f), 0.5f, autoCenterPivot: true); } /// /// 直接在 prefab asset 上做朝向 + 缩放 + pivot 校正,无需中转场景。 /// public static void CalibratePrefab(string prefabPath, Vector3 euler, float uniformScale, bool autoCenterPivot) { var prefabRoot = PrefabUtility.LoadPrefabContents(prefabPath); if (prefabRoot == null) { Debug.LogError($"[VAT] Cannot load prefab contents: {prefabPath}"); return; } try { var view = prefabRoot.transform.Find("View"); if (view == null) { Debug.LogError("[VAT] View transform not found"); return; } view.localRotation = Quaternion.Euler(euler); view.localScale = Vector3.one * uniformScale; if (autoCenterPivot) { var mf = view.GetComponentInChildren(); if (mf != null && mf.sharedMesh != null) { var meshCenter = mf.sharedMesh.bounds.center; // model-space var scaled = Vector3.Scale(meshCenter, view.localScale); var offset = view.localRotation * scaled; // view-parent space view.localPosition = -offset; } } PrefabUtility.SaveAsPrefabAsset(prefabRoot, prefabPath); Debug.Log($"[VAT] Calibrated {prefabPath}: euler={euler}, scale={uniformScale}, pos={view.localPosition}"); } finally { PrefabUtility.UnloadPrefabContents(prefabRoot); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } [MenuItem("Tools/OctoberStudio/VAT/3. Restore Wizard (Original Sprite)", priority = 3)] public static void RestoreOriginal() { var db = AssetDatabase.LoadAssetAtPath(DatabasePath); if (db == null) { Debug.LogError("Database not found"); return; } var template = AssetDatabase.LoadAssetAtPath(TemplatePath); if (template == null) { Debug.LogError("Wizard.prefab not found"); return; } var so = new SerializedObject(db); var characters = so.FindProperty("characters"); if (characters.arraySize > 0) { characters.GetArrayElementAtIndex(0).FindPropertyRelative("prefab").objectReferenceValue = template; so.ApplyModifiedProperties(); AssetDatabase.SaveAssets(); Debug.Log("[VAT] Restored original Wizard.prefab"); } } // ========================= IMPL ========================= public static bool MakeWizardR02() { var template = AssetDatabase.LoadAssetAtPath(TemplatePath); var vatSrc = AssetDatabase.LoadAssetAtPath(VATSourcePath); if (template == null) { Debug.LogError($"[VAT] Template prefab not found: {TemplatePath}"); return false; } if (vatSrc == null) { Debug.LogError($"[VAT] VAT prefab not found: {VATSourcePath}"); return false; } // 1) Instantiate Wizard as base var instance = (GameObject)PrefabUtility.InstantiatePrefab(template); instance.name = "Wizard_R02_tmp"; try { // 2) Instantiate R02 VAT as child "View" // R02_VAT 自身是 Root + Mesh 两层结构, Mesh 节点上已经烤入 Y=180 (面相机), // 所以这里 View 的 localRotation 保持 identity 即可,无需再做朝向校正 var view = (GameObject)PrefabUtility.InstantiatePrefab(vatSrc, instance.transform); view.name = "View"; view.transform.localPosition = Vector3.zero; view.transform.localRotation = Quaternion.identity; // VAT 模型默认尺寸是真实角色级 (~1.7m);OctoberStudio 单位较小,先给个 0.5 缩放避免太大 // 用户可以在 prefab 里再调 view.transform.localScale = Vector3.one * 0.5f; // 3) Add RendererAdapter on root, mode=Mesh, fetchRoot=View var adapter = instance.AddComponent(); { var soAdapter = new SerializedObject(adapter); var modeProp = soAdapter.FindProperty("mode"); if (modeProp != null) modeProp.enumValueIndex = (int)RendererMode.Mesh; var rootProp = soAdapter.FindProperty("fetchRoot"); if (rootProp != null) rootProp.objectReferenceValue = view.transform; soAdapter.ApplyModifiedProperties(); adapter.FetchComponents(); adapter.ApplyMode(); } // 4) Wire adapter into CharacterBehavior.rendererAdapter var character = instance.GetComponent(); if (character != null) { var soChar = new SerializedObject(character); var fieldProp = soChar.FindProperty("rendererAdapter"); if (fieldProp != null) fieldProp.objectReferenceValue = adapter; soChar.ApplyModifiedProperties(); } else { Debug.LogWarning("[VAT] CharacterBehavior not found on Wizard root"); } // 5) Save as new prefab (规则的 prefab,不是 variant,独立可改) var savedAsset = PrefabUtility.SaveAsPrefabAsset(instance, OutputPath, out var success); if (!success || savedAsset == null) { Debug.LogError("[VAT] SaveAsPrefabAsset failed"); return false; } Debug.Log($"[VAT] Saved: {OutputPath}"); } finally { Object.DestroyImmediate(instance); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return true; } public static bool ApplyR02ToDatabase() { var db = AssetDatabase.LoadAssetAtPath(DatabasePath); if (db == null) { Debug.LogError("Database not found"); return false; } var newPrefab = AssetDatabase.LoadAssetAtPath(OutputPath); if (newPrefab == null) { Debug.LogError($"Output prefab not found: {OutputPath}"); return false; } var so = new SerializedObject(db); var characters = so.FindProperty("characters"); if (characters == null || characters.arraySize == 0) { Debug.LogError("Database has no characters"); return false; } characters.GetArrayElementAtIndex(0).FindPropertyRelative("prefab").objectReferenceValue = newPrefab; so.ApplyModifiedProperties(); AssetDatabase.SaveAssets(); Debug.Log("[VAT] CharactersDatabase: WIZARD prefab → Wizard_R02.prefab"); return true; } } }