Bukkit Spigot Japan Wikiは、Hello Minecraftを応援しています。

超まいくらひろば2017バナー

 

 

Developer/チュートリアル

提供: Bukkit Spigot Japan Wiki
移動: 案内検索

本ページの内容は、Bukkit WikiPlugin Tutorialを和訳した物となります。(一部は省略しています)
最新ではない可能性があるため、より新しい情報を確認する場合は、本家を参照するようにして下さい。

目次

始めに

重要:当ページには、原文の翻訳ではない、日本語読者向けの独自の記述を行っている箇所が少量あります。そのような内容を含む節ではNoteにて通知しています。
重要:当ページでは、技術的な解説やたとえ話の原意を理解し易くするために、一部意訳を行っています。
重要:当ページは訳文であるため、訳文自体に対する文責は原著者にありません。当ページの内容を受けて原著者へ何らかのアクションを行う場合、その点を考慮して必ず原文にも目を通して下さい。
このチュートリアルは、Bukkitプラグインを開発するための基本的なノウハウを網羅しております。
Javaを習得する、IDEで開発環境を構築する、などの非常に基本的な部分から説明が始まっていますが、既にご理解頂いている場合は読み飛ばしていただいて構いません。

Javaの習得

Note:当項目の一部は原文訳ではありません。日本語読者向けに独自の内容を記述しています。
このチュートリアルの読者には、Java言語プログラミングの基礎がわかる方を対象としています。
Javaの基礎知識が少ない方は、下記の情報に触れてください。

Javaがどうしても理解できない場合は、この他にも様々な資料があるので探して触れてください。

Javaの開発環境

Note:当項目の一部は原文訳ではありません。日本語読者向けに独自の内容を記述しています。
プラグインを開発する(またはJavaを学ぶ)前に、開発環境(IDE (Integrated Development Environment))をインストールして下さい。
開発環境は、プラグインのコンパイルとデバッグを行うために使います。
Javaの主要なIDEには、Eclipse, Netbeans, IntelliJ IDEA の3つがあります。
EclipseはBukkit開発者の中では最も人気があり、IntelliJの方は業界内で広く利用されています。

あなたがJavaの初心者なら、当チュートリアルが解説に利用しているEclipseを利用する事をお勧めします。
Eclipseのお勧めのバージョンの配布元は、日本語 Eclipse / Pleiades All in One 日本語ディストリビューションです。
これはMergedocProjectが配布する拡張されたパッケージであり、Eclipseの一次配布元が提供するパッケージではありません。

Pleiadesを利用する場合、「開発対象用 JDK」と、ビルドツールMavenが実行できるプラグイン「m2e」が同梱されているものを選択してください。
(Eclipseを既に利用している場合でも、m2eを後から追加インストールすることは可能です。)
以下、m2eが既に導入されている前提で説明します。

プラグイン用プロジェクトを始めるために

プロジェクトの作成

始めるために、新しいワークスペースを作成する必要があります。
Eclipseを起動し、ファイル>新規>プロジェクト... と選択して下さい。

新規プロジェクトのダイアログが開きます。Mavenフォルダを開いて、Mavenプロジェクト を選択し、次へ を押してください。
次のダイアログで、「シンプルなプロジェクトの作成」にチェックを入れて、次へ を押してください。
次のダイアログで、作成するプラグインの「グループID」と「アーティファクトID」を入力します。
(アーティファクトIDとは、これから作ろうとするプラグインの名前と同義と思っていただいて構いません。)

グループIDは下記の方法で命名して下さい。

  • ドメインを持っているなら、そのドメインの逆引き名にしましょう。
    • ドメイン名がi-am-a-bukkit-developer.comの場合、パッケージ名はcom.i_am_a_bukkit_developerとなります。
  • ドメイン名を持っていない場合、下記の選択肢があります。おすすめ順です。
    • 1.githubやsourceforgeのようなソース管理サイトにアカウント登録します。
      • githubの場合: GitHub Pagesから作成したユーザページのURLを元に、com.github.<username>と命名します。GitHub Pagesの使い方は,各自で調べてみてください。
    • 2.Eメールアドレスを元に命名します。(<username>@gmail.comであればcom.gmail.<username>とします)
    • 3.重複しなさそうなパッケージ名を指定します(他者の開発物と重複する可能性があるので非推奨)

下記の名前で始まるような、他者の開発物と重複するパッケージで命名すべきではありません

  • org.bukkit
  • net.bukkit
  • com.bukkit
  • net.minecraft

ベースとなるグループIDを決めたら、次にプラグイン名を決めましょう。ここでは例として、TutorialPlugin と名付けます。
ダイアログには次のように入力し、「完了」を押してください。

Eclipseの画面に戻ると、左側に TutorialPlugin プロジェクトが作成されているはずです。ここまで、うまく作成できましたか?

Bukkit API の参照設定

次に、左側に作成された TutorialPlugin プロジェクトの中にある、「pom.xml」というファイルをダブルクリックしてください。そして、画面右下のあたりにある「pom.xml」というタブをクリックしてください。

まず、Java実行環境(JRE)の参照設定をします。
一番最後の行に </project> というタグがありますが、その1行上に、次の内容を挿入してください。
(なお、これは Java 8 を参照してビルドするための設定です。もし Java 7 を参照してビルドしたい場合は、sourceタグとtargetタグに書かれている 1.7 のところを 1.6 に変更してください。)

<build>
   <plugins>
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-compiler-plugin</artifactId>
       <configuration>
         <source>1.8</source>
         <target>1.8</target>
       </configuration>
     </plugin>
   </plugins>
 </build>

次に、BukkitリポジトリのURL設定を追加します。
下記の内容を追記してください。

<repositories>
   <repository>
     <id>bukkit-repo</id>
     <url>http://repo.bukkit.org/content/groups/public/</url>
   </repository>
 </repositories>

最後に、Bukkit API の参照設定を追加します。
下記の内容を追記してください。

<dependencies>
   <dependency>
     <groupId>org.bukkit</groupId>
     <artifactId>bukkit</artifactId>
     <version>1.7.9-R0.2</version>
   </dependency>
 </dependencies>

この設定では、Bukkit API 1.7.9-R0.2 が参照されます。
別のバージョンを参照したい場合は、versionタグの中を変更してください。
設定が可能なバージョン番号の一覧は、こちら を参照してください。

ここまで編集をすると、pom.xmlは次のようになっているはずです。確認してみてください。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>my.test.plugin</groupId>
  <artifactId>TutorialPlugin</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.78</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <repositories>
    <repository>
      <id>bukkit-repo</id>
      <url>http://repo.bukkit.org/content/groups/public/</url>
    </repository>
  </repositories>
  <dependencies>
    <dependency>
      <groupId>org.bukkit</groupId>
      <artifactId>bukkit</artifactId>
      <version>1.7.9-R0.2</version>
      <type>jar</type>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>

pom.xmlの編集が完了したら、Ctrl + S でファイルを保存してください。

次に、プロジェクトを右クリックして、Maven>プロジェクトの更新... と選択してください。

Mavenプロジェクトの更新ダイアログが開きます。そのまま OK を押してください。

Eclipseが、Bukkit API のダウンロードを開始します。
しばらく待つと、プロジェクトが更新され、「Maven依存関係」というところにダウンロードされたBukkit APIが表示されます。
また、JREシステム・ライブラリ のところも、指定したJavaビルドバージョンが反映されていることを確認して下さい。

訳者追記: ダウンロードされた bukkit-x.x.x-Rxx.jar を右クリックし、Maven > Javadoc のダウンロード を実行しておくとよいでしょう。

パッケージの作成

次に、作成したプラグインのプロジェクトに、パッケージを追加します。
「src/main/java」のところを右クリックして、新規>パッケージ と選択してください。

パッケージ名は、先ほど設定したグループIDに、プラグイン名を小文字に変換した名前を後ろに付けたものが望ましいです。
ここでは例として、パッケージIDに my.test.plugin、プラグイン名に TutorialPlugin を使っているので、my.test.plugin.tutorialplugin と設定します。

メインクラスの作成

次に、プラグインのメインクラスを作成します。
メインクラスは、JavaPlugin を継承する必要があります。
(逆に、メインクラス以外のクラスは、直接的にも間接的にも、JavaPlugin を継承しないようにしてください。CraftBukkit 1.7.2-R0.3 以降では、プラグインが正しく動作しなくなります。)
メインクラスは、プラグイン名と同じ名前にすることが望ましいです。

先ほど作成したパッケージを右クリックして、新規>クラス と選択してください。

名前の欄にクラス名を入力してください。
スーパークラスの欄に「org.bukkit.plugin.java.JavaPlugin」と入力してください。
(参照... ボタンを押して、開いたダイアログに「JavaPlugin」と入力して該当クラスを検索して選択しても構いません。)

完了 を押して、新規クラスが作成されたことを確認して下さい。ソースコードは次のようになっているはずです。

package my.test.plugin.tutorialplugin;
 
import org.bukkit.plugin.java.JavaPlugin;
 
public class TutorialPlugin extends JavaPlugin {
 
}

注意:メインクラスは、コンストラクタを実行したり、新しいインスタンスを作成したりしないでください。

plugin.ymlの作成

メインクラスが作成できたら、次は plugin.yml ファイルを作成します。
plugin.yml ファイルは、プラグインとしてBukkitに読み込みされるときに、プラグインの設定を記述しておくファイルです。
必須のファイルですから、必ず作成してください。

プロジェクトの src/main/resources を右クリックして、新規>ファイル を選択してください。

開いたダイアログの ファイル名 の欄で、「plugin.yml」と入力し、完了を押してください。

src/main/resources ディレクトリに、plugin.yml が作成されたことを確認してください。
作成された plugin.yml を、画面の右側へドラッグアンドドロップしてください。
画面右側で plugin.yml の編集画面が開きます。
plugin.yml に、下記の3行を書いてください。

name: (あなたのプラグイン名)
version: (あなたのプラグインのバージョン)
main: (作成したパッケージ名).(作成したメインクラス)

このチュートリアルでは、次のように作成します。

name: TutorialPlugin
version: 0.0.1
main: my.test.plugin.tutorialplugin.TutorialPlugin

メインクラスの設定は、大文字小文字が区別されるので、大文字小文字に注意して設定してください。

plugin.yml の詳細な内容一覧は、Developer/plugin.yml をご参照ください。

onEnable()メソッドとonDisable()メソッド

このメソッドは、プラグインが有効/無効になったときに、Bukkitから呼び出しされます。
デフォルトでは、プラグインは自動的に読み込まれたときに、イベントを登録やデバッグ出力を行うことが出来ます。
onEnable()は、Bukkitが起動するときに、プラグインが有効化されたときに呼び出されます。onDisable()は、Bukkitが停止するときに、プラグインが無効化されたときに呼び出されます。

onEnable()とonDisable()の基本

前のセクションで作成したメインクラスに、onEnable()とonDisable()のメソッドを作成します。

    package my.test.plugin.tutorialplugin;
 
    import org.bukkit.plugin.java.JavaPlugin;
 
    public class TutorialPlugin extends JavaPlugin {
 
        @Override
        public void onEnable() {
            // TODO ここに、プラグインが有効化された時の処理を実装してください。
        }
 
        @Override
        public void onDisable() {
            // TODO ここに、プラグインが無効化された時の処理を実装してください。
        }
    }

現時点では、このメソッドは何も行いません。

Loggerを利用したメッセージ出力

始めに、プラグインが実際に動作するかどうかを確認するため、シンプルなメッセージをサーバコンソールに表示させてみましょう。
ログを出力するには、getLogger()メソッドを実行してLoggerを取得し、そのinfoメソッドを呼び出します。

getLogger().info("onEnableメソッドが呼び出されたよ!!");

onDisable()メソッドについても同等の記述を行います。
メインクラスに、次のように実装してみてください。

    package my.test.plugin.tutorialplugin;
 
    import org.bukkit.plugin.java.JavaPlugin;
 
    public class TutorialPlugin extends JavaPlugin {
 
        @Override
        public void onEnable() {
            getLogger().info("onEnableメソッドが呼び出されたよ!!");
        }
 
        @Override
        public void onDisable() {
            getLogger().info("onDisableメソッドが呼び出されたよ!!");
        }
    }

ここまで作ったところで,プラグインのJarファイルの発行を行ってプラグインを作成し、実際にCraftBukkitのpluginsフォルダへ導入して、コンソールに設定したログメッセージが表示されることを確認してみてください。

Reloadを制御する

サーバーが終了処理をするときや、開始処理をするときに限らず、 サーバーが /reload コマンドにより、プラグインがdisableされenableされる動作について理解しておくことも重要です。
サーバーが開始したときについてだけ初期化処理を考慮することは危険です。
なぜなら、プレイヤーは既にオンライン状態で接続していますし、ワールドデータやチャンクは既にロードされていますし、他にもいろいろ想定していない違いがあります。

  • /reload が実行されると、プラグインとしては、onDisable() が実行されて、onEnable() が実行されます。
  • staticで保持していないデータは、全て失われます。
  • 上記に書いてあるように、プレイヤーやワールドやチャンクは既にロード済みの状態です。

リスナー

Developer/リスナーの実装と、Developer/イベント解説をご参照ください。

コマンド

onCommand()メソッド

さて、どのようにイベントを登録しいつ発生するかについて理解しました。 しかし、コマンドを利用して何かを起こしたい場合はどのようなデータ型を利用すればよいのでしょうか?それには、onCommand()メソッドを利用します。 このメソッドは、plugin.ymlに設定したプラグインのコマンドが実行されたときに呼び出されます。 今のところ、onCommand()はまだプログラムしていないので何も起こらないでしょう。

コマンド名は、Bukkitや他のプラグインが提供しているものや、 提供するであろうものとの重複を避け、 ユニークなものとなるように考慮して下さい。 具体的には、"give"コマンドは既にいくつかのプラグインで利用されています。 もし独自に"give"コマンドを実装した場合は、 "give"コマンドを実装している他のプラグインとの互換性は無くなります。

onCommand()は、常にboolean型の値としてtrue,falseのどちらかを戻り値として返さねばなりません。 trueを返した場合は、情報表示のためのイベントは発生しません。 falseを返した場合は、コマンドを実行したプレイヤーに、コマンドの利用方法を通知するメッセージ(plugin.yml の usage: のところに設定したメッセージ)を表示します。

onCommand()を利用する際は、4つのパラメータを登録しなければなりません。

  • CommandSender sender - コマンドの発行元
  • Command cmd - 実行されたコマンドの内容
  • String commandLabel - 利用されたコマンドエイリアス
  • String[] args - コマンドの引数を格納した配列(例:/hello abc defコマンドが入力された場合の内容は、args[0]がabc、args[1]がdefとなる)

コマンドの設定

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
        // プレイヤーが「/basic」コマンドを投入した際の処理...
	if(cmd.getName().equalsIgnoreCase("basic")){ 
		// 何かの処理
		return true;
                // コマンドが実行された場合は、trueを返して当メソッドを抜ける。
	}
	return false; 
        // コマンドが実行されなかった場合は、falseを返して当メソッドを抜ける。
}

onCommand()をコーディングする際は、上記の例のように、メソッドの最終ステップにfalseをreturnする行を記述するのが良い方法です。
falseが返る事で、plugin.yml(下記参照)内に記述されたメッセージが表示され、onCommand()処理が正しく動作しなかった事をメッセージから検知できるため、動作確認の助けになるからです。
逆に、メソッドの最後でtrueを返す処理構造にしてしまった場合は、onCommand()内の各処理に対して、処理結果チェック処理と結果が不正である場合にfalseを返すような、同じ処理を何度も記述する必要が出てきますので、大変な無駄となります。

なお、コード「.equalsIgnoreCase("basic")」のパラメータ「basic」には、大文字小文字の区別はありません。

plugin.ymlへのコマンドの追加

コマンドを用意する際は、plugin.ymlファイルにも定義を追加する必要があります。plugin.ymlの最後に次の行を追加します。

commands:
   basic:
      description: This is a demo command.
      usage: /<command> [player]
      permission: tutorialplugin.basic
      permission-message: You don't have <permission>
  • basic - コマンド名。
  • description - コマンドの説明文。
  • usage - コマンドの使い方。onCommand() でfalseを返したときに、コマンド実行ユーザに向けて表示されるメッセージの内容。あなたの作ろうとしているコマンドの使い方を、わかりやすく説明してください。
  • permission - コマンドの実行権限。このコマンドの実行に必要なパーミッションノードを設定します。あなたのプラグイン名と、コマンド名を、ピリオド(.)でつなげたパーミッションノードを設定することを推奨します(例:myplugin.test)。
  • permission-message - 上で設定したコマンド実行権限を持たないユーザがコマンドを実行した場合に、実行権限が無いことを同ユーザに知らせるメッセージ。

なお、usage の欄では <command>、permission-message の欄では <permission> というキーワードをそのまま指定できます。それぞれ、設定したコマンド名と、設定したパーミッションノードに置き換えられます。
詳しい書き方は、plugin.ymlの書き方の、コマンドのオプション設定の節 を参照してください。

Note: ymlファイルには、インデントに2個以上の半角スペースを記述する必要があります。タブ文字は構文エラーとなるため利用できません。

コンソールコマンドとプレイヤーコマンド

察しの良い人は、CommandSender sender パラメタに着目しているかもしれません。 CommandSenderクラスは、プラグイン開発者に2つの点で有用な、Bukkitが提供するインタフェースです。

CommandSenderインタフェースの実装: Player と ConsoleCommandSender (正確には Playerもインタフェース)です。 プラグインを作る際は、次の2点に注意しながら作る事が非常に良い方法です。

  • プラグインが動作している事をサーバコンソールで確認しながら作る事
  • ログインプレイヤー向けのコマンドが、実際にログインしているプレイヤーのみ実行できる事

プラグインは、senderがプレイヤーではない場合(例えばコンソールからプラグインのコマンドが投入された場合)であっても、サーバコンソールには明解でシンプルな動作結果を返します。(例えば天気を変えるコマンドの実行結果は、ゲーム内では無くサーバコンソール上のメッセージから確認すれば間違いありません)

記述例:

@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
	if (cmd.getName().equalsIgnoreCase("basic")) { 
		// プレイヤーが /basic を実行すると、この部分の処理が実行されます...
		return true;
	} else if (cmd.getName().equalsIgnoreCase("basic2")) {
                // プレイヤーが /basic2 を実行すると、この部分の処理が実行されます...
		if (!(sender instanceof Player)) {
			sender.sendMessage("このコマンドはゲーム内から実行してください。");
		} else {
			Player player = (Player) sender;
			// コマンドの実行処理...
		}
		return true;
	}
	return false;
}

この例では、basic コマンドはログインプレイヤーとサーバコンソールのどちらからでも実行できます。しかし、basic2 コマンドは、ログインプレイヤーしか実行できません。 一般的に、コマンドはプレイヤーとサーバコンソールの両者に実行を許可しますが、投入されたコマンドの実行可否をチェックする必要があるコマンドについては、ログインプレイヤー向けのコマンドとして実装されるべきでしょう。

つまり、プレイヤーに依存する処理(テレポートコマンドやアイテムを与えるコマンド等は、対象となるプレイヤーが必要です)を行うコマンドが、プレイヤーコマンドとして実装されるべきものと言えます。

凝ったコマンドを作りたい場合、コマンドのパラメタ(上記例では argsパラメタ)を利用して独自の処理実装を行うことができます。例えば、プレイヤー名を指定したテレポートコマンドのみが、サーバコンソールから実行できる実装にする等が考えられます。

訳者補記: コマンド実装方法には、プレイヤー向け・サーバコンソール向けの2種類があり、それぞれの区別はどのような考え方で行うべきかについて説明しています。

CommandExecutorのクラス分割

上記の例では、onCommand()メソッドをプラグインのメインクラスに記述していました。小さなプラグインでは良い方法ですが、大きなプラグインに拡張していくのであれば、適切なクラスを作成してそちらに配置するべきです。幸い、難しい事ではありません。

  • プラグインのpackage内に、MyPluginCommandExecutor のような名前で新規のコマンド実行クラスを作成する(当然、MyPluginはあなたのプラグイン名に合わせて変えましょう)。
  • MyPluginCommandExecutorに、BukkitのCommandExecutorインタフェースを実装(implements)させる。
  • プラグインのonEnable()メソッドの処理で、MyPluginCommandExecutorクラスのインスタンスを生成する。
  • MyPluginCommandExecutorインスタンスをパラメタとして、処理getCommand("basic").setExecutor(myExecutor);を実行させる。
Note: "basic"は実行されたコマンドであり、myExecutorはMyPluginCommandExecutorインスタンスです。

とっても良い具体例
MyPlugin.java (プラグインのメインクラス)

    private static Plugin instance;
 
    @Override
    public void onEnable() {
    	instance = this;
    	// ...
 
    	// plugin.yml に basic というコマンドを定義していないと、
            //実行した時にNullPointerExceptionが発生します。注意してください。
    	getCommand("basic").setExecutor(new MyPluginCommandExecutor());
 
    	// ...
    }
 
    public static Plugin getInstance() {
    	return instance;
    }</pre>
''MyPluginCommandExecutor.java''
<pre>    public class MyPluginCommandExecutor implements CommandExecutor {
    	private Plugin instance = MyPlugin.getInstance(); 
            // メインクラスの参照です。処理の中でメインクラスのメソッドを利用しない場合は、省略して構いません。
 
    	@Override
    	public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
    		// コマンドの実行内容...
    	}
    }

どのように、メインプラグインのインスタンスがMyPluginCommandExecutorのコンストラクタを実行するかに注目しましょう。

この節で紹介した方法により、メインのonCommand()メソッドが巨大で複雑になったとしても簡単に整理する事ができ、結果として、プラグインのメインクラスを複雑化させずに、処理分割する事ができます。

Note: プラグインが複数のコマンドを持つ場合、個々のコマンドに対応するCommandExecutorをコーディングする必要があります。
訳者追記: 積極的に、大きな処理は小さく分割していきましょう。そのための仕組みをBukkitが用意しています,という事を教示しています。

堅牢なonCommandの記述

onCommand()メソッドを記述する際、パラメタの利用に関して、決め付けて掛かってはいけない事がありますので忘れないでください。
これらに留意した処理を記述する事で、堅牢なonCommand()をコーディングする事ができます。

senderの内部データ型をチェックする

senderの内部データがPlayer型であるとは限らない事を念頭において、チェックを行って下さい。
処理例:

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
    if ((sender instanceof Player)) {
        Player player = (Player) sender;
        // doSomething
    } else {
        sender.sendMessage(ChatColor.RED + "ゲーム内から実行してください!");
        return false;
    }
    return false;
}

コマンドのパラメタ長をチェックする

senderインスタンスは、妥当なパラメータを持っているとは限りません。パラメータ長をチェックして下さい。
処理例:

    public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
    	if (args.length > 4) {
               sender.sendMessage(ChatColor.RED + "パラメタが多すぎます!");
               return false;
            } 
            if (args.length < 2) {
               sender.sendMessage(ChatColor.RED + "パラメタが足りません!");
               return false;
            }
    }

プレイヤーがオンラインである事を確認する

特定のプレイヤーのPlayerインスタンスを利用したい場合、必ずそのプレイヤーがオンラインである必要があります。
オンラインであるかどうかをチェックして下さい。
処理例:

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
    Player other = getPlayer(args[0]);
    if (other == null) {
        sender.sendMessage(ChatColor.RED + args[0] + "さんはオフラインです!");
        return false;
    }
    return false;
}
 
private Player getPlayer(String name) {
    for ( Player player : Bukkit.getOnlinePlayers() ) {
        if ( player.getName().equals(name) ) {
            return player;
        }
    }
    return null;
}

プラグインの設定の保存

Developer/コンフィグの利用をご参照ください。

権限

Developer/パーミッションをご参照ください。

スケジューリングタスクとバックグラウンドタスク

現在、Minecraftサーバはゲームロジックのほとんどがシングルスレッドで稼動しています。
このため、発生する個々の処理はごく小さいものにする必要があります。
プラグイン中に複雑なコードが存在して、それが適切に処理されないような場合は、ゲームロジックに多大なラグや処理遅延を発生させる原因となります。

幸運なことに、Bukkitはプラグインに対してスケジューリングのためのコーディング方法を提供しています。
どこかのタイミングで一度だけRunnableタスクを実行したり、小さなタスクに分けた定期的な繰り返しであったり、新規に独立したスレッドを派生させたり・・・
といった複数の方法で、長大なタスクをゲームロジックと並行で処理させる事ができます。

詳しくは、Developer/スケジューラのプログラミング中の、同期タスクと非同期タスクのスケジューリング方法に関する解説を参考にして下さい。

ブロックの操作

ブロックを生成する簡単な方法は、既存のブロックを取得して変更する事です。
例えば、あなたの5ブロック上方にあるブロックを変更したい場合、まずはそのブロックを取得した上で、変更を加えることになります。
PlayerMoveイベントの処理中でこれを行う例を示します。

    public void onPlayerMove(PlayerMoveEvent evt) {
        // プレイヤーの位置を取得します。
        Location loc = event.getPlayer().getLocation();
        // 位置のY座標を+5します。位置情報を変更しているだけで、実際にプレイヤーの位置が移動するわけではないことに注意してください。
        loc.setY(loc.getY() + 5);
        // 指定位置のブロックを取得します。
        Block b = loc.getBlock();
        // ブロックの種類に石(STONE)を設定します。
        b.setType(Material.STONE);
    }

このコードの処理は、プレイヤーが移動する(=PlayerMoveイベントが発生する)度に、プレイヤーの5ブロック上方のブロックがStoneに変化する動作として見えるでしょう。
ブロック取得までの流れは・・・

  1. 取得したプレイヤーの位置から、ワールドを取得する
    1. 位置のY座標を+5する
      1. loc.getBlockAt(loc);で、指定位置に存在するブロックを得る

となります。

位置(loc)中のブロックインスタンス(b)に対して、Materialを変えたり、ブロックデータ自体を変更する事もできます。 詳しくは、JavaDocを参照してください。

建物の生成や、一定のアルゴリズムに従ったブロック生成処理などを行う一例を示します。
例えば、固体ブロックによる立方体を生成する処理は、3階層にネストしたforループ処理によって記述できます。

 public void generateCube(Location loc, int length){
        // 与えられたLocationから、立方体の端の座標を取得します。
        // getN()メソッドを使うと intへキャストする必要がありますが、
        // getBlockN()メソッドを使えばそのままintで座標を取得できます。
        int x1 = loc.getBlockX(); 
        int y1 = loc.getBlockY();
        int z1 = loc.getBlockZ();
 
        // 一辺の長さを足すことで、立方体の反対側の座標を計算します。
        int x2 = x1 + length;
        int y2 = y1 + length;
        int z2 = z1 + length;
 
        World world = loc.getWorld();
 
        // x座標方向のループ
        for (int xPoint = x1; xPoint <= x2; xPoint++) { 
            // y座標方向のループ
            for (int yPoint = y1; yPoint <= y2; yPoint++) {
                // z座標方向のループ
                for (int zPoint = z1; zPoint <= z2; zPoint++) {
                    // ループで処理する座標のブロックを取得します。
                    Block currentBlock = world.getBlockAt(xPoint, yPoint, zPoint);
                    // ダイアモンドブロックに設定します!
                    currentBlock.setType(Material.DIAMOND_BLOCK);
                }
            }
        }
    }

このメソッドは、辺の長さと開始点の指定を受けて、任意のサイズと位置を持つ直方体を生成する処理です。
ブロックの削除処理の場合も、このロジックを真似て同様のアルゴリズムで実装する事ができます。
ただし、セットするブロックの種類はMaterial.AIRになりますね。

プレイヤーインベントリの操作

この節では、プレイヤーのインベントリ操作を特に扱っていますが、チェストのインベントリの操作にも適用できますので、必要なら応用して下さい(゚∀゚)
インベントリ操作のシンプルな例:

@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
    Player player = event.getPlayer(); // Joinしたプレイヤー
    PlayerInventory inventory = player.getInventory(); // プレイヤーのインベントリ
    ItemStack diamondstack = new ItemStack(Material.DIAMOND, 64); // 山積みのダイヤモンド!
 
    if (inventory.contains(diamondstack)) {
        inventory.addItem(diamondstack); // プレイヤーインベントリに山積みのダイヤモンドを加える
        player.sendMessage(ChatColor.GOLD + "よく来たな!もっとダイヤモンドをくれてやろう、このとんでもない成金め!!");
    }
}

さて、onPlayerJoin()メソッドの先頭で,player, inventorydiamondstack変数を用意して下さい。
inventoryはプレイヤーのインベントリで、diamondstackは(アイテムとしての)64個のダイヤモンドです。

次に、プレイヤーのインベントリがダイヤモンドを含んでいるかをチェックします。
プレイヤーがダイヤモンドをインベントリに所持している場合、inventory.addItem(diamondstack)処理にて 同プレイヤーのインベントリに別のスタックを与え、黄金色のメッセージを送信します。
インベントリ操作は全然難しくありません。

ダイヤモンドのスタックを削除したい場合でも単純に、inventory.addItem(diamondstack)inventory.remove(diamondstack)に置き換えるだけです。
(メッセージにも少しイタズラしておきましょう)

アイテムの操作

アイテムはスタックという単位で操作します。
スタックデータに対する全ての操作は、ItemStackクラスの仕様を参照して下さい。

エンチャント

アイテムにエンチャントを付与するには、ItemStackクラスのaddEnchantment(Enchantment enchant, int level)メソッドを使用します。

addEnchantment()メソッドでは、元々エンチャントが出来ないアイテムに対して、独自にエンチャントを付与する処理は書けません。
もし、通常ありえないエンチャントを設定したい場合は、addEnchantment()メソッドの代わりにaddUnsafeEnchantment()メソッドを使ってください。

    // 木の棒のインスタンスを生成する
    ItemStack myItem = new ItemStack(Material.STICK);
 
    // 木の棒にFireAspectレベル100を付与する
    //(ただしFireAspectレベル100は通常存在しないので、付与は成功しない) 
    myItem.addEnchantment(Enchantment.FIRE_ASPECT, 100);

ItemMeta

ItemMetaを使用すると、アイテムの表示名を変更することが可能です。

ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  // アイテムを生成
ItemMeta im = myItem.getItemMeta(); // ItemMetaを取得
im.setDisplayName("すんごい剣"); // アイテム表示名を設定
myItem.setItemMeta(im); // ItemMetaをアイテムに再設定</pre>
他にも、loreを設定することができます。loreとは、アイテムにカーソルを当てた時に表示される、小さい注釈のような部分のことです。<br />
<pre>List&lt;String> lores = new ArrayList<String>();
lores.add("注釈のサンプルです。", "ここは2行目になります。");
 
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  // アイテムを生成
ItemMeta im = myItem.getItemMeta(); // ItemMetaを取得
im.setDisplayName("すんごい剣"); // アイテム表示名を設定
im.setLore(lores); // 注釈を設定
myItem.setItemMeta(im); // ItemMetaをアイテムに再設定

Metadata

Bukkit では、プラグインの開発をより簡単にするため、Playerクラス、Entityクラス、Worldクラスに紐づく追加データをMetadataという形式で管理できるようになっています。 今までは、それぞれのプラグイン内で、Player、Entity、World などをキーとしたHashMap型の変数内で管理していたと思いますが、それをMetadataで置き換えすることができます。 Metadataのデータは、全てMetadatableのメンバーで構成されます(javadocも参照してください)。 動作は非常に単純です。 Metadatableクラスは、それぞれのインスタンスが自分のMetadataのHashMapを持っています。 つまり例えば、経済プラグインを作る場合、HashMap<Player, Double> のようなデータをプラグイン内で持つ必要はありません。 プレイヤーにMetadataを直接設定すればよいのです!

Metadataを使うメリット

  • Metadataは全てBukkit側で管理されます。プラグイン側で管理する必要がありません。
  • プラグイン間で共通にアクセスできるので、データの共有に使用できます。

Metadataを使うデメリット

  • データの取得・設定を行うときに、ひと手間が必要になります。
  • Bukkitが停止すると、全てのMetadataが消えます。

Metadataの使い方

 /**
     * PlayerにMetadataを設定するサンプルメソッドです。
     * @param player 対象プレイヤー
     * @param key Metadataのキー名
     * @param value Metadataの値
     * @param plugin プラグインクラス
     */
    public void setMetadata(Player player, String key, Object value, Plugin plugin) {
        player.setMetadata(key, new FixedMetadataValue(plugin, value));
    }
 
    /**
     * PlayerからMetadataを取得するサンプルメソッドです。
     * @param player 対象プレイヤー
     * @param key Metadataのキー名
     * @param plugin プラグインクラス
     * @return Metadataの値
     */
    public Object getMetadata(Player player, String key, Plugin plugin) {
        List<MetadataValue> values = player.getMetadata(key);
        for (MetadataValue value : values) {
            if (value.getOwningPlugin().getDescription().getName()
                    .equals(plugin.getDescription().getName())) {
                return value.value();
            }
        }
        return null;
    }

Note: もしあなたが、必ず boolean、int、String としてMetadataの値を取得したいのであれば、asBoolean()、asInt()、asString() メソッドを使うことで、キャストせずに直接取得が可能です。

データベース

Developer/データベース利用をご参照ください。

プラグインの配布

プラグインを実装し終わったら、Mavenを使ってビルドして、リリース用のJarファイルを作成してみましょう。

プロジェクトを右クリックして、実行>Maven install と選択してください。

Eclipseのコンソールに、ビルド情報が流れます。
ビルドがうまくいけば、コンソールに「BUILD SUCCESSFUL」と表示されてビルドが終了します。
もしビルドが失敗したなら、エラー情報を元に、エラーの解決を試みてください。

このチュートリアルで紹介したJDKが同梱のPleiadesを利用していない場合、MavenがJDKを見つけられずにエラーになっていることが多いです。
その場合は、Eclipseの設定を開いて、正しいJDKを選択しなおしてください。
Eclipseの設定は、「ウィンドウ」メニュー>設定 を選択し、開いたダイアログで、Java>インストール済みのJRE を選択します。
ここで、正しいバージョンのJDKが選択されていることを確認してください。


ビルドがうまくいった場合、プロジェクトのフォルダの中に target フォルダが作成されており、そのフォルダの中にビルドされたjarファイルがあります。

プラグインのコードとplugin.ymlに不備が無ければ、エクスポートしたJarファイルはすぐにBukkitプラグインとして動作します。Jarファイルを、Bukkitサーバの"plugins"フォルダの中に配置し、Bukkitサーバを起動し、プラグインの動作確認をしてみましょう。なお、ローカルマシン上で起動するBukkitサーバへは、Minecraftクライアントのマルチプレイヤーサーバの接続先IPアドレスに"localhost"を指定して接続する事でログインできます。

もしプラグインが上手く動かず、それがどうしても自分で解決できない場合は、当Wikiやその原文をもう一度よく読み、それでも駄目ならBukkitプラグイン開発者フォーラム(英語圏), bukkitdev Bukkit公式サイトの開発者向けIRCチャンネル(英語圏)マインクラフト非公式日本ユーザフォーラム(日本語圏),をたずねてみて下さい。有用なプラグインが作れたら、dev.bukkitに登録し、プラグインを広く公開する事を検討してみて下さい。他のBukkitユーザ・開発者に貢献する事ができます。

ヒントとノウハウ

CraftBukkit APIは、すばらしい機能をたくさん提供しています。 下記に、面白い効果を実現するコードを示します。

プレイヤーに着火する

下記は、指定されたプレイヤーに着火するサンプルです。例えば /ignite Notch と実行すると、Notchが燃えます!

@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
    // equals() の代わりに equalsIgnoreCase() を使うと、大文字小文字に関係なく、
    // 文字列の一致を確認できます。"ignite"でも"IgNiTe"でも指定可能になります。
    if (cmd.getName().equalsIgnoreCase("ignite")) {
        // コマンドのパラメータに、燃やすプレイヤーが指定されているかどうかを
        // 確認します。
        if (args.length != 1) {
            // onCommandでfalseを戻すと、plugin.ymlのusageに設定したメッセージを
            // コマンド実行者の画面に表示します。
            return false;
        }
 
        // 燃やすプレイヤーを取得します。
        Player target = getPlayer(args[0]);
 
        // 対象プレイヤーが、オンラインかどうかを確認します。
        if (target == null) {
            sender.sendMessage(args[0] + " というプレイヤーが見つかりません!");
            return true;
        }
 
        // 対象プレイヤーを、1000tick(=50秒) の間、燃えるようにします。
        target.setFireTicks(1000);
        return true;
    }
    return false;
}
 
private Player getPlayer(String name) {
    for ( Player player : Bukkit.getOnlinePlayers() ) {
        if ( player.getName().equals(name) ) {
            return player;
        }
    }
    return null;
}

プレイヤーを殺す

同じ要領で、プレイヤーを殺害するコマンドの例を紹介します。 onCommand()メソッドに記述します。

    public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
        if(cmd.getName().equalsIgnoreCase("KillPlayer")){
            Player target = getPlayer(args[0]);
            // 対象プレイヤーがオンラインかどうかを確認します。
            if (target == null) {
                sender.sendMessage(args[0] + " というプレイヤーは見つかりません!");
                return true;
            }
            // 対象に1000ダメージを与える
            target.damage(1000);
            return true;
        }
        return false;
    }
 
    private Player getPlayer(String name) {
        for ( Player player : Bukkit.getOnlinePlayers() ) {
            if ( player.getName().equals(name) ) {
                return player;
            }
        }
        return null;
    }

上記の拡張版として、プレイヤーを爆死させる処理を下記に示します。

    float explosionPower = 4F; //This is the explosion power - TNT explosions are 4F by default
    Player target = getPlayer(args[0]);
    target.getWorld().createExplosion(target.getLocation(), explosionPower);
    target.damage(1000);

爆発を起こす

このコードは、TNTの爆発と同様の音とヴィジュアルを再現します。 これは、TNTの爆発効果は無効化しつつ、音とヴィジュアル効果を発生させる処理に転用できます。

@EventHandler
public void onExplosionPrime(ExplosionPrimeEvent event) {	
    Entity entity = event.getEntity();
 
    // このイベントは、点火されたTNTにより発生したのかどうかを確認します。
    // (つまり、TNTの爆発はこれで無効化されますが、クリーパーの爆発は無効化されません)
    if (entity instanceof TNTPrimed) {
        event.setCancelled(true); // イベントをキャンセルして、爆発を無かったことにする
        entity.getWorld().createExplosion(entity.getLocation(), 0); // 偽物の爆発を発生させる
    }
}

プレイヤーを非表示にする

これは指定したプレイヤーから自分を非表示にするサンプルです。 指定したプレイヤー以外のプレイヤーからは、自分が見えたままになっています。

    public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        if(cmd.getName().equalsIgnoreCase("HideMe") && args.length == 1) {
 
            // コマンド実行者がプレイヤーかどうかを確認します。
            if (!(sender instanceof Player)) {
                sender.sendMessage("このコマンドはゲーム内から実行してください!");
                return true;
            }
            // sender instanceof Player の検査が終わっているので、Playerクラスへ安全にキャストできます。
            Player s = (Player) sender;
 
            // 指定されたプレイヤーを取得します。
            // 指定されたプレイヤーがサーバーに接続していない場合、targetはnullになります。
            Player target = getPlayer(args[0]);
            if (target == null) {
                sender.sendMessage("Player " + args[0] + " というプレイヤーは見つかりません!");
                return true;
            }
            // プレイヤー "s" を、指定したプレイヤー "target" から、非表示に設定します。
            target.hidePlayer(s);
            return true;
        }
        return false;
    }
 
    private Player getPlayer(String name) {
        for ( Player player : Bukkit.getOnlinePlayers() ) {
            if ( player.getName().equals(name) ) {
                return player;
            }
        }
        return null;
    }

クリックした場所に雷を落とす

下記のサンプルは、釣竿を持ってクリックしたときに、クリックした場所を取得して、その場所に雷を落とします。

@EventHandler
public void onPlayerInteractBlock(PlayerInteractEvent event) {
 
    Player player = event.getPlayer();
 
    if (player.getItemInHand().getType() == Material.FISHING_ROD) {
        // プレイヤーが見ている場所に雷をおとします。
 
        Block target = getTargetBlock(player);
        if (target != null) {
            target.getWorld().strikeLightning(target.getLocation());
        }
    }
}
 
private Block getTargetBlock(Player player) {
 
    // 視線上のブロックを100ブロック先まで取得
    BlockIterator it = new BlockIterator(player, 100);
 
    // 手前側から検証を行う。
    // Blockが取得できた時点でreturnして終了する。
    while ( it.hasNext() ) {
 
        Block block = it.next();
 
        if ( block.getType() != Material.AIR ) {
            // ブロックが見つかった
            return block;
        }
    }
 
    // 最後までブロックがみつからなかった
    return null;
}

リクエストに応じて書かれた記事

英Wikiでは、こちらをご参照ください。

[1]

非公式日本フォーラムでは、こちらをご参照ください。

[2]

もし、欲しい情報が見つからないなら、質問の仕方についてのトピックをよく読んでから、上記トピックへ質問をしてみてください。

プラグインのサンプル兼雛形

Developer/サンプル集をご参照ください。