Deine erste Flüssigkeit erstellen
VORAUSSETZUNGEN
Zunächst musst du verstehen, wie man einen Block erstellt und wie man ein Item erstellt.
Dieses Beispiel wird die Erstellung einer Säureflüssigkeit behandeln, die Entitäten, die darin steht, Schaden zufügt, schwächt und blind macht. Dazu benötigen wir zwei Flüssigkeits-Instanzen für den Ausgangs- und den Endzustand, einen Flüssigkeitsblock, ein Eimer-Item und ein Flüssigkeits-Tag.
Eine Flüssigkeitsklasse erstellen
Zunächst erstellen wir eine abstrakte Klasse, in diesem Fall mit dem Namen AcidFluid, die von der Basisklasse FlowingFluid erbt. Dann werden wir alle Methoden überschreiben, die für die Quelle und die fließende Flüssigkeit gleich sein sollen.
Achte besonders auf die folgenden Methoden:
animateTickwird zur Darstellung von Partikeln und zur Wiedergabe von Sound verwendet. Das unten gezeigte Verhalten basiert auf Wasser, das beim Fließen Geräusche abspielt und unter Wasser sprudelnde Partikel hat.entityInsidedient dazu, zu steuern, was geschehen soll, wenn eine Entität die Flüssigkeit berührt. Wir nehmen Wasser als Grundlage und löschen damit jedes Feuer bei Entitäten, sorgen aber auch dafür, dass es den Entitäten im Inneren Schaden zufügt, sie schwächt und blind macht - es ist schließlich Säure.canBeReplacedWithübernimmt einen Teil der fließenden Logik - beachte, dassModFluidTags.ACIDnoch nicht definiert ist; darauf gehen wir am Ende ein.
Wenn wir all das zusammenfassen, erhalten wir folgende Klasse:
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
Innerhalb von AcidFluid werden wir zwei Unterklassen für die Flüssigkeiten Source und Flowing erstellen.
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
Flüssigkeiten registrieren
Als Nächstes erstellen wir eine Klasse, um alle Flüssigkeits-Instanzen zu registrieren. Wir nennen sie 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
Genau wie bei Blöcken musst du sicherstellen, dass die Klasse geladen ist, damit alle statischen Felder, die deine Flüssigkeits-Instanzen enthalten, initialisiert werden. Du kannst dies tun, indem du eine Dummy-Methode initialize erstellst, die in deinem Mod-Initialisierer aufgerufen werden kann, um die statische Initialisierung auszulösen.
Kehren nun zur Klasse AcidFluid zurück und füge diese Methoden hinzu, um die registrierten Flüssigkeits-Instanzen mit dieser Flüssigkeit zu verknüpfen:
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
Bisher haben wir den Ausgangszustand der Flüssigkeit und ihren Strömungszustand erfasst. Als Nächstes müssen wir einen Eimer und einen LiquidBlock dafür registrieren.
Flüssigkeitsblöcke registrieren
Fügen wir nun einen Flüssigkeitsblock für unsere Flüssigkeit hinzu. Dies wird von einigen Befehlen wie setblock benötigt, damit deine Flüssigkeit in der Welt existieren kann. Falls du das noch nicht getan hast, solltest du dir ansehen, wie du deinen ersten Block erstellst.
Öffne deine Klasse ModBlocks und registriere den folgenden 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
Überschreibe dann diese Methode in AcidFluid, um deinen Block mit der Flüssigkeit zu verknüpfen:
java
@Override
protected BlockState createLegacyBlock(FluidState state) {
return ModBlocks.ACID.defaultBlockState().setValue(LiquidBlock.LEVEL, getLegacyLevel(state));
}1
2
3
4
2
3
4
Eimer registrieren
Flüssigkeiten werden in Minecraft normalerweise in Eimern aufbewahrt. Schauen wir uns also an, wie wir ein Item mit dem Namen Eimer mit Säure hinzufügen können. Falls du das noch nicht getan hast, solltest du dir ansehen, wie du dein erstes Item erstellst.
Öffne deine Klasse ModItems und registriere das folgende 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
Überschreibe dann diese Methode in AcidFluid, um deinen Eimer mit der Flüssigkeit zu verknüpfen:
java
@Override
public Item getBucket() {
return ModItems.ACID_BUCKET;
}1
2
3
4
5
2
3
4
5
Denke daran, dass Items eine Übersetzung, eine Textur, ein Modell und ein Client-Item mit dem Namen acid_bucket benötigen, damit sie korrekt dargestellt werden. Eine Beispiel-Textur ist unten aufgeführt.
Es wird außerdem empfohlen, den Eimer deines Mods dem Item-Tag ConventionalItemTags.BUCKET hinzuzufügen, damit andere Mods ihn entsprechend verarbeiten können. Entweder manuell oder durch Datengenerierung.
Taggen deiner Flüssigkeiten
INFO
Benutzer von Datengenerierung möchten möglicherweise Tags über FabricTagProvider.FluidTagProvider registrieren, anstatt sie manuell zu schreiben.
Da eine Flüssigkeit im fließenden und im ruhenden Zustand als zwei getrennte Blöcke betrachtet wird, wird häufig ein Tag verwendet, um beide Zustände gemeinsam zu überprüfen. Wir erstellen ein Flüssigkeits-Tag in 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 bietet außerdem weitere Tags, mit denen sich das Verhalten von Flüssigkeiten steuern lässt:
- Wenn die Flüssigkeit deines Mods sich wie Wasser verhalten soll (Wassernebel, von Schwämmen aufgesaugt, schwimmbar, verlangsamt Entitäten ...), solltest du in Erwägung ziehen, sie dem Flüssigkeits-Tag
minecraft:waterhinzuzufügen. - Wenn sie sich wie Lava verhalten soll (Lavanebel, für Striders/Ghasts schwimmbar, verlangsamt Entitäten…), solltest du es dem Flüssigkeits-Tag
minecraft:lavahinzufügen. - Wenn du nur einige dieser Funktionen benötigst, kannst du Mixins verwenden, um das Verhalten genau anzupassen.
Für diese Demo fügen wir außerdem das Tag der Säureflüssigkeit zum Tag Wasserflüssigkeit hinzu, data/minecraft/tags/fluid/water.json.
json
{
"values": [
"#example-mod:acid"
]
}1
2
3
4
5
2
3
4
5
Deine Flüssigkeiten texturieren
Um deiner Flüssigkeit eine Textur zuzuweisen, solltest du die FluidRenderHandlerRegistry der Fabric-API verwenden.
TIP
Der Einfachheit halber verwendet diese Demo BlockTintSources.constant, um der Standard-Wassertextur einen konstanten Grünstich zu anzuwenden. Weitere Informationen zu der BlockTintSource findest du unter Blockfärbung.
Füge die folgenden Zeilen zu deinem ClientModInitializer hinzu, um ein FluidModel.Unbaked zu erstellen, das zwei Material für die Texturen – eines für die ruhende Quelle und eines für die fließende Flüssigkeit – sowie eine Blockfärbungs-Quelle für die Farbe, mit der es eingefärbt werden soll, verwendet.
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
Jetzt haben wir alles, was wir brauchen, um die Säure im Spiel zu sehen! Du kannst setblock oder das Item Säureeimer verwenden, um Säure in der Welt zu platzieren.




















