Unity编辑器拓展(三|窗口绘制)
前面文章里的拓展都局限在Inspector窗口中,依附于挂载MonoBehaviour脚本的游戏物体存在。下面我们将学习如何创建一个和Inspector同级的窗口,作为独立的编辑器使用。
我们假设这样一个需求:敌人都挂载一个叫“Enemy”的脚本,并在这个脚本上有一个“EnemyData”的ScriptableObjct的引用。我们需要设计一个EnemyEditor,拥有独立的窗口来编辑EnemyData,并可以创建EnemyData或在场景创建拥有EnemyData的敌人物体。
EnemyData:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17using UnityEngine;
[ ]
public class EnemyData : ScriptableObject
{
public int id;
public string enemyName; //名称
public Sprite portrait; //头像
public float maxHealth; //血量
public bool canAttack; //是否可攻击
public float attack; //攻击力
public float attackCoolDown; //攻击冷却时间
public string description; //简介
public string dropItems; //掉落物名称
public float dropRate; //掉落概率
}
Enemy:1
2
3
4
5
6
7
8
9
10
11using UnityEngine;
public class Enemy : MonoBehaviour
{
[private EnemyData data; ]
public void SetData(EnemyData data)
{
this.data = data;
}
}
接下来我们编写具体的窗口。Unity中的自定义窗口需要继承EditorWindow类,可以且最好放进Editor文件夹。窗口类(采用IMGUI方案)有以下两个核心方法:
- OpenWindow:自定义的公共静态方法,名称随意。用于唤出窗口
- OnGUI:和Start、Update等相同的生命周期函数,用于绘制自定义的GUI元素和交互逻辑
大致结构是这样的:上面的代码已经可以允许我们通过Unity编辑器上方菜单栏打开名为“敌人编辑器”的空窗口。而还未编辑的OnGUI是我们的老朋友,想必大家已经很清楚该如何自由发挥了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16using UnityEngine;
using UnityEditor;
public class EnemyEditor : EditorWindow
{
[//将唤出按钮添加到顶部菜单栏 ]
public static void OpenWindow()
{
EnemyEditor wd = GetWindow<EnemyEditor>(); //唤出窗口
wd.titleContent = new GUIContent("敌人编辑器"); //添加标题
}
private void OnGUI()
{
//绘制元素和交互逻辑
}
}
直接上代码和效果演示效果展示1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94using UnityEngine;
using UnityEditor;
using System.IO;
public class EnemyEditor : EditorWindow
{
private EnemyData editingData; //正在编辑的数据
public const string DATA_PATH = "Assets/Data/EnemyData"; //储存Asset的文件夹地址
[//将唤出按钮添加到顶部菜单栏 ]
public static void OpenWindow()
{
EnemyEditor wd = GetWindow<EnemyEditor>(); //唤出窗口
wd.titleContent = new GUIContent("敌人编辑器"); //添加标题
}
private void OnEnable()
{
editingData = new EnemyData();
}
private void OnGUI()
{
GetInput();
//两个按钮
if (GUILayout.Button("创建EnemyData"))
{
CreateEnemyData();
editingData = new EnemyData();
}
if (GUILayout.Button("创建敌人到场景"))
{
CreateEnemy();
editingData = new EnemyData();
}
}
/// <summary>
/// 将当前编辑中的EnemyData作为Asset储存到指定路径
/// 创建的资源根据id命名,同id资源将会被覆盖
/// </summary>
/// <returns>创建的资源</returns>
private EnemyData CreateEnemyData()
{
if (!Directory.Exists(DATA_PATH)) Directory.CreateDirectory(DATA_PATH);
string path = Path.Combine(DATA_PATH, "EnemyData" + string.Format("{0:D3}", editingData.id) + ".asset");
EnemyData has = AssetDatabase.LoadAssetAtPath(path, typeof(EnemyData)) as EnemyData;
if (has == editingData) return editingData;
if (has != null) AssetDatabase.DeleteAsset(path);
AssetDatabase.CreateAsset(editingData, path);
return editingData;
}
/// <summary>
/// 将当前编辑中的EnemyData作为Asset储存到指定路径,
/// 并在场景中添加拥有该数据的敌人对象
/// </summary>
/// <returns>创建的敌人对象</returns>
private GameObject CreateEnemy()
{
GameObject g = new GameObject(editingData.enemyName);
Enemy e = (Enemy)g.AddComponent(typeof(Enemy));
e.SetData(CreateEnemyData());
return null;
}
/// <summary>
/// 创建各参数的输入框,接受输入
/// </summary>
private void GetInput()
{
SerializedObject so = new SerializedObject(editingData);
so.UpdateIfRequiredOrScript();
editingData.id = EditorGUILayout.IntField(new GUIContent("ID"), editingData.id);
editingData.enemyName = EditorGUILayout.TextField(new GUIContent("名称"), editingData.enemyName);
editingData.portrait = EditorGUILayout.ObjectField(new GUIContent("头像"),
editingData.portrait, typeof(Sprite), false) as Sprite;
editingData.maxHealth = EditorGUILayout.FloatField(new GUIContent("生命值"), editingData.maxHealth);
editingData.canAttack = EditorGUILayout.Toggle(new GUIContent("是否可攻击"), editingData.canAttack);
if (editingData.canAttack)
{
editingData.attack = EditorGUILayout.FloatField(new GUIContent("攻击力"), editingData.attack);
editingData.attackCoolDown = EditorGUILayout.FloatField(new GUIContent("攻击冷却时间"),
editingData.attackCoolDown);
}
editingData.dropItem = EditorGUILayout.TextField(new GUIContent("掉落物"), editingData.dropItem);
editingData.dropRate = EditorGUILayout.Slider(new GUIContent("掉落率"), editingData.dropRate, 0, 1);
EditorGUILayout.LabelField(new GUIContent("简介"));
editingData.description = EditorGUILayout.TextArea(editingData.description,
GUILayout.Height(EditorGUIUtility.singleLineHeight * 4));
so.ApplyModifiedProperties();
}
}