注册表介绍
Minecraft 使用注册表系统处理游戏内的几乎一些,作为模组开发者,需要将添加到游戏的绝大多数内容注册进去,这有利于:
- 让游戏知道你的内容存在
- 在客户端和服务器之间验证游戏内容
- 存储时处理无效内容
- 避免不同模组之间的冲突
- 利于客户端和服务器之间的沟通和数据储存
- 抽象化隐藏数字 ID
注册任何类型内容时,你传入一个 Identifier
对象,也就是你需要加入的内容的标识符。标识符,简称 ID,通常拥有一个命名空间(namespace)和路径(path)。大多数情况下,命名空间是你的模组的 ID(见 terms),路径则是你要注册的内容的(英文)名称。命名空间和路径不能包含大写字母或其他语言(包括中文)的字符。例如,原版的泥土方块的 ID 为 minecraft:dirt
。
不注册就使用自定义内容容易导致 bug,比如缺失纹理、世界保存问题和崩溃。游戏通常会让你知道你是否忘记注册了某个东西。
注册表类型
注册内容时,你需要指定将内容加入哪个注册表。基本的游戏提供所有原版内容的注册表,可以在 Registries
(1.19.3 以上)或 Registry
(1.19.2 以下)中找到。
例如,你很有可能会使用用于物品的 Registries.ITEM
(1.19.3 以上)/Registry.ITEM
(1.19.2 以下)和用于方块的 Registries.BLOCK
(1.19.3 以上)/Registry.BLOCK
(1.19.2 以下)。
如需详细了解所有可用的注册表,请看 Registries
或 Registry
类。registry types 也有各注册表类型的简要描述。
基本用法
注册你的内容
使用 Registry.register
将你的内容注册到注册表。记住,每个内容不能注册超过 1 次。基本的语法是:
Registry.register(registry, id, content);
- registry - 你需要添加内容进的注册表的实例。
- id - 你的内容在注册表内的 ID。
- content - 你需要注册的内容的实例。
例如:
Registry.register(Registries.ITEM, Identifier.of("tutorial", "example_item", EXAMPLE_ITEM));
记住:只能在模组初始化器中注册。否则,会因为注册表冻结而无法注册。
通过 ID 获取对象
get
返回注册表内与 ID 关联的内容。如果内容不存在,SimpleDefaultedRegistry
会返回默认的注册表值,而 SimpleRegistry
就返回 null。可以使用 containsId
或 getOrEmpty
方法检测 ID 是否存在,例如:
Registries.ITEM.containsId(Identifier.ofVanilla("diamond")); // 返回 true Registries.ITEM.containsId(Identifier.ofVanilla("invalid_name")); // 返回 false Registries.ITEM.get(Identifier.ofVanilla("diamond")); // 返回 Items.DIAMOND Registries.ITEM.get(Identifier.ofVanilla("invalid_name")); // 返回 Items.AIR Registries.ITEM.getOrEmpty(Identifier.ofVanilla("diamond")); // 返回 Optional.of(Items.DIAMOND) Registries.ITEM.getOrEmpty(Identifier.ofVanilla("invalid_name")); // 返回 Optional.empty()
获取对象的 ID
getId
返回注册表内与对象关联的 Identifier
,如果这个项不存在, SimpleDefaultedRegistry
返回默认的注册表 ID,SimpleRegistry
返回 null。例如:
Registries.BLOCK.getId(Blocks.STONE); // 返回 Identifier.ofVanilla("stone"))
相关概念
注册表键
有些类型的注册表不是静态存储在 Registries
类中的。例如,你无法在 Registries
类中找到生物群系的注册表或战利品表的注册表。但是这些注册表在 Minecraft 中确实存在,这是因为这些注册表是动态的:在不同世界之间加载,因为是由数据包定义。相比之下,静态的注册表,例如 Registries.ITEM
和 Regitries.BLOCK
,还不是由数据包定义的,所以不管在哪个世界都保持不变。
但是,每个注册表都有个注册表键(RegistryKey
),可以在 RegistrKeys
类中找到。可以通过 RegistryWrapper.WrapperLookup
(如果有 World
对象,通常是 world.getRegistryManager
,注册命令时则通常是 CommandRegistryAccess
)对象根据注册表键找到注册表。参见下面的例子,假设你有 World
对象。
final DynamicRegistryManager registryManager = world.getRegistryManager(); // 下面两个语句都返回当前世界的沙漠的 ''Biome'' 对象 registryManager.get(RegistryKeys.BIOME).get(Identifier.ofVanilla("desert")); registryManager.getWrapperOrThrow(RegistryKeys.BIOME).getOrThrow(RegistryKey.of(RegistryKeys.BIOME, Identifier.ofVanilla("desert"))).value();
不仅注册表有注册表键。所有的注册表内容都有注册表键(因为注册表本身也属于 Registries.REGISTRIES
的注册表内容)。注册表内容在不同世界之间可能改变,但是注册表键是不会改变的。
注册表项
注册表项(RegistryEntry
)是属于有注册表的类型的内容。有两种类型:
RegistryEntry.Reference
:在注册表内注册的东西,因此有 ID。RegistryEntry.Direct
:不在注册表内注册的东西,可能是通过某种方式匿名指定的。
看看下面两个命令,以清晰地展示出了两者的区别:
/loot give @s loot minecraft:blocks/stone
/loot give @s loot {pools:[{rolls:1, entries:[{type:"item", name:"minecraft:stone"}]}]}
两个命令都会给你一个石头。前者会从世界的战利品表注册表中获取战利品表 minecraft:blocks/stone
,所以是 RegistryEntry.Reference
。后者会从 SNBT 中解码一个战利品表. 没有 ID,所以是 RegistryEntry.Direct
。两种类型的注册表项都可以通过调用 value()
方法来获取其实际的内容。
注册表项列表
和注册表项类似,注册表项列表(RegistryEntryList
)用得很多,也有两种类型:
RegistryEntryList.Named
:通过由数据包定义的标签定义的列表。RegistryEntryList.Direct
:直接通过列举出所有内容定义的列表。