UnityVATTool/Assets/Common/Scripts/Editor/CharacterVATConverter.cs

219 lines
9.7 KiB
C#
Raw Permalink 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.

/*
* 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);
}
/// <summary>
/// 直接在 prefab asset 上做朝向 + 缩放 + pivot 校正,无需中转场景。
/// </summary>
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<MeshFilter>();
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<CharactersDatabase>(DatabasePath);
if (db == null) { Debug.LogError("Database not found"); return; }
var template = AssetDatabase.LoadAssetAtPath<GameObject>(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<GameObject>(TemplatePath);
var vatSrc = AssetDatabase.LoadAssetAtPath<GameObject>(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<RendererAdapter>();
{
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<CharacterBehavior>();
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<CharactersDatabase>(DatabasePath);
if (db == null) { Debug.LogError("Database not found"); return false; }
var newPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(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;
}
}
}