概述
在Minecraft中,体会到Mod之间的相互协作,正是Minecraft游戏的乐趣之一。这自然也包括自己的Mod本身,向其他Mod提供相应的API用于让其他Mod调用。本部分通过提供自定义金铁炉子的配方API的方式,向读者一步一步展示一个API,通常情况下是如何形成的。
API包
大部分情况下,Mod都将其的API放在一个特定的包里。这里我们采用com.github.ustc_zzzz.fmltutor.api
包。我们新建这样的一个包,然后新建我们的package-info.java
文件。众所周知,package-info.java
这一文件通常用于描述一个包的相关信息,我们很快就可以知道这个文件在Mod之间具体的作用。
src/main/java/com/github/ustc_zzzz/fmltutor/api/package-info.java:
/**
* @author ustc_zzzz
*/
@net.minecraftforge.fml.common.API(apiVersion = "0.1.0", owner = "fmltutor", provides = "FMLTutorAPI")
package com.github.ustc_zzzz.fmltutor.api;
实际上,和其他的package-info.java
相比,我们只为其添加了一个@API
注解。这个注解用于通知FML这个包内包含有一个Mod的API:
apiVersion
代表这个API的版本owner
代表这个API的所属Mod,应与Mod的唯一标识符(这里是fmltutor
)一致provides
代表这个API的名称,不同的API应该不相同,名称对大小写等不做限制,不过通常是Mod名称和“API”这三个字母组成
在上一节中我们提到过,虽然我们不推荐把API的代码Shade进自己的Mod,但是有的时候,有一些API,比如说cofh.api
包下的Redstone Flux API——对很多Mod都是十分必要的,虽然说这个API是由cofhlib
这一Mod提供的,但是在很多情况下运行该Mod的时候根本没有cofhlib
的存在——因此所有使用到Redstone Flux API的Mod不得不Shade一份Redstone Flux API的二进制文件,以保证自己的Mod可以正常使用。
在这种情况下,Forge会注意到一个整合包里的所有Mod,包含有很多份Redstone Flux API的二进制代码——也就是cofh.api
包下的代码出现了很多次。为了解决这一问题,FML会检查添加了@API
注解的包对应的package-info
,并注意到CoFHAPI
这一API出现了多次,那么FML便只会选择一份API加载,从而避免了潜在的冲突。
总而言之,由于一份API可能在Mod列表中出现多次包含,通过@API
注解,FML保证相应的API只会加载一份。
具体实现
我们在其下新建一个FMLTutorRecipeManager
抽象类:
src/main/java/com/github/ustc_zzzz/fmltutor/api/FMLTutorRecipeManager.java:
package com.github.ustc_zzzz.fmltutor.api;
import net.minecraft.item.ItemStack;
public abstract class FMLTutorRecipeManager
{
public abstract void addRecipe(ItemStack input, ItemStack output);
public abstract ItemStack getResult(ItemStack input);
}
这个抽象类的两个抽象方法本身很简单,在此不再赘述。
我们在com.github.ustc_zzzz.fmltutor
包下新建一个FMLTutorRecipeManagerImpl
类,作为这个类的一个实现:
src/main/java/com/github/ustc_zzzz/fmltutor/FMLTutorRecipeManagerImpl.java:
package com.github.ustc_zzzz.fmltutor;
import com.github.ustc_zzzz.fmltutor.api.FMLTutorRecipeManager;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.FurnaceRecipes;
import java.util.LinkedList;
public class FMLTutorRecipeManagerImpl extends FMLTutorRecipeManager
{
public static final FMLTutorRecipeManagerImpl INSTANCE = new FMLTutorRecipeManagerImpl();
private final LinkedList<Recipe> recipes = new LinkedList<Recipe>();
@Override
public void addRecipe(ItemStack input, ItemStack output)
{
this.recipes.addFirst(new Recipe(input, output));
}
@Override
public ItemStack getResult(ItemStack input)
{
for (Recipe recipe : recipes)
{
if (recipe.input.isItemEqual(input))
{
return recipe.output;
}
}
return FurnaceRecipes.instance().getSmeltingResult(input);
}
private static class Recipe
{
private final ItemStack input;
private final ItemStack output;
private Recipe(ItemStack input, ItemStack output)
{
this.input = input;
this.output = output;
}
}
}
这个实现本身很简单,这里也不加赘述了。我们这里添加了一个INSTANCE
字段用于稍后在API中获取实现:
src/main/java/com/github/ustc_zzzz/fmltutor/api/FMLTutorRecipeManager.java(部分):
public static final FMLTutorRecipeManager INSTANCE;
static
{
try
{
Class<?> implClass = Class.forName("com.github.ustc_zzzz.fmltutor.FMLTutorRecipeManagerImpl");
INSTANCE = (FMLTutorRecipeManager) implClass.getDeclaredField("INSTANCE").get(null);
}
catch (Exception e)
{
throw new RuntimeException("Cannot find implementation", e);
}
}
这里使用了一点简单的Java反射API,用于获取该字段,对于读者来说应该不难理解。对反射API不熟悉的读者可以先行阅读附录里的相关内容。
然后我们修改一下金属炉子的相关代码:
src/main/java/com/github/ustc_zzzz/fmltutor/tileentity/TileEntityMetalFurnace.java(部分):
// previous code: ItemStack furnaceRecipeResult = FurnaceRecipes.instance().getSmeltingResult(itemStack);
ItemStack furnaceRecipeResult = FMLTutorRecipeManager.INSTANCE.getResult(itemStack);
启动游戏,一切依旧正常运转。
打包
我们如果想要让自己的Mod的API供其他Mod使用,就要提供相应的JAR文件——实际上,我们可以通过Gradle很轻松地生成这一文件。我们在build.gradle
文件下加入以下代码:
build.gradle:
task apiJar(type: Jar) {
classifier = 'api'
from sourceSets.main.output
from sourceSets.main.allSource
include 'com/github/ustc_zzzz/fmltutor/api/**'
}
artifacts {
archives apiJar
}
上面这段代码定义了一个生成JAR的Task:apiJar
。classifier
用于设置最后生成文件的后缀,这里将导致最后生成fmltutor-1.0.0-api.jar
文件。紧随其后的两句from
开头的语句指定Gradle分别从生成的字节码二进制文件和源代码中提取文件,而最后的include
开头的语句指定Gradle提取com.github.ustc_zzzz.fmltutor.api
包下的所有文件。
最后的以artifacts
开头的部分代码指定我们需要使用apiJar
这一Task生成JAR。因此,如果不出所料,在运行相应的构建语句,比如(针对Linux或者Mac OS X):
./gradlew build
或者(针对Microsoft Windows):
gradlew.bat build
之后的build/libs
目录下应该同时会生成fmltutor-1.0.0-api.jar
这一文件。
这个JAR文件目前应该只包含五个文件:
META-INF/MANIFEST.MF
com/github/ustc_zzzz/fmltutor/api/package-info.class
com/github/ustc_zzzz/fmltutor/api/FMLTutorRecipeManager.class
com/github/ustc_zzzz/fmltutor/api/package-info.java
com/github/ustc_zzzz/fmltutor/api/FMLTutorRecipeManager.java
这同时包含了编译后的二进制和源代码,应该是我们想要看到的。
其他Mod只需要引用这一JAR,就可以完成和我们编写的Mod的交互了。
读者也可以只单独执行这一Task,比如(针对Linux或者Mac OS X):
./gradlew apiJar
或者(针对Microsoft Windows):
gradlew.bat apiJar
最后将只生成fmltutor-1.0.0-api.jar
这一文件。