流体
原版 Minecraft 的两种流体——水和熔岩——是特殊的方块,能够向一定距离内的相邻方块扩散。它们通常不是固体,实体可以进入并在其中“游泳”。
在模组 Minecraft 中,尤其是许多科技 Mod 中,流体还会充当配方原料。这是因为流体存在于独立的注册表中,只是借助流体方块放入世界;因此,流体实际上可以完全独立于方块理解。
本文介绍如何添加自己的流体。世界中的流体行为请参阅世界中的流体;在配方中使用流体请参阅配方中的流体。
警告 原版只有两种流体,并且对它们进行了大量特殊处理。因此,本分类中的部分系统实现方式比较迂回,又存在难以通过测试全面覆盖的边界情况,不一定总能正确工作。如果发现流体相关错误,请通过 Discord 联系我们,或在 GitHub 提交问题。
Fluid 与 FluidType
注册流体前,需要先了解 Minecraft 和 NeoForge 的一些设计选择。
Minecraft 中,水和熔岩各有流动流体和源流体两个变体。它们主要通过硬编码运作,同时与 FluidState 有一定关联,详见下文。这种硬编码不易使用,有时甚至难以复用。因此,NeoForge 引入 FluidType 类,并修改了大量位置来使用它。FluidType 主要保存流体共用的逻辑,例如声音、是否支持划船等,只将实际流动逻辑留在流体自身中。FluidType 位于 NeoForge 新增的独立注册表中,所以除了注册 Fluid,还必须注册 FluidType。
了解这些概念后,就可以开始创建流体。以下以熔融铁流体为例。首先需要两个注册表:
public static final DeferredRegister<Fluid> FLUIDS =
DeferredRegister.create(Registries.FLUID, ExampleMod.MOD_ID);
public static final DeferredRegister<FluidType> FLUID_TYPES =
DeferredRegister.create(NeoForgeRegistries.FLUID_TYPES, ExampleMod.MOD_ID);
创建 Fluid 需要 FluidType,因此先创建后者。FluidType 的选项通过 Properties 对象定义,与方块属性类似。
public static final DeferredHolder<FluidType, FluidType> MOLTEN_IRON_TYPE = FLUID_TYPES.register(
// The registry name of the fluid type. Usually it makes sense to name it the same as the `Fluid`.
"molten_iron",
// The factory for the fluid type, accepting a `FluidType.Properties` object.
id -> new FluidType(FluidType.Properties.create()
// The translation key of the fluid. While this will not be visible in vanilla Minecraft,
// it will be visible if the fluid is stored in e.g. a modded tank, or when looked at
// in-world with WAILA (What Am I Looking At?) or similar mods installed.
// `id` is the lambda parameter we got passed in.
.descriptionId(Util.makeDescriptionId("fluid", id))
// Set lava-like sounds for our fluid. This is only relevant if you have a bucket item,
// which we will look at later.
.sound(SoundActions.BUCKET_FILL, SoundEvents.BUCKET_FILL_LAVA)
.sound(SoundActions.BUCKET_EMPTY, SoundEvents.BUCKET_EMPTY_LAVA)
// We cannot swim or drown in molten iron.
.canDrown(false)
.canSwim(false)
// We want molten iron to slightly glow.
.lightLevel(5)
));
建议
FluidType中还有许多其他方法。例如,创建类似水的流体时,可以关注supportsBoating()和isWaterLike()。完整方法列表请查看FluidType.Properties的源代码。并非所有方法都由原版系统使用。其中一些方法,例如
temperature()和density(),是在FluidType系统最初设计阶段为 Mod 兼容需求提出的,具体 Mod 系统可能会使用,也可能不会。
创建 FluidType 后,接着创建 Fluid。NeoForge 提供了基础类 BaseFlowingFluid,它有 Source、Flowing 和 Properties 三个内部类。Source 与 Flowing 都继承 BaseFlowingFluid,结构沿用原版 WaterFluid 和 LavaFluid 的设计。Properties 同样类似方块属性对象,用于关联流体类型、源流体、流动流体,以及后面会介绍的桶和流体方块等。
源流体、流动流体依赖流体属性,而流体属性又依赖这两种流体,因此需要注意静态初始化顺序,并在部分位置加上类名限定。假设流体统一放在 ModFluids 类中,代码如下:
// The source fluid. This is usually named without specifying "source" in the name.
public static final DeferredHolder<Fluid, BaseFlowingFluid.Source> MOLTEN_IRON = FLUIDS.register(
// The registry name.
"molten_iron",
// The source fluid supplier. Qualify the properties with the class name here.
() -> new BaseFlowingFluid.Source(ModFluids.MOLTEN_IRON_PROPERTIES));
// The flowing fluid. The name is commonly prefixed with "flowing_".
public static final DeferredHolder<Fluid, BaseFlowingFluid.Flowing> FLOWING_MOLTEN_IRON = FLUIDS.register(
// The registry name.
"flowing_molten_iron",
// The flowing fluid supplier. Again, qualify the properties with the class name.
() -> new BaseFlowingFluid.Flowing(ModFluids.MOLTEN_IRON_PROPERTIES));
// The fluid properties. We will use this later to connect additional stuff
// to the fluid, for example the bucket.
public static final BaseFlowingFluid.Properties MOLTEN_IRON_PROPERTIES =
// Parameters are the fluid type, the source fluid and the flowing fluid.
new BaseFlowingFluid.Properties(MOLTEN_IRON_TYPE, MOLTEN_IRON, FLOWING_MOLTEN_IRON);
完成后,流体应已加载到游戏中,并能被配方使用,但渲染还不正常。需要在仅客户端的 Mod 总线事件处理器中注册流体模型:
@SubscribeEvent // on the mod event bus only on the physical client
private static void registerFluidModels(RegisterFluidModelsEvent event) {
event.register(new FluidModel.Unbaked(
// The still, flowing and overlay texture materials. The overlay material is nullable;
// if it is null, no overlay will be displayed. Overlays are only used for in-world fluids,
// so if you don't have an in-world fluid, it should always be null.
new Material(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "block/molten_iron_still")),
new Material(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "block/molten_iron_flowing")),
new Material(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "block/molten_iron_overlay")),
// The fluid tint source. We leave it at null, which means no tint. See below for more info.
null,
// The fluid renderer. Can be supplied for entirely custom fluid rendering.
// This parameter is optional and will default to null if omitted.
null),
// Suppliers for the still and flowing fluids.
ModFluids.MOLTEN_IRON,
ModFluids.FLOWING_MOLTEN_IRON
);
}
资源
流体虽然已经存在,但还需要添加资源文件,包括纹理、模型和翻译。
首先添加纹理。制作资源时,建议以原版水或熔岩纹理为基础。流动流体尤其需要注意:它实际上使用 2×2 的纹理,由流体渲染器采样。文件命名和位置如下,其中 examplemod 为你的 Mod ID:
- 静止纹理:
assets/examplemod/textures/block/molten_iron_still.png。 - 流动纹理:
assets/examplemod/textures/block/molten_iron_flowing.png。 - 覆盖纹理(需要时):
assets/examplemod/textures/block/molten_iron_overlay.png。
警告 这些路径与先前传入
RegisterFluidModelsEvent#register()的路径对应。也可以放在其他位置,但必须同步调整渲染器中的路径。流体纹理位于方块图集中,因此应放在
textures/block文件夹中。
大多数流体具有动画,因此还需要配套的 .png.mcmeta 文件。同样可以参考原版文件。更多信息请参阅纹理文档。
最后是翻译。流体的翻译键由 FluidType#descriptionId() 定义,可以通过 FluidType#getDescriptionId() 获取:
// In your LanguageProvider
@Override
protected void addTranslations() {
add(ModFluids.MOLTEN_IRON_TYPE.getDescriptionId(), "Molten Iron");
}
更多信息请参阅国际化、本地化与数据生成。
流体着色源
另见:着色
流体和方块一样可以着色。原版水使用着色,熔岩则不使用。NeoForge 修改了该系统以支持 Mod。相关逻辑均通过 FluidTintSource 接口实现;简单实现只需重写 #color():
// If possible, we want to use a singleton.
public final class MoltenIronTintSource implements FluidTintSource {
public static final MoltenIronTintSource INSTANCE = new MoltenIronTintSource();
private MoltenIronTintSource() {}
@Override
public int color(FluidState state) {
// Return whatever color you want here. The value is in ARGB; make sure that you include
// a proper alpha value, otherwise the rendering will be invisible.
return 0xff000000;
}
}
创建着色源后,在 RegisterFluidModelsEvent 中使用:
@SubscribeEvent // on the mod event bus only on the physical client
private static void registerFluidModels(RegisterFluidModelsEvent event) {
event.register(new FluidModel.Unbaked(
new Material(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "block/molten_iron_still")),
new Material(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "block/molten_iron_flowing")),
new Material(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "block/molten_iron_overlay")),
// Use our tint source instance here.
MoltenIronTintSource.INSTANCE),
ModFluids.MOLTEN_IRON,
ModFluids.FLOWING_MOLTEN_IRON
);
}
建议 如果实现只重写
#color(FluidState),也可以用函数式接口 lambda 代替单例类。
需要更复杂的行为时,还可以使用以下方法。它们默认均委托给 #color(FluidState):
colorInWorld(FluidState fluidState, BlockState blockState, BlockAndTintGetter level, BlockPos pos):在世界中显示流体时使用,可以根据位置决定颜色。水通过它实现随生物群系变化的颜色。colorAsStack(FluidStack stack):根据FluidStack决定颜色,例如根据数据组件着色。原版不使用它,因为FluidStack是 NeoForge 提供的系统。
此外,FluidTintSource 继承 BlockTintSource,因此根据 BlockState 着色的全部方法也都可用。
来源: NeoForged/Documentation(26.1)。中文翻译依据提交
4a680cdd4d15。