Mission · 先看最终成果
完成的不只是一段代码,而是一条完整的方块开发链。
学完后,你能解释 Java、方块状态与资源 JSON 各自负责什么,并能在游戏中亲手验证结果。
注册方块和物品
得到唯一 ID examplemod:right_click_light_block,并让它出现在创造模式物品栏。
保存亮灭状态
用布尔属性 LIT 保存“亮”或“灭”,新放下时默认为 false。
处理玩家右键
重写 use 方法;服务端切换状态,同时播放杠杆点击声。
连接外观与数据
添加方块状态、两个模型、名称、掉落表和工作台合成配方。
/give @s examplemod:right_click_light_block 能拿到方块;放下后不发光;第一次右键变亮并照亮周围;第二次右键熄灭;破坏后掉落自身。File map · 文件要放对位置
路径就是资源 ID 的一部分,一个字符错位都会断开连接。
这次新增一个 Java 类和七个资源文件,同时修改入口类与两份语言文件。所有资源文件名都使用小写英文和下划线。
src/main/
├─ java/com/example/examplemod/
│ ├─ ExampleMod.java
│ └─ block/RightClickLightBlock.java
└─ resources/
├─ assets/examplemod/
│ ├─ blockstates/right_click_light_block.json
│ ├─ models/block/right_click_light_block_off.json
│ ├─ models/block/right_click_light_block_on.json
│ ├─ models/item/right_click_light_block.json
│ └─ lang/zh_cn.json + en_us.json
└─ data/examplemod/
├─ loot_tables/blocks/right_click_light_block.json
└─ recipes/right_click_light_block.jsonblock 包
Java 行为:怎样响应玩家右键、怎样改变状态。
ExampleMod
注册入口:告诉 Forge 这个方块和对应物品存在。
assets
客户端显示:亮灭模型、物品模型和中英文名称。
data
游戏规则:方块怎样掉落、怎样在工作台中合成。
Concept · 先理解再敲代码
一个方块,两个状态:lit=false 与 lit=true。
lit=false光照等级 0use() + !LITlit=true光照等级 15boolean willBeLit = !state.getValue(LIT);Block 对象会被很多世界位置共同使用。BlockState 可以让每个位置独立保存状态,所以左边的灯可以亮,右边的灯可以灭。客户端和服务端怎样分工?
客户端 Client
显示手部动作、方块模型与光照。收到服务器同步的新状态后刷新画面。
服务端 Server
真正修改世界状态。代码用 !level.isClientSide() 保证同一次右键只修改一次。
Step 1 · 编写方块类
创建 RightClickLightBlock.java,让方块学会响应右键。
在 src/main/java/com/example/examplemod/block 中创建这个类。下面是当前工程里已经通过编译的完整代码。
package com.example.examplemod.block;
import net.minecraft.core.BlockPos;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.BlockHitResult;
public class RightClickLightBlock extends Block {
public static final BooleanProperty LIT = BlockStateProperties.LIT;
public RightClickLightBlock(Properties properties) {
super(properties);
registerDefaultState(stateDefinition.any().setValue(LIT, false));
}
@Override
protected void createBlockStateDefinition(
StateDefinition.Builder<Block, BlockState> builder) {
builder.add(LIT);
}
@Override
public InteractionResult use(BlockState state, Level level,
BlockPos pos, Player player, InteractionHand hand,
BlockHitResult hit) {
boolean willBeLit = !state.getValue(LIT);
if (!level.isClientSide()) {
level.setBlock(pos, state.setValue(LIT, willBeLit), Block.UPDATE_ALL);
level.playSound(null, pos, SoundEvents.LEVER_CLICK,
SoundSource.BLOCKS, 0.35F, willBeLit ? 0.7F : 0.5F);
}
return InteractionResult.sidedSuccess(level.isClientSide());
}
}声明 LIT
BooleanProperty 只有 true 和 false 两种值,正好表示灯的开关。
设置默认状态
新放下的方块默认 lit=false,所以一开始不发光。
把属性加入状态定义
漏掉 builder.add(LIT) 会在启动或放置方块时出现属性错误。
只在服务端改世界
Block.UPDATE_ALL 会更新邻居,并把新状态同步给客户端。
Step 2 · 注册方块与物品
在 ExampleMod.java 中接入现有注册系统。
一个方块要能拿在手里,通常需要同时注册 Block 与 BlockItem。
import com.example.examplemod.block.RightClickLightBlock;public static final RegistryObject<Block> RIGHT_CLICK_LIGHT_BLOCK =
BLOCKS.register("right_click_light_block",
() -> new RightClickLightBlock(BlockBehaviour.Properties.of()
.mapColor(MapColor.COLOR_CYAN)
.strength(1.5F)
.lightLevel(state ->
state.getValue(RightClickLightBlock.LIT) ? 15 : 0)));
public static final RegistryObject<Item> RIGHT_CLICK_LIGHT_BLOCK_ITEM =
ITEMS.register("right_click_light_block",
() -> new BlockItem(
RIGHT_CLICK_LIGHT_BLOCK.get(), new Item.Properties()));加入创造模式物品栏
// EXAMPLE_TAB 的 displayItems 中
output.accept(RIGHT_CLICK_LIGHT_BLOCK_ITEM.get());
// addCreative 方法中
if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS) {
event.accept(RIGHT_CLICK_LIGHT_BLOCK_ITEM);
}Step 3 · 连接状态、外观和数据
让两个 LIT 状态选择两个不同模型。
本课复用 Minecraft 原版红石灯贴图,所以不需要新增 PNG。方块状态文件负责选择模型,模型文件负责选择贴图。
{
"variants": {
"lit=false": {
"model": "examplemod:block/right_click_light_block_off"
},
"lit=true": {
"model": "examplemod:block/right_click_light_block_on"
}
}
}{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "minecraft:block/redstone_lamp"
}
}{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "minecraft:block/redstone_lamp_on"
}
}{
"parent": "examplemod:block/right_click_light_block_off"
}添加中文和英文名称
"block.examplemod.right_click_light_block":
"右键发光方块""block.examplemod.right_click_light_block":
"Right-click Light Block"破坏后掉落自身
{
"type": "minecraft:block",
"pools": [{
"rolls": 1,
"entries": [{
"type": "minecraft:item",
"name": "examplemod:right_click_light_block"
}],
"conditions": [{
"condition": "minecraft:survives_explosion"
}]
}]
}添加工作台配方
{
"type": "minecraft:crafting_shaped",
"pattern": ["GGG", "GRG", "GGG"],
"key": {
"G": { "item": "minecraft:glowstone_dust" },
"R": { "item": "minecraft:redstone_lamp" }
},
"result": {
"item": "examplemod:right_click_light_block"
}
}Step 4 · 编译、运行、验收
先让 Gradle 检查代码,再进入游戏做完整测试。
快速编译
.\gradlew.bat compileJava processResources启动开发客户端
.\gradlew.bat runClient获得方块并切到夜晚
/give @s examplemod:right_click_light_block
/time set midnight放下时熄灭
外观是未点亮红石灯,周围没有额外光照。
右键后点亮
外观变亮,周围出现 15 级光照,并听到点击声。
再次右键熄灭
说明 LIT 能从 false 到 true,再从 true 回到 false。
重新进入世界
方块仍保持离开前的状态,说明 BlockState 被保存。
破坏后掉落
方块会掉落同一个可重新放置的方块物品。
配方可用
八个萤石粉围住一个红石灯,可以合成新方块。
compileJava、processResources 与完整 build 均显示 BUILD SUCCESSFUL。Debug · 看现象找原因
不要靠猜,先判断错误属于 Java、注册还是资源层。
| 现象 | 最可能原因 | 检查方法 |
|---|---|---|
| 启动时报属性不存在 | 忘记 builder.add(LIT) | 确认属性既声明,又加入状态定义。 |
/give 提示未知物品 | 注册名不同,或没有注册 BlockItem | 全文搜索 right_click_light_block,保证完全一致。 |
| 显示紫黑格 | 模型路径或 JSON 语法错误 | 在 latest.log 搜索 Unable to load model。 |
| 右键变化但不发光 | lightLevel 没有读取同一个 LIT | 确认表达式返回 15 或 0。 |
| 右键没有反应 | 手中物品抢占交互,或只在客户端改状态 | 先空手测试;确认服务端调用 setBlock。 |
| 破坏后没有掉落 | 掉落表缺失或目录错误 | 确认路径是 data/examplemod/loot_tables/blocks。 |
latest.log,再检查资源路径,最后才反复改代码。每次只改一个原因,然后重新测试。Practice · 从照着做走向自己改
每次只改变一个变量,先预测结果,再运行验证。
降低亮度
把点亮亮度从 15 改成 8,夜里比较照明范围。
更换声音
尝试 SoundEvents.AMETHYST_BLOCK_CHIME。
只允许空手
检查 player.getItemInHand(hand).isEmpty()。
添加粒子
研究 animateTick 与 ParticleTypes.END_ROD。
五分钟自测
1. 为什么注册 Block 后还要注册 BlockItem?
Block 是世界里的方块,BlockItem 是背包中可以拿取和放置它的物品。没有 BlockItem,学生很难通过正常物品系统获得和放置这个方块。
2. BlockState 与普通 Java 字段有什么区别?
BlockState 属于世界中的具体方块位置,每一盏灯都能保存自己的值;普通字段属于共享的 Block 对象。
3. 为什么只在服务端调用 setBlock?
服务器拥有世界的最终状态,并负责把结果同步给客户端。两边都修改会造成重复执行或状态不同步。
4. blockstates 和模型 JSON 分别负责什么?
blockstates 根据属性值选择模型;模型文件再决定几何形状与贴图资源。
5. 看到紫黑格时应该先检查哪里?
先检查模型文件名、路径、命名空间和 JSON 语法。命令已经能获得方块,通常说明 Java 注册已经成功。
Lesson complete
你已经完成了一个真正会互动的 Forge 方块。
记住这条开发链:注册 ID → LIT 状态 → use() → 光照 15/0 → on/off 模型 → 游戏验收。以后制作门、按钮、机器开关和多状态装饰方块,都可以从这套方法继续扩展。