• 游戏
  • 工业
  • 资源
  • 社区
  • 学习
  • 支持
开发
Unity 引擎
为任何平台构建2D和3D游戏
下载计划和定价
商业化
应用内购买(IAP)
发现并管理各商店的IAP
聚合平台
最大化收入并优化变现
Ad Quality
保护您应用的用户体验
Tapjoy
建立长期用户忠诚度
所有变现产品
用户获取
用户获取
被发现并获取移动用户
Unity向量AI
将玩家与合适的游戏连接
Aura设备内广告
在用户高峰参与时触达用户
所有增长产品
使用案例
3D协作
实时构建和审查3D项目
沉浸式培训
在沉浸式环境中培训
客户体验
创建互动3D体验
所有行业解决方案
行业
制造业
实现运营卓越
零售
将店内体验转化为在线体验
汽车
提升创新和车内体验
所有行业
技术库
文档
官方用户手册和API参考
开发者工具
发布版本和问题跟踪器
路线图
查看即将推出的功能
术语表
技术术语库
洞察
案例分析
真实成功案例
最佳实践指南
专家提示和技巧
所有资源
新增功能
博客
更新、信息和技术提示
新闻
新闻、故事和新闻中心
社区中心
讨论
讨论、解决问题和连接
事件
全球和本地活动
社区故事
Made with Unity
展示Unity创作者
直播活动
加入开发者、创作者和内部人员
Unity奖项
庆祝全球的Unity创作者
适合每个级别
Unity Learn
免费掌握Unity技能
专业培训
通过Unity培训师提升您的团队
Unity新手
准备开始
开始您的学习
Unity基础路径
你是Unity 新手?开始您的旅程
使用指南
可操作的技巧和最佳实践
教育
对于学生
开启您的职业生涯
对于教育者
增强您的教学
教育资助许可证
将Unity的力量带入您的机构
认证
证明您的Unity精通
支持选项
获取帮助
帮助您在Unity中取得成功
成功计划
通过专家支持更快实现目标
常见问题解答
常见问题解答
联系我们
与我们的团队联系
计划和定价
语言
  • English
  • Deutsch
  • 日本語
  • Français
  • Português
  • 中文
  • Español
  • Русский
  • 한국어
社交
货币
采购
  • 产品
  • Unity Ads
  • 订阅
  • Unity Asset Store
  • 经销商
教育
  • 学生
  • 教师
  • 机构
  • 认证
  • 学习
  • 技能发展计划
下载
  • Unity Hub
  • 下载存档
  • Beta 版测试
Unity Labs
  • 实验室
  • 作品
资源
  • 学习平台
  • 社区
  • 文档
  • Unity QA
  • 常见问题解答
  • 服务状态
  • 案例分析
  • Made with Unity
Unity
  • 我们公司
  • 新闻简报
  • 博客
  • 事件
  • 工作机会
  • 帮助
  • 新闻
  • 合作伙伴
  • 投资人
  • 附属机构
  • 安防
  • 社会影响力
  • 包容性与多样性
  • 联系我们
版权所有 © 2025 Unity Technologies
  • 法律
  • 隐私政策
  • Cookie
  • 不要出售或分享我的个人信息

“Unity”、Unity 徽标及其他 Unity 商标是 Unity Technologies 或其分支机构在美国及其他地区的商标或注册商标(单击此处获取更多信息)。其他名称或品牌是其各自所有者的商标。

Hero background image
Last updated January 2020, 10 min. read

使用 ScriptableObject 构建游戏的三种方法

为方便起见,此网页已进行机器翻译。我们无法保证翻译内容的准确性或可靠性。如果您对翻译内容的准确性有疑问,请参阅此网页的官方英文版本。
请点击这里。

本页内容简介有关如何通过使用脚本化对象构建游戏代码以便轻松更改和调试的技巧。

这些技巧由 Schell Games 首席工程师 Ryan Hipple 提供。他在使用脚本化对象构建游戏方面拥有先进的经验。您可以在此处观看 Ryan 有关脚本化对象的 Unite 演讲;我们还建议您观看 Unity 工程师 Richard Fine 的演讲,以获取有关脚本化对象的精彩介绍。

  • 什么是 ScriptableObject?
  • 游戏工程的三大支柱
  • 变量的设计
  • 示例:玩家生命值
  • 事件的设计
  • 代码示例:GameEvent ScriptableObject
  • 代码示例:GameEventListener
  • 处理玩家死亡的事件系统
  • 其他系统的设计

什么是 ScriptableObject?

ScriptableObject 是一个可序列化的 Unity 类,能够让您在脚本实例之外存储大量共享数据。使用 ScriptableObject 可以更轻松地管理更改和调试。您可以在游戏中的不同系统之间建立一定程度的灵活通信,以便在整个生产过程中以及重用组件时进行更易于管理的更改和调整。

游戏工程的三大支柱

采用模块化设计:

  • 避免创建直接相互依赖的系统。例如,库存系统应该能够与游戏中的其他系统通信,但您不会希望在它们之间创建硬引用,因为这会导致将系统重新组装成不同的配置和关系变得很难。
  • 从头开始创建场景:避免场景之间存在瞬态数据。每当点击一个场景时,该场景应该是光洁的断面和负载。这可以让您获得其他场景中所没有的独特行为的场景,而不需要强制去摸索。
  • 设置预制件,使其独立工作。您拖入场景的每一个预制件都应包含所有内部功能。这有助于大型团队控制源代码,其中场景是预制件列表,并且预制件包含单独的功能。这样,您的大部分签入都处于预制件级别,从而减少了场景中的冲突。
  • 让每个组件主要解决一个问题,这样可以更轻松地将多个组件拼凑在一起以构建新的组件。

简化更改和编辑部分:

  • 尽可能多利用数据驱动游戏。当您将游戏系统设计成像机器一样,将数据作为指令处理时,即使游戏正在运行,也能够更有效地对游戏进行更改。
  • 如果尽可能将系统设置为模块化和基于组件的,美术师和设计师将能更轻松地进行编辑。如果设计师能够在游戏中将事物拼凑在一起而无需要求具有明确的功能,很大程度上要归功于每个只实现一个功能的微小组件,然后他们可以通过不同方式组合这些组件,以找到新的游戏玩法/机制。Ryan 说,他的团队在他们的游戏中使用的一些主要功能来自于此过程,他称之为“紧急设计”。
  • 团队可以在运行时更改游戏的能力至关重要。您可以在运行时对游戏进行的更改更多,就可以找到更多平衡和值。如果您还能够将运行时状态保存回来(就像脚本化对象一样),那就更好了。

简化调试:

这更像是前面两个支柱的子支柱。游戏模块化程度越高,就越容易测试其中的任何一个部分。也就是说,游戏越是可编辑,在自己的 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

GameEvent ScriptableObject:

代码示例:GameEventListener

GameEventListener:

处理玩家死亡的事件系统

处理玩家死亡的事件系统

其中一个示例是在游戏中处理玩家死亡。这里大部分执行都可以更改,但可能很难确定对所有逻辑进行编码的位置。玩家脚本应该触发游戏结束 UI 还是音乐更改?敌人应该每一帧都检查玩家是否还活着吗?事件系统可以让我们避免出现像这样有问题的依赖关系。

如果玩家死亡,玩家脚本会在 OnPlayerDied 事件上调用 Raise。玩家脚本不需要知道哪些系统与之相关,因为它只是一个广播。游戏结束 UI 正在侦听 OnPlayerDied 事件并开始制作动画,摄像机脚本可以侦听它并开始淡化为黑色,音乐系统可以对音乐变化作出响应。我们也可以让每个敌人侦听 OnPlayerDied,触发讽刺动画或状态更改以重新回到空闲行为。

这种模式可以非常容易地为玩家死亡添加新的响应。此外,还可以通过从 Inspector 中的某些测试代码或按钮的事件调用 Raise,轻松地对玩家死亡响应进行测试。

他们在 Schell Games 中构建的事件系统已经变得越来越复杂,并且具有可传递数据和自动生成类型的功能。本示例基本上是他们现在使用某些内容的起点。

其他系统的设计

脚本化对象不一定只是数据。使用您在 MonoBehaviour 中实现的任意系统,看看您是否可以将实现移动到 ScriptableObject。不要在 DontDestroyOnLoad MonoBehaviour 上使用 InventoryManager,而是将其放置在 ScriptableObject 上。

由于它与场景无关,因此它没有 Transform 函数,也无法获得 Update 函数,但是它将在场景加载之间保持状态,而不需要进行任何特定初始化。当需要脚本访问库存时,对库存系统对象的公共引用(而不是使用单例)。这使得在测试库存或操作指引库存中进行交换比使用单例更简单。

在这里,您可以想像一下引用库存系统的玩家脚本。当玩家生成时,它可以向库存询问所有拥有的对象并生成任何设备。设备 UI 还可以引用库存并循环遍历项目以确定要绘制的内容。