版权声明:

  • 本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明
  • 文章内容不得删减、修改、演绎
  • 本文涉及的资源都在文末参考链接中

已经很久没有在这里写文章了。终于,是时候在这里分享一些新东西了 :)

今天,我将一步步解释如何为即将推出的移动游戏创建基础框架。您无需在每个仓库中一次又一次地重新发明轮子。此外,该框架将通过强制所有开发者使用相同的类/函数来提高团队效率和和谐。

在一切之前,请记住,本文和 github 仓库是根据我的需求准备的。因此,您最好根据您的需求进行调整/扩展。

另一方面,没有一种方法能够满足所有游戏开发限制的最佳方案。因此,首先优先考虑您的项目需求。

1- 文件夹结构

初看之下,项目初期文件夹结构应该井然有序,因为所有项目贡献者都必须轻松找到资产文件夹。这非常重要。此外,随着即将到来的提交,文件夹数量将迅速增长。因此,我们首先应该考虑的是文件夹结构。

在那个时刻,让我快速提供一些建议和最佳实践。

1- 一些文件夹名称是 Unity 引擎特有的,因此请按照 Unity 的设计使用它们。

请参阅链接:Unity - Manual: Reserved folder name reference

我的文件夹结构

2- 一些文件夹名称在 Unity 开发者中非常常见。因此,最好使用这些名称。

例如,预制件、脚本、艺术等。

3- 将第三方资产保存在不同的文件夹中,例如插件文件夹。

为了减少编译时间,一个特定的“第三方文件夹”使我们能够在它们没有任何的情况下创建一个组件定义文件。

4- 将脚本文件夹与命名空间结构对齐是一种良好的做法。

在顶级,编辑器、运行时和测试文件夹应该足够,除非你需要任何额外的特定文件夹。

请参考我的框架以了解脚本文件夹作为示例(参见文末链接)

顶级脚本文件夹

5- 使用 AssetPostProcessor 在导入资产时自动设置首选项。这将加快将资产导入特定文件夹的过程。

请参考:Unity - Scripting API: AssetPostprocessor

2- 场景结构

第二个主题是场景结构。在深入细节之前,我们应该分析需求并相应地创建场景结构。

- 我看到的第一种结构是场景由场景和常用预制件组成。所有场景都是独立的。然而,我不建议这种做法。这是最容易和最基本的开发方式,但维护起来最糟糕。

-第二结构是一个启动场景和其它关卡场景。在我看来,这对关卡式游戏来说是个不错的选择。启动场景为游戏做准备并创建将在游戏过程中保持活跃的 DontDestroyOnLoad 对象。我将在本文中提供更多细节。

- 第三结构,一个起始场景和其上的附加场景。
这种方法对于可能需要更多优化的大型游戏来说可能很好。特别是,与 Addressables 结合时它可能非常强大。然而,这种结构需要更多经验。不同场景中 gameobjects 的通信可能成为问题。

让我解释一下我的结构。

  • 我有一个包含我的 DontDestroyOnLoad 管理器的起始场景。
  • 为几帧时间,这些经理准备自己。之后,GameStarter 对象改变场景并开启游戏。

启动场景层次结构

  • 当新关卡场景打开时,启动场景的 DontDestroyOnLoad 管理器与打开关卡的游戏对象和谐工作。
  • 当最新关卡完成时,我使用 SceneManager 打开了新关卡。永驻的管理器要设置为 DontDestroyOnLoad。基于关卡的游戏的一个优点是,你可以利用游戏的地图切换时间来调用 Resources.UnloadUnusedAssets()以从内存中卸载对象。然而,我们仍然可以使用 Addressables 在这里获得更多控制。(参考“Unity全栈开发大师》一小时极速掌握Addressables资源热更新”)

示例层级结构

3- 预制结构

就像 C#中的继承一样,我强烈建议在 Unity 中使用 Prefab 变体。

请找到链接:https://docs.unity3d.com/Manual/PrefabVariants.html

4-代码结构

4-0 命名规范

关于编码,开发者首先应该达成一致的是风格和逻辑上的命名规范。一切都应该清晰且符合风格。

请参阅链接获取详细信息:https://unity.com/how-to/naming-and-code-style-tips-c-scripting-unity

Unreal 与 Unity 相比,我观察到的一点是命名规范更好。

4-1 编程范式

Unity 最终发布了用于生产的实体-组件系统。它提供了更高的性能和大量的 GameObject。然而,我认为它仍然离我们脑海中第一个出现的范式相去甚远。因此,我们很可能会使用面向对象编程。我将提供一些关于它的实际生活建议。

  • 继承:在某些情况下,继承使我们能够通过避免重复代码来节省大量时间。
    在 C#中,一个类只能从特定的类继承。然而,我们可以通过接口提供更多的功能。如果你之前没有使用它们,请从现在开始使用吧 :)
  • 封装:通过封装,其他对象可以访问但不能更改它。在 Unity 中,我们可以像下面图片所示轻松实现封装。还有其他实现方式,但对我来说这是最简单的一种。

  • 多态性:以下是一个快速示例,请参考下面的图片。第一个函数实现传送,第二个函数实现位置插值。唯一的区别是参数数量。这是一个基本且常见的多态性示例。实际上,多态性远不止于此。请参考下面的链接。

更多详情:小白的游戏梦》玩游戏,学C#

多态示例

4-2 核心与系统架构

将您的运行时代码分成两块“核心”和“系统”。这样做有几个优点。

  • 第一个原因是我们可以从基础仓库分叉仓库后,在所有项目中使用这些“系统”而无需更改。此外,我们可以将它们转换为 Unity 包。因此,我们可以在目标 Unity 项目中单独使用它们。
  • 第二个原因,我们能够为系统使用不同的汇编定义。因此,当我们更改核心游戏系统的代码时,我们不需要等待编译系统的代码。
  • 第三,我们将更容易测试我们的代码。

4–3- 设计模式

在 Unity 的设计模式如游戏循环、更新、原型(预制件)和组件的基础上,我发现实施以下设计模式很有用。正如我之前提到的,这些设计模式取决于您项目的需求。

  • 观察者模式:观察者模式简单来说就是存在一个广播对象和一些监听对象。如果广播对象触发一个事件,所有监听者都将被通知。
    这种设计模式的一个好处是,广播对象不关心哪些对象正在监听事件。
    与 Unreal 不同,Unity 和 C#在观察者模式方面非常出色。在我看来,它对于大型项目来说非常强大且易于维护。然而,请注意,在我们禁用监听器时,您需要取消订阅它们。否则,可能会发生内存泄漏。

    作为最佳实践,项目中应只有一个 Event Manager 来处理所有事件。对象不应直接相互通信以处理事件。
    让我在这里举一个很好的例子。“OnGameStateChange”是一个几乎所有 gameobject 都需要遵循的事件。在 GameManager 中,我们可以通过 EventManager 触发此事件,因此所有已订阅的对象都将收到通知。

触发一个事件,例如

监听器对象为例

  • 服务定位器 & 单例模式

与 Unreal 不同,Unity 非常适合单例模式。我们可以创建一个类的静态实例。然而,当项目变得更大时,使用单例模式会导致复杂的情况。此外,所有开发者都应该了解项目中的所有单例。这就是服务定位器设计模式解决问题的所在。

单例设计模式

在服务定位器模式中,只有一个单例对象为我们提供我们需要的其他对象。快速且安全的方式组织。

  • 工厂模式 & 对象池模式

我不会过多介绍如何在 Unity 中实例化对象和对象池。YouTube 上已经有了很多视频。我只是想说,不要在任何类中创建对象。这会使跟踪变得困难。计划中的对象应在工厂中创建。这是一种更易于维护的方法。

我为我基础框架创建了一个简单的抽象工厂类。它为我们提供了从实例化函数或对象池中获取对象的选择。顺便说一句,我认为能够从可寻址系统中创建和返回 GameObject 会更好。(进行中)

  • 命令模式 & 状态机模式
    尽管我在以前的项目中很少使用它们。我发现它们非常高效且有用。最好去了解一下。

4–4 设计原则

  • SOLID: SOLID

请参阅更多信息:https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/may/csharp-best-practices-dangers-of-violating-solid-principles-in-csharp

SOLID 原则迫使开发者编写更干净、更可重用和更易于维护的代码。我强烈建议在可能的情况下随时随地进行遵循。

让我快速分享一下我的想法。

单一职责 => 每件事应该只有一个职责。请勿创建庞大的单体类、函数等。我认为这是最重要且最实用的一个。我在 Unity 中以及生活中都非常认真地采取这种做法 :)

O, L, I, D => 一些最佳实践包括;不要使用与枚举一起的 switch。使用接口代替。这些接口应仅涵盖特定部分,而不是一个单体。对象应通过抽象而不是具体类型相互依赖。

5- 核心

核心是项目的核心。所有项目特定的代码都应该在这里。我将其拆分为不同的部分。

5-1 游戏框架

当我阅读虚幻引擎的文档时,游戏框架对我来说似乎非常不同且有趣,因为我们习惯在 Unity 中自己编写一切。
如果您的项目中存在一个文档齐全的游戏框架,该框架将提高效率。此外,它还能防止游戏开发者之间的一些主要冲突。
根据上述信息,我开始在 Unity 中创建自己的游戏框架

5-1-1 演员(Actor) & 角色(Character)

我定义“Actor”为具有特定行为的 gameobject。另一方面,我将“Character”定义为从“Actor 类”继承的特定版本的 actors。因此,Characters 除了 Actor 的行为外,还拥有独特的行为。我们将要创建的大多数 gameobject 都将继承自 Actor 类和 Character 类。

一些 Actor 对象的行為

5-2 经理与控制器

管理人员是他们领域内所有逻辑的主要责任对象。

  • 大多数都依赖于一个为管理者提供额外功能的系统。
  • 大部分是 DontDestroyedOnload
  • 大多数它们都有只负责特定领域部分的控制器对象。因此,管理者能够分配责任。

管理者

5–3 可脚本对象
在我看来,可脚本化对象是 Unity 游戏开发中最基本的部分之一。我认为这对于所有开发者来说都是必须的:)

  • 轻量级
  • 能够具有多种用途:存储数据、提供逻辑等。
  • 能够调整核心和系统,无需在检查器中触摸任何组件。
  • 它们可以在编辑器和运行时使用/更改/调整
  • 独立于场景
  • 艺术家和游戏设计师会非常感激你 :)

6- 系统

  • 系统独立于项目的核心。
  • 仅有少数依赖于某些其他系统或第三方插件。
  • 系统不需要从 MonoBehaviour 继承。它们可以是纯 C#类。然而,我更喜欢它们是 MonoBehaviour,这样我在检查器上调试起来更方便。这是我的个人选择:)
  • 它们可以被转换成 Unity 包。我计划在以下版本中进行转换。

谢谢阅读。

参考:

  • 以下资源请加alice17173,注明需要的资源:
    • 移动游戏代码框架
    • Unity全栈开发》一小时极速掌握Addressables热更新
    • 小白的游戏梦》玩游戏,学C#
Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐