提供: Minecraft Modding Wiki
移動先: 案内検索

解説が考えつかなかったので、ForgeDocに沿って解説します。※編集時点220f7216094def0d75802ef7d23c94d449d3a591


Capabilityシステムの動的で柔軟な方法によって、たくさんのインターフェースを実装しなくても様々な機能を公開できるようになります。

大雑把に言うとそれぞれのCapabilityは、Interfaceの形式、並びに要求可能なデフォルト実装、そして少なくともデフォルト実装に対して機能する保存用ハンドラを提供します。
保存用ハンドラは他の実装もサポート可能ですが、これはCapability実装クラスによりますので、デフォルト実装以外でデフォルト保存ハンドラを使おうと試みる前にそれらのドキュメントを読むようにしてください。

ForgeによってTileEntityとEntityとItemStackに対してCapabilityのサポートが追加されています。これによってイベントを通じてもしくは自身のオブジェクトであればCapability関係のメソッドをオーバーライドすることで付加し機能を公開することができます。
このことは下の節でより詳細に説明します。

Forgeによって提供されるCapability

ForgeはIItemHandler、IFluidHandlerそしてIEnergyStorageの3つのCapabilityを提供します。

IItemHandlerはインベントリのスロットを制御するためのインターフェースを公開します。TileEntity(チェスト、機械など)やEntity(プレーヤスロットの拡張、モブや生物のインベントリやバッグ)、ItemStack(持ち運び可能なバックパックなど)に適用できます。
IInventoryISidedInventoryは、この自動化に適応したシステムによって置き換えられます。

IFluidHandlerは流体インベントリを制御するためのインターフェースを公開します。これもTileEntityやEntity、ItemStackに適用できます。
IFluidHandlerは、この統一性があって自動化に適応したシステムによって置き換えられます。

IEnergyStorageはエネルギコンテナを制御するためのインターフェースを公開します。これもTileEntityやEntity、ItemStackに適用できます。
これはTeamCoFHのRedstoneFlux APIにもとづいています。

既存のCapabilityの利用

簡単に言うと、TileEntityとEntityとItemStackのCapabilityの提供の実装はICapabilityProviderを通じています。
このインターフェースはhasCapabilitygetCapabilityというCapabilityを参照するための2つのメソッドを提供します。

Capabilityを取得するためには、一意なインスタンスを参照する必要があります。
アイテムのハンドラであれば、このCapabilityはCapabilityItemHandler.ITEM_HANDLER_CAPABILITYに格納されていますが、@CapabilityInjectアノテーションを用いて他のインスタンス参照を得ることも可能です。

@CapabilityInject(IItemHandler.class)
static Capability<IItemHandler> ITEM_HANDLER_CAPABILITY = null;

このアノテーションはフィールドとメソッドに付与できます。
フィールドに適用した場合、そのフィールドはCapabilityのインスタンス(すべてのフィールドで共通)がCapabilityの登録時に代入されます。もしCapabilityが登録されなければ、既存の値 (null)となります。
ローカルの静的フィールドアクセスは速いので、Capabilityとともに動作するオブジェクトの参照のローカルコピーを保持するのはいいアイデアでしょう。
メソッドにも適用でき、Capabilityが登録されたことの通知を受け取ることで、状況に応じた機能のオンオフが可能になります。

hasCapabilitygetCapabilityのどちらもがEnumFacing型の第二引数を持ちます。これは一つの面に対してのインスタンスを要求するために使用できます。
nullを渡した場合、ブロック内または面が意味をなさない場所での要求が想定できます。この場合は面に依存しない一般的なCapabilityが代わりに返されます。
getCapabilityの返す型はメソッドに渡されたCapabilityで宣言された型によって決まります。Item HandlerのCapabilityであれば、IItemHandlerとなります。

Capabilityの公開

Capabilityを公開するためには、まず根本的なCapability型のインスタンスを必要とします。
Capabilityは殆どの場合それを含むオブジェクトに結び付けられていますので、Capabilityを保持するそれぞれのオブジェクトに分けてインスタンスを代入すべきことを心がけましょう。

そのインスタンスを取得する方法は、Capability自体を通して、もしくは明示的にCapabilityの実装をインスタンス化する、という2つがあります。
一つ目の方法は、デフォルトの実装を使用するためにデザインされています。
Item HandlerのCapabilityの場合、デフォルトの実装では一スロットインベントリを公開します。おそらくあなたの望むものではありませんが。

二つ目の方法は、カスタムした実装を提供するために使用できます。
IItemHandlerの場合、デフォルト実装はItemStackHandlerを使用しますが、このクラスはスロット数を指定できるコンストラクタを持ちます。
しかしながら、デフォルト実装の存在を確信するのは避けるべきでしょう。Capabilityが存在しない状況でのロード時エラーを防ぐためにもCapabilityが登録されているかを先に確かめておくべきです。

もしあなたがCapabilityインタフェースのインスタンスを持っているならば、あなたはこのCapabilityを公開していることをCapabilityシステムのユーザーに通知したいと考えるでしょう。
これはhasCapabilityをオーバーライドし、公開しているCapabilityとインスタンス比較することによって達成できます。
もし面によって対応するスロットが異なるのであれば、facing引数で検査できます。
EntityやItemStackではこの引数は無視できますが、アーマースロット(上面なら頭のスロットとか?)やインベントリ内の周辺ブロック(西面なら左のスロットとか?)などのように活用もできます。
オーバーライドするときにはsuperの呼び出しを忘れないよう気をつけてください。(もちろん機能停止する場合は別ですが)

@Override
public boolean hasCapability(Capability<?> capability, EnumFacing facing) {
  if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
    return true;
  }
  return super.hasCapability(capability, facing);
}

同様に、リクエストされた際にインターフェースの参照を提供できます。繰り返しますが、superの呼び出しをお忘れなく。

@Override
public <T> T getCapability(Capability<T> capability, EnumFacing facing) {
  if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
    return (T) inventory;
  }
  return super.getCapability(capability, facing);
}

Capabilityのチェックは毎Tickに様々なブロックから為されますので、インスタンスチェックのみに留めることを強く推奨します。
ゲームの実行を遅らせたくはありませんからね。

Capabilityの付加

先に伝えましたが、Capabilityの付加はAttachCapabilityEventを使ってできます。
すべてのCapability提供可能オブジェクトに対して同じイベントが利用できます。
AttachCapabilityEventには以下に示す4つの有効なジェネリックタイプを持ちます。

  • AttachCapabilityEvent<Entity>: Entityに対してのみ。
  • AttachCapabilityEvent<TileEntity>: TileEntityに対してのみ。
  • AttachCapabilityEvent<Item>: ItemStackに対してのみ。
  • AttachCapabilityEvent<World>: Worldに対してのみ。

上に示した以上に絞って指定することはできません。
例えばEntityPlayerにCapabilityを付加したい場合、AttachCapabilityEvent<Entity>からオブジェクトを検査した後にCapabilityを付加する必要があります。

すべての場合において、addCapabilityというメソッドから対象のオブジェクトへCapabilityを付加できます。
Capability自体を追加するだけでなく、Capabilityプロバイダも追加できます。
プロバイダはICapabilityProviderを実装するだけでできます。
もしデータを永続的に保持する必要があるならICapabilitySerializable<T extends NBTBase>を実装することでCapabilityを返すこととNBTのセーブ・ロードの両方が可能になります。

ICapabilityProviderの実装方法についてはCapabilityの公開を参照してください。

自作Capabilityの作成

一般的に言えば、CapabilityはCapabilityManager.INSTANCE.register()を通して宣言し登録できます。
一つの可能性として、静的なregister()メソッドをCapabilityのためのクラスに定義するということが考えられますが、これはCapabilityシステムでは要求されません。
このドキュメントではすべて名付きクラスにして解説しますが、匿名クラスにすることもできます。

CapabilityManager.INSTANCE.register(capability interface class, storage, default implementation factory);

第一引数ではCapabilityの機能を伝えます。この例ではIExampleCapability.classとしています。

第二引数ではCapability.IStorage<T>実装クラスのインスタンスを渡します。Tは第一引数と同じ型になります。
ストレージクラスはではデフォルト実装のセーブとロードの管理が可能であり、もし可能であるのならば他の実装もサポートできます。

private static class Storage
    implements Capability.IStorage<IExampleCapability> {

  @Override
  public NBTBase writeNBT(Capability<IExampleCapability> capability, IExampleCapability instance, EnumFacing side) {
    // return an NBT tag
  }

  @Override
  public void readNBT(Capability<IExampleCapability> capability, IExampleCapability instance, EnumFacing side, NBTBase nbt) {
    // load from the NBT tag
  }
}

第三引数ではデフォルト実装のインスタンスファクトリを渡します。

private static class Factory implements Callable<IExampleCapability> {

  @Override
  public IExampleCapability call() throws Exception {
    return new Implementation();
  }
}

最後に、デフォルト実装自体を必要とする時は、ファクトリにおいてインスタンス化することで可能です。
このクラスのデザインはあなた次第ですが、完全に使えるというほどではなくともCapabilityのテストに使える程度には基本的な骨組みを提供すべきです。

Tips

クライアントとの同期

デフォルトでは、Capabilityのデータはクライアントと同期されません。
これを変えるには、パケットを使うことで同期用コードを管理する必要があります。

同期用パケットを送りたくなるであろう3つの異なる状況を以下に示します。

  1. Entityのスポーン時、Blockの設置時など初期化時の値をクライアントと共有したいとき。
  2. 保持しているデータが変わった際にクライアントに通知したいとき。
  3. 新たなクライアントがEntityまたはBlockを観測し始めた際にすでに存在するデータを通知したいとき。

パケットの実装については、ForgeのNetworkingシステム(1.7のパケットについてForgeDoc Networking)を参照してください。

死亡時を越えての持続

デフォルトでは、Capabilityのデータは死亡時に消失します。
これを変えるには、プレイヤーがリスポーンの過程でクローンされるときに手動でコピーされる必要があります。

これはPlayerEvent.Cloneイベントを利用することで可能です。もとのEntityからデータを読み込み、新たなEntityに代入することでできます。
このイベント内で、wasDeadフィールドによってEndからの帰還時か死亡からのリスポーン時か判定できます。
End帰還時はCapabilityのデータは破棄されませんので、重複した値を与えないためにもこの判定は重要です。

IExtendedEntityPropertiesからの移行

CapabilityシステムはIExtendedEntityProperties(以下IEEP)がしてきた、またそれ以上の機能を実現できます。
しかしながら、この2つのコンセプトは完全に対応しているわけではありません。IEEPをCapabilityに移行する方法を説明しましょう。

下はIEEPとCapabilityの対応早見リストです

  • 固有の 名前/ID (String): Capabilityのキー (ResourceLocation)
  • 登録 (EntityConstructing): (AttachCapabilityEvent<Entity>)での付加。Capability自体の登録はpre-initです。
  • NBTのread/write : 自動で実行はされません。 イベントでICapabilitySerializableを付加し、serializeNBT/deserializeNBTからread/writeを呼び出してください。

必要とされないであろうCapabilityの機能(IEEPを内部的にしか使っていないのであれば)

  • デフォルト実装を提供するCapability。内部的ならば、ファクトリはnullで良いでしょう。
  • IStorageを提供するCapability。デフォルト実装を提供しないのであれば、これは要求されません。空のままで良いです。

以下の手順は、このドキュメントをしっかり読みCapabilityについて理解したという前提で書いています。

移行手順

  1. IEEP key/id の文字列をResourceLocationに変更する。ドメインにはModIDを指定します。
  2. ハンドラクラス内(Capabilityインターフェースを実装したクラスではなく)で、Capabilityを保持するフィールドを作成。
  3. EntityConstructingイベントをAttachCapabilityEventイベントに変え、IEEPの代わりにICapabilityProviderを付加する。
  4. 登録メソッドを作成し、その中でCapability登録メソッドを実行する。