组织 Mod 代码结构

组织 Mod 代码结构

良好的 Mod 代码结构有助于维护、接受贡献,也能让人更容易理解代码库。下面列出 Java、Minecraft 和 NeoForge 中的一些建议。

提示 你可以按自己认为合适的方式组织 Mod,不必强制遵循以下建议。不过,我们仍然强烈推荐采用这些做法。

包结构

组织 Mod 时,应选择唯一的顶层包结构。不同开发者经常会为类、接口等使用相同的名称。Java 允许不同包中的类同名;但如果两个类的包名和类名都相同,就只有其中一个会被加载,通常会导致游戏崩溃。

a.jar
    - com.example.ExampleClass
b.jar
    - com.example.ExampleClass // This class will not normally be loaded

加载模块时,这一点尤其重要。如果不同模块中存在同名的包及其类文件,由于 Mod 模块会向游戏和其他 Mod 导出内容,Mod 加载器会在启动时崩溃。

module A
    - package X
        - class I
        - class J
module B
    - package X // This package will cause the mod loader to crash, as there already is a module with package X being exported
        - class R
        - class S
        - class T

因此,顶层包应使用你拥有的标识,例如域名、电子邮箱地址或网站的子域名。如果能确保在目标使用范围内具有唯一性,也可以使用你的姓名或用户名。另外,顶层包还应与你的 group ID 相匹配。

类型 值 顶层包
域名 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 使用相同的 ID(这种情况本就不应发生),包的加载通常就不会出现冲突。

其他命名约定可参阅 Oracle 教程页面。

子包组织

除顶层包外,还强烈建议将 Mod 中的类分到不同子包。主要有两种组织方式:

  • 按功能分类:将用途相同的类放在同一个子包中。例如,方块放在 block,物品放在 item,实体放在 entity。Minecraft 本身也采用类似的结构,不过有一些例外。
  • 按逻辑分类:将实现同一项逻辑的类放在同一个子包中。例如,制作一种新的工作台时,可以将其方块、菜单、物品等都放在 feature.crafting_table 下。

客户端、服务端和数据包

通常,只用于特定端或运行环境的代码,应放在单独的子包中,与其他类隔离。例如,数据生成相关代码应放在 data 包中,仅用于独立服务器的代码应放在 server 包中。

强烈建议将仅客户端代码放在 client 子包中。独立服务器无法访问 Minecraft 中仅供客户端使用的包;如果 Mod 尝试访问这些包,就会崩溃。为客户端代码设置专用包,也能让你在检查代码时更容易发现跨端访问问题。

类命名方式

统一的类命名方式能帮助读者理解类的用途,也方便定位特定的类。

常见做法是在类名末尾附加其类型,例如:

  • 名为 PowerRing 的 Item → PowerRingItem。
  • 名为 NotDirt 的 Block → NotDirtBlock。
  • Oven 的菜单 → OvenMenu。

建议 Mojang 通常也采用类似的结构,但实体类例外:实体类直接使用其名称,例如 Pig、Zombie 等。

同一任务采用一致的方法

注册对象、监听事件等任务往往有多种实现方式。通常建议在同一任务上保持一致,只使用一种方式。这能提高可读性,也能避免意外交互或重复执行,例如同一个事件监听器运行两次。


来源: NeoForged/Documentation(26.1)。中文翻译依据提交 4a680cdd4d15。