EntityDataManager
注意事項
このリファレンスは筆者が記述した時点での拙い知識で翻訳、解釈したもののメモです。
誤訳や理解不足の可能性を多分に含みます。ご覧になる際はご注意ください。
ソースは1.9-12.16.0.1797-1.9-snapshotのものを使用しています。
EntityDataManager
Minecraft1.9からDataWatcherに変わって導入されたデータ同期のためのオブジェクトです。
DataWatcherの問題点であった、クラスごとにIDの指すオブジェクトの型や意味が違う、アクセス時にタイプを特定しないため、キャストが必要となり最悪の場合エラーの原因になる、IDでのみアクセスするため、そのアクセスが何を意図したものかわかりづらい、などを解決しています。以下、DataManagerと略して表記します。
DataManagerの利用法
DataManagerは、エンティティのプロパティ*1のうち、同期の必要のあるものを一括で管理するオブジェクトです。エンティティのインスタンス1つにつき1つが用意され、DataWatcherと同様に、初期化の際に監視するプロパティを登録します。
プロパティ生成と登録
プロパティはDataParamater<T>で実装されており、利用するときはDataManagerのcreateKeyメソッドから生成します。生成されたDataParamaterをフィールドとして保持しておき、これに対するアクセスをアクセサで管理することでプロパティとしての機能を実現しています。
生成の例:Entityクラスから
private static final DataParameter<Byte> FLAGS = EntityDataManager.<Byte>createKey(Entity.class, DataSerializers.BYTE); private static final DataParameter<Integer> AIR = EntityDataManager.<Integer>createKey(Entity.class, DataSerializers.VARINT); private static final DataParameter<String> CUSTOM_NAME = EntityDataManager.<String>createKey(Entity.class, DataSerializers.STRING); private static final DataParameter<Boolean> CUSTOM_NAME_VISIBLE = EntityDataManager.<Boolean>createKey(Entity.class, DataSerializers.BOOLEAN); private static final DataParameter<Boolean> SILENT = EntityDataManager.<Boolean>createKey(Entity.class, DataSerializers.BOOLEAN);
ジェネリクスでデータの型が明らかになっていること、フィールド名で用途を明示できることが大きな利点です。
生成時にcreateKeyメソッドに渡しているDataSirializarsの定数は、格納されるデータの型を表します。
DataSirializerの種類
名前 | 型 | 予想される用途 |
BLOCK_POS | BlockPos | ブロック基準の座標 |
BOOLEAN | boolan | 各種フラグ |
BYTE | Byte | 8bitのデータやフラグの組み合わせ |
FACING | EnumFacing | ブロックが向いている方向 |
FLOAT | Float | 単精度浮動小数 |
OPTIONAL_BLOCK_POS | Optional<BlockPos> | OptionalでラップしたBlockPos |
OPTIONAL_BLOCK_STATE | Optional<IBlockState> | OptionalでラップしたBlockState |
OPTIONAL_ITEM_STACK | Optional<ItemStack> | OptionalでラップしたItemStack |
OPTIONAL_UNIQUE_ID | Optional<UUID> | OptionalでラップしたUUID。プレイヤーやエンティティの識別子 |
ROTATIONS | Rotations | 現在のところArmorStandの部位の回転方向のみ |
STRING | String | 文字列 |
TEXT_COMPONENT | ITextComponent | チャットメッセージ用の装飾指定つき文字列 |
VARINT | Integer | 整数 |
登録には、DataManagerのregister(DataParamater<T>, T)を使用します。
DataWatcherのaddObject(int,Object)に相当しますが、数値ではなく生成しておいたDataParamater自体をキーに使用しますので、可読性が格段に高くなります。また、内部的にはIDに数値を使用していますが、利用者はそれを知っている必要はありません。DataManagerが自動的に採番して重複を避けるようになっています。
登録の例:Entityクラスから
this.dataWatcher = new EntityDataManager(this);//エンティティ自身を渡してDataManagerを生成 this.dataWatcher.register(FLAGS, Byte.valueOf((byte)0));//悪名高いflags this.dataWatcher.register(AIR, Integer.valueOf(300)); this.dataWatcher.register(CUSTOM_NAME_VISIBLE, Boolean.valueOf(false)); this.dataWatcher.register(CUSTOM_NAME, ""); this.dataWatcher.register(SILENT, Boolean.valueOf(false));
登録されたDataParameterへのアクセスも、DataManagerを通して行います。
アクセスに使用するメソッドはget(DataParameter)とset(DataParameter,T value)でシンプルです。
と言いたいところですが、古いバージョンのソースでプライマリ型を指定されているプロパティについては、キャストやvalueOfで型変換行う必要があります。自作modで新しくプロパティを追加するときには、プライマリ型ではなくラッパークラスを使用するように心がけた方がよさそうです*2。
DataParamaterを定数として宣言していますので、実際のソースは以下のようになります。
アクセサの例:Entityクラスから
public int getAir() { return ((Integer)this.dataWatcher.get(AIR)).intValue(); } public void setAir(int air) { this.dataWatcher.set(AIR, Integer.valueOf(air)); }
DataWatcherの時とは違い、どのデータにアクセスしようとしているかが明確になっています。また、IDEを使用しているのであればデータの型も確かめやすいので、コーディングは格段に楽になるといえるでしょう。先ほども述べましたが、型を宣言しているのに前方互換のためにプライマリ型に戻さなくてはならなかったりするのは、仕様上仕方がありません。
セッターから新しい値が与えられると、エンティティのnotifyDataManagerChange(DataParamater key)が呼び出されます。
エンティティはこのkeyがどのプロパティかを確認して、必要があるならばアニメーションを開始したり、SEを鳴らしたりといった処理を行います。このメソッドの中では親クラスの挙動を変更したい場合を除いて、親クラスの同メソッドを呼び出す必要があります。忘れるとどのような不具合が発生するかわかりませんので、忘れないようにしましょう。
notifyDataManagerChangeの実装の例:EntityLivingクラスから
public void notifyDataManagerChange(DataParameter<?> key) { super.notifyDataManagerChange(key); if (HAND_STATES.equals(key) && this.worldObj.isRemote) { if (this.isHandActive() && this.activeItemStack == null) { this.activeItemStack = this.getHeldItem(this.getActiveHand()); if (this.activeItemStack != null) { this.activeItemStackUseCount = this.activeItemStack.getMaxItemUseDuration(); } } else if (!this.isHandActive() && this.activeItemStack != null) { this.activeItemStack = null; this.activeItemStackUseCount = 0; } } }
この例では(おそらく)1.9で追加された両手持ちのアイテムを入れ替える処理を行っています。
DataWatcherから変更点
利用法とその目的はDataWatcherから大きく変わってはいませんが、その仕組みは大きく変化しており、いくつかの制限が撤廃されているようです。
登録できるプロパティが255個(ID0~254)になった
DataManagerのregisterメソッドを見ると、自動採番のIDが255未満で新しいオブジェクトを追加できるようになっています。
登録できる型が増えた
今後も増えるかもしれません。
変更部分のみが送信されるようになった
ゲームサイクルのアップデートの際に、実際に数値の変更があったプロパティのみを送信するように設計されています。DataWatcherでは送信されながら使用されないデータが多く、無駄が多かったという判断でしょうか。格納できるデータの量が増えていることも理由のひとつかもしれません。