Создание вашей первой жидкости 26.1.2
Узнайте, как создать жидкость в Minecraft.
ТРЕБОВАНИЯ
Сначала вам необходимо понять, как создать блок и как создать предмет.
Этот пример охватывает создание технической жидкости — кислоты, которая наносит урон, накладывает слабость и слепоту на сущностей, находящихся внутри неё. Для этого нам понадобятся два экземпляра жидкости (для состояний источника и текучей жидкости), блок жидкости, предмет ведра и тег жидкости.
Создание класса жидкости
Мы начнем с создания абстрактного класса (назовём его 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
2
3
4
5
6
7
8
9
10
11
12
13
14
На данный момент мы зарегистрировали состояние источника жидкости и её текучее состояние. Далее нам нужно зарегистрировать ведро и 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 обычно используют вёдра, поэтому давайте посмотрим, как мы можем добавить предмет «Ведро кислоты» (Bucket of Acid). Если вы такого ещё не делали, вам следует ознакомиться с тем, как создать свой первый предмет.
Откройте ваш класс 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
2
3
4
Не забудьте, что для корректного рендеринга предметам требуются перевод, текстура, модель и клиентский предмет с именем acid_bucket. Пример текстуры приведен ниже.
Также рекомендуется добавить ведро из вашего мода в тег предметов ConventionalItemTags.BUCKET, чтобы другие моды могли правильно обрабатывать его — либо вручную, либо посредством генерации данных.
Тегирование ваших жидкостей
INFO
Пользователи datagen могут регистрировать теги через 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. - Если вам нужны только некоторые из этих свойств, возможно, стоит использовать миксины (mixins) для более точечной настройки поведения.
Для этой демонстрации мы также добавим тег жидкости acid (кислота) в тег жидкости воды data/minecraft/tags/fluid/water.json.
json
{
"values": [
"#example-mod:acid"
]
}1
2
3
4
5
2
3
4
5
Добавление текстуры
Для наложения текстур на жидкость вам следует использовать FluidRenderHandlerRegistry из Fabric API.
TIP
Для простоты в этой демонстрации используется BlockTintSources.constant для применения постоянного зеленого оттенка к стандартной (ванильной) текстуре воды. Для получения подробной информации о BlockTintSource см. раздел «Тонирование блоков».
Добавьте следующие строки в ваш ClientModInitializer, чтобы создать FluidModel.Unbaked. Он принимает два материала (Materials) для текстур — один для неподвижного источника и один для текущей жидкости — а также источник тонировки блока (block tint source) для окрашивания в нужный цвет.
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(ARGB.opaque(0x075800))
)
);1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
На данном этапе у нас есть всё необходимое, чтобы увидеть нашу Кислоту в игре! Вы можете использовать команду setblock или предмет «Ведро кислоты», чтобы разместить кислоту в мире.




















