UnityPreviewEditor
UnityPreviewEditor:使用PreviewRenderUtility创建预览窗口。
简介
- Example -> PreviewRenderWindow 打开
- 效果图:
代码
// PreviewRenderWindow.cs 负责自定义管理界面
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class PreviewRenderWindow : EditorWindow
{
[MenuItem("Example/PreviewRenderWindow")]
static void ShowWindow()
{
GetWindow<PreviewRenderWindow>("PreviewRenderWindow").Show();
}
GameObject _gameObject;
GameObject _lastGameObject;
PreviewRenderEditor _editor;
bool _load = true;
Vector2 _lightRot;
Vector2 _lastLightRot;
void OnGUI()
{
_gameObject = (GameObject) EditorGUILayout.ObjectField("预览预制体", _gameObject, typeof(GameObject), true);
_lightRot = EditorGUILayout.Vector2Field("光源方向", _lightRot);
if (_editor == null)
{
_editor = Editor.CreateEditor(this, typeof(PreviewRenderEditor)) as PreviewRenderEditor;
}
if(_editor)
{
if (_lastLightRot != _lightRot)
{
_lastLightRot = _lightRot;
_editor.RefreshLightRot(_lightRot);
}
_editor.DrawPreview(GUILayoutUtility.GetRect(400, 400));
}
if (_gameObject && _load)
{
_editor.RefreshPreviewInstance(_gameObject);
_load = false;
_lastGameObject = _gameObject;
}
if (_lastGameObject != _gameObject)
{
_load = true;
}
}
}
// PreviewRenderEditor.cs 负责预览界面
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class PreviewRenderEditor : Editor
{
private PreviewRenderUtility _previewRenderUtility;
private GameObject _previewInstance;
private GameObject _targetObj;
private static bool _loaded = true;
private Vector2 _drag = new Vector2(250f, -30f);
private Vector2 _lightRot = new Vector2(180f, 0);
public void RefreshLightRot(Vector2 rot)
{
_lightRot = rot;
}
public void RefreshPreviewInstance(GameObject obj)
{
_targetObj = obj;
if (_previewInstance)
UnityEngine.Object.DestroyImmediate(_previewInstance);
_previewInstance = null;
_loaded = true;
}
private void OnEnable()
{
if (_previewRenderUtility == null)
{
_previewRenderUtility = new PreviewRenderUtility();
}
}
private void OnDisable()
{
if (_previewRenderUtility != null)
{
// 必须进行清理,否则会存在残留对象
_previewInstance = null;
_previewRenderUtility.Cleanup();
_previewRenderUtility = null;
}
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
// _loaded 确保只加载一次物体
if (_loaded && _targetObj)
{
_previewInstance = Instantiate(_targetObj as GameObject, Vector3.zero, Quaternion.identity);
// AddSingleGO 添加物体
_previewRenderUtility.AddSingleGO(_previewInstance);
_loaded = false;
}
// 获取拖拽向量
_drag = Drag2D(_drag, r);
// 事件为绘制时,才进行绘制
if (Event.current.type == EventType.Repaint)
{
_previewRenderUtility.BeginPreview(r, background);
//调整相机位置与角度
Camera camera = _previewRenderUtility.camera;
var cameraTran = camera.transform;
cameraTran.position = Vector2.zero;
cameraTran.rotation = Quaternion.Euler(new Vector3(-_drag.y, -_drag.x, 0));
cameraTran.position = cameraTran.forward * -6f;
var pos = cameraTran.position;
cameraTran.position = new Vector3(pos.x, pos.y + 0.6f, pos.z);
EditorUtility.SetCameraAnimateMaterials(camera, true);
camera.cameraType = CameraType.Preview;
camera.enabled = false;
camera.clearFlags = CameraClearFlags.Skybox;
camera.fieldOfView = 30;
camera.farClipPlane = 10.0f;
camera.nearClipPlane = 2.0f;
camera.backgroundColor = new Color(49.0f / 255.0f, 77.0f / 255.0f, 121.0f / 255.0f, 0f);
// // 设置光源数据
_previewRenderUtility.lights[0].intensity = 0.7f;
_previewRenderUtility.lights[0].transform.rotation = Quaternion.Euler(_lightRot.x, _lightRot.y, 0f);
_previewRenderUtility.lights[1].intensity = 0.7f;
_previewRenderUtility.lights[1].transform.rotation = Quaternion.Euler(_lightRot.x, _lightRot.y, 0f);
_previewRenderUtility.ambientColor = new Color(0.3f, 0.3f, 0.3f, 0f);
// camera.transform.LookAt(_previewInstance.transform);
// 相机渲染
camera.Render();
// 结束并绘制
_previewRenderUtility.EndAndDrawPreview(r);
}
}
// Drag2D 来自源码
private static int sliderHash = "Slider".GetHashCode();
public static Vector2 Drag2D(Vector2 scrollPosition, Rect position)
{
// 每次获得独一无二的 controlID
int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive);
Event current = Event.current;
// 获取对应 controlID 的事件
switch (current.GetTypeForControl(controlID))
{
case EventType.MouseDown:
{
bool flag = position.Contains(current.mousePosition) && position.width > 50f;
if (flag)
{
// 鼠标摁住拖出预览窗口外,预览物体任然能够旋转
GUIUtility.hotControl = controlID;
// 采用事件
current.Use();
// 让鼠标可以拖动到屏幕外后,从另一边出来
EditorGUIUtility.SetWantsMouseJumping(1);
}
break;
}
case EventType.MouseUp:
{
bool flag2 = GUIUtility.hotControl == controlID;
if (flag2)
{
GUIUtility.hotControl = 0;
}
EditorGUIUtility.SetWantsMouseJumping(0);
break;
}
case EventType.MouseDrag:
{
bool flag3 = GUIUtility.hotControl == controlID;
if (flag3)
{
// shift 加速
scrollPosition -= current.delta * (float) (current.shift ? 3 : 1) /
Mathf.Min(position.width, position.height) * 140f;
// 以下两条缺少任意一个,会导致延迟更新,拖动过程中无法实时更新
// 直到 repaint事件触发才重新绘制
current.Use();
GUI.changed = true;
}
break;
}
}
return scrollPosition;
}
}
Unity 预览界面
预览界面
在Unity编辑器界面上可以看到除了Game视图、Scene视图,其他的视图也会出现绘制三维物体的地方,比如检视器的预览窗口,当选中网格时,会对网格进行预览,如下所示:
绘制的方法都是使用 UnityEditor 未公开文档的PreviewRenderUtility类进行的。
检视器预览界面
资产或脚本实现预览窗口可参考Editor类的文档说明,重载带有Preview关键字的接口。
开启预览功能
默认脚本对象的检视器窗口是没有预览窗口的,如下所示:
想要开启预览窗口,那么得创建自己的检视器窗口类,然后重载HasPreviewGUI接口,完整代码如下:
using UnityEngine;
public class PreviewExample : MonoBehaviour
{
}
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(PreviewExample))]
public class PreviewExampleInspector : Editor
{
public override bool HasPreviewGUI()
{
return true;
}
}
可以看到有黑色的预览窗口了,如下所示:
标题栏绘制
默认显示的是物体的名称,重载 GetPreviewTitle 接口可以更改标题名称:
public override GUIContent GetPreviewTitle()
{
return new GUIContent("预览");
}
标题栏右边可以绘制其他的信息或者按钮等,重载 OnPreviewSettings 接口方便对预览窗口进行控制:
public override void OnPreviewSettings()
{
GUILayout.Label("文本", "preLabel");
GUILayout.Button("按钮", "preButton");
}
预览内容的绘制
最后预览内容的绘制,只需要重载 OnPreviewGUI 接口即可:
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
GUI.Box(r, "Preview");
}
最后显示如下所示:
摄像机渲染
不仅仅在预览窗口进行绘制控件,还可以绘制三维物体,实质是绘制独立的摄像机所照射的信息,例如动画片段预览窗口:
鼠标可以拖动旋转等,还可以看其他方向,就像操作摄像机一样。
这都是通过 PreviewRenderUtility 来实现的,对于这个类没有官方文档,可以通过网上其他人的分享,还有 UnityEditor 内部的使用来学习。
基础绘制
PreviewRenderUtility 的构造和销毁,还有要预览物体的构造和销毁,以及调用绘制,以 BeginPreview 和 EndAndDrawPreview 包围,在其中进行摄像机的渲染 Camera.Render 调用,代码如下:
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(PreviewExample))]
public class PreviewExampleInspector : Editor
{
private GameObject _lastGameObj;
private bool _canRefreshPreviewGo = false;
public override void OnInspectorGUI()
{
// target 当前操作的对象
PreviewExample pe = (PreviewExample) target;
pe.previewGo = EditorGUILayout.ObjectField("预览目标", pe.previewGo, typeof(GameObject)) as GameObject;
if (pe.previewGo != _lastGameObj)
{
_lastGameObj = pe.previewGo;
_canRefreshPreviewGo = true;
}
serializedObject.ApplyModifiedProperties();
}
public override bool HasPreviewGUI()
{
return true;
}
public override GUIContent GetPreviewTitle()
{
return new GUIContent("预览");
}
public override void OnPreviewSettings()
{
GUILayout.Label("文本", "preLabel");
GUILayout.Button("按钮", "preButton");
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
InitPreview();
if (Event.current.type != EventType.Repaint)
{
return;
}
_previewRenderUtility.BeginPreview(r, background);
Camera camera = _previewRenderUtility.camera;
if (_previewInstance)
{
camera.transform.position = _previewInstance.transform.position + new Vector3(0, 5f, 3f);
camera.transform.LookAt(_previewInstance.transform);
}
camera.Render();
_previewRenderUtility.EndAndDrawPreview(r);
}
private PreviewRenderUtility _previewRenderUtility;
private GameObject _previewInstance;
private void InitPreview()
{
if (_previewRenderUtility == null)
{
// 参数true代表绘制场景内的游戏对象
_previewRenderUtility = new PreviewRenderUtility(true);
// 设置摄像机的一些参数
_previewRenderUtility.cameraFieldOfView = 30f;
}
if (_canRefreshPreviewGo)
{
_canRefreshPreviewGo = false;
// 创建预览的游戏对象
CreatePreviewInstances();
}
}
private void DestroyPreview()
{
if (_previewRenderUtility != null)
{
// 务必要进行清理,才不会残留生成的摄像机对象等
_previewRenderUtility.Cleanup();
_previewRenderUtility = null;
}
}
private void CreatePreviewInstances()
{
DestroyPreviewInstances();
// 绘制预览的游戏对象
if (_lastGameObj)
{
_previewInstance = Instantiate(_lastGameObj);
_previewRenderUtility.AddSingleGO(_previewInstance);
}
}
private void DestroyPreviewInstances()
{
if (_previewInstance)
{
DestroyImmediate(_previewInstance);
}
_previewInstance = null;
}
void OnDestroy()
{
DestroyPreviewInstances();
DestroyPreview();
}
}
最后效果如下所示:
拖动旋转
在预览窗口鼠标拖动可以旋转进行预览,就像Cube物体预览一样。要想让摄像机旋转,得知道游戏对象的中心,才能绕着它进行旋转。
private static int sliderHash = "Slider".GetHashCode();
private static Vector2 Drag2D(Vector2 scrollPosition, Rect position)
{
// 每次获得独一无二的 controlID
int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive);
Event current = Event.current;
// 获取对应 controlID 的事件
switch (current.GetTypeForControl(controlID))
{
case EventType.MouseDown:
{
bool flag = position.Contains(current.mousePosition) && position.width > 50f;
if (flag)
{
// 鼠标摁住拖出预览窗口外,预览物体任然能够旋转
GUIUtility.hotControl = controlID;
// 采用事件
current.Use();
// 让鼠标可以拖动到屏幕外后,从另一边出来
EditorGUIUtility.SetWantsMouseJumping(1);
}
break;
}
case EventType.MouseUp:
{
bool flag2 = GUIUtility.hotControl == controlID;
if (flag2)
{
GUIUtility.hotControl = 0;
}
EditorGUIUtility.SetWantsMouseJumping(0);
break;
}
case EventType.MouseDrag:
{
bool flag3 = GUIUtility.hotControl == controlID;
if (flag3)
{
// shift 加速
scrollPosition -= current.delta * (float) (current.shift ? 3 : 1) /
Mathf.Min(position.width, position.height) * 140f;
// 以下两条缺少任意一个,会导致延迟更新,拖动过程中无法实时更新
// 直到 repaint事件触发才重新绘制
current.Use();
GUI.changed = true;
}
break;
}
}
return scrollPosition;
}
最后效果,如下图所示:
自定义视图的预览
在自定义视图上的预览,可以采用类似以上的方式进行绘制,也可以创建相应的检视器类,直接调用绘制预览接口。代码如下:
using UnityEngine;
using UnityEditor;
public class PreviewExampleWindow : EditorWindow
{
private Editor m_Editor;
[MenuItem("Example/PreviewExample")]
static void ShowWindow()
{
GetWindow<PreviewExampleWindow>("PreviewExample");
}
private void OnDestroy()
{
if (m_Editor != null)
{
DestroyImmediate(m_Editor);
}
m_Editor = null;
}
void OnGUI()
{
if (m_Editor == null)
{
// 第一个参数这里暂时没关系,因为编辑器没有取目标对象
m_Editor = Editor.CreateEditor(this, typeof(PreviewExampleInspector));
}
m_Editor.DrawPreview(GUILayoutUtility.GetRect(300, 200));
}
}
打开测试窗口,如下图所示: