User Tools

Site Tools


zh_cn:tutorial:fluids

This is an old revision of the document!


创建流体

Overview

在这里,我们将介绍自定义流体的创建。 如果计划创建多个流体,建议创建一个抽象的基本流体类,在其中设置必要的默认值,这些默认值将在其子类中共享。 我们还将使其像湖泊一样在世界上产生。

使抽象流畅

香草流体扩展了net.minecraft.fluid.BaseFluid类,我们的抽象流体也将扩展。 可能是这样的:

public abstract class BasicFluid extends BaseFluid
{
    /**
     * @return does it produce infinite fluid (like water)?
     */
    @Override
    protected boolean isInfinite()
    {
        return false;
    }
 
    // make it transparent
    @Override
    protected BlockRenderLayer getRenderLayer()
    {
        return BlockRenderLayer.TRANSLUCENT;
    }
 
    /**
     *
     * @return an associated item that "holds" this fluid
     */
    @Override
    public abstract Item getBucketItem();
 
    /**
     *
     * @return a blockstate of the associated {@linkplain net.minecraft.block.FluidBlock} with {@linkplain net.minecraft.block.FluidBlock#LEVEL}
     */
    @Override
    protected abstract BlockState toBlockState(FluidState var1);
 
    /**
     *
     * @return flowing static instance of this fluid
     */
    @Override
    public abstract Fluid getFlowing();
 
    /**
     *
     * @return still static instance of this fluid
     */
    @Override
    public abstract Fluid getStill();
 
    // how much does the height of the fluid block decreases
    @Override
    protected int getLevelDecreasePerBlock(ViewableWorld world)
    {
        return 1;
    }
 
    /**
     * 
     * @return update rate
     */
    @Override
    public int getTickRate(ViewableWorld world)
    {
        return 5;
    }
 
    @Override
    protected float getBlastResistance()
    {
        return 100;
    }
 
    // this seems to determine fluid's spread speed (higher value means faster)
    @Override
    protected int method_15733(ViewableWorld world)
    {
        return 4;
    }
 
    // I don't know what this does, but it's present in the water fluid
    @Override
    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);
 
}

Implementation

Now let's make an actual fluid; it will have a still and flowing variants; will name it “Acid”:

public abstract class Acid extends BasicFluid
{
    @Override
    public Item getBucketItem()
    {
        return null;
    }
    @Override
    protected BlockState toBlockState(FluidState var1)
    {
        return null;
    }
 
    @Override
    public Fluid getFlowing()
    {
        return null;
    }
 
    @Override
    public Fluid getStill()
    {
        return null;
    }
 
    @Override
    public boolean matchesType(Fluid fluid)
    {
        return false;
    }
 
    // still acid
    public static class Still extends Acid
    {
 
        @Override
        public boolean isStill(FluidState fluidState)
        {
            return true;
        }
 
        /**
         * @return height of the fluid block
         */
        @Override
        public int getLevel(FluidState fluidState)
        {
            return 8;
        }
    }
 
    // flowing acid
    public static class Flowing extends  Acid
    {
 
        @Override
        public boolean isStill(FluidState fluidState)
        {
            return false;
        }
 
        /**
         * @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);
        }
    }
}

Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your ModInitializer:

 
    public static Acid stillAcid;
    public static Acid flowingAcid;
 
    public static BucketItem acidBucket;
 
    @Override
    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);
    }    

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:

{
  "replace": false,
  "values": [
    "modid:acid_still",
    "modid:acid_flowing"
  ]
}

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:

public class BaseFluidBlock extends FluidBlock
{
    public BaseFluidBlock(BaseFluid fluid, Settings settings)
    {
        super(fluid, settings);
    }
}

Now make a static block instance:

    ...
 
    public static FluidBlock acid;
 
    @Override
    public void onInitialize()
    {
 
        ...
 
        acid = new BaseFluidBlock(stillAcid, FabricBlockSettings.of(Material.WATER).dropsNothing().build());
        Registry.register(Registry.BLOCK, new Identifier(MODID, "acid_block"), acid);
    }    

Now when we have these static objects, we go back to Acid class and complete the overridden methods:

public abstract class Acid extends BasicFluid
{
    @Override
    public Item getBucketItem()
    {
        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()
    {
        return Mod.flowingAcid;
    }
 
    @Override
    public Fluid getStill()
    {
        return Mod.stillAcid;
    }
 
    @Override
    public boolean matchesType(Fluid fluid_1)
    {
        return fluid_1==Mod.flowingAcid || fluid_1==Mod.stillAcid;
    }
 
    ...
 
}    

Now we can assert that the Acid class is complete.

Rendering setup

Time to do client-side things. In 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.

    @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 a 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
                @Override
                public int getFluidColor(ExtendedBlockView view, BlockPos pos, FluidState state)
                {
                    return 0x4cc248;
                }
            };
 
            // registering the same renderer for both fluid variants is intentional
 
            FluidRenderHandlerRegistry.INSTANCE.register(Mod.stillAcid, acidRenderHandler);
            FluidRenderHandlerRegistry.INSTANCE.register(Mod.flowingAcid, acidRenderHandler);
        });

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.

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:

        LakeFeature acidFeature = Registry.register(Registry.FEATURE, new Identifier(MODID,"acid_lake"), new LakeFeature(dynamic -> new LakeFeatureConfig(acid.getDefaultState())));

Then put it into desired biomes to generate:

        // 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)));

This is the end of the tutorial.

zh_cn/tutorial/fluids.1576750765.txt.gz · Last modified: 2019/12/19 10:19 by lightcolour