/*
* 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;
}
}
}