实现基本功能
显示所有的 Prefab
Prefab Center 首先必须可以显示所有的 Prefab ,这里默认都存放在路径 Assets/Prefabs
下。Unity 内提供了一套查找资源的方式,主要通过 AssetDatabase
实现。
在此之前,为了便于后期对 Prefab 的分类,在所有要显示的 Prefab 上添加一个组件 PrefabCenterItem
,由脚本 Assets/Scripts/PrefabCenter/PrefabCenterItem.cs
定义:
using UnityEngine;
namespace EditorWindowExtension.PrefabCenter {
public enum PrefabCategory {
None = 0,
Players,
Enemies,
Collectables,
Blocks
};
public class PrefabCenterItem : MonoBehaviour {
[SerializeField]
PrefabCategory _prefabType = PrefabCategory.None;
public PrefabCategory Type {
get { return _prefabType; }
}
}
}
这样就可以通过 GetComponent<PrefabCenterItem>
确认是否要显示在 Prefab Center 中。具体通过下边的函数实现:
private List<GameObject> GetPrefabsFromAssetDatabase() {
string [] guids = AssetDatabase.FindAssets ("t:Prefab", new string [] { "Assets/Prefabs" });
List<GameObject> prefabs = new List<GameObject> ();
foreach (string guid in guids) {
var assetPath = AssetDatabase.GUIDToAssetPath (guid);
var asset = AssetDatabase.LoadAssetAtPath (assetPath, typeof (GameObject)) as GameObject;
if (asset.GetComponent<PrefabCenterItem> ()) {
prefabs.Add (asset);
}
}
return prefabs;
}
上述代码首先通过 AssetDatabase.FindAssets
获取所有 Prefab 的 GUID,然后根据 GUID 获取到资源的路径,再通过路径找到资源本身,最后判断资源下是否存在 PrefabCenterItem
组件,从而筛选出所有要显示的 Prefab 并以列表的形式返回。
获取到所有的 Prefab 后就可以具体实现绘制函数 OnGUI
:
private void OnGUI () {
DrawToolBar ();
var buttonWidth = (position.width - 25) / 4;
var buttonHeight = buttonWidth + 15;
GUIStyle guiStyle = new GUIStyle (GUI.skin.button);
guiStyle.fontStyle = FontStyle.Bold;
guiStyle.alignment = TextAnchor.MiddleCenter;
guiStyle.imagePosition = ImagePosition.ImageAbove;
int i = 0;
foreach (var item in _prefabItems) {
var buttonLeft = 5 + (i % 4) * (buttonWidth + 5);
var buttonTop = 27 + (i / 4) * (buttonHeight + 5);
Rect buttonPos = new Rect (buttonLeft, buttonTop, buttonWidth, buttonHeight);
DrawPrefabItem (item, buttonPos, guiStyle);
i++;
}
}
绘制单个 Prefab 的函数 DrawPrefabItem
定义如下:
private void DrawPrefabItem (GameObject item, Rect pos, GUIStyle style) {
Texture2D texture = AssetPreview.GetAssetPreview (item);
GUI.Button (
pos,
new GUIContent (item.name, texture),
style
);
}
根据种类筛选
用户想要显示的 Prefab 种类由之前绘制的工具栏返回,对应变量 _toolBarIndex
;Prefab 实际的种类包含在组件 PrefabCenterItem
中。如果二者相等则予以绘制,否则不绘制。此外,当用户选择的是 All
(即 _toolBarIndex
为 0)时,一律都予以绘制:
private void OnGUI () {
// ...
foreach (var item in _prefabItems) {
if (_toolBarIndex == 0 || (int)item.GetComponent<PrefabCenterItem> ().Type == _toolBarIndex) {
// ...
DrawPrefabItem (item, buttonPos, guiStyle);
// ...
}
}
}
添加基本操作
目前所有的 Prefab 都以按钮的形式显示在编辑器窗口中。通过判断按钮是否被按下,即可添加对应的响应事件。
在 DrawPrefabItem
中添加 if 语句:
if (GUI.Button (
pos,
new GUIContent (item.name, texture),
style
)) {
_showDetail = true;
_shownItem = item;
_detailPosition = pos;
}
即当某个 Prefab 对应的按钮被按下时,表示接下来要进行 _showDetail
的操作,并记录要显示详情的 _shownItem
和要显示的位置 _detailPosition
。
然后在绘制函数中添加显示详细操作的逻辑:
private void OnGUI () {
// ...
foreach (var item in _prefabItems) {
if (_toolBarIndex == 0 || (int)item.GetComponent<PrefabCenterItem> ().Type == _toolBarIndex) {
var buttonLeft = 5 + (i % 4) * (buttonWidth + 5);
var buttonTop = 27 + (i / 4) * (buttonHeight + 5);
Rect buttonPos = new Rect (buttonLeft, buttonTop, buttonWidth, buttonHeight);
if (buttonPos != _detailPosition || !_showDetail)
DrawPrefabItem (item, buttonPos, guiStyle);
i++;
}
}
DrawDetail ();
}
private void DrawDetail () {
if (_showDetail) {
BeginWindows ();
GUI.Window (1, _detailPosition, OnDetailGUI, _shownItem.name);
EndWindows ();
}
}
其中由 OnDetailGUI
定义详情窗口的绘制方法,包含一个添加该 Prefab 到场景中的按钮和关闭按钮:
private void OnDetailGUI (int id) {
if (GUI.Button (new Rect (5, 20, _detailPosition.width - 10, 17), "Add to scene")) {
Instantiate (_shownItem).name = _shownItem.name;
}
if (GUI.Button (new Rect (_detailPosition.width - 16, _detailPosition.height - 16, 12, 12), GUIContent.none, "WinBtnCloseMac")) {
_showDetail = false;
}
GUI.FocusWindow (1);
}
完善细节
上述步骤完成后已经可以达到基本的效果,打开 Prefab Center 后可以看到所有的 Prefab 并且可以进行分类和简单操作。但是可以发现打开一个 Prefab 的详细菜单后,更换 Prefab 种类或者改变窗口的大小后,菜单会以原来的尺寸留在原地,视觉效果不好。为解决这个问题,在代码中添加以下简单逻辑:
- 当编辑器窗口的宽度发生变化时,发送 Resize 事件
private void Update () { if (_prevPosition.width != position.width) { SendEvent (EditorGUIUtility.CommandEvent ("Resize")); } _prevPosition = new Rect (position); }
- 当更改 Prefab 的种类时,发送 SwitchType 事件:
private void DrawToolBar () { // ... if (_toolBarIndex != _prevIndex) { SendEvent (EditorGUIUtility.CommandEvent("SwitchType")); } }
- 接收到 Resize 或者 SwitchType 事件时,不绘制详情窗口:
private void OnGUI () { // ... HandleEvent (Event.current); DrawDetail (); }
private void HandleEvent (Event e) { switch (e.commandName) { case "SwitchType": case "Resize": _showDetail = false; break; } Repaint (); }