純規の暇人趣味ブログ

首を突っ込んで足を洗う

[Bukkitプラグイン制作講座-其之十一]メタデータを扱う

      2016/12/16    HimaJyun

不慣れ(はじめて)な事にしばらく手出ししていると、(記事を書けるレベルの)慣れている物に手が付けられなくなるのでネタが枯渇してきた感がありまする。

と言う訳で今回は小ネタ感ありますが、メタデータを利用してEntityなどに紐づく追加のデータを楽に管理する方法でも紹介しましょう。

前回の記事は「[Bukkitプラグイン制作講座-其之十]音を鳴らす、爆発を起こす」です。

めたでぇたってなんぞや?

メタデータは早い話が値をエンティティとかに紐付けして管理をBukkitに丸投げ出来る仕組みです。

前までHashMap<Entity,HogeFuga>なんてやっていたのをBukkitに丸投げしてしまえます、しかもプラグイン間でのデータ共有も可能。

メリット/デメリット

メリットとしては以下の通り。

  • なんたってBukkitに任せたので良いので楽
  • プラグインを跨いで利用出来るのでデータ共有にも使える

しかしデメリットだってありますよ。

  • 出し入れするのがObject型なので型安全性に疑問が残る
  • サーバが停止すると全てのメタデータが消える

どちらかと言うとキャッシュみたいな仮になくてもどうでも良いデータに使うのがメインって所ですかね。

でも型安全とか言い出したらプラグイン内でHashMap持った方が良いような……

今回の目的

今回は例示の為に「MOBを倒した際にそのMOBが生まれた場所を表示する」と言うのを目的として解説して行こうと思います。(ま、HashMapでも出来るんですけど……)

「○○した際に~」はイベント関連の話題なので割愛、イベントに関しては「[Bukkitプラグイン制作講座-其之六]特定の出来事(イベント)に反応する」をご覧ください。

要はスポーンした際にLocationをメタデータに入れて、倒した際にそれを元に情報を表示する訳ですね、簡単です。

さっそくやってみよう

と言っても、メタデータ周りの操作はあまり種類が多くないので速攻で終わっちゃいそうなのがメニミエなのですが……頑張って盛ろう……

さっそく、そのまえに。

メタデータは基本的に「Plugin型」のインスタンスを要求されます。

何らかの方法でPlugin型、すなわちメインクラス(「extends JavaPlugin」を記載したクラス)のインスタンスを渡す必要があるでしょう。

// TODO:何らかの方法で「Plugin」のインスタンスを渡す必要があります。
// (今回はメインクラスにイベントリスナを書いているのでthisが使えますが、実際はコンストラクタなどで渡す必要あり)
Plugin plugin = this;

それから、メタデータを扱う際にはよく「key」が必要になります、HashMapのkeyと同じ感じですね。

全体で同じ物を使う場合は定数として宣言した方が良いでしょう。

// 識別子的な、こうやって定数化(final+static)しておくと後の変更が楽
private final static String DATA_KEY = "SPAWNLOCATION";

値を設定する

値を設定するには「setMetadata()」を利用します。

第一引数にはkeyを、第二引数には「FixedMetadataValue」を指定する必要があります。

FixedMetadataValueは普通にnewで作成できます、コンストラクタの第一引数にPluginを、第二引数に設定したい値を渡します。

要は以下の通り、実質引数が3つあるような物ですね。

entity.setMetadata(
		DATA_KEY, // key
		new FixedMetadataValue(
				plugin, // プラグイン
				entity.getLocation().clone() // 設定したい値
		));

これでもう値のセットは完了、とっても簡単。

値を削除する

え?なんで値の取得を先に説明しないのかって?記事の都合よ、つ、ご、う。

値の削除はさっきよりも簡単、「removeMetadata()」を呼べばOK

entity.removeMetadata(
		DATA_KEY, // key
		plugin // プラグイン
);

値を持つか確認する

値を持つかどうかなら更に簡単、「hasMetadata()」を呼ぶだけ

entity.hasMetadata(DATA_KEY);

プラグインの指定は要らない

値を取得する

しかし全てが簡単に、とは行かないのが世の常、世界というのはそういう物です。

とりあえず値の取得自体は以下の様にすれば可能です。

List<MetadataValue> values = entity.getMetadata(DATA_KEY);

しかし、見りゃ分かりますが、得られる値がList<MetadataValue>です、イミワカンナイ

MetadataValueの取り扱い

たかだかgetする如きにList<MetadataValue>なんか渡しやがって……

と言う訳でこのMetadataValueを上手い具合に取り扱う必要があります。

ListからMetadataValueを取り出す

HashMapを使った事のある方だと疑問に思いますよね、「keyが他のプラグインと同じだったら(衝突したら)どうなるの?」

そして、更に進んだ方であれば「だから渡したPluginでどうにかするんだよね?」と気付くはず(そこまで気付くならこのページなんか読まないか……)

そうです、MetadataValueではその値を設定したPluginを取得出来るようになっているので、それが自分のPluginと同じか確認する必要がある訳です。

こんな感じでね。

MetadataValue value = null;

// ループで全部チェックする
for (MetadataValue v : values) {
	// 名前を比較して同じプラグインか確認
	if (v.getOwningPlugin().getName().equals(plugin.getName())) {
		// 同じなら値をセットしてループ抜ける
		value = v;
		break;
	}
}

// nullのまま(見つからなかった)
if (value == null) {
	return; // TODO:何か処理
}

これで自分自身が設定した正真正銘のMetadataValueが取得出来ました。

値を使えるようにする

このMetadataValueから自分が設定した値が取得できるのですが、ここで帰って来るのはObject型、例の何でも入れられる魔法の箱ですね。(危ないから本当はそんな使い方しちゃダメだよ)

今回は自分自身が設定した正真正銘の物なのが分かっているので、気にせずキャスト(変換)します。(まぁやろうと思えば他のプラグインからの偽装も可能ですが)

Location location = (Location) value.value();

(型安全?知らねぇよ、的な……)

うむ、まぁ、ここまで出来れば大抵の目的は達成出来ますね。

一応他にも出来るみたいなので紹介。

キャスト不要のやり方

基本型なんかはas型名()でキャストが要らない取得が可能です。

きっと型安全になった訳ではないでしょうから、例えば数値じゃない物を数値として取得しようとしたらどうなるかの保証はない。

value.asBoolean(); // boolean
value.asByte(); // byte
value.asDouble(); // double
value.asFloat(); // float
value.asInt(); // int
value.asLong(); // long
value.asShort(); // short
value.asString(); // String

どうでも良いですけど、MetadataValueでObjectを返すんだから数値とかもintじゃなくてInteger(すなわちラッパークラス)で持っているはずなんですよね……

なのにasInt()に掛けるとint(すなわち基本型の方)で返って来る……これ中でオートボクシングしてるんじゃ……(流石に気にし過ぎか?

as系はちょっとイマイチかも……

最後に

やっぱり大した文量にならなかったね、メタデータは言うほど難しくないし……

本当はもっと別の事を書くつもりだったけど確認を進めると無理な事が判明したので急遽ネタ変更したらこうなった

とは言え、メタデータ、便利に見えてイマイチかも……プラグインの中だけで完結するデータならHashMapで持った方が良い気がする。

例えばプラグイン同士でのデータの受け渡しとかだと手軽で丁度良いし有用かもしれない。

もしくは、例えば敵MOBとかで、そのMOBの消滅と同時に要らなくなったデータも消えて欲しい時とか(本当に消しているのかどうかは別として)

使いどころを考えて上手に使おう!!

ちなみに今回のサンプルコードはGitHubにある。

 - プログラミング , ,