创建你的第一个流体
本示例将介绍如何创建一种酸液流体:站在其中的实体会受到伤害、虚弱并失明。 为此,我们需要两个流体实例,分别用于源状态和流动态;还需要一个液体方块、一个桶物品,以及一个流体标签。
创建流体类
首先,我们会创建一个抽象类,这里命名为 AcidFluid,并让它继承基础的 FlowingFluid 类。 随后,我们会重写那些源流体和流动流体都应保持一致的方法。
请特别注意以下方法:
animateTick用于显示粒子和播放声音。 下面展示的行为基于水:水在流动时会播放声音,并会产生水下气泡粒子。entityInside用于处理实体接触流体时应发生的事情。 我们会以水为基础,使其熄灭实体身上的火焰,同时也让其中的实体受到伤害、虚弱并失明——毕竟这是酸液。canBeReplacedWith处理一部分流动逻辑。请注意,ModFluidTags.ACID目前还没有定义,我们会在最后处理它。
把这些内容整合起来后,我们会得到以下类:
java
public abstract class AcidFluid extends FlowingFluid {
@Override
public void animateTick(Level world, BlockPos pos, FluidState state, RandomSource random) {
if (!state.isSource() && !(Boolean) state.getValue(FALLING)) {
if (random.nextInt(64) == 0) {
world.playLocalSound(
pos.getX() + 0.5,
pos.getY() + 0.5,
pos.getZ() + 0.5,
SoundEvents.BUBBLE_COLUMN_WHIRLPOOL_AMBIENT, // Bubbling poison/swamp sound
SoundSource.AMBIENT,
random.nextFloat() * 0.25F + 0.75F,
random.nextFloat() + 0.5F,
false);
}
} else if (random.nextInt(10) == 0) {
world.addParticle(
ParticleTypes.UNDERWATER, pos.getX() + random.nextDouble(), pos.getY() + random.nextDouble(),
pos.getZ() + random.nextDouble(), 0.0, 0.0, 0.0);
}
}
@Nullable
@Override
public ParticleOptions getDripParticle() {
return ParticleTypes.DRIPPING_WATER;
}
@Override
protected boolean canConvertToSource(ServerLevel world) {
return world.getGameRules().get(GameRules.WATER_SOURCE_CONVERSION);
}
@Override
protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state) {
BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
Block.dropResources(state, world, pos, blockEntity);
}
@Override
protected void entityInside(Level world, BlockPos pos, Entity entity, InsideBlockEffectApplier handler) {
handler.apply(InsideBlockEffectType.EXTINGUISH);
if (!(world instanceof ServerLevel serverLevel) || !(entity instanceof LivingEntity livingEntity)) return;
if (world.getGameTime() % 20 == 0) {
// Hurt and weaken entities that step inside.
livingEntity.hurtServer(serverLevel, world.damageSources().magic(), 2.0F); // 1 heart/sec
livingEntity.addEffect(new MobEffectInstance(MobEffects.WEAKNESS, 300, -3));
livingEntity.addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 300, -3));
}
}
@Override
protected int getSlopeFindDistance(LevelReader world) {
return 4;
}
@Override
public int getDropOff(LevelReader world) {
return 1;
}
@Override
public int getTickDelay(LevelReader world) {
return 5;
}
@Override
public boolean canBeReplacedWith(FluidState state, BlockGetter world, BlockPos pos, Fluid fluid,
Direction direction) {
return direction == Direction.DOWN && !fluid.is(ModFluidTags.ACID);
}
@Override
protected float getExplosionResistance() {
return 100.0F;
}
@Override
public Optional<SoundEvent> getPickupSound() {
return Optional.of(SoundEvents.BUCKET_FILL);
}
}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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
在 AcidFluid 内部,我们会为 Source 和 Flowing 流体创建两个子类。
java
public static class Flowing extends AcidFluid {
@Override
protected void createFluidStateDefinition(StateDefinition.Builder<Fluid, FluidState> builder) {
super.createFluidStateDefinition(builder);
builder.add(LEVEL);
}
@Override
public int getAmount(FluidState state) {
return state.getValue(LEVEL);
}
@Override
public boolean isSource(FluidState state) {
return false;
}
}
public static class Source extends AcidFluid {
@Override
public int getAmount(FluidState state) {
return 8;
}
@Override
public boolean isSource(FluidState state) {
return true;
}
}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
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
注册流体
接下来,我们会创建一个用于注册所有流体实例的类, 并将其命名为 ModFluids。
java
public class ModFluids {
public static final FlowingFluid ACID_FLOWING = register("flowing_acid", new AcidFluid.Flowing());
public static final FlowingFluid ACID_STILL = register("acid", new AcidFluid.Source());
private static FlowingFluid register(String name, FlowingFluid fluid) {
return Registry.register(BuiltInRegistries.FLUID, Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, name), fluid);
}
public static void initialize() {
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
和方块一样,你需要确保该类被加载,这样所有包含流体实例的静态字段才会完成初始化。 你可以创建一个空的 initialize 方法,并在你的模组初始化器中调用它,以触发静态初始化。
现在,回到 AcidFluid 类,添加以下方法,将已注册的流体实例与该流体关联起来:
java
@Override
public Fluid getFlowing() {
return ModFluids.ACID_FLOWING;
}
@Override
public Fluid getSource() {
return ModFluids.ACID_STILL;
}
@Override
public boolean isSame(Fluid fluid) {
return fluid == ModFluids.ACID_STILL || fluid == ModFluids.ACID_FLOWING;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
到目前为止,我们已经注册了流体的源状态和流动状态。 接下来,还需要为它注册一个桶和一个 LiquidBlock。
注册流体方块
现在,让我们为该流体添加一个液体方块。 某些命令(例如 setblock)会需要它,这样你的流体才能存在于世界中。 如果你还没有了解过,建议先阅读如何创建你的第一个方块。
打开你的 ModBlocks 类,并注册以下 LiquidBlock:
java
public static final Block ACID = register(
"acid",
(props) -> new LiquidBlock(ModFluids.ACID_STILL, props),
BlockBehaviour.Properties.ofFullCopy(Blocks.WATER),
false
);1
2
3
4
5
6
2
3
4
5
6
然后,在 AcidFluid 中重写以下方法,将你的方块与该流体关联起来:
java
@Override
protected BlockState createLegacyBlock(FluidState state) {
return ModBlocks.ACID.defaultBlockState().setValue(LiquidBlock.LEVEL, getLegacyLevel(state));
}1
2
3
4
2
3
4
注册桶
Minecraft 中的流体通常可以装入桶中,因此我们来看看如何为酸液添加一个酸液桶物品。 如果你还没有了解过,建议先阅读如何创建你的第一个物品。
打开你的 ModItems 类,并注册以下 BucketItem:
java
public static final Item ACID_BUCKET = register(
"acid_bucket",
props -> new BucketItem(ModFluids.ACID_STILL, props),
new Item.Properties()
.craftRemainder(Items.BUCKET)
.stacksTo(1)
);1
2
3
4
5
6
7
2
3
4
5
6
7
然后,在 AcidFluid 中重写以下方法,将你的桶与该流体关联起来:
java
@Override
public Item getBucket() {
return ModItems.ACID_BUCKET;
}1
2
3
4
5
2
3
4
5
不要忘记,为了正确渲染,物品需要名称为 acid_bucket 的翻译、纹理、模型以及客户端物品。 下面提供了一个示例纹理。
另外,建议将你的模组中的桶添加到 ConventionalItemTags.BUCKET 物品标签中,这样其他模组就能正确处理它。你可以手动添加,也可以通过数据生成添加。
为流体添加标签
INFO
使用数据生成的用户可能会希望通过 FabricTagProvider.FluidTagProvider 注册标签,而不是手写标签文件。
由于一个流体在流动状态和静止状态下会被视为两个独立的流体,通常会使用标签来同时检查这两种状态。 我们会在 data/example-mod/tags/fluid/acid.json 中创建一个流体标签:
json
{
"values": [
"example-mod:acid",
"example-mod:flowing_acid"
]
}1
2
3
4
5
6
2
3
4
5
6
TIP
Minecraft 还提供了其他标签,用于控制流体的行为:
- 如果你需要让模组的流体表现得像水一样(水下迷雾、可被海绵吸收、可游泳、减缓实体速度等),可以考虑将其添加到
minecraft:water流体标签中。 - 如果你需要让它表现得像熔岩一样(熔岩迷雾、可被炽足兽/恶魂游泳通过、减缓实体速度等),可以考虑将其添加到
minecraft:lava流体标签中。 - 如果你只需要其中的_部分_行为,则可以考虑使用 Mixin 对行为进行更精细的控制。
在这个演示中,我们还会将酸液流体标签添加到水流体标签 data/minecraft/tags/fluid/water.json 中。
json
{
"values": [
"#example-mod:acid"
]
}1
2
3
4
5
2
3
4
5
为流体添加纹理
若要为流体添加纹理,应使用 Fabric API 的 FluidRenderHandlerRegistry。
TIP
为简化示例,本演示使用 BlockTintSources.constant 为原版水纹理应用一个恒定的绿色色调。 有关 BlockTintSource 的更多细节,请参阅方块着色。
将以下代码行添加到你的 ClientModInitializer 中,以创建一个 FluidModel.Unbaked。它会接收两个用于纹理的 Material:一个用于静止的源流体,另一个用于流动流体;同时还会接收一个方块着色源,用于指定要应用的颜色。
java
FluidRenderingRegistry.register(
ModFluids.ACID_STILL,
ModFluids.ACID_FLOWING,
new FluidModel.Unbaked(
new Material(Identifier.withDefaultNamespace("block/water_still")),
new Material(Identifier.withDefaultNamespace("block/water_flow")),
new Material(Identifier.withDefaultNamespace("block/water_overlay")),
BlockTintSources.constant(0xFF075800)
)
);1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
至此,我们已经具备了在游戏中看到酸液所需的一切! 你可以使用 setblock,也可以使用酸液桶物品,将酸液放置到世界中。




















