本篇教程的视频
本篇教程的源代码
GitHub地址:
流体类型基类
在Forge中,添加流体的方法与Fabric有些不一样
当然总体上还是差不多的,我们需要完成流体类型、流体和流体方块三部分内容的编写
简单来说,FluidType决定“是什么液体”,Fluid决定“怎么流”,LiquidBlock决定“如何作为方块存在于世界中”
FluidType: Forge特有的抽象层,它决定流体的各种属性,如密度、粘度、温度、雾效等等
Fluid:真正的流体对象,决定流体的流动速度、衰减规则等
LiquidBlock:负责流体的显示、交互逻辑(能够倒出来的流体,基本都需要依赖LiquidBlock来实现)
首先我们来创建一个BaseFluidType类
1 2 3 4 5
| public class BaseFluidType extends FluidType { public BaseFluidType(Properties properties) { super(properties); } }
|
这个类作为我们流体类型的基类,在这里面我们来定义一些流体的属性
添加字段
当然,这里我们首先要修改构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private final ResourceLocation stillTexture; private final ResourceLocation flowingTexture; private final ResourceLocation overlayTexture; private final int tintColor; private final Vector3f fogColor;
public BaseFluidType(ResourceLocation stillTexture, ResourceLocation flowingTexture, ResourceLocation overlayTexture, int tintColor, Vector3f fogColor, Properties properties) { super(properties); this.stillTexture = stillTexture; this.flowingTexture = flowingTexture; this.overlayTexture = overlayTexture; this.tintColor = tintColor; this.fogColor = fogColor; }
|
这里我们加上五个新的字段
stillTexture:静止状态流体的纹理
flowingTexture:流动状态流体的纹理
overlayTexture:覆盖非不透明方块的纹理
tintColor:流体本身的颜色
fogColor:流体中迷雾的颜色
然后在构造函数中,添加各自的形参,并初始化这些属性
重写 initializeClient 方法
接下来我们还要重写initializeClient方法,让渲染器能正确渲染流体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @Override public void initializeClient(Consumer<IClientFluidTypeExtensions> consumer) { consumer.accept(new IClientFluidTypeExtensions() { @Override public ResourceLocation getStillTexture() { return stillTexture; }
@Override public ResourceLocation getFlowingTexture() { return flowingTexture; }
@Override public ResourceLocation getOverlayTexture() { return overlayTexture; }
@Override public int getTintColor() { return tintColor; }
@Override public @NotNull Vector3f modifyFogColor(Camera camera, float partialTick, ClientLevel level, int renderDistance, float darkenWorldAmount, Vector3f fluidFogColor) { return fogColor; }
@Override public void modifyFogRender(Camera camera, FogRenderer.FogMode mode, float renderDistance, float partialTick, float nearDistance, float farDistance, FogShape shape) { RenderSystem.setShaderFogStart(1f); RenderSystem.setShaderFogEnd(6f); } }); }
|
我们需要实例化IClientFluidTypeExtensions,然后里面还需要重写一些方法,将我们上面的属性传入
前几个方法我就不用解释了,显而易见的,我就说说最后一个modifyFogRender方法
setShaderFogStart:设置迷雾的起始距离
setShaderFogEnd:设置迷雾的结束距离
这个是决定我们在流体之中显示的迷雾渲染距离的,具体的可结合实际调整
注册 FluidType
接下来我们再创建一个ModFluidTypes类,用来注册我们的流体类型
1 2 3 4 5 6 7 8
| public class ModFluidTypes { public static final DeferredRegister<FluidType> FLUID_TYPES = DeferredRegister.create(ForgeRegistries.Keys.FLUID_TYPES, TutorialMod.MOD_ID);
public static void register(IEventBus eventBus) { FLUID_TYPES.register(eventBus); } }
|
同样的,把延迟注册器和初始化注册方法先写上,并在主类调用register方法
1
| ModFluidTypes.register(modEventBus);
|
回过头来,我们注册流体类型
1 2 3 4 5 6 7 8 9
| public static final ResourceLocation WATER_STILL_RL = ResourceLocation.parse("block/water_still"); public static final ResourceLocation WATER_FLOWING_RL = ResourceLocation.parse("block/water_flow"); public static final ResourceLocation WATER_OVERLAY_RL = ResourceLocation.parse("block/water_overlay");
public static final RegistryObject<FluidType> SEWAGE_FLUID_TYPE = FLUID_TYPES.register("sewage", () -> new BaseFluidType(WATER_STILL_RL, WATER_FLOWING_RL, WATER_OVERLAY_RL, 0xFF2F4F4F, new Vector3f(47f / 255f, 79f / 255f, 79f / 255f), FluidType.Properties.create().density(15).viscosity(5)));
|
上面我们先写了三条ResourceLocation,它们分别对应静止状态、流动状态和覆盖非透明方块的纹理,当然为了方便起见,我们引用的是原版水的纹理
而后使用FLUID_TYPES.register来注册我们的流体类型,实例化我们自己写的BaseFluidType,把相应的参数传入
这里简单解释一下最后的FluidType.Properties,显然,这是流体的属性
这俩默认是1000,得设置一下,不然实体进去动弹不得(这让我想起了GTNH里的原油)
自定义流体
接下来我们来写自定义流体类
1 2 3 4 5
| public abstract class SewageFluid extends ForgeFlowingFluid { protected SewageFluid() { super(); } }
|
注意这是一个抽象类,并继承ForgeFlowingFluid,这里的构造方法我们先置空
Source
接下来我们新建一个内部类,继承SewageFluid这个类本身
1 2 3 4 5 6 7 8 9 10 11 12
| public static class Source extends SewageFluid {
@Override public boolean isSource(FluidState pState) { return true; }
@Override public int getAmount(FluidState pState) { return 8; } }
|
还要重写isSource和getAmount方法
isSource:判断是否是流体源(像我们桶倒出来的,都是源)
getAmount:获取流体的高度,这个数值会经过计算,水默认的值是8
Flowing
接下来我们再新建一个内部类,同样继承SewageFluid这个类本身
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static class Flowing extends SewageFluid {
@Override protected void createFluidStateDefinition(StateDefinition.Builder<Fluid, FluidState> pBuilder) { super.createFluidStateDefinition(pBuilder); pBuilder.add(LEVEL); }
@Override public boolean isSource(FluidState pState) { return false; }
@Override public int getAmount(FluidState pState) { return pState.getValue(LEVEL); } }
|
另外需要额外重写createFluidStateDefinition方法
这个方法是决定流体方块的方块状态的,这里的LEVEL是流体的液面高度
getAmount方法根据LEVEL来决定,因为这是流动的流体,液面高度会随着流动距离而降低
注册流体
接下来我们新建ModFluids类,在这里面注册流体
1 2 3 4 5 6 7 8
| public class ModFluids { public static final DeferredRegister<Fluid> FLUIDS = DeferredRegister.create(ForgeRegistries.FLUIDS, TutorialMod.MOD_ID);
public static void register(IEventBus eventBus) { FLUIDS.register(eventBus); } }
|
同样还是延迟注册器和初始化注册方法,在主类调用register方法
1
| ModFluids.register(modEventBus);
|
然后我们来注册流体
1 2 3 4 5 6 7 8 9 10 11 12
| public static final RegistryObject<FlowingFluid> STILL_SEWAGE = FLUIDS.register("sewage", SewageFluid.Source::new);
public static final RegistryObject<FlowingFluid> FLOWING_SEWAGE = FLUIDS.register("flowing_sewage", SewageFluid.Flowing::new);
public static final ForgeFlowingFluid.Properties SEWAGE_PROPERTIES = new ForgeFlowingFluid.Properties( ModFluidTypes.SEWAGE_FLUID_TYPE, STILL_SEWAGE, FLOWING_SEWAGE) .slopeFindDistance(2) .levelDecreasePerBlock(1) .block(ModBlocks.SEWAGE_BLOCK) .bucket(ModItems.SEWAGE_BUCKET);
|
一共是三个部分,一个静止流体,一个流动流体,一个流体属性
在流体属性中:
slopeFindDistance:这个是流体方块寻找斜坡的间隔
levelDecreasePerBlock:这个是流体方块每流过一个方块,高度就会减少多少
block:这个是流体方块
bucket:这个是流体桶
当然后面两个东西我们还没写,等会来写
我们先回过头去把SewageFluid的构造函数补充完整
1 2 3
| protected SewageFluid() { super(ModFluids.SEWAGE_PROPERTIES); }
|
流体方块类
接下来我们创建一个流体方块类,当然,如果你没有特殊的交互需求可以跳过这部分内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class SewageFluidBlock extends LiquidBlock { public SewageFluidBlock(RegistryObject<FlowingFluid> fluid, Properties settings) { super(fluid, settings); }
@Override public void entityInside(BlockState pState, Level pLevel, BlockPos pPos, Entity pEntity) { if (!pLevel.isClientSide() && pEntity instanceof LivingEntity livingEntity) { if (livingEntity.tickCount % 20 == 0) { livingEntity.hurt(pLevel.damageSources().magic(), 2.0F); } } } }
|
这里我们重写了entityInside方法,这里我们给实体添加了伤害,每1秒执行一次
工业污水能造成伤害也很合理吧,当然这个是终末地过来的案例
注册方块
接下来我们来注册方块
1 2 3 4
| public static final RegistryObject<LiquidBlock> SEWAGE_BLOCK = BLOCKS.register("sewage", () -> new SewageFluidBlock(ModFluids.STILL_SEWAGE, BlockBehaviour.Properties.of() .replaceable().noCollission().strength(100f).liquid() .pushReaction(PushReaction.DESTROY).noLootTable()));
|
如果你省略了上面的自定义流体方块的编写,那么这里实例化可以直接使用LiquidBlock
注册流体桶物品
我们来注册流体桶物品
1 2
| public static final RegistryObject<Item> SEWAGE_BUCKET = ITEMS.register("sewage_bucket", () -> new BucketItem(ModFluids.STILL_SEWAGE, new Item.Properties().stacksTo(1)));
|
和一般的流体桶一样,我们可以将其最大堆叠数量设置为1
这里我们大体上就和水一样好了
然后ModFluids相关的内容记得添加
不要忘记将流体桶添加到物品组中
1
| pOutput.accept(ModItems.SEWAGE_BUCKET.get());
|
客户端渲染设置
流体属于半透明类型的,所以它的渲染还需要在客户端类中进行注册的
在模组主类的ClientModEvents类的onClientSetup方法中加上以下语句
1 2 3 4
| event.enqueueWork(() -> { ItemBlockRenderTypes.setRenderLayer(ModFluids.FLOWING_SEWAGE.get(), RenderType.translucent()); ItemBlockRenderTypes.setRenderLayer(ModFluids.STILL_SEWAGE.get(), RenderType.translucent()); });
|
数据文件
语言文件
我们添加一下桶装流体的语言文件,方块也可以加一下
1 2
| add(ModItems.SEWAGE_BUCKET.get(), "Sewage Bucket"); add(ModBlocks.SEWAGE_BLOCK.get(), "Sewage");
|
模型文件
流体桶物品的模型文件
1
| basicItem(ModItems.SEWAGE_BUCKET.get());
|
方块的模型文件
1
| simpleBlockWithoutBlockModel(ModBlocks.SEWAGE_BLOCK);
|
当然我们只需要它的方块状态文件即可
流体标签
另外,对于可以倒出来的流体而言,我们还得写一个流体的标签,不然它会失去流体应有的属性,如让玩家浮起来等
这里我们就不使用数据生成来写了,直接手动创建标签
我们在resource/data/minecraft/tags/fluids文件夹下,创建一个water.json文件
1 2 3 4 5 6 7
| { "replace": false, "values": [ "tutorial_mod:sewage", "tutorial_mod:flowing_sewage" ] }
|
我们将replace设置为false,不然原版的水就被我们顶掉了
values中添加我们的流体和流动的流体
材质文件
我们还要将流体桶的材质文件放到对应的位置
方块模型
然后我们还要写一个流体方块的模型文件
1 2 3 4 5
| { "textures": { "particle": "block/water_still" } }
|
这里就指定一下它的粒子即可
那么到这里为止,我们的流体就完成了,接下来我们就可以进行测试了
测试
跑完数据生成之后,我们就可以启动游戏进行测试了