考虑到部分读者可能对环境搭建流程不熟,所以本章教程简单地过一遍环境搭建,并在项目中整合Mixin。
下载MDK
打开https://files.minecraftforge.net/net/minecraftforge/forge/,选择需要的mdk版本,点击下载。本教程使用1.18.2 - 40.2.0。在下载前会打开一个广告链接,由于某些不可描述的原因导致广告链接打开很慢或失败,采用以下办法跳过广告:
-
右键“Mdk”,点击“复制链接地址”
-
将链接复制到地址栏中,图中高亮部分就是Mdk下载链接,访问该链接即可
搭建项目
解压缩Mdk,将文件夹名改成自己项目的名字,笔者以ZjTutor
作为项目名。~/README.txt
(这里‘~’指模组项目文件夹路径)中记录了搭建项目的流程,笔者使用eclipse(也可以使用idea,拥有更强大的插件,搭建流程略有不同)跟着做一遍:
- 进入项目文件夹,在地址栏中输入
cmd
点击Enter
打开控制台
- 在控制台中运行
gradlew genEclipseRuns
,如果是第一次运行需要等待较长时间下载大量依赖文件,这个过程可能因为网络原因导致失败,可以多次或换个网络重试 - 打开Eclipse,Import > Existing Gradle Project > 选择模组的项目文件夹并导入
项目配置——build.gradle
mdk是基于gradle的项目,使用build.gradle对其进行配置。在~/gradle.properties
添加以下参数,这些参数将在~/build.gradle
中调用,关于parchment将在后面说明
mod_id=zjtutor//模组id
mod_group=com.zjqc.zjtutor//包名
mod_name=ZjTutor//模组名字
version_name=Demo=Demomod_version=0.0.1//模组版本
mc_version=1.18.2//mc版本
forge_version=40.2.0//forge版本
mappings_version=2022.11.06-1.18.2//映射表版本mapping_channel=parchment//映射表,用于反混淆
注意,mod_id
只能是下划线+小写字母,并且不能与其它模组的id重名。
按以下修改~/build.gradle
,这里保留原文件详细的注释。其中涉及到Mixin的内容将在后面说明。
buildscript {repositories {// These repositories are only for Gradle plugins, put any other repositories in the repository block further belowmaven { url = 'https://maven.minecraftforge.net' }maven { url = 'https://maven.parchmentmc.org' }//mixin仓库地址maven {name = "SpongePowered"url = 'https://repo.spongepowered.org/repository/maven-public/'}mavenCentral()}dependencies {classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: trueclasspath 'org.parchmentmc:librarian:1.+'classpath group: 'org.spongepowered', name: 'mixingradle', version: '0.7-SNAPSHOT'}
}apply plugin: 'net.minecraftforge.gradle'
// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
apply plugin: 'org.parchmentmc.librarian.forgegradle'
apply plugin: 'org.spongepowered.mixin'version =property("mc_version")+"-"+property("mod_version")+ "-" + property("version_name")
group = property("mod_group")
archivesBaseName = property("mod_name")// Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17.
java.toolchain.languageVersion = JavaLanguageVersion.of(17)compileJava.options.encoding = 'UTF-8'
tasks.withType(JavaCompile) {options.encoding = 'UTF-8'
}println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch'))
println(archivesBaseName+'-'+version)
minecraft {// The mappings can be changed at any time and must be in the following format.// Channel: Version:// snapshot YYYYMMDD Snapshot are built nightly.// stable # Stables are built at the discretion of the MCP team.// official MCVersion Official field/method names from Mojang mapping files//// You must be aware of the Mojang license when using the 'official' mappings.// See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md//// Use non-default mappings at your own risk. They may not always work.// Simply re-run your setup task after changing the mappings to update your workspace.mappings (channel: property("mapping_channel"), version: property("mappings_version"))accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')// Default run configurations.// These can be tweaked, removed, or duplicated as needed.runs {client {workingDirectory project.file('run')// Recommended logging data for a userdev environment// The markers can be added/remove as needed separated by commas.// "SCAN": For mods scan.// "REGISTRIES": For firing of registry events.// "REGISTRYDUMP": For getting the contents of all registries.property 'forge.logging.markers', ''// Recommended logging level for the console// You can set various levels here.// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levelsproperty 'forge.logging.console.level', 'info'// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.//property 'forge.enabledGameTestNamespaces', 'examplemod'arg "-mixin.config=" + mod_id + ".mixins.json"mods {zjtutor {source sourceSets.main}}}server {workingDirectory project.file('run')// Recommended logging data for a userdev environment// The markers can be added/remove as needed separated by commas.// "SCAN": For mods scan.// "REGISTRIES": For firing of registry events.// "REGISTRYDUMP": For getting the contents of all registries.property 'forge.logging.markers', ''// Recommended logging level for the console// You can set various levels here.// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levelsproperty 'forge.logging.console.level', 'info'// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.//property 'forge.enabledGameTestNamespaces', 'examplemod'arg "-mixin.config=" + mod_id + ".mixins.json"mods {zjtutor {//注意,“zjtutor”要改成读者自己的模组idsource sourceSets.main}}}// This run config launches GameTestServer and runs all registered gametests, then exits.// By default, the server will crash when no gametests are provided.// The gametest system is also enabled by default for other run configs under the /test command.gameTestServer {workingDirectory project.file('run')// Recommended logging data for a userdev environment// The markers can be added/remove as needed separated by commas.// "SCAN": For mods scan.// "REGISTRIES": For firing of registry events.// "REGISTRYDUMP": For getting the contents of all registries.property 'forge.logging.markers', ''// Recommended logging level for the console// You can set various levels here.// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levelsproperty 'forge.logging.console.level', 'info'// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.//property 'forge.enabledGameTestNamespaces', 'examplemod'mods {zjtutor {//注意,“zjtutor”要改成读者自己的模组idsource sourceSets.main}}}data {workingDirectory project.file('run')// Recommended logging data for a userdev environment// The markers can be added/remove as needed separated by commas.// "SCAN": For mods scan.// "REGISTRIES": For firing of registry events.// "REGISTRYDUMP": For getting the contents of all registries.property 'forge.logging.markers', ''// Recommended logging level for the console// You can set various levels here.// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levelsproperty 'forge.logging.console.level', 'info'// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.args '--mod', 'zjtutor', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')mods {zjtutor {//注意,“zjtutor”要改成读者自己的模组idsource sourceSets.main}}}}
}// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }mixin {add sourceSets.main, mod_id + ".refmap.json"mixin.debug=truemixin.checks=truemixin.hotSwap=true
}repositories {// Put repositories for dependencies here// ForgeGradle automatically adds the Forge maven and Maven Central for you// If you have mod jar dependencies in ./libs, you can declare them as a repository like so:// flatDir {// dir 'libs'// }flatDir {dir 'libs'}maven {name = "Progwml6 maven"url = "https://dvs1.progwml6.com/files/maven/"}maven {name = "ModMaven"url = "https://modmaven.dev"}
}dependencies {// Specify the version of Minecraft to use. If this is any group other than 'net.minecraft', it is assumed// that the dep is a ForgeGradle 'patcher' dependency, and its patches will be applied.// The userdev artifact is a special name and will get all sorts of transformations applied to it.minecraft "net.minecraftforge:forge:$mc_version-$forge_version"annotationProcessor 'org.spongepowered:mixin:0.8.3:processor'// Real mod deobf dependency examples - these get remapped to your current mappings// compileOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}:api") // Adds JEI API as a compile dependency// runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}") // Adds the full JEI mod as a runtime dependency// implementation fg.deobf("com.tterrag.registrate:Registrate:MC${mc_version}-${registrate_version}") // Adds registrate as a dependency// Examples using mod jars from ./libs// implementation fg.deobf("blank:coolmod-${mc_version}:${coolmod_version}")// For more info...// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html// http://www.gradle.org/docs/current/userguide/dependency_management.html
}// Example for how to get properties into the manifest for reading at runtime.
jar {manifest {attributes(["Specification-Title" : "zjtutor",//The title of the specification."Specification-Vendor" : "zjqc",//The vendor of the specification."Specification-Version" : 1, // The version of the specification."Implementation-Title" : project.name,//The build number of the implementation."Implementation-Version" : project.jar.archiveVersion,//The build number of the implementation."Implementation-Vendor" : "zjqc",//The vendor of the implementation."Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")])}
}// Example configuration to allow publishing using the maven-publish plugin
// This is the preferred method to reobfuscate your jar file
jar.finalizedBy('reobfJar')
// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing
// publish.dependsOn('reobfJar')publishing {publications {mavenJava(MavenPublication) {artifact jar}}repositories {maven {url "file://${project.projectDir}/mcmodsrepo"}}
}
模组信息——mods.toml
~/src/main/resources/META-INF/mods.toml
中记录了mod的相关信息,这里保留原文件中详细的注释,不做过多说明。
# This is an example mods.toml file. It contains the data relating to the loading mods.
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
# The overall format is standard TOML format, v0.5.0.
# Note that there are a couple of TOML lists in this file.
# Find more information on toml format here: https://github.com/toml-lang/toml
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
modLoader="javafml" #mandatory
# A version range to match for said mod loader - for regular FML @Mod it will be the forge version
loaderVersion="[39,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
license="MIT"
# A URL to refer people to when problems occur with this mod
#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
# A list of mods - how many allowed here is determined by the individual mod loader
[[mods]] #mandatory
# The modid of the mod
modId="zjtutor" #mandatory
# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
# ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata
# see the associated build.gradle script for how to populate this completely automatically during a build
version="${file.jarVersion}" #mandatory# A display name for the mod
displayName="Zjtutor" #mandatory
# A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
# A URL for the "homepage" for this mod, displayed in the mod UI
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
# A file name (in the root of the mod JAR) containing a logo for display
#logoFile="logo.png" #optional
# A text field displayed in the mod UI
credits=""" """ #optional
# A text field displayed in the mod UI
authors="指间青春" #optional
# The description text for the mod (multi line!) (#mandatory)
description='''这是一个教程模组。
'''
修改源文件修饰符——accesstransformer.cfg
创建~/src/main/resources/META-INF/accesstransformer.cfg
。这个文件用于修改Minecraft源文件类的访问修饰符和final
修饰符,用法参考Access Transformers,当前这里不添加任何内容。
修改源文件——Mixin
某些情况下需要修改Minecraft源文件中某个类中的成员或者方法,可以使用Mixin实现,这里只提供简单的整合方法,具体用法请参考Mixins on Minecraft Forge和Mouse0w0翻译的在Minecraft Forge中使用Mixin。
在build.gradle中,我们已经添加了Mixin必要的依赖。接下来添加包com.zjqc.zjtutor.mixins
,可以修改成自己的包名,用于存放Mixin类。添加文件/ZjTutor/src/main/resources/zjtutor.mixins.json
:
{"required": true,"package": "com.zjqc.zjtutor.mixins","compatibilityLevel": "JAVA_17","refmap": "zjtutor.refmap.json","mixins": [],"client": [],"minVersion": "0.8"
}
其中package
对应的value是存放Mixin类的包名,refmap
的value改成<modid>.refmap.json
, mixins
的key记录记录自己添加的Mixin类,目前为空。
Mod类
接下来,需要在项目中添加一个类作为模组的主类。这里,在包com.zjqc.zjtutor
下添加了ZjTutorMod.java
。并添加注解@Mod
,其中参数填入自己的模组id,在游戏启动的时候,FML(Forge Mod Loader)会扫描带有@Mod
的类并将其当作一个模组,一个项目中可以存在多个模组。
import net.minecraftforge.fml.common.Mod;@Mod(ZjTutorMod.MOD_ID)
public class ZjTutorMod {public static final String MOD_ID = "zjtutor";public ZjTutorMod() {}
}
运行与Build
现在模组可以运行了,在控制台输入gradlew runClient
启动游戏。
输入gradlew build
构建打包,在~\build\libs
下找到打包后的jar文件
Parchment
Minecraft是闭源项目,发布的游戏文件是经过混淆的,如果将游戏文件反编译将看到如下代码:
这样的源码是难以看懂的,为了方便模组开发,Mojang公布了部分反混淆的代码,Parchment在Mojang的基础上做了一定的补充。前面已经整合了Parchment,详细的用法请参考ParchmentMC Getting Started。