组织你的 Mod 结构(Structuring Your Mod)
结构化的 Mod 有助于后期维护、协作开发,并让代码库更清晰易懂。下面列出了一些来自 Java、Minecraft 和 NeoForge 的组织建议。
你并不一定要遵循以下建议;你可以按照自己的方式组织 Mod 结构。不过,强烈建议你采纳这些做法。
包结构(Packaging)
在组织你的 Mod 时,应该选择一个独特的顶级包(package)结构。许多程序员会为不同的类、接口等使用相同的名字。Java 允许类名重复,只要它们在不同的包中。因此,如果两个类处于同名包下,只有一个会被加载,这很可能导致游戏崩溃。
a.jar
- com.example.ExampleClass
b.jar
- com.example.ExampleClass // 这个类通常不会被加载
当涉及到模块加载时,这一点尤为重要。如果两个模块下存在相同名字的包和类文件,这会导致 Mod 加载器在启动时崩溃,因为 Mod 模块会被导出到游戏和其他 Mod。
module A
- package X
- class I
- class J
module B
- package X // 由于已有模块导出了 package X,这个包会导致 Mod 加载器崩溃
- class R
- class S
- class T
因此,你的顶级包应该是你拥有的某个标识:比如域名、邮箱地址、(子)网站等。只要你能保证其在目标范围内唯一,也可以使用你的名字或用户名。此外,顶级包还应与你的 group id 保持一致。
| 类型 | 示例值 | 顶级包(Top-Level Package) |
|---|---|---|
| 域名 | example.com | com.example |
| 子域名 | example.github.io | io.github.example |
| 邮箱 | example@gmail.com | com.gmail.example |
下一层包应该是你的 Mod 的 id,例如 com.example.examplemod,其中 examplemod 就是 mod id。这样可以保证,除非你有两个同名 mod(这绝不应该发生),你的包不会出现加载冲突。
你可以在 Oracle 的命名规范页面 上找到更多命名约定。
子包组织 (Sub-package Organization)
除了顶级包之外,强烈建议你将 Mod 的类分别放在不同的子包中。主要有两种常见的组织方式:
- 按功能分组(Group By Function):为具有相同用途的类创建子包。例如,方块相关的类可以放在
block包下,物品相关的放在item包下,实体相关的则放在entity包下。Minecraft 本身也采用了类似的结构(但有一些例外)。 - 按逻辑分组(Group By Logic):为具有相同逻辑的类创建子包。例如,如果你要创建一种新的工作台类型,可以将其方块、菜单、物品等都放在
feature.crafting_table包下。
客户端、服务端与数据包(Client, Server, and Data Packages)
通常,专属于某一端或运行时环境的代码,应当与其他类隔离,放在单独的子包中。例如,与 数据生成 相关的代码应放在 data 包中,而仅在专用服务端运行的代码则应放在 server 包中。
强烈建议将 仅限客户端的代码 隔离在 client 子包中。因为专用服务端无法访问 Minecraft 中任何仅限客户端的包,如果你的模组尝试访问这些内容,服务端会直接崩溃。因此,单独的包结构可以作为有效的自检机制,帮助你确认没有在模组内部跨端访问。
类命名规范(Class Naming Schemes)
统一的类命名规范可以让你更容易理解类的用途,也便于快速定位特定的类。
类名通常会以其类型作为后缀,例如:
- 一个名为
PowerRing的Item→PowerRingItem - 一个名为
NotDirt的Block→NotDirtBlock - 一个用于
Oven的菜单 →OvenMenu
Mojang 通常也采用类似的结构,唯一的例外是实体(entity)类。实体类通常只用其名称表示(例如 Pig、Zombie 等)。
多种方法中选择一种(Choose One Method from Many)
完成某项任务(如注册对象、监听事件等)通常有多种方法。建议你在同一项目中保持一致,只使用一种方法来完成某项任务。这样可以提升代码可读性,并避免出现奇怪的交互或冗余现象(例如事件监听器被触发两次)。