能力系统(Capabilities)
能力系统(Capabilities)
能力系统(Capabilities)允许以动态和灵活的方式暴露功能,而无需直接实现多个接口。
总的来说,每个能力都以接口的形式提供一种功能。
NeoForge 为方块(Blocks)、实体(Entities)和物品堆栈(Item Stacks)添加了能力支持。以下部分将详细解释这些内容。
为什么要使用能力?
能力的设计目的是将方块、实体或物品堆栈的功能与实现方式分离。如果你在考虑能力是否适合你的需求,可以问自己以下问题:
- 你是否只关心方块、实体或物品堆栈能做什么,而不关心它如何实现?
- 这种行为是否只适用于某些方块、实体或物品堆栈,而不是所有?
- 这种行为的实现是否依赖于特定的方块、实体或物品堆栈?
以下是一些适合使用能力的例子:
- “我希望我的流体容器与其他模组的流体容器兼容,但我不了解每个流体容器的具体实现。” —— 是的,使用
IFluidHandler
能力。 - “我想计算某个实体中有多少物品,但我不了解实体如何存储它们。” —— 是的,使用
IItemHandler
能力。 - “我想为某个物品堆栈充能,但我不了解物品堆栈如何存储能量。” —— 是的,使用
IEnergyStorage
能力。 - “我想为玩家当前瞄准的方块上色,但我不了解方块如何被转换。” —— 是的。NeoForge 没有提供为方块上色的能力,但你可以自己实现一个。
以下是一个不推荐使用能力的例子:
- “我想检查一个实体是否在我的机器范围内。” —— 不,使用辅助方法代替。
NeoForge 提供的能力
NeoForge 为以下三个接口提供了能力支持:IItemHandler
、IFluidHandler
和 IEnergyStorage
。
IItemHandler
:暴露了处理物品栏槽位的接口。相关能力包括:-
Capabilities.ItemHandler.BLOCK
:方块的自动化可访问物品栏(如箱子、机器等)。 -
Capabilities.ItemHandler.ENTITY
:实体的物品栏内容(如额外的玩家槽位、生物背包等)。 -
Capabilities.ItemHandler.ENTITY_AUTOMATION
:实体的自动化可访问物品栏(如船、矿车等)。 -
Capabilities.ItemHandler.ITEM
:物品堆栈的内容(如便携背包等)。
-
IFluidHandler
:暴露了处理流体物品栏的接口。相关能力包括:-
Capabilities.FluidHandler.BLOCK
:方块的自动化可访问流体物品栏。 -
Capabilities.FluidHandler.ENTITY
:实体的流体物品栏。 -
Capabilities.FluidHandler.ITEM
:物品堆栈的流体物品栏(由于桶的特殊性,此能力为IFluidHandlerItem
类型)。
-
IEnergyStorage
:暴露了处理能量容器的接口,基于 TeamCoFH 的 RedstoneFlux API。相关能力包括:-
Capabilities.EnergyStorage.BLOCK
:方块内部的能量。 -
Capabilities.EnergyStorage.ENTITY
:实体内部的能量。 -
Capabilities.EnergyStorage.ITEM
:物品堆栈内部的能量。
-
创建能力
NeoForge 支持为方块、实体和物品堆栈创建能力。
能力允许通过一些调度逻辑查找某些 API 的实现。NeoForge 中实现了以下几种能力:
-
BlockCapability
:用于方块和方块实体的能力;行为依赖于特定的Block
。 -
EntityCapability
:用于实体的能力;行为依赖于特定的EntityType
。 -
ItemCapability
:用于物品堆栈的能力;行为依赖于特定的Item
。
提示:为了与其他模组兼容,建议尽可能使用 NeoForge 提供的 Capabilities
类中的能力。否则,可以按照本节描述的方式创建自己的能力。
创建能力只需调用一个函数,结果对象应存储在静态最终字段中。必须提供以下参数:
能力的名称(
ResourceLocation
)。- 使用相同名称多次创建能力将始终返回相同的对象。
- 不同名称的能力是完全独立的,可以用于不同的用途。
被查询的行为类型(
T
类型参数)。查询中的额外上下文类型(
C
类型参数)。
例如,以下是如何声明一个支持方向感知的方块 IItemHandler
能力:
public static final BlockCapability<IItemHandler, @Nullable Direction> ITEM_HANDLER_BLOCK =
BlockCapability.create(
// 提供唯一标识能力的名称
new ResourceLocation("mymod", "item_handler"),
// 提供被查询的类型。这里我们要查找 `IItemHandler` 实例。
IItemHandler.class,
// 提供上下文类型。我们允许查询接收一个额外的 `Direction side` 参数。
Direction.class
);
对于不需要上下文的能力,可以使用 Void
类型,并有一个专用的辅助方法:
public static final BlockCapability<IItemHandler, Void> ITEM_HANDLER_NO_CONTEXT =
BlockCapability.createVoid(
new ResourceLocation("mymod", "item_handler_no_context"),
IItemHandler.class
);
对于实体和物品堆栈,EntityCapability
和 ItemCapability
也有类似的方法。
查询能力
一旦我们在静态字段中有了 BlockCapability
、EntityCapability
或 ItemCapability
对象,就可以查询能力。
对于实体和物品堆栈,可以使用 getCapability
尝试查找能力的实现。如果结果为 null
,则表示没有可用的实现。
例如:
var object = entity.getCapability(CAP, context);
if (object != null) {
// 使用 object
}
var object = stack.getCapability(CAP, context);
if (object != null) {
// 使用 object
}
方块能力的查询方式略有不同,因为没有方块实体的方块也可以具有能力。查询现在在世界上执行,并额外提供目标位置:
var object = level.getCapability(CAP, pos, context);
if (object != null) {
// 使用 object
}
如果已知方块实体和/或方块状态,可以传递它们以节省查询时间:
var object = level.getCapability(CAP, pos, blockState, blockEntity, context);
if (object != null) {
// 使用 object
}
以下是一个更具体的例子,展示如何从 Direction.NORTH
方向查询方块的 IItemHandler
能力:
IItemHandler handler = level.getCapability(Capabilities.ItemHandler.BLOCK, pos, Direction.NORTH);
if (handler != null) {
// 使用 handler 进行某些物品相关操作。
}
方块能力缓存
当查询能力时,系统会在幕后执行以下步骤:
- 如果没有提供方块实体和方块状态,则获取它们。
- 获取已注册的能力提供者(见下文)。
- 遍历提供者并询问它们是否能提供能力。
- 其中一个提供者将返回能力实例,可能会分配一个新对象。
虽然实现相当高效,但对于频繁执行的查询(例如每游戏刻一次),这些步骤可能会占用大量服务器时间。BlockCapabilityCache
系统为在给定位置频繁查询的能力提供了显著的加速。
提示:通常,BlockCapabilityCache
会创建一次,然后存储在执行频繁查询的对象的字段中。具体何时何地存储缓存由你决定。
要创建缓存,请调用 BlockCapabilityCache.create
,并提供要查询的能力、世界、位置和查询上下文。
// 声明字段:
private BlockCapabilityCache<IItemHandler, @Nullable Direction> capCache;
// 稍后,例如在方块实体的 `onLoad` 中:
this.capCache = BlockCapabilityCache.create(
Capabilities.ItemHandler.BLOCK, // 要缓存的能力
level, // 世界
pos, // 目标位置
Direction.NORTH // 上下文
);
// 查询缓存:
IItemHandler handler = this.capCache.getCapability();
if (handler != null) {
// 使用 handler 进行某些物品相关操作。
}
缓存会自动被垃圾回收器清除,无需手动注销。
注册能力
能力提供者是最终提供能力的函数。能力提供者可以返回能力实例,如果无法提供能力则返回 null
。提供者特定于:
- 它们为之提供的能力。
- 它们为之提供的方块实例、方块实体类型、实体类型或物品实例。
需要在 RegisterCapabilitiesEvent
中注册提供者。
例如,以下是如何为方块注册能力:
private static void registerCapabilities(RegisterCapabilitiesEvent event) {
event.registerBlock(
Capabilities.ItemHandler.BLOCK, // 要注册的能力
(level, pos, state, be, side) -> <返回 IItemHandler>,
// 要注册的方块
MY_ITEM_HANDLER_BLOCK,
MY_OTHER_ITEM_HANDLER_BLOCK
);
}
通常,注册会针对某些方块实体类型,因此提供了 registerBlockEntity
辅助方法:
event.registerBlockEntity(
Capabilities.ItemHandler.BLOCK, // 要注册的能力
MY_BLOCK_ENTITY_TYPE, // 要注册的方块实体类型
(myBlockEntity, side) -> myBlockEntity.myIItemHandlerForTheGivenSide
);
注意:如果方块或方块实体提供者先前返回的能力不再有效,则必须通过调用 level.invalidateCapabilities(pos)
来使缓存失效。有关更多信息,请参阅上面的失效部分。