1. Capability

让我们再来看一下 TileEntity:我们可以给它附加诸如存储物品等等的功能,具体使用的时候只需要转型就可以了:

// 假想的情景,不要照抄
TileEntity tile = world.getTileEntity(pos);
if (tile instanceof Something) { // 假定这个 TileEntity 实现了名为 Something 的接口
    ((Something)tile).interact(...);
}

但不同的功能需要的接口也不尽相同,这些接口自然是没办法统一的。我们可能会写出这种东西:

if (tile instanceof Something) {
    // …
} else if (tile instanceof AnotherThing) {
    // …
} else if (tile instanceof SomethingElse) {
    // …
} else {
    // …
}

不难发现:我们总是在检查某个 TileEntity 是否支持某种功能(以 instanceof 的形式呈现)。类似的情况实际上也出现在了 ItemStackEntity 甚至是 WorldChunk 上。我们希望给这些东西追加一些功能,并且希望别人能通过指定的接口使用这些功能。但是新的问题也浮出水面:

// 如果 Something 并不存在会发生什么?
if (tile instanceof Something) {
    // …
}

Something 是来自别的 Mod 的接口,而你希望你的 Mod 不依赖别的 Mod 就能运行,上面这样的写法,一般来说就行不通了。
有办法解决这个问题吗? 让我们再审视一遍需求:“为游戏对象扩展新功能,并允许其他人使用”。也就是说,只要我们能让其他人拿到这些功能对应的接口就可以了。我们能不能这么做:

if (tile.hasFeature("Something")) {
    Something sth = tile.getFeature("Something");
}

这便是 Capability 系统存在的理由: 将功能转化为可反复使用的零件,用作某一个更大的“实体”(可以是狭义上的实体、TileEntity 甚至是物品)的基础,同时避免过强的耦合。

1.1. Capability<?>:Token

上文中我们写下了这么一段代码:

if (tile.hasFeature("Something")) {
    Something sth = tile.getFeature("Something");
}

在 Capability 系统中,它实际上是这样的:

if (tile.hasCapability(CapabilitySomething.CAP_TOKEN, EnumFacing.EAST)) {
    Something sth = tile.getCapability(CapabilitySomething.CAP_TOKEN, EnumFacing.EAST);
}

我们通过一个 CapabilitySomething.CAP_TOKEN 来标记我们需要的功能,或者说,这是我们“能力”(“capability”)的标记(token)。 它的类型是 Capability<Somthing>Something 便是实际功能的接口。
有了这样一个标记,我们就能从其他地方查询并请求这个功能的接口。这个“其他地方”指的是实现了 ICapabilityProvider 的类的实例。

1.2. ICapabilityProvider:不同功能的容器

ICapabilityProvider 便是各种不同功能的实现整合在一起的结果。 你可以问它“支持不支持 xxx”(hasCapability),也可以问它“要 xxx 功能的接口”(getCapability)。
在 Forge patch 过若干原版类后,这些原版类实现了 ICapabilityProvider,意味着我们可以通过某种方式给它们追加功能。这些实现了 ICapabilityProvider 的类包括但不限于:

  • TileEntity
  • Entity
  • ItemStack
  • Village
  • World