本期我们继续探讨Minecraft Forge Mod的开发。在前面的教程中,我们已经介绍了方块状态(Block State),它可以用来存储简单的参数。但是,如果我们想要存储更复杂的参数类型,该怎么办呢?
举个例子,假设我们想要创建一个展示桌,这个展示桌可以展示Minecraft游戏中的任意物品,并将其放置在桌面上。如果我们使用方块状态来实现,我们需要自己实现一个参数来存储物品类型,这样游戏就会为每种物品都创建一个方块状态。而且,如果物品拥有耐久度等其他参数,这样的处理方式可能会变得更加复杂。
因此,对于这种情况,我们需要使用方块实体(Block Entity)。方块实体可以简单理解为附着在方块上的实体,它并不是游戏系统内真正的实体,而是一种以实体的方式来扩展方块功能的机制。
我已经为展示桌写好了相应的类,但是它现在还没有方块实体。接下来,我们要实现这个方块实体,让我们的方块拥有这个功能。我们需要实现一个名为BlockEntity的接口,并重写两个方法:newBlockEntity 和 getTicker。
然而,我们暂时无法实现这两个方法,因为它们需要返回BlockEntity和BlockEntityTicker类型的对象,这些对象需要我们自己实现。所以,我们首先要创建一个方块实体的类。
我们可以在这里新建一个Java类,命名为DeskBlockEntity。根据文档,要创建方块实体,我们需要继承BlockEntity类。接着,我们创建构造函数。
让我们回顾一下我们需要实现的功能:我们需要在展示桌上展示一种物品。首先,我们需要有一个物品的存储空间。当然,我们也可以自己实现这个存储空间,毕竟只有一个物品,我们只需要一个变量就可以了。但是,如果我们要存储多个物品,或者考虑与其他Mod的兼容性等问题,这种方式可能就不够灵活了。
对于这种情况,Forge为我们提供了一个解决方案:我们可以直接使用它提供的一些类和接口。
首先,我们来实现我们的存储空间。我们使用私有 final 字段 `itemStackHandler`,这是 Forge 为我们提供的 `ItemStackHandler`。我们只需要保存一样物品,所以我们创建一个新的 `ItemStackHandler` 实例。
privatefinalItemStackHandler itemStackHandler =newItemStackHandler(1);
但是在这里我们还需要写一些额外的东西。我们可以看到,在 `ItemStackHandler` 中,有两个方法没有实现,一个是 `onContentsChanged`,另一个是 `unload`。我们需要重写这两个方法。
@OverrideprotectedvoidonContentsChanged(intslot){super.onContentsChanged(slot);// 在物品发生变化时执行的操作}// 这里不实现 unload 方法,因为我们暂时不需要在方块卸载时执行操作
另外,我们需要一个 `lazyOptionalItemHandler` 字段,它是一个懒加载的可选项,用于存储我们的物品。我们需要在以后的代码中使用它。
privatefinalLazyOptional<IItemHandler> lazyOptionalItemHandler = LazyOptional.of(() -> itemStackHandler);
然后,我们需要实现 `getCapability` 方法,这个方法和之前写的类似。接着,我们需要编写保存和读取数据的方法 `saveData` 和 `loadData`。这些方法都需要用到 `CompoundTag`,而这个标签是经常要用到的。
@OverridepublicvoidsaveData(CompoundTag tag){super.saveData(tag);tag.put("TECHNITEMS", itemStackHandler.serializeNBT());}@OverridepublicvoidloadData(CompoundTag tag){super.loadData(tag);itemStackHandler.deserializeNBT(tag.getCompound("TECHNITEMS"));}
接着,根据文档的要求,我们需要实现同步数据的方法 `syncData` 和 `syncUpdates`。此外,还需要实现存储和读取数据的方法。
@OverridepublicvoidsyncData(){super.syncData();if(world !=null&& !world.isClient) {BlockState state = world.getBlockState(pos);world.updateListeners(pos, state, state, Block.NOTIFY_ALL);}}@OverridepublicvoidsyncUpdates(){super.syncUpdates();if(world !=null&& !world.isClient) {world.updateListeners(pos, world.getBlockState(pos), world.getBlockState(pos), Block.NOTIFY_ALL);}}
至此,我们的类基本完成了。`saveData` 和 `loadData` 方法的实现方式基本固定,其他方法也都是类似的。当然,如果你想插入其他逻辑,也可以在相应的位置额外编写。在没有其他逻辑的情况下,代码的基本结构就是这样的。接下来,我们需要实现 `BlockEntity` 的逻辑。
publicvoiduseBlockEntity(ServerPlayerEntity player){// 在服务器端执行的操作// 这里我们只是展示物品,不需要任何特殊的逻辑}
以上就是我们的代码,接下来我们可以继续实现其他的逻辑。
首先我们来实现方块实体的每课逻辑。在服务端,我们需要在 `tick` 方法中执行一些操作,但是在客户端我们并不需要。所以,我们需要对客户端和服务端分别处理。
@Overridepublicvoidtick(){super.tick();// 在服务器端执行的每课逻辑//TODO:在这里实现服务器端的逻辑}
然后是客户端的逻辑,一般情况下我们不需要在客户端执行太多逻辑,因为客户端的逻辑会导致数据不同步。但如果你有一些不会影响游戏世界的逻辑,可以在这里执行。
@OverridepublicvoidtickClient(){super.tickClient();// 在客户端执行的每课逻辑//TODO:在这里实现客户端的逻辑}
有了以上的代码,我们就可以在服务器端和客户端分别处理每课逻辑了。接下来,我们需要在 `BlockEntityType` 中返回我们的方块实体,并且注册这个方块实体的标签。
publicstaticfinalRegistryObject<BlockEntityType<DeskBlockEntity>> DESK_BLOCK_ENTITY = Registration.BLOCK_ENTITIES.register("desk_block_entity", () ->BlockEntityType.Builder.create(DeskBlockEntity::new, ModBlocks.DESK_BLOCK.get()).build(null));publicstaticfinalDeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID);publicstaticfinalRegistryObject<Item> DESK_BLOCK_ITEM = ITEMS.register("desk_block", () ->newBlockItem(ModBlocks.DESK_BLOCK.get(),newItem.Properties().group(ItemGroup.DECORATIONS)));publicstaticfinalDeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, MOD_ID);
以上就是注册方块实体和方块项目的代码。我们还需要在客户端和服务端分别获取方块实体的标签,这样我们就可以在游戏中正确地处理方块实体了。
我们需要为我们的桌子创建一个渲染器,以便能够展示物品。我们可以通过 `BlockEntityRenderer` 类来实现这个渲染器。以下是实现的代码:
publicclassDeskBlockEntityRendererextendsBlockEntityRenderer<DeskBlockEntity>{publicDeskBlockEntityRenderer(BlockEntityRenderDispatcher dispatcher){super(dispatcher);}@Overridepublicvoidrender(DeskBlockEntity blockEntity,floatpartialTicks, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider,intlight,intoverlay){super.render(blockEntity, partialTicks, matrixStack, vertexConsumerProvider, light, overlay);ItemStack itemStack = blockEntity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).map(itemHandler -> itemHandler.getStackInSlot(0)).orElse(ItemStack.EMPTY);if(!itemStack.isEmpty()) {matrixStack.push();matrixStack.scale(0.5f,0.5f,0.5f);matrixStack.translate(1.0,3.0,1.0);// 调整物品的位置MinecraftClient.getInstance().getItemRenderer().renderItem(itemStack, ModelTransformation.Mode.FIXED, light, overlay, matrixStack, vertexConsumerProvider);matrixStack.pop();}}}
在这个渲染器中,我们首先获取方块实体上的物品。然后,我们对物品进行渲染,使用 `MinecraftClient.getInstance().getItemRenderer().renderItem` 方法。在渲染之前,我们进行了一些参数的设置,比如缩放和位置偏移。
最后,我们需要注册这个渲染器,以确保在游戏中生效:
@Environment(EnvType.CLIENT)publicclassModClientSetup{publicstaticvoidinit(){BlockEntityRendererRegistry.INSTANCE.register(ModBlocks.DESK_BLOCK_ENTITY.get(),newDeskBlockEntityRenderer());}}
以上就是为我们的桌子创建一个渲染器的代码。现在,我们的桌子应该能够正确地展示放置在上面的物品了。
这个渲染器的功能还可以进一步拓展,大家可以根据自己的需求进行修改和优化。另外,我们还可以参考原版游戏中类似的渲染逻辑,了解更多的渲染技巧和效果。记得,渲染器是在客户端执行的,所以需要将其注册到客户端代码中。
@Environment(EnvType.CLIENT)publicclassModClientSetup{publicstaticvoidinit(){// 注册方块实体渲染器BlockEntityRendererRegistry.INSTANCE.register(ModBlocks.DESK_BLOCK_ENTITY.get(),newDeskBlockEntityRenderer());}@SubscribeEventpublicstaticvoidregisterBlockEntityRenderers(EntityRenderersEvent.RegisterBlockEntityRenderersevent){// 注册方块实体渲染器供客户端使用event.registerRenderer(ModBlocks.DESK_BLOCK_ENTITY.get(),newDeskBlockEntityRenderer());}}
在注册渲染器时,我们还需要注意确保在客户端代码中注册,因为渲染器只在客户端执行。另外,我们还需要在方块实体的 setChanged 方法中发送区块更新以确保数据同步。这样,我们就能够在游戏中展示放置在桌子上的物品了。如果想要进一步完善功能,比如右键交互放置物品等,可以继续实现相应的逻辑。