Skip to main content

2.1 创建方块

Part A. 实战

定义一个方块很简单,和 Item 非常类似:

先定义指定方块类,并设置基本属性:

// 继承于 net.minecraft.block.Block 类
public class ObsidianBlock extends Block {
public ObsidianBlock() {
// 从材质创建 AbstractBlock.Properties (不是之前的 Item.Properties),并且定义硬度
super(Properties.create(Material.ROCK).hardnessAndResistance(5));
}
}

注意到与创建 Item 不同的是,Block 类需要指定创建的材质。

我们可以从之前的理论分析中发现,指定材质是为了让某些 ToolItem 效果选中,例如 AxeItem 作用于所有 EFFECTIVE_ON_MATERIAL 材质的方块。这只是其中之一的原因,具体还有很多其他用途,遇到再作分析。

Minecraft 中有许多内置的 material,你可以在 net.minecraft.block.material.Material 中看到。

Item 一样,接下来需要注册定义的类:

public class BlockRegistry {
public static final DeferredRegister<Block> blockDeferredRegister = DeferredRegister.create(ForgeRegistries.BLOCKS, Utils.MOD_ID);
public static final RegistryObject<Block> obsidianBlock = blockDeferredRegister.register("obsidian_block", ObsidianBlock::new);
}

别忘了,我们换了一个 DeferredRegister,我们还需要将新的 register 注册到 Mod 总线中:

BlockRegistry.blockDeferredRegister.register(FMLJavaModLoadingContext.get().getModEventBus());

结束了吗?很可惜,还差一步。

我们知道,Minecraft 的 block 是真实出现在场景中的对象,而 item 是可以被玩家手持 / 装备 / 存放的对象,二者渲染的位置不一样。

我们现在只是定义了 ObsidianBlock 作为一个 Block 对象的信息,它还不能出现在物品栏 / 创造模式物品组,因为它没有以 item 形式存在的信息。

现在,我们需要为 block 类型定义一个 item 的呈现方式,也就是 把 block 类型同样注册到 ItemRegistry 中,并且关联 “放置 item 成为 block” 的效果。

public static final RegistryObject<Item> obsidianBlock = itemDeferredRegister.register(
"obsidian_block",
() -> new BlockItem(
BlockRegistry.obsidianBlock.get(),
// 这里本人新建了一个 ItemGroup
new Item.Properties().group(ModGroup.OBSIDIAN_BLOCK_GROUP)
)
);

这里我们直接使用内置类 BlockItem 从注册好的 Block 对象生成 Item 对象,并且把这个新对象注册到 item 的 register 中。

如此一来,方块就基本创建完毕了,现在还剩下方块的材质信息没有加入,我们在后面叙述。

Part B. 理论:Block & Material & LootTable

2.1.B.1 Block & AbstractBlock

先来关注一下 Block 构造函数:

传入 block 并构造的属性还可以通过检查 AbstractBlock.Properties 类型,以及父类 AbstractBlock 得到:

我们能看到,通过定制构造 Block 的 Abstract.Properties,至少可以指定:

  • 方块材料(material);
  • 爆炸抗性(resistance,距爆炸中心点多远会被破坏的数值表征);
  • 方块是否需要随机 tick 来进行粒子效果渲染(ticksRandomly);
  • 速度限制(speedFactor,表征实体在方块上表面行走时的移速限制情况,默认 1.0F):例如灵魂沙可以降低玩家移速;
  • 光滑程度(slipperiness,表征实体在方块上表面行走时移动速度的维持情况,默认 0.6F):例如冰面可以在玩家停止行进时,维持更长时间保持前进速度;
  • 跳跃系数(jumpFactor,表征实体在方块上反弹的高度比例,默认 1.0F):例如玩家可以在史莱姆方块上弹跳;
  • 收获等级(harvestLevel,方块被破坏后,绝不会被收获等级低于这个等级的 Item 生成掉落物,默认 -1);
  • 指定收获工具(harvestTool,规定只有指定类型的 ToolItem,并且高于 harvestLevel 的工具破坏该方块后才会形成掉落物,否则即便收获等级高于限制也不会形成掉落物);
  • 战利品表(lootTable);
  • 发光强度(lightLevel)、方块音效(soundType);
  • ……

现在,我们看到两个比较有趣的参数:方块材料(material)和战利品表(lootTable),我们一一了解。

2.1.B.2 Material

方块材料是 AbstractBlock(父类)代为构建的,它和 AbstractBlock / Block 中的其他属性一样,共同描述了一个方块类的特性。其构造函数如下:

一个方块材料主要描述了方块的颜色、是否为流体(流体涉及的渲染和运动逻辑在后面介绍)、是否可以着火、被活塞等实体推动后的行为,等等。

Material 构造参数很多,使用 Builder 模式进行构建。如果需要自行定义一个 Material 类型,可以模仿 Material 类中已经定义好的常量。我们在定义方块时就可以传入自定义的 Material 进行使用了。

正如之前所说,Material 还有一个重要的作用就是让 ToolItem 识别一类材质的方块并作用上去。例如,前面提到的 AxeItem 对一类材质的方块破坏速度更快:

这里细心的同学可能注意到,识别一个方块的 material 使用的是 BlockState,而不是 Block 本身,这是为什么?我们留个悬念,下一节介绍 BlockState 的时候再继续讨论。

2.1.B.3 LootTable 战利品表

我们知道敲碎一个方块 / 击败一个 mob 后可能会产生掉落物(drop),例如收获等级高于石镐的 PickaxeItem 类型的工具在破坏钻石矿(diamond ore)后会直接掉落钻石,空手撸掉草方块后会掉落一个泥土方块,击败一个 zombie 可能会掉落一个胡萝卜,钓鱼钓出经验修补附魔书、要塞里的奖励箱的内容,等等。

注:官方定义 mob

An AI-driven game entity resembling a living creature,short for mobile entity

游戏中的所有看起来有生命的、由 AI 驱动的实体都是 mob,是 mobile entity 的简称。

而游戏的 entity 则是指一切能移动的对象,包含其他非 entity 所没有的属性,例如:

  • 位置、旋转角、速度;
  • 未转动情况下三维容积长方体的几何尺寸(或称碰撞箱尺寸);
  • 是否着火(表现为在 entity 周围渲染 flame);
  • 是否存在效果状态(Effects);

这些生成掉落物 / 奖励箱内容的一切行为,全部被 lootTable 所定义。

注:像是史莱姆分裂、蠹虫从虫蚀方块(infested block)中出现的逻辑都不属于战利品表的范畴。

战利品表实际上是标准的 JSON 文件,定义了一个 Item 什么时候以掉落物的方式出现。战利品表的一些特性如下:

  • 生存模式下不可破坏方块(基岩、末地传送门方块等)没有战利品表;
  • 一些方块共享一份战利品表,例如墙类、地板类;
  • 目前某些情况下掉落的策略在战利品表中不存在,有几种特殊情况:
    • 凋零掉落下界之心的逻辑不在战利品表的管理范围内;
    • 这是一个 bug:MC-94610(击杀充能 creeper 掉落生物头颅不被战利品表管理。而掉落唱片的问题已经在 18w43a 被修复);

我们开发 mod 既可以修改已存在的方块的战利品表来改变掉落逻辑,也可以新增一些方块并且为它们创建战利品表。

那么如何创建战利品表呢?详细内容请参见 Chapter EX-2;