方块实体
Minecraft 使用名为方块实体(Block Entity)的机制来实现一些一般情况下方块做不到或很难做到的事情,例如:
- 持有更复杂的数据(物品、文本等)
- 实现像熔炉那样持续不断地行为
- 拥有奇妙的渲染特效
方块实体类型
Minecraft 中所有的方块实体均属于 BlockEntity
类型。我们首先需要创建我们自己的 BlockEntity
类。
- 核心代码
- 完整代码
public static final class MyMachineEntity extends BlockEntity {
public MyMachineEntity(BlockEntityType<MyMachineEntity> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
}
package org.teacon.xiaozhong;
import com.mojang.datafixers.DSL;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RenderShape;
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.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import org.jetbrains.annotations.NotNull;
@Mod("xiaozhong")
public class Xiaozhong {
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.Blocks.createBlocks("xiaozhong");
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, "xiaozhong");
public static final DeferredHolder<Block, MyMachine> MY_MACHINE;
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MyMachineEntity>> MY_MACHINE_BLOCK_ENTITY;
static {
MY_MACHINE = BLOCKS.register("my_machine",
() -> new MyMachine(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)));
MY_MACHINE_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("my_machine",
() -> BlockEntityType.Builder.of(MyMachineEntity::new, MY_MACHINE.get()).build(DSL.remainderType()));
}
public Xiaozhong(IEventBus modEventBus) {
BLOCKS.register(modEventBus);
BLOCK_ENTITY_TYPES.register(modEventBus);
}
public static final class MyMachine extends BaseEntityBlock {
public static final MapCodec<MyMachine> CODEC = simpleCodec(MyMachine::new);
public MyMachine(Properties props) {
super(props);
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@NotNull
@Override
public RenderShape getRenderShape(@NotNull BlockState state) {
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) {
return new MyMachineEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, @NotNull BlockState state, @NotNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, MY_MACHINE_BLOCK_ENTITY.get(), MyMachineEntity::tick);
}
}
public static final class MyMachineEntity extends BlockEntity {
public MyMachineEntity(BlockEntityType<MyMachineEntity> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
public MyMachineEntity(BlockPos worldPosition, BlockState blockState) {
this(MY_MACHINE_BLOCK_ENTITY.get(), worldPosition, blockState);
}
private int count = 0;
public static void tick(Level level, BlockPos pos, BlockState state, MyMachineEntity entity) {
entity.count += 1;
if (entity.count > 100) {
entity.count = 0;
if (level != null && !level.isClientSide()) {
var player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5.0, false);
if (player != null) {
player.sendSystemMessage(Component.translatable("chat.xiaozhong.welcome"));
}
}
}
}
}
}
然后我们需要注册一个 BlockEntityType
实例,它代表了「一种特定的方块实体」,指明了创建对应方块实体实例的工厂方法,及哪些方块允许持有这种方块实体。
BlockEntityType
由注册表统一管理,我们可以通过 DeferredRegister
来注册新的 BlockEntityType
。
使用 DeferredRegister
注册 BlockEntityType
:
- 核心代码
- 完整代码
// 注册 BlockEntityType
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, "xiaozhong");
// 注册 xiaozhong:my_machine
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MyMachienEntity>> MY_MACHINE_BLOCK_ENTITY;
MY_MACHINE_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("my_machine",
() -> BlockEntityType.Builder.of(MyMachineEntity::new, MY_MACHINE.get()).build(DSL.remainderType()));
// 添加监听器
BLOCK_ENTITY_TYPES.register(modEventBus);
package org.teacon.xiaozhong;
import com.mojang.datafixers.DSL;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RenderShape;
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.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import org.jetbrains.annotations.NotNull;
@Mod("xiaozhong")
public class Xiaozhong {
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.Blocks.createBlocks("xiaozhong");
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, "xiaozhong");
public static final DeferredHolder<Block, MyMachine> MY_MACHINE;
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MyMachineEntity>> MY_MACHINE_BLOCK_ENTITY;
static {
MY_MACHINE = BLOCKS.register("my_machine",
() -> new MyMachine(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)));
MY_MACHINE_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("my_machine",
() -> BlockEntityType.Builder.of(MyMachineEntity::new, MY_MACHINE.get()).build(DSL.remainderType()));
}
public Xiaozhong(IEventBus modEventBus) {
BLOCKS.register(modEventBus);
BLOCK_ENTITY_TYPES.register(modEventBus);
}
public static final class MyMachine extends BaseEntityBlock {
public static final MapCodec<MyMachine> CODEC = simpleCodec(MyMachine::new);
public MyMachine(Properties props) {
super(props);
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@NotNull
@Override
public RenderShape getRenderShape(@NotNull BlockState state) {
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) {
return new MyMachineEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, @NotNull BlockState state, @NotNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, MY_MACHINE_BLOCK_ENTITY.get(), MyMachineEntity::tick);
}
}
public static final class MyMachineEntity extends BlockEntity {
public MyMachineEntity(BlockEntityType<MyMachineEntity> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
public MyMachineEntity(BlockPos worldPosition, BlockState blockState) {
this(MY_MACHINE_BLOCK_ENTITY.get(), worldPosition, blockState);
}
private int count = 0;
public static void tick(Level level, BlockPos pos, BlockState state, MyMachineEntity entity) {
entity.count += 1;
if (entity.count > 100) {
entity.count = 0;
if (level != null && !level.isClientSide()) {
var player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5.0, false);
if (player != null) {
player.sendSystemMessage(Component.translatable("chat.xiaozhong.welcome"));
}
}
}
}
}
}
注意到 MyMachineEntity::new
此时会报错。刚才提到的「方块实体实例的工厂方法」就是这个,然而原版要求「这个工厂方法只接受两个参数:BlockPos
和 BlockState
」。我们向 MyMachineEntity
添加额外构造器来解决此问题:
- 核心代码
- 完整代码
public MyMachineEntity(BlockPos worldPosition, BlockState blockState) {
this(MY_MACHINE_BLOCK_ENTITY.get(), worldPosition, blockState);
}
package org.teacon.xiaozhong;
import com.mojang.datafixers.DSL;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RenderShape;
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.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import org.jetbrains.annotations.NotNull;
@Mod("xiaozhong")
public class Xiaozhong {
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.Blocks.createBlocks("xiaozhong");
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, "xiaozhong");
public static final DeferredHolder<Block, MyMachine> MY_MACHINE;
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MyMachineEntity>> MY_MACHINE_BLOCK_ENTITY;
static {
MY_MACHINE = BLOCKS.register("my_machine",
() -> new MyMachine(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)));
MY_MACHINE_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("my_machine",
() -> BlockEntityType.Builder.of(MyMachineEntity::new, MY_MACHINE.get()).build(DSL.remainderType()));
}
public Xiaozhong(IEventBus modEventBus) {
BLOCKS.register(modEventBus);
BLOCK_ENTITY_TYPES.register(modEventBus);
}
public static final class MyMachine extends BaseEntityBlock {
public static final MapCodec<MyMachine> CODEC = simpleCodec(MyMachine::new);
public MyMachine(Properties props) {
super(props);
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@NotNull
@Override
public RenderShape getRenderShape(@NotNull BlockState state) {
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) {
return new MyMachineEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, @NotNull BlockState state, @NotNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, MY_MACHINE_BLOCK_ENTITY.get(), MyMachineEntity::tick);
}
}
public static final class MyMachineEntity extends BlockEntity {
public MyMachineEntity(BlockEntityType<MyMachineEntity> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
public MyMachineEntity(BlockPos worldPosition, BlockState blockState) {
this(MY_MACHINE_BLOCK_ENTITY.get(), worldPosition, blockState);
}
private int count = 0;
public static void tick(Level level, BlockPos pos, BlockState state, MyMachineEntity entity) {
entity.count += 1;
if (entity.count > 100) {
entity.count = 0;
if (level != null && !level.isClientSide()) {
var player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5.0, false);
if (player != null) {
player.sendSystemMessage(Component.translatable("chat.xiaozhong.welcome"));
}
}
}
}
}
}
这个案例也展示了 DeferredRegister
的一个优势:可以方便开发者打破这种出现循环依赖的情况。
BlockEntity
此时像是一个纯粹的数据容器:我们在我们的 MyMachineEntity
中可以随意添加新的成员字段,并视情况决定是否创建对应的访问器。
- 核心代码
- 完整代码
public static final class MyMachineEntity extends BlockEntity {
public MyMachineEntity(BlockEntityType<MyMachineEntity> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
public MyMachineEntity(BlockPos worldPosition, BlockState blockState) {
this(MY_MACHINE_BLOCK_ENTITY.get(), worldPosition, blockState);
}
private int count = 0;
}
package org.teacon.xiaozhong;
import com.mojang.datafixers.DSL;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RenderShape;
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.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import org.jetbrains.annotations.NotNull;
@Mod("xiaozhong")
public class Xiaozhong {
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.Blocks.createBlocks("xiaozhong");
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, "xiaozhong");
public static final DeferredHolder<Block, MyMachine> MY_MACHINE;
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MyMachineEntity>> MY_MACHINE_BLOCK_ENTITY;
static {
MY_MACHINE = BLOCKS.register("my_machine",
() -> new MyMachine(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)));
MY_MACHINE_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("my_machine",
() -> BlockEntityType.Builder.of(MyMachineEntity::new, MY_MACHINE.get()).build(DSL.remainderType()));
}
public Xiaozhong(IEventBus modEventBus) {
BLOCKS.register(modEventBus);
BLOCK_ENTITY_TYPES.register(modEventBus);
}
public static final class MyMachine extends BaseEntityBlock {
public static final MapCodec<MyMachine> CODEC = simpleCodec(MyMachine::new);
public MyMachine(Properties props) {
super(props);
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@NotNull
@Override
public RenderShape getRenderShape(@NotNull BlockState state) {
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) {
return new MyMachineEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, @NotNull BlockState state, @NotNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, MY_MACHINE_BLOCK_ENTITY.get(), MyMachineEntity::tick);
}
}
public static final class MyMachineEntity extends BlockEntity {
public MyMachineEntity(BlockEntityType<MyMachineEntity> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
public MyMachineEntity(BlockPos worldPosition, BlockState blockState) {
this(MY_MACHINE_BLOCK_ENTITY.get(), worldPosition, blockState);
}
private int count = 0;
public static void tick(Level level, BlockPos pos, BlockState state, MyMachineEntity entity) {
entity.count += 1;
if (entity.count > 100) {
entity.count = 0;
if (level != null && !level.isClientSide()) {
var player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5.0, false);
if (player != null) {
player.sendSystemMessage(Component.translatable("chat.xiaozhong.welcome"));
}
}
}
}
}
}
方块类
脱离方块的方块实体是不存在的。能容纳方块实体的方块需实现 EntityBlock
接口。实现这一接口需实现 newBlockEntity
方法。
Minecraft 原版为我们提供了 BaseEntityBlock
类方便我们实现:
- 核心代码
- 完整代码
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.Blocks.createBlocks("xiaozhong");
public static final RegistryObject<Block> MY_MACHINE;
MY_MACHINE = BLOCKS.register("my_machine",
() -> new MyMachine(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)));
BLOCKS.register(modEventBus);
public static final class MyMachine extends BaseEntityBlock {
public static final MapCodec<MyMachine> CODEC = simpleCodec(MyMachine::new);
public MyMachine(Properties props) {
super(props);
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@NotNull
@Override
public RenderShape getRenderShape(@NotNull BlockState state) {
// 注意:这一方法在 BaseEntityBlock 中默认返回 INVISIBLE,
// 这代表 Minecraft 会跳过该方块的渲染。
// 为保证正常渲染,我们在此返回 MODEL,代表使用普通方块模型渲染。
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) {
// 这里返回我们刚刚定义的方块实体
return new MyMachineEntity(pos, state);
}
}
package org.teacon.xiaozhong;
import com.mojang.datafixers.DSL;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RenderShape;
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.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import org.jetbrains.annotations.NotNull;
@Mod("xiaozhong")
public class Xiaozhong {
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.Blocks.createBlocks("xiaozhong");
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, "xiaozhong");
public static final DeferredHolder<Block, MyMachine> MY_MACHINE;
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MyMachineEntity>> MY_MACHINE_BLOCK_ENTITY;
static {
MY_MACHINE = BLOCKS.register("my_machine",
() -> new MyMachine(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)));
MY_MACHINE_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("my_machine",
() -> BlockEntityType.Builder.of(MyMachineEntity::new, MY_MACHINE.get()).build(DSL.remainderType()));
}
public Xiaozhong(IEventBus modEventBus) {
BLOCKS.register(modEventBus);
BLOCK_ENTITY_TYPES.register(modEventBus);
}
public static final class MyMachine extends BaseEntityBlock {
public static final MapCodec<MyMachine> CODEC = simpleCodec(MyMachine::new);
public MyMachine(Properties props) {
super(props);
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@NotNull
@Override
public RenderShape getRenderShape(@NotNull BlockState state) {
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) {
return new MyMachineEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, @NotNull BlockState state, @NotNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, MY_MACHINE_BLOCK_ENTITY.get(), MyMachineEntity::tick);
}
}
public static final class MyMachineEntity extends BlockEntity {
public MyMachineEntity(BlockEntityType<MyMachineEntity> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
public MyMachineEntity(BlockPos worldPosition, BlockState blockState) {
this(MY_MACHINE_BLOCK_ENTITY.get(), worldPosition, blockState);
}
private int count = 0;
public static void tick(Level level, BlockPos pos, BlockState state, MyMachineEntity entity) {
entity.count += 1;
if (entity.count > 100) {
entity.count = 0;
if (level != null && !level.isClientSide()) {
var player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5.0, false);
if (player != null) {
player.sendSystemMessage(Component.translatable("chat.xiaozhong.welcome"));
}
}
}
}
}
}
方块实体刻
默认情况下,方块实体并不具备跟随游戏刻刷新(亦即方块实体刻)的能力,若要获得此能力,方块实体所在的那个方块需要明确声明一个所谓的「Ticker」,亦即 BlockEntityTicker<?>
。这通过覆盖 EntityBlock
的 getTicker
方法实现。
此外,你可以根据 Level
是在逻辑服务器上还是逻辑客户端上来返回不同的 Ticker。通常我们在客户端不需要特别的逻辑,因此我们会在 level.isClientSide()
返回 true
时,令 getTicker
返回 null
。
首先在 MyMachineEntity
中创建静态方法 tick
:
- 核心代码
- 完整代码
private int count = 0;
public static void tick(Level level, BlockPos pos, BlockState state, MyMachineEntity entity) {
// 计数。每一个游戏刻中,游戏都会调用一次 tick 方法,所以我们只需要在这里让 count 自增 1 即可。
entity.count += 1;
// 当计数超过 100 时……
if (entity.count > 100) {
// 首先重置计数
entity.count = 0;
// 检查 Level 实例是否存在,以及是不是在服务器上
if (level != null && !level.isClientSide()) {
// 然后寻找半径 5 方块以内的玩家,最后的 false 表示无视创造或观察模式的玩家
var player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5.0, false);
// 如果找到了这样的玩家……
if (player != null) {
// 向这名玩家的聊天栏发送问候语。
player.sendSystemMessage(Component.translatable("chat.xiaozhong.welcome"));
}
}
}
}
package org.teacon.xiaozhong;
import com.mojang.datafixers.DSL;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RenderShape;
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.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import org.jetbrains.annotations.NotNull;
@Mod("xiaozhong")
public class Xiaozhong {
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.Blocks.createBlocks("xiaozhong");
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, "xiaozhong");
public static final DeferredHolder<Block, MyMachine> MY_MACHINE;
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MyMachineEntity>> MY_MACHINE_BLOCK_ENTITY;
static {
MY_MACHINE = BLOCKS.register("my_machine",
() -> new MyMachine(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)));
MY_MACHINE_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("my_machine",
() -> BlockEntityType.Builder.of(MyMachineEntity::new, MY_MACHINE.get()).build(DSL.remainderType()));
}
public Xiaozhong(IEventBus modEventBus) {
BLOCKS.register(modEventBus);
BLOCK_ENTITY_TYPES.register(modEventBus);
}
public static final class MyMachine extends BaseEntityBlock {
public static final MapCodec<MyMachine> CODEC = simpleCodec(MyMachine::new);
public MyMachine(Properties props) {
super(props);
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@NotNull
@Override
public RenderShape getRenderShape(@NotNull BlockState state) {
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) {
return new MyMachineEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, @NotNull BlockState state, @NotNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, MY_MACHINE_BLOCK_ENTITY.get(), MyMachineEntity::tick);
}
}
public static final class MyMachineEntity extends BlockEntity {
public MyMachineEntity(BlockEntityType<MyMachineEntity> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
public MyMachineEntity(BlockPos worldPosition, BlockState blockState) {
this(MY_MACHINE_BLOCK_ENTITY.get(), worldPosition, blockState);
}
private int count = 0;
public static void tick(Level level, BlockPos pos, BlockState state, MyMachineEntity entity) {
entity.count += 1;
if (entity.count > 100) {
entity.count = 0;
if (level != null && !level.isClientSide()) {
var player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5.0, false);
if (player != null) {
player.sendSystemMessage(Component.translatable("chat.xiaozhong.welcome"));
}
}
}
}
}
}
然后在我们的方块类中,覆写 getTicker
方法:
- 核心代码
- 完整代码
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
// BaseEntityBlock 提供了 createTickerHelper 帮助你生成 BlockEntityTicker 的实例,这里直接调用并传入 tick 方法引用即可。
return level.isClientSide() ? null : createTickerHelper(blockEntityType, MY_MACHINE_BLOCK_ENTITY.get(), MyMachineEntity::tick);
}
package org.teacon.xiaozhong;
import com.mojang.datafixers.DSL;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RenderShape;
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.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import org.jetbrains.annotations.NotNull;
@Mod("xiaozhong")
public class Xiaozhong {
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.Blocks.createBlocks("xiaozhong");
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, "xiaozhong");
public static final DeferredHolder<Block, MyMachine> MY_MACHINE;
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MyMachineEntity>> MY_MACHINE_BLOCK_ENTITY;
static {
MY_MACHINE = BLOCKS.register("my_machine",
() -> new MyMachine(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)));
MY_MACHINE_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("my_machine",
() -> BlockEntityType.Builder.of(MyMachineEntity::new, MY_MACHINE.get()).build(DSL.remainderType()));
}
public Xiaozhong(IEventBus modEventBus) {
BLOCKS.register(modEventBus);
BLOCK_ENTITY_TYPES.register(modEventBus);
}
public static final class MyMachine extends BaseEntityBlock {
public static final MapCodec<MyMachine> CODEC = simpleCodec(MyMachine::new);
public MyMachine(Properties props) {
super(props);
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@NotNull
@Override
public RenderShape getRenderShape(@NotNull BlockState state) {
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) {
return new MyMachineEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, @NotNull BlockState state, @NotNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, MY_MACHINE_BLOCK_ENTITY.get(), MyMachineEntity::tick);
}
}
public static final class MyMachineEntity extends BlockEntity {
public MyMachineEntity(BlockEntityType<MyMachineEntity> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
public MyMachineEntity(BlockPos worldPosition, BlockState blockState) {
this(MY_MACHINE_BLOCK_ENTITY.get(), worldPosition, blockState);
}
private int count = 0;
public static void tick(Level level, BlockPos pos, BlockState state, MyMachineEntity entity) {
entity.count += 1;
if (entity.count > 100) {
entity.count = 0;
if (level != null && !level.isClientSide()) {
var player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5.0, false);
if (player != null) {
player.sendSystemMessage(Component.translatable("chat.xiaozhong.welcome"));
}
}
}
}
}
}
以下演示了一个最基础方块实体的实现及的注册流程——没有相应的语言文件,没有模型,什么都没有,甚至没有为该方块注册对应的物品:
package org.teacon.xiaozhong;
import com.mojang.datafixers.DSL;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RenderShape;
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.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import org.jetbrains.annotations.NotNull;
@Mod("xiaozhong")
public class Xiaozhong {
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.Blocks.createBlocks("xiaozhong");
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, "xiaozhong");
public static final DeferredHolder<Block, MyMachine> MY_MACHINE;
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MyMachineEntity>> MY_MACHINE_BLOCK_ENTITY;
static {
MY_MACHINE = BLOCKS.register("my_machine",
() -> new MyMachine(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)));
MY_MACHINE_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("my_machine",
() -> BlockEntityType.Builder.of(MyMachineEntity::new, MY_MACHINE.get()).build(DSL.remainderType()));
}
public Xiaozhong(IEventBus modEventBus) {
BLOCKS.register(modEventBus);
BLOCK_ENTITY_TYPES.register(modEventBus);
}
public static final class MyMachine extends BaseEntityBlock {
public static final MapCodec<MyMachine> CODEC = simpleCodec(MyMachine::new);
public MyMachine(Properties props) {
super(props);
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@NotNull
@Override
public RenderShape getRenderShape(@NotNull BlockState state) {
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) {
return new MyMachineEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, @NotNull BlockState state, @NotNull BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? null : createTickerHelper(blockEntityType, MY_MACHINE_BLOCK_ENTITY.get(), MyMachineEntity::tick);
}
}
public static final class MyMachineEntity extends BlockEntity {
public MyMachineEntity(BlockEntityType<MyMachineEntity> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
public MyMachineEntity(BlockPos worldPosition, BlockState blockState) {
this(MY_MACHINE_BLOCK_ENTITY.get(), worldPosition, blockState);
}
private int count = 0;
public static void tick(Level level, BlockPos pos, BlockState state, MyMachineEntity entity) {
entity.count += 1;
if (entity.count > 100) {
entity.count = 0;
if (level != null && !level.isClientSide()) {
var player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5.0, false);
if (player != null) {
player.sendSystemMessage(Component.translatable("chat.xiaozhong.welcome"));
}
}
}
}
}
}
这个方块实体的效果是,每 100 个游戏刻检查是否有玩家距离该方块实体距离不到 5 方块,若有,随机抽一位向其打招呼。
打开游戏,执行 /setblock ~ ~-1 ~ xiaozhong:my_machine
看看效果吧~