18. 方块注册(BB 模型 + 箱子)¶
本章讲三件事: 1. 方块怎么注册(含方块物品、资源文件、数据生成) 2. 怎么用 Blockbench(BB)模型当方块外观 3. 怎么做一个「箱子」(BlockEntity + 容器 + 界面 + 掉落/漏斗/比较器)
环境:Minecraft 1.20.1 / Forge 47.x。示例 modid 用
mymod,包名com.example.mymod。
0. 最小可用清单¶
一个"能放出来、能拿在手里、能挖掉、有贴图"的方块,需要这些东西:
| 类别 | 文件/代码 |
|---|---|
| 代码 | ModBlocks(方块注册)、ModItems(方块物品)、主类里挂到事件总线 |
| 方块状态 | assets/mymod/blockstates/my_block.json |
| 方块模型 | assets/mymod/models/block/my_block.json |
| 物品模型 | assets/mymod/models/item/my_block.json |
| 贴图 | assets/mymod/textures/block/my_block.png |
| 掉落表 | data/mymod/loot_tables/blocks/my_block.json |
| 语言 | assets/mymod/lang/en_us.json、zh_cn.json |
| 箱子额外 | BlockEntity 注册、容器/Menu、BER(可选)、方块实体语言键 |
1. 注册方块本体¶
1.1 注册器¶
package com.example.mymod.block;
import com.example.mymod.MyMod;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.MapColor;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModBlocks {
public static final DeferredRegister<Block> BLOCKS =
DeferredRegister.create(ForgeRegistries.BLOCKS, MyMod.MODID);
public static final RegistryObject<Block> MY_BLOCK = BLOCKS.register("my_block",
() -> new Block(BlockBehaviour.Properties.of()
.mapColor(MapColor.STONE)
.strength(2.0F, 6.0F) // 硬度、抗爆
.sound(SoundType.STONE) // 音效/挖掘音
.requiresCorrectToolForDrops() // 需要正确工具才掉落
));
}
主类里挂上(方块走 mod 事件总线):
public MyMod() {
IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
ModBlocks.BLOCKS.register(bus);
ModItems.ITEMS.register(bus);
}
1.2 常用 Properties(1.20.1)¶
BlockBehaviour.Properties.of() // 全默认
.mapColor(MapColor.WOOD) // 地图颜色
.strength(2.0F) // 硬度(挖的时间)
.strength(2.0F, 6.0F) // 硬度 + 抗爆
.sound(SoundType.WOOD)
.lightLevel(s -> 12) // 发光
.noCollission() // 无碰撞(花草)
.noOcclusion() // 不遮挡相邻面(模型有洞/透明必须加)
.dynamicShape() // 形状随状态变化
.randomTicks()
.instabreak()
.requiresCorrectToolForDrops()
.explosionResistance(1200F)
.copy(Blocks.STONE) // 复制原版方块属性(最省事)
💡
Properties.copy(Blocks.OAK_PLANKS)是最常用的偷懒写法;想微调再链式覆盖。
1.3 方块物品(BlockItem)¶
方块要进背包就得注册 BlockItem,名字必须和方块同名(同一个 id):
public class ModItems {
public static final DeferredRegister<Item> ITEMS =
DeferredRegister.create(ForgeRegistries.ITEMS, MyMod.MODID);
public static final RegistryObject<Item> MY_BLOCK_ITEM = ITEMS.register("my_block",
() -> new BlockItem(ModBlocks.MY_BLOCK.get(), new Item.Properties()));
}
1.4 加进创造模式标签页¶
public class ModTabs {
public static final DeferredRegister<CreativeModeTab> TABS =
DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MyMod.MODID);
public static final RegistryObject<CreativeModeTab> MAIN = TABS.register("main",
() -> CreativeModeTab.builder()
.title(Component.translatable("itemGroup.mymod"))
.icon(() -> new ItemStack(ModItems.MY_BLOCK_ITEM.get()))
.displayItems((params, output) -> {
output.accept(ModItems.MY_BLOCK_ITEM.get());
})
.build());
}
⚠️ 别用
FMLCommonSetupEvent里CreativeModeTab.builder()之外的老写法;DeferredRegister+mod事件总线是在 1.20.1 的标准做法。
2. 资源文件¶
2.1 方块状态 blockstates¶
assets/mymod/blockstates/my_block.json
有朝向后(比如箱子)就要写四个朝向:
{
"variants": {
"facing=north": { "model": "mymod:block/my_chest" },
"facing=east": { "model": "mymod:block/my_chest", "y": 90 },
"facing=south": { "model": "mymod:block/my_chest", "y": 180 },
"facing=west": { "model": "mymod:block/my_chest", "y": 270 }
}
}
2.2 模型¶
assets/mymod/models/block/my_block.json(手写最简版)
{
"parent": "block/block",
"textures": {
"particle": "mymod:block/my_block",
"all": "mymod:block/my_block"
},
"elements": [
{
"from": [0, 0, 0],
"to": [16, 16, 16],
"faces": {
"down": { "texture": "#all", "cullface": "down" },
"up": { "texture": "#all", "cullface": "up" },
"north": { "texture": "#all", "cullface": "north" },
"south": { "texture": "#all", "cullface": "south" },
"west": { "texture": "#all", "cullface": "west" },
"east": { "texture": "#all", "cullface": "east" }
}
}
]
}
assets/mymod/models/item/my_block.json(方块物品直接套用方块模型)
2.3 贴图与语言¶
- 贴图:
assets/mymod/textures/block/my_block.png(16×16 起步,建议 16 的整数倍) - 语言键(
lang/zh_cn.json/en_us.json):
方块是
block.<modid>.<id>;方块物品不写item.键(它跟方块共用block.)。
2.4 掉落表¶
data/mymod/loot_tables/blocks/my_block.json(1.20.1 是 loot_tables;1.21 才改叫 loot_table)
{
"type": "minecraft:block",
"pools": [
{
"rolls": 1,
"entries": [ { "type": "minecraft:item", "name": "mymod:my_block" } ],
"conditions": [ { "condition": "minecraft:survives_explosion" } ]
}
]
}
用 DataGen(
GatherDataEvent)能自动生成 loot table / 模型 / blockstate / lang,长期项目强烈建议开;本章手写为主,方便照着改。
3. 用 Blockbench(BB)模型¶
3.1 三种"方块外观"路线,先选对¶
| 需求 | 用什么 | BB 导出格式 |
|---|---|---|
| 普通方块,形状在 1×1×1 左右,静态 | 方块模型 JSON(baked model) | Java Block/Item Model |
| 形状超过 1 格、部件很多、要单独贴图/动态 | BlockEntityRenderer(BER) | Java Block/Item Model(当几何源)或 Modded Entity Model |
| 需要骨骼动画(转动/挥舞) | GeckoLib 或自写 BER 动画 | Modded Entity Model(GeckoLib)/ Animated Java 插件 |
一句话:能塞进方块模型就别上 BER;超出了、要动了,才上 BlockEntity。
3.2 BB 建模 → 导出的标准流程¶
- 打开 Blockbench → 新建时选
Java Block/Item Model模板(不要选 Bedrock 系列!) - 在 Textures 面板导入你的 png(建议在 BB 里直接画或从像素画工具导入)
- 摆方块(
Add Cube),16 单位 = 1 格,原点在方块西北下角 - UV:小模型推荐 Per-face UV(比 Box UV 更省、更可控)
- 想调物品拿在手里/在 GUI 里的角度 → Display 面板调(导出会写进
display段) - File → Export → Java Block/Item Model,导出 JSON
- 把导出文件放
assets/mymod/models/block/<名字>.json,贴图放textures/block/ - 手改两处:
textures里的贴图路径加命名空间;需要透明/半透明时加render_type
3.3 导出后必查的 6 项¶
textures键值:BB 导出常写成"texture": "my_block"(裸文件名),要改成"mymod:block/my_block",否则紫黑格子。parent:保留"block/block"(它带方块的基础显示变换与 AO 设置)。render_type(Forge 1.19+ 支持):模型有透明/镂空必须加,否则透明处发黑或不通透:ambientocclusion:模型细节多、想更"平"可以设false。- 尺寸:
from/to超出[-16, 32]范围时,方块模型会被剔除/裁剪(相邻区块看不到),这时请改走 BER。 - 面朝向:法线朝外的面才受光正常;朝里的面会发黑,BB 里用
Flip/重新拉面修正。
3.4 BB 建模注意事项(踩坑合集)¶
- 源文件要留:
.bbmodel一定保留(后续改模全靠它),别只存导出的 JSON。 - 纹理尺寸:方块模型建议 16/32/64;
128+会增加显存与加载时间,复杂模型建议交给 BER 单独贴图。 - cube 数量:单个方块模型 cube 数建议 < 100;全服铺满时每个 cube 都要进区块渲染,多了直接掉帧。能合面就合面,能用一张贴图就别用十张。
- 透明与 AO 冲突:半透明模型 + AO 容易出现"黑边/闪烁",可试
"ambientocclusion": false+translucent。 - 不要指望 BB 动画直接跑:BB 里的动画不会自动在 Java 生效;动画要 GeckoLib(或第三方
Animated Java插件生成代码)。用第三方插件前先确认它支持的 MC/加载器版本。 - 物品形态别忘 models/item:BB 导出的只是方块模型,方块物品要另写
models/item/<id>.json({"parent": "mymod:block/<id>"}),或者在导出模型里把display调好再让它当物品模型。 - 光照/阴影参数:BB 导出的
shade默认 true;关掉会有"平涂"效果,看风格决定。 - 单位/网格:BB 里 1 像素 = 1 单位 = 1/16 格;别用"整格"思维直接填 1、2、3。
- 模型名与文件名:
blockstates → models → textures三层命名保持统一(都用my_block),出问题时最省排查。 - 多部件模型:BB 的 "Groups"(组)导出成 Java 模型时会丢掉组名(除非用 BER/GeckoLib),别指望在 Java 里按组操作。
3.5 BB 里的「分组」到底要不要做?¶
结论:看渲染路线。分两种情况,别一腔热血分了一堆组结果游戏里全丢。
| 你的做法 | 分组还有用吗? |
|---|---|
| 导出成普通方块模型 JSON(静态外观) | 没用——导出时分组被拍平,JSON 里只剩一个 elements 数组,组名/层级全丢;分组此时只是方便你自己编辑 |
| 走 BER / GeckoLib(要开合、动画、隐藏部件) | 必须做——分组 = 骨骼 = 代码里的 ModelPart,名字就是接口,改个名字代码就编译不过 |
做箱子推荐的三个组
| 组名 | 内容 | 备注 |
|---|---|---|
base(或 body) |
箱体 | 一个组就够 |
lid |
盖子 | 独立组,Pivot 必须设在铰链上 |
lock / latch |
锁扣、把手 | 可单独,也可并进 lid |
组名用英文小写、无空格;不要用中文/空格/特殊符号(代码里要当标识符用)。
最关键的一步:盖子的旋转原点(Pivot)放在铰链位置
- 放在盖子中心 → 转起来像翻跟头;放在箱体后上边缘才会像真箱子开盖
- 操作:BB 里选中
lid组 → 点 Pivot/Origin 按钮 → 把原点拖到铰链处 - 例:16×14×16 的箱子、朝北(-Z),铰链大约在
[8, 14.5, 15.5](按你的模型微调) - 开合角度:常见做法
0 → -90°(绕 X 轴),用openness(0~1)做插值;叠加上方块朝向的y旋转即可
导出格式怎么选
- 静态(盖子不开)→
Java Block/Item Model→ 放models/block/<id>.json - 要开合/动画 →
Modded Entity Model(骨骼会保留,转成ModelPart)或 GeckoLib 格式; Blockbench 还有第三方插件 Animated Java(能直接生成 Java 代码/资源),用前先确认它支持的 MC / 加载器版本 - 走 BER 时:方块
getRenderShape()返回ENTITYBLOCK_ANIMATED,并注册BlockEntityRenderer(见 4.5)
再提醒一句:方块四个朝向不要在模型里做四份,交给 blockstates 的 y 旋转(见 2.1),模型只做朝北那一张。
4. 做一个箱子(BlockEntity + 容器)¶
"箱子"= 方块 + 方块实体(BlockEntity) + 容器逻辑 + 界面。分四步。
4.1 方块:BaseEntityBlock + 朝向 + 打开界面¶
public class MyChestBlock extends BaseEntityBlock implements SimpleWaterloggedBlock /* 可选 */ {
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public MyChestBlock(Properties props) {
super(props);
registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH));
}
// 1) 让方块知道自己有 BlockEntity
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new MyChestBlockEntity(pos, state);
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.ENTITYBLOCK_ANIMATED; // 交给 BER 渲染(用方块 JSON 模型就写 MODEL)
}
// 2) 右键打开界面
@Override
public InteractionResult use(BlockState state, Level level, BlockPos pos,
Player player, InteractionHand hand, BlockHitResult hit) {
if (!level.isClientSide && level.getBlockEntity(pos) instanceof MyChestBlockEntity be) {
player.openMenu(be); // be 实现 MenuProvider
}
return InteractionResult.sidedSuccess(level.isClientSide());
}
// 3) 放置朝向
@Override
public BlockState getStateForPlacement(BlockPlaceContext ctx) {
return defaultBlockState().setValue(FACING, ctx.getHorizontalDirection().getOpposite());
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FACING);
}
// 4) 被破坏时把里面的东西吐出来(否则物品凭空消失)
@Override
public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (!state.is(newState.getBlock())) {
BlockEntity be = level.getBlockEntity(pos);
if (be instanceof MyChestBlockEntity chest) {
Containers.dropContents(level, pos, chest); // 需要 chest 实现 net.minecraft.world.Container
}
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
// 5) 比较器输出(原版箱子那样)
@Override
public boolean hasAnalogOutputSignal(BlockState state) { return true; }
@Override
public int getAnalogOutputSignal(BlockState state, Level level, BlockPos pos) {
BlockEntity be = level.getBlockEntity(pos);
return be instanceof MyChestBlockEntity chest
? AbstractContainerMenu.getRedstoneSignalFromContainer(chest) : 0;
}
@Override
public BlockState rotate(BlockState state, Rotation rot) { return state.setValue(FACING, rot.rotate(state.getValue(FACING))); }
@Override
public BlockState mirror(BlockState state, Mirror mirror) { return state.rotate(mirror.getRotation(state.getValue(FACING))); }
@Override
public BlockEntityType<?> getBlockEntityType() { return ModBlockEntities.MY_CHEST.get(); }
}
4.2 注册 BlockEntityType¶
public class ModBlockEntities {
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES =
DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, MyMod.MODID);
public static final RegistryObject<BlockEntityType<MyChestBlockEntity>> MY_CHEST =
BLOCK_ENTITIES.register("my_chest",
() -> BlockEntityType.Builder
.of(MyChestBlockEntity::new, ModBlocks.MY_CHEST.get())
.build(null)); // null = 用默认(方块实体不参与数据修复)
}
4.3 BlockEntity:存东西 + 存盘 + 提供菜单¶
推荐做法:直接实现原版 Container(这样漏斗、比较器、Containers.dropContents 都能直接吃)。
public class MyChestBlockEntity extends BlockEntity implements Container, MenuProvider {
public static final int SLOTS = 27;
private final NonNullList<ItemStack> items = NonNullList.withSize(SLOTS, ItemStack.EMPTY);
private int openCount; // 用于盖子动画/音效(可选)
public MyChestBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.MY_CHEST.get(), pos, state);
}
// ---------- Container 接口 ----------
@Override public int getContainerSize() { return SLOTS; }
@Override public boolean isEmpty() { return items.stream().allMatch(ItemStack::isEmpty); }
@Override public ItemStack getItem(int slot) { return items.get(slot); }
@Override public ItemStack removeItem(int slot, int amount) {
ItemStack s = ContainerHelper.removeItem(items, slot, amount);
if (!s.isEmpty()) setChanged();
return s;
}
@Override public ItemStack removeItemNoUpdate(int slot) { return ContainerHelper.takeItem(items, slot); }
@Override public void setItem(int slot, ItemStack stack) {
items.set(slot, stack);
if (stack.getCount() > getMaxStackSize()) stack.setCount(getMaxStackSize());
setChanged();
}
@Override public boolean stillValid(Player player) {
return Container.stillValidBlockEntity(this, player); // 距离校验
}
@Override public void clearContent() { items.clear(); }
@Override public int getMaxStackSize() { return 64; }
// ---------- MenuProvider ----------
@Override public Component getDisplayName() { return Component.translatable("block.mymod.my_chest"); }
@Override
public AbstractContainerMenu createMenu(int id, Inventory playerInv, Player player) {
return new MyChestMenu(id, playerInv, this);
}
// ---------- 存盘 ----------
@Override
protected void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
ContainerHelper.saveAllItems(tag, items);
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
items.clear();
ContainerHelper.loadAllItems(tag, items);
}
// ---------- 让漏斗能从上面塞、下面抽(可选,进阶)----------
// 想精细控制就实现 WorldlyContainer,重写 getSlotsForFace / canPlaceItemThroughFace / canTakeItemThroughFace
}
如果更习惯 Forge 的写法,把
NonNullList<ItemStack>换成ItemStackHandler(net.minecraftforge.items.ItemStackHandler),再实现ICapabilityProvider暴露CapabilityItemHandler.ITEM_HANDLER_CAPABILITY。代价是:漏斗/比较器/Containers.dropContents不会自动认识它,需要额外适配。
4.4 菜单 + 界面¶
Menu(服务端/客户端共用逻辑):
public class MyChestMenu extends AbstractContainerMenu {
private final Container container;
// 客户端构造(由 MenuType 调用)
public MyChestMenu(int id, Inventory playerInv, FriendlyByteBuf extra) {
this(id, playerInv, new SimpleContainer(MyChestBlockEntity.SLOTS));
}
public MyChestMenu(int id, Inventory playerInv, Container container) {
super(ModMenus.MY_CHEST.get(), id);
this.container = container;
container.startOpen(playerInv.player);
// 箱子本体:3 行 × 9 列
for (int row = 0; row < 3; row++)
for (int col = 0; col < 9; col++)
addSlot(new Slot(container, col + row * 9, 8 + col * 18, 18 + row * 18));
// 玩家背包
for (int row = 0; row < 3; row++)
for (int col = 0; col < 9; col++)
addSlot(new Slot(playerInv, col + row * 9 + 9, 8 + col * 18, 84 + row * 18));
for (int col = 0; col < 9; col++)
addSlot(new Slot(playerInv, col, 8 + col * 18, 142));
addDataSlots(...); // 需要同步数字(比如盖子开合进度)时用
}
@Override
public boolean stillValid(Player player) { return container.stillValid(player); }
// Shift 点击:把物品在箱子和背包之间搬
@Override
public ItemStack quickMoveStack(Player player, int index) {
ItemStack result = ItemStack.EMPTY;
Slot slot = slots.get(index);
if (slot.hasItem()) {
ItemStack stack = slot.getItem();
result = stack.copy();
int chestSize = MyChestBlockEntity.SLOTS;
if (index < chestSize) {
if (!moveItemStackTo(stack, chestSize, slots.size(), true)) return ItemStack.EMPTY;
} else {
if (!moveItemStackTo(stack, 0, chestSize, false)) return ItemStack.EMPTY;
}
if (stack.isEmpty()) slot.set(ItemStack.EMPTY); else slot.setChanged();
}
return result;
}
@Override
public void removed(Player player) {
super.removed(player);
container.stopOpen(player);
}
}
MenuType 注册:
public class ModMenus {
public static final DeferredRegister<MenuType<?>> MENUS =
DeferredRegister.create(ForgeRegistries.MENU_TYPES, MyMod.MODID);
public static final RegistryObject<MenuType<MyChestMenu>> MY_CHEST =
MENUS.register("my_chest",
() -> IForgeMenuType.create(MyChestMenu::new)); // 带 FriendlyByteBuf 的工厂
}
Screen(客户端):
public class MyChestScreen extends AbstractContainerScreen<MyChestMenu> {
private static final ResourceLocation TEX = new ResourceLocation("mymod", "textures/gui/my_chest.png");
public MyChestScreen(MyChestMenu menu, Inventory inv, Component title) {
super(menu, inv, title);
this.imageWidth = 176;
this.imageHeight = 166;
this.inventoryLabelY = 72;
}
@Override
protected void renderBg(GuiGraphics g, float partialTick, int mouseX, int mouseY) {
g.blit(TEX, leftPos, topPos, 0, 0, imageWidth, imageHeight);
}
@Override
public void render(GuiGraphics g, int mouseX, int mouseY, float partialTick) {
renderBackground(g);
super.render(g, mouseX, mouseY, partialTick);
renderTooltip(g, mouseX, mouseY);
}
}
注册 Screen(客户端):
// Forge 1.20.1 推荐:mod 事件总线
@Mod.EventBusSubscriber(modid = MyMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class ClientSetup {
@SubscribeEvent
public static void onRegisterScreens(RegisterMenuScreensEvent event) {
event.register(ModMenus.MY_CHEST.get(), MyChestScreen::new);
}
}
如果用的 Forge 版本没有
RegisterMenuScreensEvent,就退回FMLClientSetupEvent里调MenuScreens.register(ModMenus.MY_CHEST.get(), MyChestScreen::new)。
4.5 可选:RenderShape 与 BER¶
- 用 方块 JSON 模型(静态外观)→
getRenderShape返回MODEL,不用 BER。 - 用 BB 导出的模型 + 自定义渲染(要盖子开合、旋转、超格模型)→
ENTITYBLOCK_ANIMATED+ 写 BER:
public class MyChestRenderer implements BlockEntityRenderer<MyChestBlockEntity> {
public MyChestRenderer(BlockEntityRendererProvider.Context ctx) { }
@Override
public void render(MyChestBlockEntity be, float partialTick, PoseStack pose,
MultiBufferSource buffers, int packedLight, int packedOverlay) {
pose.pushPose();
pose.translate(0.5, 0.0, 0.5);
// 这里画盖子/箱体:用 ModelPart(实体模型)或自定义 Model,光照用 packedLight
pose.popPose();
}
}
@Mod.EventBusSubscriber(modid = MyMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class ClientSetup {
@SubscribeEvent
public static void onRegisterRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerBlockEntityRenderer(ModBlockEntities.MY_CHEST.get(), MyChestRenderer::new);
}
}
只想要"原版那样的箱子"?最省事的偏方:方块继承
ChestBlock、BlockEntity 继承ChestBlockEntity,直接白嫖原版的箱子渲染与开合动画(还要实现ChestBlockEntity.ITickableChest/ 提供ChestBlockEntity.getOpenNess)。缺点是外观/交互被原版逻辑绑死,改造空间小。
4.6 箱子必须补的资源¶
- 方块模型(或 BER 用的几何)、
blockstates四个朝向(4.1 的FACING对应) - 掉落表:箱子本体要能掉(别写把内容物也掉出来的逻辑,内容物由
onRemove吐) - GUI 贴图:
assets/mymod/textures/gui/my_chest.png(176×166) - 语言:
block.mymod.my_chest
5. 自测清单(发版前逐条过)¶
- 放下来:朝向对不对(
getStateForPlacement) - 挖掉:掉落自己 + 里面的东西全吐出来(
onRemove) - 存盘:退出世界再进,东西还在(
saveAdditional/load) - 漏斗:能从上面塞、从下面抽(实现
WorldlyContainer或补 capability) - 比较器:满箱时信号增强(
getAnalogOutputSignal) - 多人:两个人同时开箱子不崩、
stillValid距离校验生效 - 数据包/资源包:模型紫黑?九成是
textures路径或命名空间问题 - 性能:
F3看渲染、区块里多放几个模型看帧率(cube 太多会很明显)
6. 一页纸速查¶
方块 DeferredRegister<Block> + BlockBehaviour.Properties + 挂 mod 事件总线
方块物品 BlockItem,id 与方块同名
创造标签页 DeferredRegister<CREATIVE_MODE_TAB> + CreativeModeTab.builder()
资源 blockstates → models/block → textures/block(三层同名)
物品模型 models/item/<id>.json = {"parent": "mymod:block/<id>"}
掉落 data/<modid>/loot_tables/blocks/<id>.json
方块实体 BaseEntityBlock.newBlockEntity + DeferredRegister<BLOCK_ENTITY_TYPE> + save/load
容器 实现 Container(原版兼容最好)/ 或 ItemStackHandler + capability
界面 MenuType(IForgeMenuType) + AbstractContainerMenu + AbstractContainerScreen
BER BlockEntityRenderer + EntityRenderersEvent.RegisterRenderers
BB 模型 选 Java Block/Item 模板 → 导出 → 改 textures 命名空间 → 需要透明加 render_type
超 1 格/要动 上 BER / GeckoLib
相关章节¶
- 本章进阶篇(交互、状态属性、形状、随机刻、含水、方块实体、BER 动画):21. 方块进阶:交互与动画
- 物品与注册表基础:03. 物品、04. 注册表
- 物品进阶(功能与动画):20. 物品进阶:功能与动画
- 数据存储与序列化:07. Capability、09. Codec
- 界面与菜单:05. Menu、06. Screen
- 依赖与构建:17. 依赖拉取