流体 1.20 Fabric 长线教程计划
本篇教程的视频
(待发布)
本篇教程的源代码
GitHub地址:TutorialMod-Fluid-1.20.1
本篇教程目标
- 理解原版流体及其注册、方块注册和流体桶的注册
- 学会编写流体和注册相关内容
查看源代码
和前面一样,我们来看看流体的源代码
这里我们先看看流体桶的注册
原版是有3个流体桶,分别是水桶、牛奶桶和岩浆桶,不过牛奶桶是不能倒出来的,而水和岩浆可以
所以这里我们重点来看水和岩浆这两个流体
桶装流体物品
我们到Items类中找到WATER_BUCKET,也就是水桶
1 | public static final Item WATER_BUCKET = register("water_bucket", new BucketItem(Fluids.WATER, new Item.Settings().recipeRemainder(BUCKET).maxCount(1))); |
可以看到,它实例化的是BucketItem,第一个参数是对应的流体,后面是物品设置
物品设置中,recipeRemainder配方剩余物品,也就是空桶会在使用后返回,后面的最大堆叠数量为1
岩浆桶也同样类似,接下去我们看看Fluids类
Fluids类
1 | public static final FlowableFluid FLOWING_WATER = register("flowing_water", new WaterFluid.Flowing()); |
这个是流体的注册类,水和岩浆一样都是有静止和流动状态的,所以它们分别有两个注册语句
register是它们的注册方法
1 | private static <T extends Fluid> T register(String id, T value) { |
第一个参数是流体名称,第二个参数是一个泛型,即流体的实例
注册语句与我们之前见到的差不多,它的注册表项调用的是FLUID
另外,在这个类中还有一个静态代码块
1 | static { |
这个静态块是将注册到FLUID注册表中的流体状态加入到STATE_IDS集合中,这是给游戏实际运行时使用的
根据上面的注册语句,我们还能看到水和岩浆有自己对应的流体类,现在我们就来看看它们
WaterFluid类
这里我们主要来看看水的,岩浆也是类似的,但是岩浆的一些逻辑是要比水复杂的,感兴趣的同学可以自己研究
1 | public abstract class WaterFluid extends FlowableFluid |
它是继承了FlowableFluid,这个类里面有流体流动的一些方法,而且这个类也很长,这里我们就不研究这个类了
1 |
|
getFlowing方法是获取流动状态的流体,返回注册类中的FLOWING_WATER
1 |
|
getStill方法是获取静止状态的流体,返回注册类中的WATER
1 |
|
getBucketItem方法是获取流体桶的物品,返回Items类中的WATER_BUCKET
1 |
|
randomDisplayTick方法是流体随机显示,也就是流体流动时的一些效果,比如粒子和声音
水的话主要是不同状态下发出的声音不同,而岩浆则会随机冒出一些粒子,或者在周围生成火焰亦或是点燃方块
1 |
|
getParticle方法是获取流体粒子,返回ParticleTypes类中的DRIPPING_WATER
1 |
|
isInfinite方法是判断流体是否是无限的,水默认是无限的,但也是由世界规则中的WATER_SOURCE_CONVERSION来决定
岩浆也是一样的,所以岩浆也是可以通过指令将其设置为无限岩浆
1 |
|
beforeBreakingBlock方法是流体在破坏方块之前调用的方法
水在破坏方块之前会将这个方块的掉落物生成后再破坏,比如用水来除草,可能会生成一堆小麦种子,但岩浆不会
1 |
|
getFlowSpeed方法是获取流体流动速度,这里设置为4
岩浆的流动速度还取决于当前维度是否是极暖维度
极暖维度即为原版游戏中的下界,在下界的岩浆会流动得比主世界的要快
1 |
|
toBlockState方法是获取流体方块状态
流体本身并不是方块,但在世界中是以方块的形式呈现的,所以还是要将它转换成相应的方块状态,让它呈现出来
FluidBlock.LEVEL是流体方块的高度,原版将其定义0至15,一共16个高度
值得注意的是,流体本身还有一个LEVEL属性,它的范围是1至8,流体本身的高度与流体方块的高度是在FlowableFluid的getBlockStateLevel方法中转换的
1 |
|
matchesType方法是判断流体类型是否匹配,如果传入的流体是水或者流动的水,那么返回true
但如果是两个不一样的流体,它们就不会相融
1 |
|
getLevelDecreasePerBlock方法是流体每流过一个方块降低的高度,流体降低到最低之后它就不流动了
水每流过一个方块降低1的高度
在极暖维度中,岩浆每流过一个方块降低1的高度,而在其他维度中,则降低2的高度
1 |
|
getTickRate方法是流体的更新速度
水每5 tick更新一次
在极暖维度中,岩浆每10 tick更新一次,而在其他维度中,则每30 tick更新一次
1 |
|
canBeReplacedWith方法是判断流体是否可以被替换,水可以被其他流体替换
1 |
|
getBlastResistance方法是获取流体爆炸抗性,水和岩浆的爆炸抗性都是100
所以原版中的那些爆炸是无法破坏水或者岩浆的
1 |
|
getBucketFillSound方法是获取流体桶装水的声音,返回SoundEvents类中的ITEM_BUCKET_FILL
另外,下面还有两个嵌套类,它们是在注册的的时候用的
1 | public static class Flowing extends WaterFluid { |
Flowing类是流动的水,它继承了WaterFluid类
appendProperties方法是添加方块状态属性,这里添加了LEVEL属性
getLevel方法是获取流体高度,返回LEVEL属性
isStill方法是判断流体是否是静止的,返回false
1 | public static class Still extends WaterFluid { |
Still类是静止的水,它继承了WaterFluid类
getLevel方法是获取流体高度,返回8
isStill方法是判断流体是否是静止的,返回true
流体方块
另外,流体方块也是要进行注册的
我们到Blocks中找到WATER
1 | public static final Block WATER = register( |
它实例化的是FluidBlock,后面的方块设置我们也见得多了
replaceable方法是可以被其他方块替换
noCollision方法是流体方块没有碰撞箱,所以流体方块是可以被穿过
dropsNothing方法是流体方块没有掉落物
liquid方法指定这个方块是流体方块
那么到这里为止,一个可以用桶装的,也可以被倒出来的流体就注册完了
后面还有一些标签和客户端渲染是设置,我们具体在后面再说
接下来就是我们自己来注册流体
注册流体
这里我们先写一个抽象流体类,这个是按照Fabric Wiki上的写法
如果你有一系列属性类似的流体,可以将它们共有的属性抽象出来,然后让它们继承这个抽象类,这样就可以避免重复写代码了
ModFluid
这里我们创建ModFluid这个抽象类,它继承FlowableFluid
1 | public abstract class ModFluid extends FlowableFluid { |
然后我们来重写一些方法,重写的方法我们前面都解释过了,这里就简单说明一下
1 |
|
这里重写matchesType方法,判断流体是否匹配
1 |
|
重写isInfinite方法,将我们的流体设置为有限
1 |
|
重写beforeBreakingBlock方法,仿照水的写法,在流体方块被破坏之前,将流体方块中的物品掉落出来
1 |
|
重写canBeReplacedWith方法,将流体方块设置为不可被替换
1 |
|
重写getFlowSpeed方法,设置流体方块流动速度为4
这里我们大体上就和水一样好了
1 |
|
重写getLevelDecreasePerBlock方法,设置流体方块每流过一个方块降低1的高度
1 |
|
重写getTickRate方法,设置流体方块更新速度为5
1 |
|
重写getBlastResistance方法,设置流体方块爆炸抗性为100
OilFluid
抽象类写好了,接下来就来写具体的实例
这里我们创建OilFluid这个类,它继承ModFluid,不过同样的,它也是一个抽象类
1 | public abstract class OilFluid extends ModFluid { |
这里我们先写两个嵌套类
1 | public static class Flowing extends OilFluid{ |
Flowing类继承OilFluid类
然后我们就重写其中的方法
appendProperties方法中添加LEVEL属性
isStill方法返回false
getLevel方法返回LEVEL属性
1 | public static class Still extends OilFluid{ |
Still类继承OilFluid类,然后重写其中的方法
isStill方法返回true
getLevel方法返回8
ModFluids
接下来就是来注册流体了,这里我们创建ModFluids
1 | public class ModFluids { |
然后我们将原版的register方法搬过来改一下
1 | private static <T extends Fluid> T register(String name, T fluid) { |
我们将模组的命名空间加上
另外还有一个静态代码块,不过这个东西写不写似乎没什么关系
1 | static { |
然后我们来注册流体,写法与原版的类似
1 | public static final FlowableFluid OIL = register("oil", new OilFluid.Still()); |
一个是流动的,一个是静止的,它们实例化相对应的类
另外还有一个用来初始化的方法
1 | public static void registerModFluids() { |
我们要在模组主类中调用这个方法
1 | ModFluids.registerModFluids(); |
物品、方块注册
物品注册
接下来我们就可以进行桶装流体的注册
1 | public static final Item OIL_BUCKET = registerItems("oil_bucket", new BucketItem( |
这里我们创建一个OIL_BUCKET,实例化BucketItem,然后我们传入我们的流体和物品设置
加入物品栏
然后我们就可以将桶装流体加入物品栏
1 | entries.add(ModItems.OIL_BUCKET); |
注册流体方块
随后我们就来注册流体方块
1 | public static final Block OIL = Registry.register(Registries.BLOCK, new Identifier(TutorialModRe.MOD_ID, "oil"), |
这里我们要使用Registry.register来注册,因为它没有对应的方块物品
实例化FluidBlock,传入我们的流体,方块设置可以直接用原版水的
OilFluid另外的重写方法
接下来我们回到OilFluid类,重写另外四个方法
1 |
|
重写getFlowing方法,返回注册的流动的流体
1 |
|
重写getStill方法,返回注册的静止的流体
1 |
|
重写getBucketItem方法,返回注册的桶装流体
1 |
|
重写toBlockState方法,将流体方块设置为我们的流体方块
客户端渲染设置
流体属于半透明类型的,所以它的渲染是要在客户端类中进行注册的
同样我们还要为流体指定颜色
1 | FluidRenderHandlerRegistry.INSTANCE.register(ModFluids.OIL, ModFluids.FLOWING_OIL, |
这里我们使用FluidRenderHandlerRegistry来注册,这个是Fabric的API
传入我们的流体和流动的流体,然后实例化SimpleFluidRenderHandler
传入静止的流体的纹理,流动的流体的纹理,当然这两个纹理都是原版水的纹理
最后一个是16进制颜色,这里我们使用0x42413b,你可以按照你的想法进行设置
还有一个是渲染层的设置
1 | BlockRenderLayerMap.INSTANCE.putFluids(RenderLayer.getTranslucent(), ModFluids.OIL, ModFluids.OIL_FLOWING); |
我们将其设置为半透明,然后传入我们的流体和流动的流体
数据文件
语言文件
我们添加一下桶装流体的语言文件
1 | translationBuilder.add(ModItems.OIL_BUCKET, "Oil Bucket"); |
模型文件
1 | itemModelGenerator.register(ModItems.OIL_BUCKET, Models.GENERATED); |
物品模型还是和一般的物品一样
流体标签
另外,对于可以倒出来的流体而言,我们还得写一个流体的标签,不然它会失去流体应有的属性,如让玩家浮起来等
这里我们就不使用数据生成来写了,直接手动创建标签
我们在resource文件夹下创建一个data文件夹,然后创建一个minecraft文件夹
注意是minecraft,不是我们模组的命名空间
再创建一个tags文件夹,然后创建一个fluids文件夹,最后创建一个water.json文件
1 | { |
我们将replace设置为false,不然原版的水就被我们顶掉了
values中添加我们的流体和流动的流体
材质文件
我们还要将流体桶的材质文件放到对应的位置
方块状态
这里我们还要写一个方块状态文件,是流体方块的
1 | { |
这个是那种最简单的方块状态文件
方块模型
然后我们还要写一个流体方块的模型文件
1 | { |
这里就指定一下它的粒子即可
那么到这里为止,我们的流体就完成了,接下来我们就可以进行测试了
测试
跑完数据生成之后,我们就可以启动游戏进行测试了