Minecraftサーバーを動かす知識

チェストの使い方

投稿:  更新:  By: HimaJyun

Bukkitプラグインを開発する上で避けて通れないのはチェストのような特殊な挙動をするブロックの扱いです。

特殊なブロックを活用する際にはアップキャストが多用されるため、IDEの入力補完にそれっぽいメソッドが出てこず分かりづらいです。

そこで、今回はチェストの操作方法を解説しようと思います。

スポンサーリンク

チェストを扱う

チェストなどの使い方がなぜ難しいか?冒頭の通り、個人的には「IDEの入力補完にそれっぽいメソッドが出てこないから」だと考えています。

だったら話は簡単。出してやりましょう、それっぽいメソッド。

出してやる方法

今回はチェストを対象に扱うので、チェストに関連する「それっぽいメソッド」を出せるようにする方法です。

Block block; // 何らかの方法で取得したブロック

// チェスト以外では使えないので忘れずチェックするようにしましょう。
if (block.getType() != Material.CHEST) {
    return;
}

// Chestには`org.bukkit.block`と`org.bukkit.block.data.type`があります。
// 必要な方を利用してください。必要なのが片方だけの場合は`import`で`org.bukkit.block...`を省略できます。

// `BlockState`が`org.bukkit.block`用
org.bukkit.block.Chest chestState = (org.bukkit.block.Chest) block.getState();
// `BlockData`が`org.bukkit.block.data.type`用
org.bukkit.block.data.type.Chest chestData = (org.bukkit.block.data.type.Chest) block.getBlockData();

こんな感じで、getState()getBlockData()Chestにキャストできます。

キャストした物を使ってIDEの入力補完を出してみてください、きっとやりたかった事をやる方法が見つかるはずです。

なぜこれで出てくるのか?

Minecraft、延いてはBukkitの世界ではあらゆる物は「ブロック」によってできています。

そのため、世界を構成する単位が「ブロック」になり、APIから操作するのも「ブロック(Blockクラス)」単位になります。

とはいえ、その辺に転がってる石や草ブロックにチェストとしての機能はないので、チェストを扱う方法は石や草とは切り分けて実装する必要になります。

チェスト専用のクラス(型)がある理由はこれで理解してもらえると思います。

最初に戻りますが、その辺に転がってる石や草ブロックにチェストとしての機能はありません。

そのため、getChest()なんてメソッドをBlockに実装する事はできないのです。なぜなら、ブロック=チェストとは限らないから。

ここではチェストを例に説明していますが、実際にはカマド、ホッパー、シュルカーボックス……とさまざまな種類があります。同様の理由でgetFurnace()getHopper()なんて物を実装してたら大変な事になるのは想像に難くないですね。

(BlockgetChest()getFurnace()getHopper()のメソッドを持っているが、使えないブロックで呼び出すとエラーになる……なんて仕様だと不便ですし、IDEの入力補完候補が増えすぎてまったく関係のない草や石を扱いたい時にすら邪魔になります)

だったらどうするか?"特殊な機能"をひとまとめにして、そこから適宜必要な型を取り出して変換するようにします。

その、"特殊な機能"を取り出すのがgetState()getBlockData()で、必要な型に変換するのがキャストです。

雑に要約すると、「getState()getBlockData()で特殊な機能を取り出して、扱える種類に変換しないと何もできない」です。

それがIDEの入力補完でそれっぽいメソッドが出てこない理由。

(この辺は継承、抽象化、ポリモーフィズム、アップキャスト辺りが理解できていると分かりやすいです)

やりたい事の一覧

継承が分からない人向けに理由を説明するとふんわりとした分かりづらい説明しかできませんが、この「出てこない理由」「出せるようになる理由」を今すぐ理解しようとする必要はありません。

私の基本的なスタンスは「やり方を学ぶ、理由は後で考える、動けばそのうち理解できる」なので、動かせるようにする方法をこれから説明します。

他のブロックをチェストにする

これはキャストする必要ありません。

BlockにはsetType()があるので、これにMaterial.CHESTを指定するだけ。

// チェストに変えてしまう
block.setType(Material.CHEST);

チェストの中身を操作する

チェストの中身の操作はorg.bukkit.block.ChestにあるgetBlockInventory​()を使います。

// getState()を使う
org.bukkit.block.Chest chest = (org.bukkit.block.Chest) block.getState();
// Inventoryを取得
Inventory inventory = chest.getBlockInventory();

getBlockInventory()から先は普通のインベントリなので、ユーザーの持ち物を操作する時と同じです。

ラージチェストの場合は片方分だけのInventoryになります。両方とも操作する場合は反対側のチェストも取得する必要があります。

チェストの向きを取得する

チェストに限りませんが一部のブロックは"向き"を持っています。

向きはorg.bukkit.block.data.type.ChestにあるgetFacing​()で取得できます。

(上級者向けに説明すると、向きを持っているブロックはorg.bukkit.block.data.Directionalを実装しています)

// getBlockData()を使う
org.bukkit.block.data.type.Chest chest = (org.bukkit.block.data.type.Chest) block.getBlockData();
// 向きを取得
chest.getFacing();

// BlockFaceはEnumなので、このように特定の向きに向いているかを判定したりして使います。
if(BlockFace.EAST == chest.getFacing()) {

}

上手く活用すれば"正面からしか開けられないチェスト"なんてものが作れますよ。

チェストの向きを変更する

向きを取得できるなら変更もできそうですよね?もちろんできます。

org.bukkit.block.data.type.ChestにあるsetFacing()で向きを設定できます。

// getBlockData()を使う
org.bukkit.block.data.type.Chest chest = (org.bukkit.block.data.type.Chest) block.getBlockData();

// 向きを設定
// 引数でBlockFaceを使って向きを指定します。
chest.setFacing(BlockFace.EAST);

// 向きを反映させる。
block.setBlockData(chest);

// BlockFaceには様々な向きがありますが、全て使える訳ではないので注意
// 使える向きは`getFaces()`で取得できます。
Set<BlockFace> faces = chest.getFaces();

変更後にsetBlockData()が必要なのをお忘れなく。これをやらないと反映されません。

上手く活用すれば"回るチェスト"とかも作れます。"正面からしか開けられないチェスト"を組み合わせると……?

ラージチェストか確認する

ラージチェストか確認するにはorg.bukkit.block.data.type.ChestgetType()を使用します。

通常のチェストの場合はType.SINGLEに、ラージチェストの場合はType.RIGHTType.LEFTになるので、それを使って判定しましょう。

// getBlockData()を使う
org.bukkit.block.data.type.Chest chest = (org.bukkit.block.data.type.Chest) block.getBlockData();

// Type.SINGLEではない == ラージチェスト
if (chest.getType() != org.bukkit.block.data.type.Chest.Type.SINGLE) {

}

ラージチェストの時はType.RIGHTType.LEFTになるので、Type.SINGLEか否かで判定する方が簡単です。

ラージチェストの反対側を取得する

ラージチェストの反対側を取得するのは少し複雑。

org.bukkit.block.DoubleChestがありますが、これを使った方法は回りくどい。個人的にはgetType()getFacing​()を組み合わせた方法を使っています。

// 取得した方向と反対側の方向の組み合わせをルックアップテーブルにする
// この2つは実際には起動時に1回だけ初期化して使いまわします
Map<BlockFace, BlockFace> LEFT_TABLE = new EnumMap<>(BlockFace.class);
Map<BlockFace, BlockFace> RIGHT_TABLE = new EnumMap<>(BlockFace.class);
LEFT_TABLE.put(BlockFace.NORTH, BlockFace.EAST);
LEFT_TABLE.put(BlockFace.SOUTH, BlockFace.WEST);
LEFT_TABLE.put(BlockFace.WEST, BlockFace.NORTH);
LEFT_TABLE.put(BlockFace.EAST, BlockFace.SOUTH);
RIGHT_TABLE.put(BlockFace.NORTH, BlockFace.WEST);
RIGHT_TABLE.put(BlockFace.SOUTH, BlockFace.EAST);
RIGHT_TABLE.put(BlockFace.WEST, BlockFace.SOUTH);
RIGHT_TABLE.put(BlockFace.EAST, BlockFace.NORTH);

// getBlockData()を使う
org.bukkit.block.data.type.Chest chest = (org.bukkit.block.data.type.Chest) block.getBlockData();

// 通常のチェストなら何もしない
if(chest.getType() == org.bukkit.block.data.type.Chest.Type.SINGLE) {
    return;
}

// チェストがどちらかに応じて使うテーブルを変える
Map<BlockFace, BlockFace> table = null;
if (chest.getType() == org.bukkit.block.data.type.Chest.Type.LEFT) {
    table = LEFT_TABLE;
} else if (chest.getType() == org.bukkit.block.data.type.Chest.Type.RIGHT) {
    table = RIGHT_TABLE;
}

// 方向を取得
BlockFace face = chest.getFacing();

// ルックアップテーブルを使って反対側の方向を特定
BlockFace pairFace = table.get(face);

// 特定した方向に反対側のチェストがあります。
Block pair = block.getRelative(pairFace);

ただ反対側を取得するだけなのに結構面倒です。

ここでは分かりやすくするために冗長に書いていますが、三項演算子などを使えばもっとシンプルに書けます。

JavaDocからのみつけ方

今回はチェストに限って説明しましたが、「何から何にキャストできるのか?」を見つける方法があります。

Bukkit APIのJavaDocにあるorg.bukkit.blockorg.bukkit.block.data.typeを確認しましょう。

自分の使いたい機能がありそうなクラスを探します。たとえばorg.bukkit.block.data.type.Chestとか。

ページを開いて継承関係を確認しましょう。
/bukkit/development/tips/chest-usage/001.png

Blockから取得できるものの中で、継承関係に含まれているものがあればキャストできる可能性が高い。

大抵はBlockDataBlockStateからキャストできます。

ソースとバージョン

今回の記事は1.15.2で確認しました。バージョンによっては内容が異なるかも知れません。

ソースコードはGitHubに掲載しているので、参考にしてみてください。