Minecraft 1.18.1、1.18.2模组开发 22.方块实体(BlockEntity)

Minecraft1.12.2 方块实体教程

MC中有许多很有趣的方块实体如旗帜、钟、附魔台…我们今天在1.18的版本下实现一个类似于熔炉的方块实体。

1.在blocks包中新建一个我们的方块实体包virusgenerator,包中新建一个我们的方块类VirusGeneratorBlock

VirusGeneratorBlock.java

package com.joy187.re8joymod.blocks.virusgenerator;

import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraftforge.network.NetworkHooks;
import org.jetbrains.annotations.Nullable;

import com.joy187.re8joymod.init.BlockEntityInit;

public class VirusGeneratorBlock extends BaseEntityBlock {
    public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
    public static final BooleanProperty LIT = BlockStateProperties.LIT;

    public VirusGeneratorBlock(Properties properties) {
        super(properties);
        this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(LIT, Boolean.valueOf(false)));
    }

    private static final VoxelShape SHAPE =  Block.box(0, 0, 0, 16, 15, 16);

    @Override
    public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) {
        return SHAPE;
    }

    /* FACING */

    @Override
    public BlockState getStateForPlacement(BlockPlaceContext pContext) {
        return this.defaultBlockState().setValue(FACING, pContext.getHorizontalDirection().getOpposite());
    }

    @Override
    public BlockState rotate(BlockState pState, Rotation pRotation) {
        return pState.setValue(FACING, pRotation.rotate(pState.getValue(FACING)));
    }

    @Override
    public BlockState mirror(BlockState pState, Mirror pMirror) {
        return pState.rotate(pMirror.getRotation(pState.getValue(FACING)));
    }

    @Override
    protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> pBuilder) {
        pBuilder.add(FACING,LIT);
    }

    
    @Override
    public RenderShape getRenderShape(BlockState pState) {
        return RenderShape.MODEL;
    }

    @Override
    public void onRemove(BlockState pState, Level pLevel, BlockPos pPos, BlockState pNewState, boolean pIsMoving) {
        if (pState.getBlock() != pNewState.getBlock()) {
            BlockEntity blockEntity = pLevel.getBlockEntity(pPos);
            if (blockEntity instanceof VirusGeneratorBlockEntity) {
                ((VirusGeneratorBlockEntity) blockEntity).drops();
            }
        }
        super.onRemove(pState, pLevel, pPos, pNewState, pIsMoving);
    }

    @Override
    public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos,
                                 Player pPlayer, InteractionHand pHand, BlockHitResult pHit) {
        if (!pLevel.isClientSide()) {
            BlockEntity entity = pLevel.getBlockEntity(pPos);
            if(entity instanceof VirusGeneratorBlockEntity) {
                NetworkHooks.openGui(((ServerPlayer)pPlayer), (VirusGeneratorBlockEntity)entity, pPos);
            } else {
                throw new IllegalStateException("Our Container provider is missing!");
            }
        }

        return InteractionResult.sidedSuccess(pLevel.isClientSide());
    }

    @Nullable
    @Override
    public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) {
        return new VirusGeneratorBlockEntity(pPos, pState);
    }

    @Nullable
    @Override
    public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level pLevel, BlockState pState, BlockEntityType<T> pBlockEntityType) {
        return createTickerHelper(pBlockEntityType, BlockEntityInit.VIRUS_GENERATOR_BLOCK_ENTITY.get(),
                VirusGeneratorBlockEntity::tick);
    }
    
}
在BlockInit中注册我们的方块:
    public static final RegistryObject<Block> VIRUS_GENERATOR_BLOCK = registerBlock("virus_generator",
            () -> new VirusGeneratorBlock(BlockBehaviour.Properties.copy(Blocks.IRON_BLOCK).noOcclusion()),
            Main.TUTORIAL_TAB);

2.在virusgenerator包中新建一个我们的实体类VirusGeneratorBlockEntity:

VirusGeneratorBlockEntity.java

package com.joy187.re8joymod.blocks.virusgenerator;


import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.Containers;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.alchemy.PotionUtils;
import net.minecraft.world.item.alchemy.Potions;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.AbstractFurnaceBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemStackHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import com.joy187.re8joymod.init.BlockEntityInit;
import com.joy187.re8joymod.init.ItemInit;
import com.joy187.re8joymod.screen.VirusGeneratorMenu;

import javax.annotation.Nonnull;
import java.util.Optional;
import java.util.Random;

public class VirusGeneratorBlockEntity extends BlockEntity implements MenuProvider {
	
	private BlockState blockState;

	   
    private final ItemStackHandler itemHandler = new ItemStackHandler(4) {
        @Override
        protected void onContentsChanged(int slot) {
            setChanged();
        }
    };

    private LazyOptional<IItemHandler> lazyItemHandler = LazyOptional.empty();

    protected final ContainerData data;
    private int progress = 0;
    //你熔炼一个物品所需要的时间刻tick
    private int maxProgress = 72;

    public VirusGeneratorBlockEntity(BlockPos pWorldPosition, BlockState pBlockState) {
        super(BlockEntityInit.VIRUS_GENERATOR_BLOCK_ENTITY.get(), pWorldPosition, pBlockState);
        this.data = new ContainerData() {
            public int get(int index) {
                switch (index) {
                    case 0: return VirusGeneratorBlockEntity.this.progress;
                    case 1: return VirusGeneratorBlockEntity.this.maxProgress;
                    default: return 0;
                }
            }

            public void set(int index, int value) {
                switch(index) {
                    case 0: VirusGeneratorBlockEntity.this.progress = value; break;
                    case 1: VirusGeneratorBlockEntity.this.maxProgress = value; break;
                }
            }

            public int getCount() {
                return 2;
            }
        };
    }

    @Override
    public Component getDisplayName() {
        return new TranslatableComponent("container.virus_generator");
    }
    
//    protected Component getDefaultName() {
//        return new TranslatableComponent("container.virus_generator");
//    }
    
    @Nullable
    @Override
    public AbstractContainerMenu createMenu(int pContainerId, Inventory pInventory, Player pPlayer) {
        return new VirusGeneratorMenu(pContainerId, pInventory, this, this.data);
    }

    @Nonnull
    @Override
    public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @javax.annotation.Nullable Direction side) {
        if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
            return lazyItemHandler.cast();
        }

        return super.getCapability(cap, side);
    }

    @Override
    public void onLoad() {
        super.onLoad();
        lazyItemHandler = LazyOptional.of(() -> itemHandler);
    }

    @Override
    public void invalidateCaps()  {
        super.invalidateCaps();
        lazyItemHandler.invalidate();
    }

    @Override
    protected void saveAdditional(@NotNull CompoundTag tag) {
        tag.put("inventory", itemHandler.serializeNBT());
        tag.putInt("virus_generator.progress", progress);
        super.saveAdditional(tag);
    }

    @Override
    public void load(CompoundTag nbt) {
        super.load(nbt);
        itemHandler.deserializeNBT(nbt.getCompound("inventory"));
        progress = nbt.getInt("virus_generator.progress");
    }

    public void drops() {
        SimpleContainer inventory = new SimpleContainer(itemHandler.getSlots());
        for (int i = 0; i < itemHandler.getSlots(); i++) {
            inventory.setItem(i, itemHandler.getStackInSlot(i));
        }

        Containers.dropContents(this.level, this.worldPosition, inventory);
    }

    public static void tick(Level pLevel, BlockPos pPos, BlockState pState, VirusGeneratorBlockEntity pBlockEntity) {
        if(hasRecipe(pBlockEntity)) {
        	pState.setValue(VirusGeneratorBlock.LIT, Boolean.valueOf(true));
            pBlockEntity.progress++;
            setChanged(pLevel, pPos, pState);
            if(pBlockEntity.progress > pBlockEntity.maxProgress) {
                craftItem(pBlockEntity);
            }
        } else {
        	pState.setValue(VirusGeneratorBlock.LIT, Boolean.valueOf(false));
            pBlockEntity.resetProgress();
            setChanged(pLevel, pPos, pState);
        }
//    	if(hasRecipe(pBlockEntity) && notReachLimit(pBlockEntity)) {
//    		craftItem(pBlockEntity);
//    	}
    }

    private static boolean hasRecipe(VirusGeneratorBlockEntity entity) {
        Level level = entity.level;
        SimpleContainer inventory = new SimpleContainer(entity.itemHandler.getSlots());
        for (int i = 0; i < entity.itemHandler.getSlots(); i++) {
            inventory.setItem(i, entity.itemHandler.getStackInSlot(i));
        }

        Optional<VirusGeneratorRecipe> match = level.getRecipeManager()
                .getRecipeFor(VirusGeneratorRecipe.Type.INSTANCE, inventory, level);

        return match.isPresent() && canInsertAmountIntoOutputSlot(inventory)
                && canInsertItemIntoOutputSlot(inventory, match.get().getResultItem())
                && hasFuelSlot(entity); // && has0Slot(entity) && has1Slot(entity);
//        boolean canUse = has0Slot(entity) && hasFuelSlot(entity) && has1Slot(entity);
//        if(canUse) entity.blockState = entity.blockState.setValue(VirusGeneratorBlock.LIT, Boolean.valueOf(true));
//        else entity.blockState = entity.blockState.setValue(VirusGeneratorBlock.LIT, Boolean.valueOf(false));
//        return canUse;
    }

//    private static boolean has0Slot(VirusGeneratorBlockEntity entity) {
//        return entity.itemHandler.getStackInSlot(0).getItem() == Items.ROTTEN_FLESH;
//    }

//    private static boolean has1Slot(VirusGeneratorBlockEntity entity) {
//        return entity.itemHandler.getStackInSlot(1).getItem() == ItemInit.LYEYE.get();
//    }
    //你的燃料槽
    private static boolean hasFuelSlot(VirusGeneratorBlockEntity entity) {
        return entity.itemHandler.getStackInSlot(2).getItem() == ItemInit.BLACKSHEEP.get()
        || entity.itemHandler.getStackInSlot(2).getItem() == Items.FERMENTED_SPIDER_EYE;
    }
    //检查物品槽是否放了原料
    private static void craftItem(VirusGeneratorBlockEntity entity) {
        Level level = entity.level;
        SimpleContainer inventory = new SimpleContainer(entity.itemHandler.getSlots());
        for (int i = 0; i < entity.itemHandler.getSlots(); i++) {
            inventory.setItem(i, entity.itemHandler.getStackInSlot(i));
        }
        
        //检查是否有这个配方
        Optional<VirusGeneratorRecipe> match = level.getRecipeManager()
                .getRecipeFor(VirusGeneratorRecipe.Type.INSTANCE, inventory, level);
        
        if(match.isPresent()) {
        //if(has0Slot(entity) && has1Slot(entity) && hasFuelSlot(entity)) {
            entity.itemHandler.extractItem(0,1, false);
            entity.itemHandler.extractItem(1,1, false);
            entity.itemHandler.extractItem(2,1, false);

            //产出物品
            entity.itemHandler.setStackInSlot(3, new ItemStack(match.get().getResultItem().getItem(),
                    entity.itemHandler.getStackInSlot(3).getCount() + 1));
//          entity.itemHandler.setStackInSlot(3, new ItemStack(ItemInit.HUMUS.get(),
//        		  entity.itemHandler.getStackInSlot(3).getCount() + 1));
          entity.resetProgress();
        }
    }

    private void resetProgress() {
        this.progress = 0;
    }
    //我们设定了3号槽是产物槽
    private static boolean canInsertItemIntoOutputSlot(SimpleContainer inventory, ItemStack output) {
        return inventory.getItem(3).getItem() == output.getItem() || inventory.getItem(3).isEmpty();
    }

    private static boolean canInsertAmountIntoOutputSlot(SimpleContainer inventory) {
        return inventory.getItem(3).getMaxStackSize() > inventory.getItem(3).getCount();
    }
    
    private static boolean notReachLimit(VirusGeneratorBlockEntity entity) {
    	return entity.itemHandler.getStackInSlot(3).getCount()<64;
    }
    
//    @Override
//    public BlockState getBlockState() {
//        return this.blockState;
//     }

}
在init包中新建一个BlockEntityInit类,注册我们模组中所有的方块实体:

BlockEntityInit.java

package com.joy187.re8joymod.init;

import com.joy187.re8joymod.Main;
import com.joy187.re8joymod.blocks.virusgenerator.VirusGeneratorBlockEntity;

import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

public class BlockEntityInit {
    public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES =
            DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, Main.MOD_ID);

    public static final RegistryObject<BlockEntityType<VirusGeneratorBlockEntity>> VIRUS_GENERATOR_BLOCK_ENTITY =
            BLOCK_ENTITIES.register("virus_generator_block_entity", () ->
                    BlockEntityType.Builder.of(VirusGeneratorBlockEntity::new,
                            BlockInit.VIRUS_GENERATOR_BLOCK.get()).build(null));
    
//    public static final RegistryObject<BlockEntityType<GeneratorBlockEntity>> GENERATOR_BLOCK_ENTITY =
//            BLOCK_ENTITIES.register("generator_block_entity", () ->
//                    BlockEntityType.Builder.of(GeneratorBlockEntity::new,
//                            BlockInit.GENERATOR_BLOCK.get()).build(null)); 
    public static void register(IEventBus eventBus) {
        BLOCK_ENTITIES.register(eventBus);
    }
}
在我们的项目主类中的Main函数中将BlockEntityInit类进行注册:
	public Main() {
		IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();

		ItemInit.ITEMS.register(bus);
		BlockInit.BLOCKS.register(bus);
		EntityInit.ENTITY_TYPES.register(bus);
		EffectInit.EFFECTS.register(bus);
		PotionInit.POTIONS.register(bus);
		SoundInit.SOUNDS.register(bus);

        //添加这个		
		BlockEntityInit.register(bus);

        
        
		MinecraftForge.EVENT_BUS.register(this);
	}

3.在virusgenerator包中新建一个我们的配方类VirusGeneratorRecipe:

VirusGeneratorRecipe.java

package com.joy187.re8joymod.blocks.virusgenerator;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.joy187.re8joymod.Main;
import com.joy187.re8joymod.init.BlockInit;
import com.joy187.re8joymod.init.ItemInit;

import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;

import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;

public class VirusGeneratorRecipe implements Recipe<SimpleContainer> {
    private final ResourceLocation id;
    private final ItemStack output;
    private final NonNullList<Ingredient> recipeItems;

    public VirusGeneratorRecipe(ResourceLocation id, ItemStack output,
                                   NonNullList<Ingredient> recipeItems) {
        this.id = id;
        this.output = output;
        this.recipeItems = recipeItems;
    }

    @Override
    public boolean matches(SimpleContainer pContainer, Level pLevel) {
        return recipeItems.get(0).test(pContainer.getItem(1))
        		&& recipeItems.get(1).test(pContainer.getItem(0));
    }

    @Override
    public NonNullList<Ingredient> getIngredients() {
        return recipeItems;
    }

    @Override
    public ItemStack assemble(SimpleContainer pContainer) {
        return output;
    }

    @Override
    public boolean canCraftInDimensions(int pWidth, int pHeight) {
        return true;
    }

    @Override
    public ItemStack getResultItem() {
        return output.copy();
    }

    @Override
    public ResourceLocation getId() {
        return id;
    }

    @Override
    public RecipeSerializer<?> getSerializer() {
        return Serializer.INSTANCE;
    }

    @Override
    public RecipeType<?> getType() {
        return Type.INSTANCE;
    }

    public static class Type implements RecipeType<VirusGeneratorRecipe> {
        private Type() { }
        public static final Type INSTANCE = new Type();
        //这个就是我们之后的配方类的字段
        public static final String ID = "virus_generator";
    }

    public static class Serializer implements RecipeSerializer<VirusGeneratorRecipe> {
        public static final Serializer INSTANCE = new Serializer();
        public static final ResourceLocation ID = new ResourceLocation(Main.MOD_ID,"virus_generator");

        @Override
        public VirusGeneratorRecipe fromJson(ResourceLocation id, JsonObject json) {
            ItemStack output = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "output"));

            JsonArray ingredients = GsonHelper.getAsJsonArray(json, "ingredients");
            //这里说明一共有几个原料,我们需要2个,就写2,需要3个,就改成3
            NonNullList<Ingredient> inputs = NonNullList.withSize(2, Ingredient.EMPTY);

            for (int i = 0; i < inputs.size(); i++) {
                inputs.set(i, Ingredient.fromJson(ingredients.get(i)));
            }

            return new VirusGeneratorRecipe(id, output, inputs);
        }

        @Override
        public VirusGeneratorRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf buf) {
            NonNullList<Ingredient> inputs = NonNullList.withSize(buf.readInt(), Ingredient.EMPTY);
            
            for (int i = 0; i < inputs.size(); i++) {
                inputs.set(i, Ingredient.fromNetwork(buf));
            }

            ItemStack output = buf.readItem();
            return new VirusGeneratorRecipe(id, output, inputs);
        }

        @Override
        public void toNetwork(FriendlyByteBuf buf, VirusGeneratorRecipe recipe) {
            buf.writeInt(recipe.getIngredients().size());
            for (Ingredient ing : recipe.getIngredients()) {
                ing.toNetwork(buf);
            }
            buf.writeItemStack(recipe.getResultItem(), false);
        }

        @Override
        public RecipeSerializer<?> setRegistryName(ResourceLocation name) {
            return INSTANCE;
        }

        @Nullable
        @Override
        public ResourceLocation getRegistryName() {
            return ID;
        }

        @Override
        public Class<RecipeSerializer<?>> getRegistryType() {
            return Serializer.castClass(RecipeSerializer.class);
        }

        @SuppressWarnings("unchecked") // Need this wrapper, because generics
        private static <G> Class<G> castClass(Class<?> cls) {
            return (Class<G>)cls;
        }
    }
}

4.方块和实体的代码部分结束,进入到GUI的制作环节。在Java包中新建一个screen包 -> screen包中新建VirusGeneratorScreen类指明我们GUI贴图存放的位置:

VirusGeneratorScreen.java

package com.joy187.re8joymod.screen;

import com.joy187.re8joymod.Main;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;

public class VirusGeneratorScreen extends AbstractContainerScreen<VirusGeneratorMenu> {
    //指明我们材质的位置
    private static final ResourceLocation TEXTURE =
            new ResourceLocation(Main.MOD_ID, "textures/gui/virus_generator.png");

    public VirusGeneratorScreen(VirusGeneratorMenu pMenu, Inventory pPlayerInventory, Component pTitle) {
        super(pMenu, pPlayerInventory, pTitle);
    }

    @Override
    protected void renderBg(PoseStack pPoseStack, float pPartialTick, int pMouseX, int pMouseY) {
        RenderSystem.setShader(GameRenderer::getPositionTexShader);
        RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
        RenderSystem.setShaderTexture(0, TEXTURE);
        int x = (width - imageWidth) / 2;
        int y = (height - imageHeight) / 2;

        this.blit(pPoseStack, x, y, 0, 0, imageWidth, imageHeight);

        if(menu.isCrafting()) {
            //blit(pPoseStack, x + 102, y + 41, 176, 0, 8, menu.getScaledProgress());
            blit(pPoseStack, x + 8, y + 54+12-13, 176, 12-13, 14, menu.getScaledProgress());
        }
    }
    //this.drawTexturedModalRect(this.guiLeft + 8, this.guiTop + 54 + 12 - k, 176, 12 - k, 14, k + 1);

    @Override
    public void render(PoseStack pPoseStack, int mouseX, int mouseY, float delta) {
        renderBackground(pPoseStack);
        super.render(pPoseStack, mouseX, mouseY, delta);
        renderTooltip(pPoseStack, mouseX, mouseY);
    }
}
在我们项目主类的clientSetup()函数中将screen时间进行注册:
    private void clientSetup(final FMLClientSetupEvent event) {
        //EntityRenderers.register(EntityInit.DUND1.get(), RenderDund::new);
        ItemBlockRenderTypes.setRenderLayer(BlockInit.EBONY_LEAVES.get(), RenderType.cutout());
        ItemBlockRenderTypes.setRenderLayer(BlockInit.HERB_BLOCK.get(), RenderType.cutout());
        ItemBlockRenderTypes.setRenderLayer(BlockInit.EBONY_SAPLING.get(), RenderType.cutout());

		//添加这个,第一个参数机器方块的GUI,后面是这个机器方块的screen
        MenuScreens.register(MenuInit.VIRUS_GENERATOR_MENU.get(), VirusGeneratorScreen::new);

    }
screen包中新建VirusGeneratorMenu类将所有的槽位的位置都指出来:

VirusGeneratorMenu.java

package com.joy187.re8joymod.screen;


import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.*;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.SlotItemHandler;
import org.jetbrains.annotations.Nullable;
import com.joy187.re8joymod.blocks.virusgenerator.VirusGeneratorBlock;
import com.joy187.re8joymod.blocks.virusgenerator.VirusGeneratorBlockEntity;
import com.joy187.re8joymod.init.BlockInit;
import com.joy187.re8joymod.init.MenuInit;
import com.joy187.re8joymod.screen.slot.ModResultSlot;

public class VirusGeneratorMenu extends AbstractContainerMenu {
    private final VirusGeneratorBlockEntity blockEntity;
    private final Level level;
    private final ContainerData data;

    public VirusGeneratorMenu(int pContainerId, Inventory inv, FriendlyByteBuf extraData) {
        this(pContainerId, inv, inv.player.level.getBlockEntity(extraData.readBlockPos()), new SimpleContainerData(2));
    }

    public VirusGeneratorMenu(int pContainerId, Inventory inv, BlockEntity entity, ContainerData data) {
        super(MenuInit.VIRUS_GENERATOR_MENU.get(), pContainerId);
        checkContainerSize(inv, 4);
        blockEntity = ((VirusGeneratorBlockEntity) entity);
        this.level = inv.player.level;
        this.data = data;

        addPlayerInventory(inv);
        addPlayerHotbar(inv);
        
        //将所有我们的槽位的位置都标注出来
        this.blockEntity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).ifPresent(handler -> {
            this.addSlot(new SlotItemHandler(handler, 0, 26, 11));
            this.addSlot(new SlotItemHandler(handler, 1, 26, 59));
            this.addSlot(new SlotItemHandler(handler, 2, 7, 35));
            
            this.addSlot(new ModResultSlot(handler, 3, 81, 36));
        });

        addDataSlots(data);
    }

    public boolean isCrafting() {
        return data.get(0) > 0;
    }
    
    //这个是播放我们方块工作时的执行进度的动画,类似于熔炉燃烧时中间的进度条
    public int getScaledProgress() {
        int progress = this.data.get(0);
        int maxProgress = this.data.get(1);  // Max Progress
        //我们的进度条高13像素
        int progressArrowSize = 13;
        return maxProgress != 0 && progress != 0 ? progress * progressArrowSize / maxProgress : 0;
    }

    // CREDIT GOES TO: diesieben07 | https://github.com/diesieben07/SevenCommons
    // must assign a slot number to each of the slots used by the GUI.
    // For this container, we can see both the tile inventory's slots as well as the player inventory slots and the hotbar.
    // Each time we add a Slot to the container, it automatically increases the slotIndex, which means
    //  0 - 8 = hotbar slots (which will map to the InventoryPlayer slot numbers 0 - 8)
    //  9 - 35 = player inventory slots (which map to the InventoryPlayer slot numbers 9 - 35)
    //  36 - 44 = TileInventory slots, which map to our TileEntity slot numbers 0 - 8)
    private static final int HOTBAR_SLOT_COUNT = 9;
    private static final int PLAYER_INVENTORY_ROW_COUNT = 3;
    private static final int PLAYER_INVENTORY_COLUMN_COUNT = 9;
    private static final int PLAYER_INVENTORY_SLOT_COUNT = PLAYER_INVENTORY_COLUMN_COUNT * PLAYER_INVENTORY_ROW_COUNT;
    private static final int VANILLA_SLOT_COUNT = HOTBAR_SLOT_COUNT + PLAYER_INVENTORY_SLOT_COUNT;
    private static final int VANILLA_FIRST_SLOT_INDEX = 0;
    private static final int TE_INVENTORY_FIRST_SLOT_INDEX = VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT;

    // THIS YOU HAVE TO DEFINE!
    private static final int TE_INVENTORY_SLOT_COUNT = 4;  // must be the number of slots you have!

    @Override
    public ItemStack quickMoveStack(Player playerIn, int index) {
        Slot sourceSlot = slots.get(index);
        if (sourceSlot == null || !sourceSlot.hasItem()) return ItemStack.EMPTY;  //EMPTY_ITEM
        ItemStack sourceStack = sourceSlot.getItem();
        ItemStack copyOfSourceStack = sourceStack.copy();

        // Check if the slot clicked is one of the vanilla container slots
        if (index < VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT) {
            // This is a vanilla container slot so merge the stack into the tile inventory
            if (!moveItemStackTo(sourceStack, TE_INVENTORY_FIRST_SLOT_INDEX, TE_INVENTORY_FIRST_SLOT_INDEX
                    + TE_INVENTORY_SLOT_COUNT, false)) {
                return ItemStack.EMPTY;  // EMPTY_ITEM
            }
        } else if (index < TE_INVENTORY_FIRST_SLOT_INDEX + TE_INVENTORY_SLOT_COUNT) {
            // This is a TE slot so merge the stack into the players inventory
            if (!moveItemStackTo(sourceStack, VANILLA_FIRST_SLOT_INDEX, VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT, false)) {
                return ItemStack.EMPTY;
            }
        } else {
            System.out.println("Invalid slotIndex:" + index);
            return ItemStack.EMPTY;
        }
        // If stack size == 0 (the entire stack was moved) set slot contents to null
        if (sourceStack.getCount() == 0) {
            sourceSlot.set(ItemStack.EMPTY);
        } else {
            sourceSlot.setChanged();
        }
        sourceSlot.onTake(playerIn, sourceStack);
        return copyOfSourceStack;
    }

    @Override
    public boolean stillValid(Player pPlayer) {
        return stillValid(ContainerLevelAccess.create(level, blockEntity.getBlockPos()),
                pPlayer, BlockInit.VIRUS_GENERATOR_BLOCK.get());
    }
    
    //3行物品栏
    private void addPlayerInventory(Inventory playerInventory) {
        for (int i = 0; i < 3; ++i) {
            for (int l = 0; l < 9; ++l) {
                this.addSlot(new Slot(playerInventory, l + i * 9 + 9, 8 + l * 18, 84 + i * 18));
            }
        }
    }
    //一行你的个人栏
    private void addPlayerHotbar(Inventory playerInventory) {
        for (int i = 0; i < 9; ++i) {
            this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142));
        }
    }
}
我们的槽位对应关系,左上角为坐标原点:

cr3.png

我们13像素的进度条就是用右上角的图案覆盖左下角的图案。

cr9.png

在screen包中新建ModResultSlot类,将我们的产物放置进行预设。

ModResultSlot.java

public class ModResultSlot extends SlotItemHandler {
    public ModResultSlot(IItemHandler itemHandler, int index, int x, int y) {
        super(itemHandler, index, x, y);
    }

    @Override
    public boolean mayPlace(ItemStack stack) {
        return false;
    }
}

5.在init包中新建MenuInit类,将我们第四步中的菜单进行注册:

MenuInit.java

package com.joy187.re8joymod.init;

import com.joy187.re8joymod.Main;
import com.joy187.re8joymod.screen.VirusGeneratorMenu;

import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraftforge.common.extensions.IForgeMenuType;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.network.IContainerFactory;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

public class MenuInit {
    public static final DeferredRegister<MenuType<?>> MENUS =
            DeferredRegister.create(ForgeRegistries.CONTAINERS, Main.MOD_ID);
    //注册我们的方块菜单界面
    public static final RegistryObject<MenuType<VirusGeneratorMenu>> VIRUS_GENERATOR_MENU =
            registerMenuType(VirusGeneratorMenu::new, "virus_generator_menu");



    private static <T extends AbstractContainerMenu>RegistryObject<MenuType<T>> registerMenuType(IContainerFactory<T> factory,
    		String name) {
        return MENUS.register(name, () -> IForgeMenuType.create(factory));
    }

    public static void register(IEventBus eventBus) {
        MENUS.register(eventBus);
    }
}
在我们的项目主类中的Main函数中将MenuInit类进行注册:
	public Main() {
		IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();

		ItemInit.ITEMS.register(bus);
		BlockInit.BLOCKS.register(bus);
		EntityInit.ENTITY_TYPES.register(bus);
		EffectInit.EFFECTS.register(bus);
		PotionInit.POTIONS.register(bus);
		SoundInit.SOUNDS.register(bus);

		BlockEntityInit.register(bus);
		
        //添加这个
        MenuInit.register(bus);

        
		MinecraftForge.EVENT_BUS.register(this);
	}

6.在init包中新建RecipeInit类,将我们第三步中的配方类进行注册:

RecipeInit.java

package com.joy187.re8joymod.init;

import com.joy187.re8joymod.Main;
import com.joy187.re8joymod.blocks.virusgenerator.VirusGeneratorRecipe;

import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

public class RecipeInit {
    public static final DeferredRegister<RecipeSerializer<?>> SERIALIZERS =
            DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, Main.MOD_ID);

    public static final RegistryObject<RecipeSerializer<VirusGeneratorRecipe>> VIRUS_GENERATOR_SERIALIZER =
            SERIALIZERS.register("virus_generator", () -> VirusGeneratorRecipe.Serializer.INSTANCE);

    public static void register(IEventBus eventBus) {
        SERIALIZERS.register(eventBus);
    }
}
在我们的项目主类中的Main函数中将RecipeInit类进行注册:
	public Main() {
		IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();

		ItemInit.ITEMS.register(bus);
		BlockInit.BLOCKS.register(bus);
		EntityInit.ENTITY_TYPES.register(bus);
		EffectInit.EFFECTS.register(bus);
		PotionInit.POTIONS.register(bus);
		SoundInit.SOUNDS.register(bus);

		BlockEntityInit.register(bus);
        MenuInit.register(bus);

        //添加这个
		RecipeInit.register(bus);

        
		MinecraftForge.EVENT_BUS.register(this);
	}
ClientModEventSubscriber类中添加我们的配方类型的类进行注册

ClientModEventSubscriber.java

    @SubscribeEvent
    public static void registerRecipeTypes(final RegistryEvent.Register<RecipeSerializer<?>> event) {
        Registry.register(Registry.RECIPE_TYPE, VirusGeneratorRecipe.Type.ID, VirusGeneratorRecipe.Type.INSTANCE);
    }

7.代码部分结束,来到资源包制做。在src\main\resources\assets\你的modid\blockstates中新建我们的方块的状态文件:

virus_generator.json

{
  "variants": {
    "facing=east,lit=false": {
      "model": "re8joymod:block/virus_generator",
      "y": 90
    },
    "facing=east,lit=true": {
      "model": "re8joymod:block/virus_generator_on",
      "y": 90
    },
    "facing=north,lit=false": {
      "model": "re8joymod:block/virus_generator"
    },
    "facing=north,lit=true": {
      "model": "re8joymod:block/virus_generator_on"
    },
    "facing=south,lit=false": {
      "model": "re8joymod:block/virus_generator",
      "y": 180
    },
    "facing=south,lit=true": {
      "model": "re8joymod:block/virus_generator_on",
      "y": 180
    },
    "facing=west,lit=false": {
      "model": "re8joymod:block/virus_generator",
      "y": 270
    },
    "facing=west,lit=true": {
      "model": "re8joymod:block/virus_generator_on",
      "y": 270
    }
  }
}
src\main\resources\assets\你的modid\models\block中新建两个我们的方块模型文件:
方块平时的模型

virus_generator.json

{
  "parent": "block/orientable",
  "textures": {
    "top": "re8joymod:block/virus_generator_side",
    "front": "re8joymod:block/virus_generator",
    "side": "re8joymod:block/virus_generator_side"
  }
}
方块工作时的模型

virus_generator_on.json

{
  "parent": "block/orientable",
  "textures": {
    "top": "re8joymod:block/virus_generator_side",
    "front": "re8joymod:block/virus_generator_on",
    "side": "re8joymod:block/virus_generator_side"
  }
}
models\item中添加我们手拿方块时的模型文件

virus_generator.json

{
  "parent": "re8joymod:block/virus_generator"
}
textures\block中添加我们方块的侧面、正面不工作、正面工作时的贴图:

cr4.png

在textures包中新建gui包 -> gui包中把我们的gui(大小为256×256像素点)放进去:

cr5.png

在lang包中的en_us.json文件中加上我们方块实体的名称和打开机器后上面显示的名称:
  "block.re8joymod.virus_generator":"Virus Analyser",
  "container.virus_generator":"Virus Analyser",

8.在src\main\resources\data\你的modid\recipes中新建几个属于我们的方块实体的配方:

记得在第三步中我们把配方类型设置为virus_generator,所以配方的type就写为virus_generator

evirus.json

{
	"type":"re8joymod:virus_generator",
	"ingredients":[
		{
			"item":"re8joymod:humus"
		},
		{
			"item":"re8joymod:herbglass"
		}
	],
	"output":{
		"item":"re8joymod:evirus"
	}
}

9.保存所有文件 -> 进入游戏调试:

首先拿出我们的机器并放置下来,外观显示正常

mac

将燃料和产物放入,成功产出了产物!

cr8.png

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jay_fearless

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值