编辑器工具的编写

编辑器工具大致可分为脚本Inspector拓展和独立窗口两大部分,这两大部分又涉及SceneView绘制和Preview窗口绘制等。

Inspector面板拓展

参考

Unity游戏开发拓展编辑器1拓展Inspector面板

Unity游戏开发拓展编辑器2

脚本序列化 编辑器拓展

新建一个MonoBehaviour脚本,命名为Enemy_Type1

1
2
3
4
5
6
7
8
using UnityEngine;

public class Enemy_Type1 : MonoBehaviour
{
public int hp = 100;
public float speed = 300;
public GameObject go;
}

我们在Project窗口中按“Assets——Scripts——AG——Chapter2——EditorExtensions——Editor”结构创建一系列文件夹

在Editor文件夹内新建Enemy_Type1Inspector文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(Enemy_Type1))]
public class Enemy_Type1Inspector : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
var concertTarget = base.target as Enemy_Type1;
if (GUILayout.Button("preset1"))
{
concertTarget.hp = 100;
concertTarget.speed = 600;
}
if (GUILayout.Button("preset2"))
{
concertTarget.hp = 200;
concertTarget.speed = 550;
}
}
}

Enemy_Type1脚本挂载时,就会出现应用预设的按钮
应用预设

在实际开发中,更推荐采用直接获取serializedObject的写法。下面就来演示一下如何获取序列化字段的绘制

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
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(Enemy_Type1))]
public class Enemy_Type1Inspector : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
var hpProp = serializedObject.FindProperty("hp");
var speedProp = serializedObject.FindProperty("speed");
var goProp = serializedObject.FindProperty("go");

using (var change = new EditorGUI.ChangeCheckScope())
{
EditorGUILayout.PropertyField(hpProp,new GUIContent("敌人生命"));
EditorGUILayout.PropertyField(speedProp,new GUIContent("敌人速度"));
EditorGUILayout.PropertyField(goProp,new GUIContent("敌人挂载"));
if (GUILayout.Button("preset1"))
{
hpProp.intValue = 100;
speedProp.floatValue = 600;
}
if (GUILayout.Button("preset2"))
{
hpProp.intValue = 200;
speedProp.floatValue = 550;
}
if (change.changed)
{
serializedObject.ApplyModifiedProperties();
}
}
}
}

我们在脚本序列化 中演示过脚本Inspector拓展方法,当我们使用serializedObject拓展时,注意在OnInspector()内的代码规范,我们比较一下它和直接从“target”中获取对象区别

  1. 使用target和serializedObject都能获得脚本的序列化信息,前者属于直接获取,后者属于在编辑器序列化反序列化流程中获取。前者不能获取private字段,后者能够获取[SerializeField] private字段
  2. 我们在脚本序列化中提到过EditorGUILayout.PropertyField是使用Unity原生方式绘制序列化对象的方法,它返回的是bool。相对地,我们使用EditorGUILayout.IntFieldEditorGUILayout.TextField等方法就属于自定义绘制,它们返回应该返回的值
  3. 我们在以前的文章中写过property.intValue = EditorGUILayout.IntField("主键", property.intValue)这样的写法,它等价于EditorGUILayout.PropertyField(property,new GUIContent("主键"))
  4. 我们在以前的文章脚本序列化中写过EditorGUI.BeginChangeCheck()EditorGUI.EndChangeCheck()的来监听面板修改的办法,这里使用的是using (var change = new EditorGUI.ChangeCheckScope()){}的方法,更加直观,相对应的,Unity也提供了HorizontalScopeScrollViewScope等方法,可以配合Unity游戏开发拓展编辑器2 中的Inspector面板拓展部分来扩宽思路

下面将展示PreviewGUI的使用。PreviewGUI可以提供监视面板下的内容预览、调试等

1
2
3
4
5
6
7
8
9
10
11
12
13
public override bool HasPreviewGUI()
{
return true;
}
public override GUIContent GetPreviewTitle()
{
return new GUIContent("Enemy_Type1 Debug");
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
base.OnPreviewGUI(r, background);
GUILayout.Box("EnemyState:...");
}

我们也在Unity游戏开发拓展编辑器2里面介绍过创建预览窗口的信息,不过在那里我们使用的是[CustomPreview]特性并继承ObjectView基类,在这里我们override的是Editor基类。

最终效果

注意上图中的“GUILayout.Box”是从下向上排列的。

使用EidtorWindow自定义窗口

在Unity开发中我们还可以使用自定义窗口进行编辑器拓展。

在Editor文件夹内新建BattleDebugerEditorWindow文件

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
using UnityEngine;
using UnityEditor;

public class BattleDebugEditorWindow : EditorWindow
{
[MenuItem("Tools/Battle Debugger")]
static void Setup()
{
GetWindow<BattleDebugEditorWindow>();
}
private void OnGUI()
{
if (GUILayout.Button("Generate Enemy_Type1"))
{

}
if (GUILayout.Button("Generate Enemy_Type1 x10"))
{

}
if (GUILayout.Button("Killed All Enemy"))
{

}
}
private void Awake()
{
SceneView.duringSceneGui += DuringSceneGUIBind;//监听duringSceneGui事件,每次窗口打开时,Scene窗口就会绘制
}
private void OnDestroy()
{
SceneView.duringSceneGui -= DuringSceneGUIBind;
}
private void DuringSceneGUIBind(SceneView sceneView)
{
var enemies = GetEnemies();
for (int i = 0; i < enemies.Length; i++)
{
var enemy = enemies[i];
Handles.DrawWireCube(enemy.Position, new Vector3(0.5f, 1f, 0.5f));//第一个代表位置,第二个代表大小
}
}
private struct EnemyInfo { public Vector3 Position { get;set; } }

private EnemyInfo[] GetEnemies()
{
return new EnemyInfo[]
{
new EnemyInfo(){ Position = new Vector3(0f,0f,0f)},
new EnemyInfo(){ Position = new Vector3(1.5f,0f,0f)},
};
}
}

我们在Unity的工具栏里面选择“Tools——Battle Debugger”,就可以弹出我们自定义的窗口了

自定义窗口

我们在Unity游戏开发拓展编辑器1里面就已经介绍了Scene窗口拓展的方法,我们在这里总结一下

  1. 直接在窗口启动函数里面调用GetWindow(EditorWindow editorWindow)就可以打开窗口,不需要调用EditorWindow.Show方法
  2. 我们在MonoBehaviour脚本里面的OnDrawGizmo方法中提供的监听代码是在Game窗口下显示的,Gizmo绘制是在游戏中显示。我们在编辑器拓展中使用的是Handles类绘制的,它是在Scene窗口中显示。

关联游戏配置数据

在游戏开发中,策划与程序需要有良好的配置环境来处理数据,可以直接使用ScriptableObject来处理数据,或者通过Excel转JSON的形式将数据表直接从Excel里抓取过来,也可以使用Sqlite进行数据存储。不过Sqlite在跨平台上存在一些兼容性问题。这里只介绍前两种进行数据配置的方案。

ScriptableObject数据配置

1
[Create]