前言
  到目前为止,我们已经实现了使用Generator
来生成怪物、不可交互物体和可拾取道具的功能。但我们发现,现有的Generator
只能做到在固定的时刻
、在固定的位置
随机生成某个Prefab,我们希望能拓展Generator
的功能。Generator
拓展后的功能需求如下:
Generator
拓展后的功能需求
- 能动态地决定是使用
固定的时间间隔
还是使用随机的时间间隔
来实例化预设对象,如果使用固定的时间间隔
,那么要设置时间间隔的长度
,如果使用随机的时间间隔
,那么要设置最短的时间间隔
和最长的时间间隔
- 能动态地决定是在
固定位置
上还是在随机位置
上实例化预设对象,如果选择在随机位置
上实例化预设对象,那么需要能分别设置X、Y坐标的随机范围
拓展Generator.cs
  在知道了Generator
拓展后的功能需求之后,我们先根据需求来改写Generator.cs
Generator.cs1 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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public enum Orientation { Left, Right, Random, None }
public class Generator : MonoBehaviour { [Tooltip("多久之后开始实例化预设对象")] public float GenerateDelay = 2f;
[Tooltip("是否使用随机时间间隔来实例化预设对象")] public bool RandomGenerateInterval = false; [Tooltip("实例化预设对象的最短时间间隔")] public float MinGenerateInterval; [Tooltip("实例化预设对象的最长时间间隔")] public float MaxGenerateInterval; [Tooltip("实例化预设对象的固定时间间隔")] public float GenerateInterval = 3f;
[Tooltip("是否在随机的X坐标上实例化预设对象")] public bool RandomGeneratePositionX = false; [Tooltip("实例化预设对象时的最小X坐标")] public float MinGeneratePositionX; [Tooltip("实例化预设对象时的最大X坐标")] public float MaxGeneratePositionX;
[Tooltip("是否在随机的Y坐标上实例化预设对象")] public bool RandomGeneratePositionY = false; [Tooltip("实例化预设对象时的最小Y坐标")] public float MinGeneratePositionY; [Tooltip("实例化预设对象时的最大Y坐标")] public float MaxGeneratePositionY;
[Tooltip("预设对象的朝向")] public Orientation PrefabOrientation = Orientation.Right; [Tooltip("预设对象")] public GameObject[] Prefabs;
private ParticleSystem m_Particle;
private void Awake() { m_Particle = GetComponent<ParticleSystem>();
if(Prefabs == null || Prefabs.Length == 0) { Debug.LogError("请至少为Prefabs添加一个预设对象"); } }
private void Start () { StartCoroutine(RandomGenerate()); }
private IEnumerator RandomGenerate() { yield return new WaitForSeconds(GenerateDelay);
while(true) { float interval = GenerateInterval; if(RandomGenerateInterval) { interval = Random.Range(MinGenerateInterval, MaxGenerateInterval); }
yield return new WaitForSeconds(interval); Generate(); } }
private void Generate() { int index = Random.Range(0, Prefabs.Length);
float x = transform.position.x; if(RandomGeneratePositionX) { x = Random.Range(MinGeneratePositionX, MaxGeneratePositionX); } float y = transform.position.y; if(RandomGeneratePositionY) { y = Random.Range(MinGeneratePositionY, MaxGeneratePositionY); }
transform.position = new Vector3(x, y, transform.position.z);
GameObject prefab = Instantiate(Prefabs[index], transform.position, Quaternion.identity);
if(m_Particle != null) { m_Particle.Play(); }
if(PrefabOrientation == Orientation.None) { return; }
if(PrefabOrientation == Orientation.Left) { Wander wander = prefab.GetComponent<Wander>(); if(wander.FacingRight) { wander.Flip(); } return; } if(PrefabOrientation == Orientation.Right) { Wander wander = prefab.GetComponent<Wander>(); if(!wander.FacingRight) { wander.Flip(); } return; }
if(PrefabOrientation == Orientation.Random) { Wander wander = prefab.GetComponent<Wander>(); if(Random.value <= 0.5) { wander.Flip(); } return; } } }
|
代码说明:
  这里,我们使用了RandomGenerateInterval
、RandomGeneratePositionX
和RandomGeneratePositionY
这三个bool变量来分别设置是否使用随机的时间间隔
来实例化预设对象、是否在随机的X坐标
上实例化预设对象和是否在随机的Y坐标
上实例化随机对象。
  改写完Generator.cs
,我们打开Generator
物体下任意一个带有Generator.cs
的子物体,可以看到在Inspector
窗口多出了很多变量。
使用自定义Inspector窗口隐藏无关变量
  我们希望在设置参数
的时候,一些无关的变量能被隐藏
不显示出来。因此,我们需要来自定义Generator.cs
在Inspector
窗口的显示规则。首先,我们在Assets\Scripts
下新建一个名为Editor
的文件夹,然后在Assets\Scripts\Editor
下新建一个名为GeneratorEditor
的C#脚本:
GeneratorEditor.cs1 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 94 95 96 97 98 99 100 101 102 103 104 105
| using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor;
[CustomEditor(typeof(Generator))] public class GeneratorEditor : Editor { private bool m_ShowPrefabs;
private Generator m_Generator; private List<GameObject> m_PrefabList = null;
public override void OnInspectorGUI() { m_Generator = (Generator)target;
m_Generator.GenerateDelay = EditorGUILayout.FloatField(new GUIContent("Generate Delay", "多久之后开始实例化预设对象"), m_Generator.GenerateDelay); m_Generator.RandomGenerateInterval = EditorGUILayout.Toggle(new GUIContent("Random Generate Interval", "多久之后开始实例化预设对象"), m_Generator.RandomGenerateInterval); if(m_Generator.RandomGenerateInterval) { m_Generator.MinGenerateInterval = EditorGUILayout.FloatField(new GUIContent("Min Generate Interval", "实例化预设对象的最短时间间隔"), m_Generator.MinGenerateInterval); m_Generator.MaxGenerateInterval = EditorGUILayout.FloatField(new GUIContent("Max Generate Interval", "实例化预设对象的最长时间间隔"), m_Generator.MaxGenerateInterval); if(m_Generator.MaxGenerateInterval < m_Generator.MinGenerateInterval) { m_Generator.MaxGenerateInterval = m_Generator.MinGenerateInterval; } } else { m_Generator.GenerateInterval = EditorGUILayout.FloatField(new GUIContent("Generate Interval", "实例化预设对象的固定时间间隔"), m_Generator.GenerateInterval); } m_Generator.RandomGeneratePositionX = EditorGUILayout.Toggle(new GUIContent("RandomG enerate PositionX", "是否在随机的X坐标上实例化预设对象"), m_Generator.RandomGeneratePositionX); if(m_Generator.RandomGeneratePositionX) { m_Generator.MinGeneratePositionX = EditorGUILayout.FloatField(new GUIContent("Min Generate PositionX", "实例化预设对象的最小X坐标"), m_Generator.MinGeneratePositionX); m_Generator.MaxGeneratePositionX = EditorGUILayout.FloatField(new GUIContent("Max Generate PositionX", "实例化预设对象的最大X坐标"), m_Generator.MaxGeneratePositionX);
if(m_Generator.MaxGeneratePositionX < m_Generator.MinGeneratePositionX) { m_Generator.MaxGeneratePositionX = m_Generator.MinGeneratePositionX; } }
m_Generator.RandomGeneratePositionY = EditorGUILayout.Toggle(new GUIContent("RandomG enerate PositionY", "是否在随机的Y坐标上实例化预设对象"), m_Generator.RandomGeneratePositionY); if(m_Generator.RandomGeneratePositionY) { m_Generator.MinGeneratePositionY = EditorGUILayout.FloatField(new GUIContent("Min Generate PositionY", "实例化预设对象的最小Y坐标"), m_Generator.MinGeneratePositionY); m_Generator.MaxGeneratePositionY = EditorGUILayout.FloatField(new GUIContent("Max Generate PositionY", "实例化预设对象的最大Y坐标"), m_Generator.MaxGeneratePositionY);
if(m_Generator.MaxGeneratePositionY < m_Generator.MinGeneratePositionY) { m_Generator.MaxGeneratePositionY = m_Generator.MinGeneratePositionY; } }
m_Generator.PrefabOrientation = (Orientation)EditorGUILayout.EnumPopup(new GUIContent("Prefab Orientation", "预设对象的朝向"), m_Generator.PrefabOrientation); if(m_PrefabList == null) { m_PrefabList = new List<GameObject>(m_Generator.Prefabs); } m_ShowPrefabs = EditorGUILayout.Foldout(m_ShowPrefabs, new GUIContent("Prefabs", "预设对象")); if(m_ShowPrefabs) { EditorGUI.indentLevel++;
for (int i = 0; i < m_Generator.Prefabs.Length; i++) { EditorGUILayout.BeginHorizontal(); m_Generator.Prefabs[i] = (GameObject)EditorGUILayout.ObjectField(new GUIContent("Prefab", "预设对象"), m_Generator.Prefabs[i], typeof(GameObject), false);
if (GUILayout.Button("Remove")) { m_PrefabList.RemoveAt(i); m_Generator.Prefabs = m_PrefabList.ToArray(); } EditorGUILayout.EndHorizontal(); } if (GUILayout.Button("Add")) { m_PrefabList.Add(null); m_Generator.Prefabs = m_PrefabList.ToArray(); }
EditorGUI.indentLevel--; }
if (GUI.changed) { EditorUtility.SetDirty(target); } } }
|
代码说明:
- 因为
GeneratorEditor.cs
是用于自定义Inspector
窗口脚本,所以必须将GeneratorEditor.cs
放在一个名为Editor
的文件夹下(Editor文件夹可以位于任意位置)
CustomEditor
是Unity提供的Attribute
,我们需要使用它来标识我们要自定义哪个脚本
的Inspector
窗口
- 由于绘制
Inspector窗口
的代码是在OnInspectorGUI
这一函数里执行的,我们需要让GeneratorEditor
这个类继承UnityEditor
命名空间下的Editor
类,并使用关键字override
覆写OnInspectorGUI
函数
target
是Editor
类提供的成员变量,我们可以通过它来获取当前脚本实例对象
- 由于我们覆写了
OnInspectorGUI
函数,所以Unity默认的脚本序列化代码
不会被执行,我们需要自己使用EditorGUILayout
提供的静态函数来序列化各个变量
(同理,脚本里面的ToolTip
也失效了,如果需要在Inspector
窗口提示变量的含义,我们也需要自己编写)
- 由于
EditorGUILayout
没有提供数组的序列化方法,所以我们需要逐一对数组的每个元素
进行序列化
- 最后,我们使用
EditorUtility
提供的静态方法SetDirty
在我们修改参数之后将target
标记为已修改,否则Prefab的实例对象上的参数会被Prefab上的值覆盖
  编写完GeneratorEditor.cs
之后,再次打开Generator
物体下任意一个带有Generator.cs
的子物体,可以看到此时Inspector
窗口少了很多变量。而且根据我们的选择,Inspector
窗口显示的变量也会动态改变。
调整各个Generator的参数
  有了拓展后的Generator.cs
,接下来,我们就要来调整各个Generator
的参数了。需要注意的是,因为Generator
的Position
,需要实例化的Prefab
以及实例化Prefab时的朝向
都没有改变,所以不再列出。
场景中各个Generator的设置
EnemyGenerator
:
Generator Delay
: 2
Random Generate Interval
: true
Min Generate Interval
: 2
Max Generate Interval
: 8
RandomG enerate PositionX
: true
Min Generate PositionX
: -17.5
Max Generate PositionX
: -6.5
RandomG enerate PositionY
: false
EnemyGenerator (1)
:
Generator Delay
: 2
Random Generate Interval
: true
Min Generate Interval
: 2
Max Generate Interval
: 8
RandomG enerate PositionX
: true
Min Generate PositionX
: -6
Max Generate PositionX
: 6
RandomG enerate PositionY
: false
EnemyGenerator (2)
:
Generator Delay
: 2
Random Generate Interval
: true
Min Generate Interval
: 2
Max Generate Interval
: 8
RandomG enerate PositionX
: true
Min Generate PositionX
: 6.5
Max Generate PositionX
: 17.5
RandomG enerate PositionY
: false
CabGenerator
:
Generator Delay
: 1
Random Generate Interval
: true
Min Generate Interval
: 5
Max Generate Interval
: 15
RandomG enerate PositionX
: false
RandomG enerate PositionY
: false
CabGenerator (1)
:
Generator Delay
: 5
Random Generate Interval
: true
Min Generate Interval
: 5
Max Generate Interval
: 15
RandomG enerate PositionX
: false
RandomG enerate PositionY
: false
BusGenerator
:
Generator Delay
: 4
Random Generate Interval
: true
Min Generate Interval
: 5
Max Generate Interval
: 15
RandomG enerate PositionX
: false
RandomG enerate PositionY
: false
BusGenerator (1)
:
Generator Delay
: 8
Random Generate Interval
: true
Min Generate Interval
: 5
Max Generate Interval
: 15
RandomG enerate PositionX
: false
RandomG enerate PositionY
: falsealse
Prefab Orientation
: Random
SwanGenerator
:
Generator Delay
: 2
Random Generate Interval
: true
Min Generate Interval
: 5
Max Generate Interval
: 15
RandomG enerate PositionX
: false
RandomG enerate PositionY
: true
Min Generate PositionY
: -7
Max Generate PositionY
: 2
SwanGenerator (1)
:
Generator Delay
: 2
Random Generate Interval
: true
Min Generate Interval
: 5
Max Generate Interval
: 15
RandomG enerate PositionX
: false
RandomG enerate PositionY
: true
Min Generate PositionY
: -7
Max Generate PositionY
: 2
PickupGenerator
:
Generator Delay
: 5
Random Generate Interval
: true
Min Generate Interval
: 15
Max Generate Interval
: 20
RandomG enerate PositionX
: true
Min Generate PositionX
: -15
Max Generate PositionX
: -5
RandomG enerate PositionY
: false
PickupGenerator (1)
:
Generator Delay
: 5
Random Generate Interval
: true
Min Generate Interval
: 15
Max Generate Interval
: 20
RandomG enerate PositionX
: true
Min Generate PositionX
: 5
Max Generate PositionX
: 15
RandomG enerate PositionY
: false
  运行游戏,可以发现此时各个Generator
能按照我们的设置,在随机的时刻
和随机的位置
随机生成某个Prefab。
后言
  至此,我们的拓展Generator
的工作就全部完成了。需要说明的是,现有的Generator
还不是最优的,因为我们没有利用资源池
来回收在场景中生成的对象,而是不断地实例化和销毁对象
。因为采用资源池
的方法来改写Generator
,我们需要改造Prefab,使得我们可以重置并回收已生成的资源,工作量较大,所以在这里我们并不提及,感兴趣的读者可以自己进一步拓展。
  最后,本篇文章所做的修改,可以在PotatoGloryTutorial这个仓库的essay16
分支下看到,读者可以clone这个仓库到本地进行查看。
参考链接
- Building a Custom Inspector
- Editor类的说明文档
- EditorGUILayout的说明文档
- EditorUtility的说明文档