Forge Java · Block interaction

右键一下,让方块亮起来

在现有 Forge 1.20.1-47.4.20 工程中添加一个完整的新方块。它会记住亮灭状态,右键时切换外观并发出 15 级光照。

Minecraft 1.20.1 Forge 47.4.20 Java 17 约 45-60 分钟
当前:熄灭点击方块,模拟游戏里的右键操作

Mission · 先看最终成果

完成的不只是一段代码,而是一条完整的方块开发链。

学完后,你能解释 Java、方块状态与资源 JSON 各自负责什么,并能在游戏中亲手验证结果。

01 · REGISTER

注册方块和物品

得到唯一 ID examplemod:right_click_light_block,并让它出现在创造模式物品栏。

02 · STATE

保存亮灭状态

用布尔属性 LIT 保存“亮”或“灭”,新放下时默认为 false。

03 · EVENT

处理玩家右键

重写 use 方法;服务端切换状态,同时播放杠杆点击声。

04 · RESOURCES

连接外观与数据

添加方块状态、两个模型、名称、掉落表和工作台合成配方。

完成标准:执行 /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.json

block 包

Java 行为:怎样响应玩家右键、怎样改变状态。

ExampleMod

注册入口:告诉 Forge 这个方块和对应物品存在。

assets

客户端显示:亮灭模型、物品模型和中英文名称。

data

游戏规则:方块怎样掉落、怎样在工作台中合成。

Concept · 先理解再敲代码

一个方块,两个状态:lit=false 与 lit=true。

核心算法:把当前布尔值取反
boolean willBeLit = !state.getValue(LIT);
为什么不用普通 Java 字段?一个 Block 对象会被很多世界位置共同使用。BlockState 可以让每个位置独立保存状态,所以左边的灯可以亮,右边的灯可以灭。

客户端和服务端怎样分工?

客户端 Client

显示手部动作、方块模型与光照。收到服务器同步的新状态后刷新画面。

服务端 Server

真正修改世界状态。代码用 !level.isClientSide() 保证同一次右键只修改一次。

Step 1 · 编写方块类

创建 RightClickLightBlock.java,让方块学会响应右键。

在 src/main/java/com/example/examplemod/block 中创建这个类。下面是当前工程里已经通过编译的完整代码。

RightClickLightBlock.java
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());
    }
}
1

声明 LIT

BooleanProperty 只有 true 和 false 两种值,正好表示灯的开关。

2

设置默认状态

新放下的方块默认 lit=false,所以一开始不发光。

3

把属性加入状态定义

漏掉 builder.add(LIT) 会在启动或放置方块时出现属性错误。

4

只在服务端改世界

Block.UPDATE_ALL 会更新邻居,并把新状态同步给客户端。

Step 2 · 注册方块与物品

在 ExampleMod.java 中接入现有注册系统。

一个方块要能拿在手里,通常需要同时注册 Block 与 BlockItem。

导入新类
import com.example.examplemod.block.RightClickLightBlock;
注册方块与 BlockItem
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);
}
常见漏项:只注册 Block、没有注册 BlockItem 时,方块可能存在于注册表中,却不能正常出现在背包里。

Step 3 · 连接状态、外观和数据

让两个 LIT 状态选择两个不同模型。

本课复用 Minecraft 原版红石灯贴图,所以不需要新增 PNG。方块状态文件负责选择模型,模型文件负责选择贴图。

blockstates/right_click_light_block.json
{
  "variants": {
    "lit=false": {
      "model": "examplemod:block/right_click_light_block_off"
    },
    "lit=true": {
      "model": "examplemod:block/right_click_light_block_on"
    }
  }
}
models/block/..._off.json
{
  "parent": "minecraft:block/cube_all",
  "textures": {
    "all": "minecraft:block/redstone_lamp"
  }
}
models/block/..._on.json
{
  "parent": "minecraft:block/cube_all",
  "textures": {
    "all": "minecraft:block/redstone_lamp_on"
  }
}
models/item/right_click_light_block.json
{
  "parent": "examplemod:block/right_click_light_block_off"
}

添加中文和英文名称

zh_cn.json
"block.examplemod.right_click_light_block":
  "右键发光方块"
en_us.json
"block.examplemod.right_click_light_block":
  "Right-click Light Block"

破坏后掉落自身

loot_tables/blocks/right_click_light_block.json
{
  "type": "minecraft:block",
  "pools": [{
    "rolls": 1,
    "entries": [{
      "type": "minecraft:item",
      "name": "examplemod:right_click_light_block"
    }],
    "conditions": [{
      "condition": "minecraft:survives_explosion"
    }]
  }]
}

添加工作台配方

recipes/right_click_light_block.json
{
  "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 检查代码,再进入游戏做完整测试。

1

快速编译

PowerShell
.\gradlew.bat compileJava processResources
2

启动开发客户端

PowerShell
.\gradlew.bat runClient
3

获得方块并切到夜晚

Minecraft 聊天栏
/give @s examplemod:right_click_light_block
/time set midnight
CHECK 1

放下时熄灭

外观是未点亮红石灯,周围没有额外光照。

CHECK 2

右键后点亮

外观变亮,周围出现 15 级光照,并听到点击声。

CHECK 3

再次右键熄灭

说明 LIT 能从 false 到 true,再从 true 回到 false。

CHECK 4

重新进入世界

方块仍保持离开前的状态,说明 BlockState 被保存。

CHECK 5

破坏后掉落

方块会掉落同一个可重新放置的方块物品。

CHECK 6

配方可用

八个萤石粉围住一个红石灯,可以合成新方块。

当前工程已经验证: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。
推荐顺序:先看 Gradle 是否编译成功,再看 latest.log,再检查资源路径,最后才反复改代码。每次只改一个原因,然后重新测试。

Practice · 从照着做走向自己改

每次只改变一个变量,先预测结果,再运行验证。

LEVEL 1

降低亮度

把点亮亮度从 15 改成 8,夜里比较照明范围。

LEVEL 2

更换声音

尝试 SoundEvents.AMETHYST_BLOCK_CHIME。

LEVEL 2

只允许空手

检查 player.getItemInHand(hand).isEmpty()。

LEVEL 3

添加粒子

研究 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 模型 → 游戏验收。以后制作门、按钮、机器开关和多状态装饰方块,都可以从这套方法继续扩展。