201 lines
8.2 KiB
C#
201 lines
8.2 KiB
C#
/*
|
||
* OrientationCalibrator
|
||
*
|
||
* 用途:调试 R02 (或任意 3D View 子物体) 的朝向 + 缩放 + 偏移,
|
||
* 一键 apply 到 prefab 上持久化保存。
|
||
*
|
||
* 默认目标:选中场景里的 GameObject (含 "View" 子物体),然后菜单 Tools/OctoberStudio/VAT/Orientation Calibrator
|
||
*/
|
||
|
||
using System.Linq;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
namespace OctoberStudio.EditorTools
|
||
{
|
||
public class OrientationCalibrator : EditorWindow
|
||
{
|
||
private GameObject _target;
|
||
private Transform _view;
|
||
|
||
private Vector3 _euler = new Vector3(0f, 180f, 0f);
|
||
private float _uniformScale = 0.5f;
|
||
private Vector3 _localPos = Vector3.zero;
|
||
|
||
[MenuItem("Tools/OctoberStudio/VAT/Orientation Calibrator", priority = 10)]
|
||
public static void ShowWindow()
|
||
{
|
||
var win = GetWindow<OrientationCalibrator>("Orientation Calibrator");
|
||
win.AutoPickTarget();
|
||
}
|
||
|
||
private void OnEnable() => AutoPickTarget();
|
||
private void OnSelectionChange() { AutoPickTarget(); Repaint(); }
|
||
|
||
private void AutoPickTarget()
|
||
{
|
||
if (Selection.activeGameObject != null)
|
||
{
|
||
_target = Selection.activeGameObject;
|
||
_view = _target.transform.Find("View");
|
||
if (_view != null)
|
||
{
|
||
_euler = _view.localEulerAngles;
|
||
var s = _view.localScale;
|
||
_uniformScale = (s.x + s.y + s.z) / 3f;
|
||
_localPos = _view.localPosition;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
EditorGUILayout.LabelField("Orientation Calibrator", EditorStyles.boldLabel);
|
||
EditorGUILayout.HelpBox(
|
||
"1) 在场景里选中 Wizard_R02 (或任意含 'View' 子物体的角色)\n" +
|
||
"2) 点 Quick Face 按钮预览朝向\n" +
|
||
"3) 调好后点 [Apply to Prefab] 持久化", MessageType.Info);
|
||
|
||
_target = (GameObject)EditorGUILayout.ObjectField("Target", _target, typeof(GameObject), true);
|
||
if (_target == null) return;
|
||
|
||
_view = _target.transform.Find("View");
|
||
if (_view == null)
|
||
{
|
||
EditorGUILayout.HelpBox("Target 没有名为 'View' 的子物体", MessageType.Warning);
|
||
return;
|
||
}
|
||
|
||
EditorGUILayout.Space();
|
||
EditorGUILayout.LabelField("Quick Face (相机看 +Z 方向)", EditorStyles.boldLabel);
|
||
using (new EditorGUILayout.HorizontalScope())
|
||
{
|
||
if (GUILayout.Button("Face Camera\n(Y=180)")) Apply(new Vector3(0, 180, 0));
|
||
if (GUILayout.Button("Face Right\n(Y=270)")) Apply(new Vector3(0, 270, 0));
|
||
if (GUILayout.Button("Face Left\n(Y=90)")) Apply(new Vector3(0, 90, 0));
|
||
if (GUILayout.Button("Face Away\n(Y=0)")) Apply(new Vector3(0, 0, 0));
|
||
}
|
||
using (new EditorGUILayout.HorizontalScope())
|
||
{
|
||
if (GUILayout.Button("Lay Down +\n(X=-90)")) Apply(new Vector3(-90, 0, 0));
|
||
if (GUILayout.Button("Lay Down -\n(X=90)")) Apply(new Vector3(90, 0, 0));
|
||
if (GUILayout.Button("Tilt Up 15°")) Apply(_euler + new Vector3(-15, 0, 0));
|
||
if (GUILayout.Button("Tilt Down 15°")) Apply(_euler + new Vector3(15, 0, 0));
|
||
}
|
||
using (new EditorGUILayout.HorizontalScope())
|
||
{
|
||
if (GUILayout.Button("Rotate Y -45")) Apply(_euler + new Vector3(0, -45, 0));
|
||
if (GUILayout.Button("Rotate Y -15")) Apply(_euler + new Vector3(0, -15, 0));
|
||
if (GUILayout.Button("Rotate Y +15")) Apply(_euler + new Vector3(0, 15, 0));
|
||
if (GUILayout.Button("Rotate Y +45")) Apply(_euler + new Vector3(0, 45, 0));
|
||
}
|
||
|
||
EditorGUILayout.Space();
|
||
EditorGUILayout.LabelField("Manual", EditorStyles.boldLabel);
|
||
EditorGUI.BeginChangeCheck();
|
||
_euler = EditorGUILayout.Vector3Field("Local Euler", _euler);
|
||
_uniformScale = EditorGUILayout.Slider("Uniform Scale", _uniformScale, 0.05f, 5f);
|
||
_localPos = EditorGUILayout.Vector3Field("Local Position", _localPos);
|
||
if (EditorGUI.EndChangeCheck()) ApplyToScene();
|
||
|
||
EditorGUILayout.Space();
|
||
EditorGUILayout.LabelField("Pivot Helper (基于 MeshRenderer.bounds 自动校正)", EditorStyles.boldLabel);
|
||
if (GUILayout.Button("Auto Center Pivot (基于 bounds 把模型对齐到原点)"))
|
||
{
|
||
AutoCenterPivot();
|
||
}
|
||
|
||
EditorGUILayout.Space();
|
||
using (new EditorGUI.DisabledScope(_view == null))
|
||
{
|
||
if (GUILayout.Button("Apply to Prefab (持久化)", GUILayout.Height(36)))
|
||
{
|
||
ApplyToPrefab();
|
||
}
|
||
}
|
||
}
|
||
|
||
private void Apply(Vector3 newEuler)
|
||
{
|
||
_euler = NormalizeEuler(newEuler);
|
||
ApplyToScene();
|
||
}
|
||
|
||
private static Vector3 NormalizeEuler(Vector3 e)
|
||
{
|
||
return new Vector3(
|
||
Mathf.Repeat(e.x + 180f, 360f) - 180f,
|
||
Mathf.Repeat(e.y + 180f, 360f) - 180f,
|
||
Mathf.Repeat(e.z + 180f, 360f) - 180f
|
||
);
|
||
}
|
||
|
||
private void ApplyToScene()
|
||
{
|
||
if (_view == null) return;
|
||
Undo.RecordObject(_view, "Calibrate View");
|
||
_view.localEulerAngles = _euler;
|
||
_view.localScale = Vector3.one * _uniformScale;
|
||
_view.localPosition = _localPos;
|
||
EditorUtility.SetDirty(_view);
|
||
SceneView.RepaintAll();
|
||
}
|
||
|
||
private void AutoCenterPivot()
|
||
{
|
||
if (_view == null) return;
|
||
var mr = _view.GetComponentInChildren<MeshRenderer>();
|
||
if (mr == null) { Debug.LogWarning("No MeshRenderer under View"); return; }
|
||
// bounds.center in world → 转 view local → 反向偏移
|
||
var centerWorld = mr.bounds.center;
|
||
var centerLocalInView = _view.InverseTransformPoint(centerWorld);
|
||
// 想让模型中心对齐到 View 的原点:localPos -= local-space offset (scaled)
|
||
// 简化:直接把 mesh.localBounds.center 当偏移 (避免世界缩放影响)
|
||
var mf = _view.GetComponentInChildren<MeshFilter>();
|
||
if (mf != null && mf.sharedMesh != null)
|
||
{
|
||
var meshCenter = mf.sharedMesh.bounds.center; // model-space
|
||
_localPos = -Vector3.Scale(meshCenter, Vector3.one * _uniformScale);
|
||
ApplyToScene();
|
||
Debug.Log($"[Calibrator] Auto-center: model bounds.center={meshCenter}, set View.localPos={_localPos}");
|
||
}
|
||
}
|
||
|
||
private void ApplyToPrefab()
|
||
{
|
||
if (_target == null) return;
|
||
ApplyToScene();
|
||
|
||
// Try outermost prefab instance and apply overrides
|
||
var inst = PrefabUtility.GetNearestPrefabInstanceRoot(_target);
|
||
if (inst == null)
|
||
{
|
||
EditorUtility.DisplayDialog("Calibrator", "Target 不是 prefab 实例", "OK");
|
||
return;
|
||
}
|
||
|
||
// 1) Apply property modifications (rotation/scale/position) of the View transform back to its corresponding prefab asset
|
||
var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(inst);
|
||
if (string.IsNullOrEmpty(path))
|
||
{
|
||
EditorUtility.DisplayDialog("Calibrator", "找不到 prefab 资产路径", "OK");
|
||
return;
|
||
}
|
||
|
||
// Apply overrides on the entire instance
|
||
try
|
||
{
|
||
PrefabUtility.ApplyPrefabInstance(inst, InteractionMode.AutomatedAction);
|
||
AssetDatabase.SaveAssets();
|
||
Debug.Log($"[Calibrator] Applied overrides to prefab: {path}");
|
||
EditorUtility.DisplayDialog("Calibrator", $"已 Apply 到 prefab:\n{path}", "OK");
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
Debug.LogException(e);
|
||
EditorUtility.DisplayDialog("Calibrator", $"Apply 失败: {e.Message}", "OK");
|
||
}
|
||
}
|
||
}
|
||
}
|