
本页内容简介有关如何通过使用脚本化对象构建游戏代码以便轻松更改和调试的技巧。
这些技巧由 Schell Games 首席工程师 Ryan Hipple 提供。他在使用脚本化对象构建游戏方面拥有先进的经验。您可以在此处观看 Ryan 有关脚本化对象的 Unite 演讲;我们还建议您观看 Unity 工程师 Richard Fine 的演讲,以获取有关脚本化对象的精彩介绍。
ScriptableObject 是一个可序列化的 Unity 类,能够让您在脚本实例之外存储大量共享数据。使用 ScriptableObject 可以更轻松地管理更改和调试。您可以在游戏中的不同系统之间建立一定程度的灵活通信,以便在整个生产过程中以及重用组件时进行更易于管理的更改和调整。
采用模块化设计:
简化更改和编辑部分:
简化调试:
这更像是前面两个支柱的子支柱。游戏模块化程度越高,就越容易测试其中的任何一个部分。也就是说,游戏越是可编辑,在自己的 Inspector 视图中拥有的功能就越多,调试也就越容易。确保可以在 Inspector 中查看调试状态,并且在为如何调试游戏制定某些计划之前从不考虑完成的功能。
您可以使用 ScriptableObject 制作的最简单的方法之一是使用自包含的基于资源的变量。以下是 FloatVariable 的示例,但该变量也可扩展到任何其他可序列化类型。
无论技术怎样,您的团队中的每个人都可以通过创建新的 FloatVariable 资源来定义新游戏变量。任何 MonoBehaviour 或 ScriptableObject 都可以使用公共 floatVariable(而不是公共浮点变量),以引用新的共享值。
更妙的是,如果一个 MonoBehaviour 更改了 FloatVariable 的值,则其他 MonoBehaviour 可以看到此更改。这在不需要彼此引用的系统之间创建了一种消息传递层。

此类示例之一是玩家的生命值 (HP)。在拥有一个本地玩家的游戏中,玩家的 HP 可以是名为 PlayerHP 的 FloatVariable。当玩家受到伤害时,它会从 PlayerHP 中减去,当玩家接受治疗时,它会加到 PlayerHP 中。
现在,想像一下场景中的生命条预制件。生命条监控 PlayerHP 变量以更新显示。如果不更改任何代码,它可以轻松地指向不同的对象,如 PlayerMP 变量。生命条对场景中的玩家一无所知,它只是从玩家写入的同一个变量中读取值。
这样设置后,我们可以很容易地添加更多内容来观看 PlayerHP。当 PlayerHP 变低时,可以改变音乐系统,当敌人知道玩家处于衰弱状态时,他们可以更改攻击模式,还可以用屏幕空间效果来强调下一次攻击的危险性等。这里的关键是玩家脚本不会将消息发送到这些系统,并且这些系统不需要知道玩家游戏对象。您还可以在游戏正在运行时进入 Inspector,更改 PlayerHP 的值,以便对某些变量进行测试。
编辑 FloatVariable 的值时,最好是将数据复制到运行时值,从而不更改存储在磁盘上的 ScriptableObject 的值。如果这样做,MonoBehaviour 应访问 RuntimeValue 以防止编辑保存到磁盘的 InitialValue。
可以在 ScriptableObject 上构建的 Ryan 最喜欢的功能之一是事件系统。事件架构可在彼此不直接了解的系统之间发送消息来帮助模块化代码。它们允许事件对状态的更改作出响应,而无需在更新循环中进行持续监控。
以下代码示例摘自包含两个部分(GameEvent ScriptableObject 和 GameEventListener MonoBehaviour)的事件系统。设计师可以在项目中创建任意数量的 GameEvent 来表示可以发送的重要消息。GameEventListener 等待引发特定 GameEvent,并通过调用 UnityEvent 作出响应(这不是一个真正的事件,而是序列化函数调用)。
GameEvent ScriptableObject:
GameEventListener:

其中一个示例是在游戏中处理玩家死亡。这里大部分执行都可以更改,但可能很难确定对所有逻辑进行编码的位置。玩家脚本应该触发游戏结束 UI 还是音乐更改?敌人应该每一帧都检查玩家是否还活着吗?事件系统可以让我们避免出现像这样有问题的依赖关系。
如果玩家死亡,玩家脚本会在 OnPlayerDied 事件上调用 Raise。玩家脚本不需要知道哪些系统与之相关,因为它只是一个广播。游戏结束 UI 正在侦听 OnPlayerDied 事件并开始制作动画,摄像机脚本可以侦听它并开始淡化为黑色,音乐系统可以对音乐变化作出响应。我们也可以让每个敌人侦听 OnPlayerDied,触发讽刺动画或状态更改以重新回到空闲行为。
这种模式可以非常容易地为玩家死亡添加新的响应。此外,还可以通过从 Inspector 中的某些测试代码或按钮的事件调用 Raise,轻松地对玩家死亡响应进行测试。
他们在 Schell Games 中构建的事件系统已经变得越来越复杂,并且具有可传递数据和自动生成类型的功能。本示例基本上是他们现在使用某些内容的起点。
脚本化对象不一定只是数据。使用您在 MonoBehaviour 中实现的任意系统,看看您是否可以将实现移动到 ScriptableObject。不要在 DontDestroyOnLoad MonoBehaviour 上使用 InventoryManager,而是将其放置在 ScriptableObject 上。
由于它与场景无关,因此它没有 Transform 函数,也无法获得 Update 函数,但是它将在场景加载之间保持状态,而不需要进行任何特定初始化。当需要脚本访问库存时,对库存系统对象的公共引用(而不是使用单例)。这使得在测试库存或操作指引库存中进行交换比使用单例更简单。
在这里,您可以想像一下引用库存系统的玩家脚本。当玩家生成时,它可以向库存询问所有拥有的对象并生成任何设备。设备 UI 还可以引用库存并循环遍历项目以确定要绘制的内容。