User Tools

Site Tools


tutorial:fluids

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
tutorial:fluids [2019/11/04 17:04]
alexiy [Rendering setup] Everything must be inside the event
tutorial:fluids [2020/01/18 15:40] (current)
virtuoel Update tutorial to 1.15.1 + some cleanup
Line 1: Line 1:
 ====== Creating a fluid ====== ====== Creating a fluid ======
 +
 ===== Overview ===== ===== Overview =====
 Here we'll cover creation of a custom fluid. If you plan to create several fluids, it is recommended to make an abstract basic fluid class where you'll set necessary defaults that will be shared in its subclasses. We'll also make it generate in the world like lakes. Here we'll cover creation of a custom fluid. If you plan to create several fluids, it is recommended to make an abstract basic fluid class where you'll set necessary defaults that will be shared in its subclasses. We'll also make it generate in the world like lakes.
 +
 ===== Making an abstract fluid ===== ===== Making an abstract fluid =====
-Vanilla fluids extend ​**net.minecraft.fluid.BaseFluid** class, and so will our abstract fluidIt can be like this: +Vanilla fluids extend ​''​net.minecraft.fluid.BaseFluid''​, and so shall we
-<code java> +<code java [enable_line_numbers="​true"​]
-public abstract class BasicFluid ​extends BaseFluid+public abstract class TutorialFluid ​extends BaseFluid
 { {
-    ​/** + /** 
-     ​* @return ​does it produce infinite ​fluid (like water)+  * @return ​is the given fluid an instance of this fluid? 
-     ​*/ +  */ 
-    @Override + @Override 
-    ​protected ​boolean ​isInfinite() + public ​boolean ​matchesType(Fluid fluid
-    +
-        return ​false; + return ​fluid == getStill() || fluid == getFlowing(); 
-    } +
- +  
-    // make it transparent + /** 
-    @Override +  * @return ​is the fluid infinite like water? 
-    protected BlockRenderLayer getRenderLayer() +  */ 
-    { + @Override 
-        return BlockRenderLayer.TRANSLUCENT+ protected boolean isInfinite() 
-    +
- + return false; 
-    /** + } 
-     * +  
-     * @return ​an associated item that "​holds"​ this fluid + /** 
-     ​*/ +  Perform actions when fluid flows into a replaceable block. Water drops 
-    @Override +  * the block'​s loot tableLava plays the "block.lava.extinguish"​ sound
-    ​public abstract Item getBucketItem(); +  */ 
- + @Override 
-    /** + protected ​void beforeBreakingBlock(IWorld world, BlockPos pos, BlockState ​state) 
-     ​+
-     ​@return a blockstate of the associated {@linkplain net.minecraft.block.FluidBlock} with {@linkplain net.minecraft.block.FluidBlock#​LEVEL} + final BlockEntity blockEntity = state.getBlock().hasBlockEntity() ? world.getBlockEntity(pos) : null; 
-     ​*/ + Block.dropStacks(state,​ world.getWorld(),​ pos, blockEntity);​ 
-    @Override + } 
-    protected ​abstract ​BlockState ​toBlockState(FluidState var1); +  
- + /** 
-    /** +  Lava returns true if its FluidState is above a certain height and the 
-     ​+  * Fluid is Water. 
-     ​@return flowing static instance of this fluid +  *  
-     */ +  * @return ​if the given Fluid can flow into this FluidState? 
-    @Override +  */ 
-    public abstract ​Fluid getFlowing();​ + @Override 
- + protected boolean method_15777(FluidState fluidState, BlockView blockView, BlockPos blockPos, ​Fluid fluid, Direction direction
-    /*+
-     * + return ​false
-     * @return ​still static instance of this fluid +
-     ​*/ +  
-    @Override + /** 
-    ​public abstract ​Fluid getStill();​ +  Possibly related to the distance checks for flowing into nearby holes? 
- +  Water returns 4. Lava returns 2 in the Overworld and 4 in the Nether. 
-    // how much does the height of the fluid block decreases +  */ 
-    @Override + @Override 
-    protected int getLevelDecreasePerBlock(ViewableWorld world+ protected ​int method_15733(WorldView worldView
-    +
-        return ​1+ return ​4
-    +
- +  
-    /** + /** 
-     ​*  +  * Water returns 1. Lava returns 1 in the Overworld and 2 in the Nether. 
-     ​@return update rate +  *
-     ​*/ + @Override 
-    @Override + protected int getLevelDecreasePerBlock(WorldView worldView
-    ​public ​int getTickRate(ViewableWorld world+
-    + return ​1
-        return ​5+
-    +  
- + /** 
-    ​@Override + * Water returns 5. Lava returns 30 in the Overworld and 10 in the Nether. 
-    ​protected float getBlastResistance() + */ 
-    { + @Override 
-        return 100; + public int getTickRate(WorldView worldView
-    } +
- + return ​5
-    ​// this seems to determine fluid'​s spread speed (higher value means faster) +
-    @Override +  
-    protected int method_15733(ViewableWorld world+ /** 
-    +  Water and Lava both return ​100.0F. 
-        return ​4+  */ 
-    + @Override 
- + protected float getBlastResistance() 
-    // I don't know what this does, but it's present ​in the water fluid +
-    @Override + return 100.0F
-    ​protected void beforeBreakingBlock(IWorld world, BlockPos blockPos, BlockState blockState{ + }
-        ​BlockEntity blockEntity = blockState.getBlock().hasBlockEntity() ? world.getBlockEntity(blockPos) : null; +
-        Block.dropStacks(blockState,​ world.getWorld(),​ blockPos, blockEntity);​ +
-    } +
- +
-    // also don't know what it does +
-    public boolean method_15777(FluidState fluidState, BlockView blockView, BlockPos blockPos, Fluid fluid, Direction direction) ​+
-        return ​direction == Direction.DOWN+
-    +
- +
-    /** +
-     ​* +
-     * @return ​is given fluid instance of this fluid? +
-     ​*/ +
-    @Override +
-    ​public abstract boolean matchesType(Fluid fluid); +
 } }
 </​code>​ </​code>​
  
 ===== Implementation ===== ===== Implementation =====
-Now let's make an actual fluid; it will have a //still// and //flowing// variantswill name it "Acid":+Now let's make an actual fluid which'​ll ​have still and flowing variants. For this tutorial, we will call it Acid. The missing references will be filled in shortly.
  
-<code java> +<code java [enable_line_numbers="​true"​]
-public abstract class Acid extends ​BasicFluid+public abstract class AcidFluid ​extends ​TutorialFluid
 { {
-    ​@Override + @Override 
-    public ​Item getBucketItem() + public ​Fluid getStill() 
-    +
-        return ​null+ return ​<​YOUR_STILL_FLUID_HERE>​
-    +
-    ​@Override +  
-    ​protected BlockState toBlockState(FluidState var1) + @Override 
-    { + public Fluid getFlowing() 
-        return null; +
-    } + return ​<​YOUR_FLOWING_FLUID_HERE>​
- +
-    ​@Override +  
-    public Fluid getFlowing() + @Override 
-    + public ​Item getBucketItem() 
-        return ​null+
-    + return ​<​YOUR_BUCKET_ITEM_HERE>​
- +
-    @Override +  
-    public ​Fluid getStill() + @Override 
-    + protected BlockState toBlockState(FluidState fluidState
-        return ​null+
-    + // method_15741 converts the LEVEL_1_8 of the fluid state to the LEVEL_15 the fluid block uses 
- + return ​<​YOUR_FLUID_BLOCK_HERE>​.getDefaultState().with(Properties.LEVEL_15,​ method_15741(fluidState))
-    @Override +
-    ​public boolean matchesType(Fluid fluid+  
-    + public static class Flowing ​extends ​AcidFluid 
-        return ​false+
-    + @Override 
- + protected void appendProperties(StateManager.Builder<​Fluid, ​FluidState> builder
-    // still acid +
-    ​public static class Still extends ​Acid + super.appendProperties(builder)
-    + builder.add(LEVEL);​ 
- +
-        ​@Override +  
-        ​public boolean isStill(FluidState ​fluidState+ @Override 
-        + public int getLevel(FluidState fluidState) 
-            ​return true+
-        + return ​fluidState.get(LEVEL)
- +
-        /** +  
-         * @return height of the fluid block + @Override 
-         */ + public boolean isStill(FluidState fluidState) 
-        ​@Override +
-        public int getLevel(FluidState fluidState) + return false; 
-        +
-            return ​8+ } 
-        +  
-    } + public static class Still extends AcidFluid 
- + { 
-    // flowing acid + @Override 
-    public static class Flowing extends ​ Acid + public int getLevel(FluidState fluidState) 
-    { +
- + return ​8
-        @Override +
-        public boolean isStill(FluidState fluidState) +  
-        + @Override 
-            return false; + public boolean isStill(FluidState ​fluidState
-        +
- + return true
-        /** +
-         * @return height of the fluid block + }
-         */ +
-        @Override +
-        public int getLevel(FluidState fluidState) +
-        +
-            return ​fluidState.get(LEVEL)+
-        +
- +
-        @Override +
-        ​protected void appendProperties(StateFactory.Builder<​Fluid, ​FluidState> stateFactoryBuilder+
-        +
-            ​super.appendProperties(stateFactoryBuilder);​ +
-            stateFactoryBuilder.add(LEVEL)+
-        +
-    }+
 } }
 </​code>​ </​code>​
  
-Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your **ModInitializer**:+Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your ''​ModInitializer''​:
  
-<code java>+<code java [enable_line_numbers="​true"​]> 
 +// ...
  
-     +public static ​BaseFluid STILL_ACID
-    ​public static ​Acid stillAcid+public static ​BaseFluid FLOWING_ACID;
-    ​public static Acid flowingAcid;​ +
-     +
-    ​public static ​BucketItem acidBucket;+
  
-    @Override +public ​static Item ACID_BUCKET
-    ​public ​void onInitialize() + 
-    { +// ...
-     +
-        stillAcid = Registry.register(Registry.FLUID,​ new Identifier(MODID,"​acid_still"​),​ new Acid.Still())+
-        ​flowingAcid = Registry.register(Registry.FLUID,​ new Identifier(MODID,"​acid_flowing"​),​ new Acid.Flowing());​ +
-         +
-        acidBucket = new BucketItem(stillAcid,​ new Item.Settings().maxCount(1));​ +
-        Registry.register(Registry.ITEM,​ new Identifier(MODID,"​acid_bucket"​),​ acidBucket);​ +
-    }     +
-</​code>​+
  
-To make the custom fluid behave like water or lava, you must add it to a corresponding fluid tag: make a file "​data/​minecraft/​tags/​fluids/​water.json"​ and write identifiers of your fluids in there: +@Override 
-<code json>+public void onInitialize()
 { {
-  ​"replace": false+ // ... 
-  "​values":​ [ +  
-    "modid:​acid_still", + STILL_ACID = Registry.FLUID.add(new Identifier(MOD_ID, ​"acid")new AcidFluid.Still());​ 
-    "modid:​acid_flowing+  
-  ]+ FLOWING_ACID = Registry.FLUID.add(new Identifier(MOD_ID, ​"flowing_acid")new AcidFluid.Flowing());​ 
 +  
 + ACID_BUCKET = Registry.ITEM.add(new Identifier(MOD_ID, ​"acid_bucket"), new BucketItem(STILL_ACID,​ new Item.Settings().recipeRemainder(Items.BUCKET).maxCount(1)));​ 
 +  
 + // ...
 } }
-</​code>​ 
  
-==== Making a fluid block ==== +// ... 
-Next we need to create a block which will represent acid in the world**net.minecraft.block.FluidBlock** is the class we need to use, but for "​mojang"​ reasons its constructor is protected. The solution is well-known - make a subclass of it and change the visibility of the constructor:​+</​code>​
  
-<​code ​java> +To make a custom fluid behave more like water or lava, you must add it to a corresponding fluid tag: For water, make a ''​data/​minecraft/​tags/​fluids/​water.json''​ file and write the identifiers of your fluids in there: 
-public class BaseFluidBlock extends FluidBlock+<​code ​json [enable_line_numbers="​true"​]>
 { {
-    public BaseFluidBlock(BaseFluid fluidSettings settings) + "​replace":​ false
-    { + "​values":​ 
-        ​super(fluidsettings); +
-    }+ "​your_mod_id:​acid"​
 + "​your_mod_id:​flowing_acid"​ 
 + ]
 } }
 </​code>​ </​code>​
  
-Now make static ​block instance:+===== Making ​fluid block ===== 
 +Next we need to create a block which will represent acid in the world. ''​net.minecraft.block.FluidBlock''​ is the class we need to use, but since its constructor is protected, we can't construct it directly. Some ways to use it are to make a subclass or an anonymous subclass. Here we will be showing the latter. In your ''​ModInitializer''​:
  
-<code java> +<code java [enable_line_numbers="​true"​]
-    ... +// ...
-     +
-    public static FluidBlock acid;+
  
-    @Override +public ​static Block ACID;
-    ​public ​void onInitialize() +
-    { +
-     +
-        ... +
-         +
-        acid = new BaseFluidBlock(stillAcid,​ FabricBlockSettings.of(Material.WATER).dropsNothing().build()); +
-        Registry.register(Registry.BLOCK,​ new Identifier(MODID,​ "​acid_block"​),​ acid); +
-    }     +
-</​code>​+
  
-Now when we have these static objects, we go back to **Acid** class and complete the overridden methods:+// ...
  
-<code java> +@Override 
-public ​abstract class Acid extends BasicFluid+public ​void onInitialize()
 { {
-    @Override + // ... 
-    public Item getBucketItem() +  
-    { + ACID = Registry.BLOCK.add(new Identifier(MOD_ID,​ "​acid"​), new FluidBlock(STILL_ACID,​ FabricBlockSettings.copy(Blocks.WATER).build()){}); 
-        return Mod.acidBucket;​ +  
-    } + // ... 
-     +}
-    @Override +
-    protected BlockState toBlockState(FluidState fluidState) +
-    { +
-        ​//don't ask me what **method_15741** does... +
-        ​return Mod.acid.getDefaultState().with(FluidBlock.LEVEL, method_15741(fluidState)); +
-    }+
  
-    @Override +// ...     
-    public Fluid getFlowing() +</​code>​
-    { +
-        return Mod.flowingAcid;​ +
-    }+
  
-    @Override +Now that we have these static objects, we can go back to ''​AcidFluid''​ and fill in the overridden methods:
-    public Fluid getStill() +
-    { +
-        return Mod.stillAcid;​ +
-    }+
  
-    ​@Override +<code java [enable_line_numbers="​true"​]>​ 
-    public ​boolean matchesType(Fluid ​fluid_1+public abstract class AcidFluid extends TutorialFluid 
-    +
-        return ​fluid_1==Mod.flowingAcid || fluid_1==Mod.stillAcid+ @Override 
-    + public ​Fluid getStill(
-     +
-    ... + return TutorialMod.STILL_ACID;​ 
-    +
 +  
 + @Override 
 + public ​Fluid getFlowing(
 +
 + return ​TutorialMod.FLOWING_ACID;​ 
 +
 +  
 + @Override 
 + public Item getBucketItem() 
 +
 + return TutorialMod.ACID_BUCKET
 +
 +  
 + @Override 
 + protected BlockState toBlockState(FluidState fluidState) 
 +
 + // method_15741 converts the LEVEL_1_8 of the fluid state to the LEVEL_15 the fluid block uses 
 + return TutorialMod.ACID.getDefaultState().with(Properties.LEVEL_15,​ method_15741(fluidState));​ 
 +
 +  
 + // ...
 }    ​ }    ​
 </​code>​ </​code>​
- 
-Now we can assert that the Acid class is complete. 
  
 ===== Rendering setup ===== ===== Rendering setup =====
 +For your fluids to have textures or be tinted with a color, you will need to register a ''​FluidRenderHandler''​ for them. Here, we will reuse water'​s textures and just change the tint color applied to them. To make sure the textures are rendered as translucent,​ you can use Fabric'​s ''​BlockRenderLayerMap''​.
  
-Time to do client-side thingsIn your **ClientModInitializer** you need to specify locations of sprites ​for your fluids ​and define their rendering. I will reuse water textures and just change the color applied to them.+<code java [enable_line_numbers="​true"​]>​ 
 +public class TutorialModClient implements ClientModInitializer 
 +
 + // ... 
 +  
 + @Override 
 + public void onInitializeClient() 
 +
 + // ... 
 +  
 + setupFluidRendering(TutorialMod.STILL_ACID,​ TutorialMod.FLOWING_ACID,​ new Identifier("​minecraft",​ "​water"​),​ 0x4CC248);​ 
 + BlockRenderLayerMap.INSTANCE.putFluids(RenderLayer.getTranslucent(),​ TutorialMod.STILL_ACID,​ TutorialMod.FLOWING_ACID);​ 
 +  
 + // ... 
 +
 +  
 + // ... 
 +  
 + public static void setupFluidRendering(final Fluid still, final Fluid flowing, final Identifier textureFluidId,​ final int color) 
 +
 + final Identifier stillSpriteId = new Identifier(textureFluidId.getNamespace(),​ "​block/"​ + textureFluidId.getPath() + "​_still"​);​ 
 + final Identifier flowingSpriteId = new Identifier(textureFluidId.getNamespace(),​ "​block/"​ + textureFluidId.getPath() + "​_flow"​);​ 
 +  
 + // If they'​re not already present, add the sprites ​to the block atlas 
 + ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register((atlasTexture,​ registry) ​-
 +
 + registry.register(stillSpriteId);​ 
 + registry.register(flowingSpriteId);​ 
 + }); 
 +  
 + final Identifier fluidId = Registry.FLUID.getId(still);​ 
 + final Identifier listenerId = new Identifier(fluidId.getNamespace(),​ fluidId.getPath() + "​_reload_listener"​);​ 
 +  
 + final Sprite[] fluidSprites = new Sprite[] { null, null }; 
 +  
 + ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(new SimpleSynchronousResourceReloadListener() 
 +
 + @Override 
 + public Identifier getFabricId() 
 +
 + return listenerId;​ 
 +
 +  
 + /** 
 +  Get the sprites from the block atlas when resources are reloaded 
 +  *
 + @Override 
 + public void apply(ResourceManager resourceManager) 
 +
 + final Function<​Identifier,​ Sprite> atlas = MinecraftClient.getInstance().getSpriteAtlas(SpriteAtlasTexture.BLOCK_ATLAS_TEX);​ 
 + fluidSprites[0] = atlas.apply(stillSpriteId);​ 
 + fluidSprites[1] = atlas.apply(flowingSpriteId);​ 
 +
 + }); 
 +  
 + // The FluidRenderer gets the sprites and color from a FluidRenderHandler during ​rendering 
 + final FluidRenderHandler renderHandler = new FluidRenderHandler() 
 +
 + @Override 
 + public Sprite[] getFluidSprites(BlockRenderView view, BlockPos pos, FluidState state) 
 +
 + return fluidSprites;​ 
 +
 +  
 + @Override 
 + public int getFluidColor(BlockRenderView view, BlockPos pos, FluidState state) 
 +
 + return ​color
 +
 + }; 
 +  
 + FluidRenderHandlerRegistry.INSTANCE.register(still,​ renderHandler);​ 
 + FluidRenderHandlerRegistry.INSTANCE.register(flowing,​ renderHandler);​ 
 +
 +  
 + // ... 
 +
 +</​code>​
  
-<code java> +If you want to use your own fluid textures, you can refer to vanilla'​s assets ​((''​assets/minecraft/​blockstates/​water.json''​\\ ''​assets/​minecraft/​models/block/water.json''​\\ ''​assets/​minecraft/​textures/​block/​water_still.png''​\\ ''​assets/​minecraft/​textures/​block/​water_still.png.mcmeta''​\\ ''​assets/​minecraft/​textures/​block/​water_flow.png''​\\ ''​assets/minecraft/textures/block/water_flow.png.mcmeta''​)) as template.
-    @Override +
-    public void onInitializeClient(+
-    { +
-         +
-        ​// adding the sprites to the block texture atlas +
-        ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register((spriteAtlasTexture,​ registry) -> { +
-         +
-            Identifier stillSpriteLocation = new Identifier("​block/​water_still"); +
-            Identifier dynamicSpriteLocation = new Identifier("​block/​water_flow"); +
-            ​// here I tell to use only 16x16 area of the water texture +
-            FabricSprite stillAcidSprite = new FabricSprite(stillSpriteLocation,​ 16, 16); +
-            ​// same, but 32 +
-            FabricSprite dynamicAcidSprite = new FabricSprite(dynamicSpriteLocation,​ 32, 32); +
-         +
-            registry.register(stillAcidSprite);​ +
-            registry.register(dynamicAcidSprite)+
-             +
-             +
-            // this renderer is responsible for drawing fluids in world +
-            FluidRenderHandler acidRenderHandler = new FluidRenderHandler() +
-            { +
-                // return the sprites: still sprite goes first into the array, flowing/​dynamic goes last +
-                @Override +
-                public Sprite[] getFluidSprites(ExtendedBlockView extendedBlockView,​ BlockPos blockPos, FluidState fluidState) +
-                { +
-                    return new Sprite[] {stillAcidSprite,​ dynamicAcidSprite};​ +
-                }+
  
-                // apply light green color +===== Generation in the world ===== 
-                ​@Override +To make lakes of acid generate in the worldyou can create a ''​net.minecraft.world.gen.feature.LakeFeature''​ in your ''​ModInitializer''​ and then add it to the biomes you want it to generate in:
-                public int getFluidColor(ExtendedBlockView viewBlockPos pos, FluidState state) +
-                { +
-                    return 0x4cc248; +
-                } +
-            };+
  
-            ​// registering the same renderer for both fluid variants is intentional+<code java [enable_line_numbers="​true"​]>​ 
 +// ...
  
-            FluidRenderHandlerRegistry.INSTANCE.register(Mod.stillAcid,​ acidRenderHandler);​ +public static LakeFeature ACID_LAKE;
-            FluidRenderHandlerRegistry.INSTANCE.register(Mod.flowingAcid,​ acidRenderHandler);​ +
-        });+
  
-</code>+// ...
  
-Then what's left to do is to create necessary Json files and textures, but you should know how to do that at this point.+@Override 
 +public void onInitialize() 
 +
 + // ... 
 +  
 + ACID_LAKE = Registry.register(Registry.FEATURE,​ new Identifier(MOD_ID,​ "​acid_lake"​),​ new LakeFeature(SingleStateFeatureConfig::​deserialize));​ 
 +  
 + // generate in swamps, similar ​to water lakes, but with a chance of 40 (the higher the number, the lower the generation chance) 
 + Biomes.SWAMP.addFeature( 
 + GenerationStep.Feature.LOCAL_MODIFICATIONS,​ 
 + ACID_LAKE.configure(new SingleStateFeatureConfig(ACID.getDefaultState())) 
 + .createDecoratedFeature(Decorator.WATER_LAKE.configure(new ChanceDecoratorConfig(40))) 
 + ); 
 +  
 + // ... 
 +}
  
-===== Generation in a world ===== +// ...
- +
-To make acid lakes generate in the world, you can use **net.minecraft.world.gen.feature.LakeFeature**,​ which you create in the ModInitializer:​ +
-<code java> +
-         +
-        LakeFeature acidFeature = Registry.register(Registry.FEATURE,​ new Identifier(MODID,"​acid_lake"​),​ new LakeFeature(dynamic -> new LakeFeatureConfig(acid.getDefaultState())));​ +
- +
-</code> +
-Then put it into desired biomes to generate: +
-<code java> +
-        ​// I tell it to generate like water lakes, with a rarity of 40 (the higher is the number, the lesser is the generation chance): +
-        Biomes.FOREST.addFeature(GenerationStep.Feature.LOCAL_MODIFICATIONS,​ Biome.configureFeature(acidFeature,​ new LakeFeatureConfig(acid.getDefaultState()),​ Decorator.WATER_LAKE, new LakeDecoratorConfig(40)));​+
 </​code>​ </​code>​
-This is the end of the tutorial. 
  
tutorial/fluids.1572887043.txt.gz · Last modified: 2019/11/04 17:04 by alexiy