ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 你的第一段模组代码 **难度分级:☆** 现在,万事俱备,你可以正式开始编写你的模组了! **以下所有代码均以EOK项目为例** ## FORGE模组目录结构 在IDE下,你可以看到类似如下的目录结构: ![](https://img.kancloud.cn/85/02/85020224160a27a3dba0e8a8509024a2_452x516.png) 在forge开发目录下,所有的源代码都需要放在\src目录下。其中java文件夹中为该模组源代码,resources文件夹中为该模组用到的所有资源文件(贴图,模型,配置文件,语言文件等)。在java文件夹中,通常情况下我们会以**组织名.用户名.模组名**的形式来命名模组根目录,其中的**包(Package)** 命名方式可根据自己的习惯或团队的规定选择。对于resources文件夹,其中模组资源的根目录必须是**assets/[你的模组ID]**,其中至少包含**方块状态(blockstates),语言(lang),模型(models),贴图(textures)** 四个文件夹。mcmod.info文件用于控制在游戏中的模组列表里显示的内容(可以在代码中使用**useMetadata = true**打开),pack.mcmeta为模组信息,用处不大。 ## 主类 任何Forge模组都需要有一个**主类** 用以被Forge所识别并加载。在EOK项目中,EOK类即为项目的主类。主类之前必须使用 **@Mod** 注释标识,Forge也只会监听此类中的@EventHandler。对于@Mod注释可以设置如下参数: | 属性名称 | 类型 | 默认值 | 描述 | | ------------ | ------------ | ------------ | ------------ | | modid | String | 必须手动赋值 | 该模组的唯一标识符,必须全小写字母且不得超过64个字符 | | name | String | "" | 对玩家显示的模组名 | | version | String | "" | 该模组版本号,只能出现数字和点(例如:1.0.0) | | dependencies | String | "" | 该模组的依赖模组,可以选择四种模式:"before", "after", "required-before", "required-after",用法参考Forge开发文档:[链接](https://mcforge.readthedocs.io/en/latest/gettingstarted/dependencymanagement/) | | useMetadata | boolean | false | 如果设置为true,则forge会使用该模组的mcmod.info中的信息 | | clientSideOnly/serverSideOnly | boolean | false | 如果模组仅需要在客户端/服务端中运行,应当设置为true | | acceptedMinecraftVersions | String | "" | 该模组可以接受的Minecraft版本范围 | | acceptableRemoteVersions | String | "" | 该模组可以接受的远程(服务器/客户端中)模组版本范围 | | acceptableSaveVersions | String | "" | 该模组可以接受的存档中的模组版本范围 | | certificateFingerprint | String | "" | 该模组的签名标识(参考Forge开发文档:[链接](https://mcforge.readthedocs.io/en/latest/concepts/jarsigning/ "forge签名")) | | modLanguage | String | "java" | 该模组源代码使用的语言,仅接受"java"或"scala" | | modLanguageAdapter | String | "" | 该模组中LanguageAdapter类的位置,如果设置了但该类没有默认的构造函数或没有实现ILanguageAdapter接口,Forge将发生崩溃 | | canBeDeactivated | boolean | false | 该mod能否被关闭 | | guiFactory | String | "" | 该模组中GuiFactory类的位置,如果设置了则该类必须实现IModGuiFactory接口 | | updateJSON | String | "" | 该模组用于更新版本的JSON文件远程地址 | 如果你的模组中不需要某一项属性或只需要将其设为默认值,你可以不用在@mod注释里写出。 例如,在EOK中@mod的设置即为: ~~~ @Mod(modid = EOK.MODID, name = EOK.NAME, version = EOK.VERSION, useMetadata = true) public class EOK { public static final String MODID = "eok"; public static final String NAME = "Evolution Of Knowledge"; public static final String VERSION = "0.0.1"; ... } ~~~ ## @EventHandler 整个Forge的运作基本都基于一种叫**事件(Event)** 的系统上。**@EventHandler** 注释使得Forge可以在不同的**游戏生命周期(Life Cycle)** 调用该模组对应函数中的指令。Forge的生命周期中有以下事件节点: | 事件名称 | 此时模组应该做的事 | | --- | --- | | FMLPreInitializationEvent | 读取配置文件,注册物品,方块等需要调用**GameRegistry**的行为 | | FMLInitializationEvent | 对模组进行设置。初始化你构建的数据结构,注册合成表,发送**FMLInterModComms**消息给别的模组 | | FMLPostInitializationEvent | 接收其他模组发来的交互信息,完成对模组的设置 | | FMLServerAboutToStartEvent | 处理服务器创建之前你需要执行的任务 | | FMLServerStartingEvent | 服务器启动时你需要执行的任务,注册命令,对服务器进行修改等 | | FMLServerStartedEvent | 服务器启动后你需要执行的任务 | | FMLServerStoppingEvent | 服务器执行关闭过程中你需要执行的任务 | | FMLServerStoppedEvent | 服务器关闭后你需要执行的清理任务(通常只在本地服务器有效) | 如果你需要将一个函数设置为某一个生命周期需要被forge调用的函数,你需要在**函数前**加上 **@EventHandler** 注释,并在参数中传入对应的**事件类型** ,例如EOK中的相关代码即为: ~~~ @EventHandler public void preInit(FMLPreInitializationEvent event) { ... } @EventHandler public void init(FMLInitializationEvent event) { ... } @EventHandler public void postInit(FMLPostInitializationEvent event) { ... } ~~~ ## 实例 和modid类似,模组的实例也是一种对模组的唯一标识方式。实例在网络的收发及模组间的联动是必不可少的,模组的实例定义必须加上 **@Mod.Instance** 注释,类型为主类的类名,如EOK的实例定义即为: ~~~ @Mod.Instance public static EOK instance; ~~~ ## 代理 和许多游戏一样,Minecraft在服务器和客户端上有两套相似却又不同的代码。在很多情况下我们也需要在服务器和客户端上运行不同的代码,这就需要依赖**代理(Proxy)** 来对他们进行分类。代理需要单独创建新的**代理类** 来实现,一般分别被命名为**ClientProxy** 以及**ServerProxy** (如果没有需要单独在服务端运行的代码,可以直接写成**CommonProxy** 来放需要同时在客户端和服务端运行的代码)。在代理类中,我们同样需要定义参数分别为Forge生命周期对应的事件类型。如果你的模组没有需要单独在服务端运行的代码,那么你可以让你的ClientProxy直接**继承(extends)** CommonProxy并通过**覆写函数+super关键字** 来减少代码量。 EOK的CommonProxy类基本结构如下: ~~~ public class CommonProxy { public void preInit(FMLPreInitializationEvent event) { ... } public void init(FMLInitializationEvent event) { ... } public void postInit(FMLPostInitializationEvent event) { ... } ~~~ ClientProxy类结构如下: ~~~ public class ClientProxy extends CommonProxy{ public void preInit(FMLPreInitializationEvent event){ super.preInit(event); } public void init(FMLInitializationEvent event){ super.init(event); } public void postInit(FMLPostInitializationEvent event){ super.postInit(event); } ~~~ 新建完代理类以后,我们需要在主类中告诉Forge去寻找相应的代理类。在Forge中,对于代理端的声明需要使用 **@SidedProxy** 进行注释,参数中的**clientSide** 为客户端代理所在的**完整类名(包名+类名)** ,**serverSide** 为服务端代理所在的完整类名。例如EOK**主类**中对于代理的声明如下: ~~~ @SidedProxy(clientSide = "com.gonggongjohn.eok.ClientProxy", serverSide = "com.gonggongjohn.eok.CommonProxy") public static CommonProxy proxy; ~~~ 随后我们在主类的**声明周期函数**中调用**代理类**中的代码即可: **什么你不知道“声明周期函数”“代理类”是啥? 再看一遍教程** ~~~ proxy.preInit(event); proxy.init(event); proxy.postInit(event); ~~~