正文:
I. 开发环境配置
我们需要一个带mixin的forge开发环境,这一步相当折磨人,网络不好的话半天时间都得砸这上面,但是不要灰心,过了这个坎接下来基本是顺风顺水。

1. 下载资源 & 修改build.gradle

首先去forge官网https:///plugin.php?id=link_redirect&target=https%3A%2F%2Ffiles.minecraftforge.net%2Fnet%2Fminecraftforge%2Fforge%2F下载一个1.12.2的mdk,将其解压

我的世界模组是什么编程python 我的世界mod编程_gradle

我选择解压到D:\LocalCodes\Minecraft\Example中,注意到解压出来的内容中有build.gradle和gradlew.bat两个文件,其他文件我们暂且忽略不管。先用记事本打开build.gradle

1. buildscript {
2.     repositories {
3.         maven { url = 'https://maven.minecraftforge.net/' }
4.         mavenCentral()
5.     }
6.     dependencies {
7.         classpath 'net.minecraftforge.gradle:ForgeGradle:3.+'
8.     }
9. }
10.         
11. apply plugin: 'net.minecraftforge.gradle'
12. // Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
13. apply plugin: 'eclipse'
14. apply plugin: 'maven-publish'
15. ...


复制代码

很长一串文本(Groovy语言,不去管他),接下来我们要对build.gradle文件进行一系列修改

我的世界模组是什么编程python 我的世界mod编程_java_02

按照上图进行修改,一共6处修改,务必确保每处修改都已完成。教程不对gradle进行深入阐述,但了解一些gradle相关的内容(不必完全掌握)会对你mod的项目管理有很大帮助,也能让你在部署开发环境的时候少走些弯路。

2. 运行gradle

接下来启动cmd,进入build.gradle所在的文件夹(我的话就是D:\LocalCodes\Minecraft\Example),调用命令


  1. gradlew build


初次运行会跑相当长一段时间,其间会下载各种游戏资源,并进行反编译。如果运行失败,并在log中显示了timeout字样,网络问题,多试几次说不定就好了。
 

我的世界模组是什么编程python 我的世界mod编程_java_03

运行成功后会出现BUILD SUCCESSFUL字样。上图是我运行gradlew build时的情况,因为之前已经下载过相关资源并反编译,所以速度很快。

注:你可能阅读过很多forge环境配置的教程,而每一篇都叫你调用gradlew eclipse之类的命令,然后说mod发布的时候调用gradlew build。一上来就build是在干什么?众所周知gradlew build是将src里的源码编译成能在生产环境(玩家的运行环境)下运行的jar,但运行gradlew build时还会在没配置过环境的电脑上“下载各种游戏资源,并进行反编”,教程正是利用了这个特点来配置无IDE的开发环境。

II. 编写你的第一个Mixin+Forge Mod

1. 写什么好呢?

我打算写个把地狱门边框从黑曜石改为木头的模组,大体效果是这样的

我的世界模组是什么编程python 我的世界mod编程_json_04

原先是搭黑曜石框点火启动,现在是搭木头框点火启动。

为了写出这个mod,我们得先知道mc里地狱门是怎么一回事,玩过mc的都知道怎么建地狱门,用黑曜石搭一个框,中间留出2x3的空白,然后用打火石点火即可,有经验的玩家还知道打雷引发的火,或者火焰弹引发的火也可以启动地狱门。事实上,用黑曜石搭一个合适的框,只要是火方块就能启动地狱门(在框内的空白中填充传送门方块)。教程专注于如何编写mixin+forge模组,在这里不对机制的代码基础进行阐述,相关内容参阅BlockFire.java、BlockPortal.java及Teleporter.java。

2. 第一个mixin

你会发现mixin指代了很多东西,有时候它指spongepowered开发的mixin library,同时它又指mixin library定义的一类文件mixin,所谓的mixin文件可以被理解为一种特殊的class文件,一定被@Mixin修饰,且在运行时被mixin library特殊处理。

我们先写个mixin对BlockPortal$Size.class进行修改,在src/main/java/com/example/examplemod下新建文件夹mixin,其下新建文件MixinBlockPortal$Size.java,用记事本打开,复制以下内容

package com.example.examplemod.mixin;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import org.objectweb.asm.Opcodes;

import net.minecraft.block.BlockPortal;
import net.minecraft.block.Block;
import net.minecraft.init.Blocks;

@Mixin(BlockPortal.Size.class)
public abstract class MixinBlockPortal$Size {
        
        @Redirect(method = "getDistanceUntilEdge", at = @At(value = "FIELD", target = "net.minecraft.init.Blocks.OBSIDIAN:Lnet/minecraft/block/Block;", opcode = Opcodes.GETSTATIC))
        private Block proxy_getDistanceUntilEdge_getStatic_OBSIDIAN() {
                return Blocks.LOG;
        }
        
        @Redirect(method = "calculatePortalHeight", at = @At(value = "FIELD", target = "net.minecraft.init.Blocks.OBSIDIAN:Lnet/minecraft/block/Block;", opcode = Opcodes.GETSTATIC))
        private Block proxy_calculatePortalHeight_getStatic_OBSIDIAN() {
                return Blocks.LOG;
        }
}

这样我们的第一个mixin就写好了,教程不会详细讲述mixin如何写,如果对这块内容有需求,参见文末的拓展阅读。

3. mixins.example.json

有了mixin文件,我们还需要告诉mixin library该mixin的存在,这样它才能应用你的mixin。首先在src/main/resources下新建文件mixins.example.json,复制以下内容

{
  "required": true,
  "compatibilityLevel": "JAVA_8",
  "package": "com.example.examplemod.mixin",
  "mixins": [
    "MixinBlockPortal$Size"
  ],
  "client": [
  ],
  "server": [
  ],
  "injectors": {
    "defaultRequire": 1
  },
  "refmap": "mixins.example.refmap.json"
}

 接着在build.gradle的最后加上代码

mixin {
     add sourceSets.main, 'mixins.example.refmap.json'
     config 'mixins.example.json'
 }

我们先暂且不管mixins.example.json以及这次对build.gradle的修改作用是什么,这块内容将在Part III 2.1(modid-1.0.jar结构分析)时提及。

4. 第二个mixin

现在写第二个mixin,在MixinBlockPortal$Size.java所处的文件夹下(即文件夹mixin)新建文件MixinTeleporter.java,复制以下内容

package com.example.examplemod.mixin;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import org.objectweb.asm.Opcodes;

import net.minecraft.world.Teleporter;
import net.minecraft.block.Block;
import net.minecraft.init.Blocks;

@Mixin(Teleporter.class)
public abstract class MixinTeleporter {
        
        @Redirect(method = "makePortal", at = @At(value = "FIELD", target = "net.minecraft.init.Blocks.OBSIDIAN:Lnet/minecraft/block/Block;", opcode = Opcodes.GETSTATIC))
        private Block proxy_makePortal_getStatic_OBSIDIAN() {
                return Blocks.LOG;
        }
        
        @Redirect(method = "placeInPortal", at = @At(value = "FIELD", target = "net.minecraft.init.Blocks.OBSIDIAN:Lnet/minecraft/block/Block;", opcode = Opcodes.GETSTATIC))
        private Block proxy_placeInPortal_getStatic_OBSIDIAN() {
                return Blocks.LOG;
        }
}

接着修改mixins.example.json,在"MixinBlockPortal$Size"后加入, "MixinTeleporter",使之变为"MixinBlockPortal$Size", "MixinTeleporter"。

III. 发布!
别着急,mc模组从开发环境到生产环境(玩家的运行环境)之间还有一段路要走,视情况可能比开发环境配置还棘手。不过,在一般情况下就是一句gradlew build的事。

1. Build看看

cmd中执行


  1. gradlew build


我的世界模组是什么编程python 我的世界mod编程_java_05

成功build,到build/libs中拿结果(modid-1.0.jar文件)。