提供: Minecraft Modding Wiki
2012年6月16日 (土) 17:24時点におけるUdonya (トーク | 投稿記録)による版 (CommandExecutorのクラス分割)
移動先: 案内検索

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

目次

Plugin用プロジェクトを始めるために

プロジェクトの作成

始める前にEclipseのワークスペースとファイルの設定を行う必要があります。
Eclipseを起動し、ファイル>新規>Java プロジェクトと選択して下さい。

NewJavaProject.png

プロジェクト名を入力し、新規プロジェクトウィザード画面の指示に従って、プロジェクトを作成してください。

Bukkit APIの参照

開発を始める前にbukkit APIライブラリをプロジェクトに追加する必要があります。 使用したい他のAPIも同じように追加することが可能です。

最新のBukkit APIはここからダウンロードが可能です。 Bukkit API - Development Snapshot


画面の左手にあるパッケージエクスプローラの(先ほど名前を付けた)プロジェクトを右クリックし、プロパティーを選択します。
開いた画面のJavaビルド・パスを選択し、ライブラリータブの中から、外部 Jar 追加ボタンを押して、ダウンロードしたBukkit APIを指定します。

BuildPathPic.png

BukkitのJavadocの利用方法

Eclipseを用いたJavaプログラミングの経験がある方なら、
Eclipseのエディタ上でクラスやメソッドにマウスカーソルを重ねた時に、
クラスやメソッドに関するドキュメントがポップアップで表示される事をご存知でしょう。
これは、Oracleのウェブサイトから得る事ができるJavadocの内容が表示されています。

Bukkitもまた、BukkitのAPIが提供するクラスやメソッドの各々のコードに同種のドキュメントを含んでおり、ポップアップで表示させる事ができます。
Eclipse上で、Bukkitのクラスやメソッドにマウスを重ねたタイミングでポップアップを表示できるようにするためには、下記の手順を行います。

  1. プロジェクトエクスプローラ上で、Bukkitのjarファイルを右クリックして、メニューを開く。
  2. "プロパティ"を選択し、表示されるポップアップ左側の"Javadoc ロケーション"項目を選択する。
  3. "Javadoc URL"の下部にあるテキストボックスに、"http://jd.bukkit.org/apidocs/" (ダブルクォートは除く)を貼り付ける。
  4. "検証"ボタンを押下し、URLがJavadocとして正しく識別される事をチェックしてから、OKボタンを押す。
次ような画面になります:
Bukkitjavadocs.png

Plugin開発の開始

MakePackage.png

onEnable() and onDisable()

このファンクションは、プラグインが有効/無効になったときに呼ばれます。
デフォルトでは、プラグインは自動的に読み込まれたときに、イベントを登録やデバッグ出力を行うことが出来ます。
onEnable()は、プラグインがBukkitから読み込まれるときに最初に呼ばれ、プラグインを実行するために必須です。

onEnable()とonDisable()の基本

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

public void onEnable(){ 
 
}
 
public void onDisable(){ 
 
}


現時点では、このメソッドは何も行いません。また、エラーが発生する事にも気付くでしょう。このエラーは、メインクラスが、プラグインの機能を継承(extends)する必要がある事を示しています。当クラスの定義文を、下記のように変更しましょう。

これを・・・

Class <classname> {}

このように変更する。

Class <classname> extends JavaPlugin {}

すると、前述の追加コードが赤の下線でハイライト表示され、何かが間違っていることを通知してくるはずです。このハイライト部にマウスを重ねると、下記のようなポップアップが表示されるので、「'JavaPlugin'をインポートします(org.bukkit.plugin.java)」を選択します。

To need import package.png

もしくは、

import org.bukkit.plugin.java.JavaPlugin;

をコード上部の定義部に記述する事でも同様の事が行えます。

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

始めに、プラグインが実際に動作するかどうかを確認するため、シンプルなメッセージをサーバコンソールに表示させてみましょう。この処理として、ログ出力機能(Logger)をメインクラスに定義して初期化します。

Logger log = this.getLogger();

その後、onEnable()メソッドに、プラグインが動作している事を通知するメッセージを出力する処理を記述します。

log.info("Your plugin has been enabled.");

onDisable()メソッドについても同等の記述を行います。ここまでのコーディングによって、メインクラスは下記の様な内容になっているはずです。

package me.<yourname>.<pluginname>;
 
import java.util.logging.Logger;
import org.bukkit.plugin.java.JavaPlugin;
 
public class <classname> extends JavaPlugin {
 
	Logger log;
 
	public void onEnable(){
		log = this.getLogger();
		log.info("Your plugin has been enabled!");
	}
 
	public void onDisable(){
		log.info("Your plugin has been disabled.");
	}
}

イベントとリスナ

Minecraft1.1向けのBukkit API以降、新しいイベントの仕組みが提供されています。
この新しい仕組みは、シンプルでかつ、汎用性・高速性・可読性等に優れています。

チュートリアル

当項目の、翻訳元記事で紹介されている動画を参照して下さい。
http://wiki.bukkit.org/Introduction_to_the_New_Event_System#Video_Tutorial

基礎

PlayerLoginEventについて説明します。
このイベントは最低限動作するための処理のみを記述するよう心がけ、
極力シンプルに保ちつつ編集していきましょう。

イベントリスナ

まず、イベントを待ち受ける処理(Event Listenerと呼ばれます)が必要です:

public void onPlayerLogin(PlayerLoginEvent event) {
    // Your code here...
}

次に、発生するイベント(Event Handlerと呼ばれます)が必要になります。

イベントハンドラ

イベントハンドラはイベントリスナに対する注釈であり、次のような書き方をします:

@EventHandler // EventPriority.NORMAL がデフォルト値

この記述は、メソッドがイベント優先度がNORMALのイベントハンドラである事を指定します。

イベントハンドラには優先度を指定でき、次のような書き方をします:

@EventHandler(priority = EventPriority.HIGHEST) // 高優先度のイベントとする
@EventHandler(priority = EventPriority.LOW) // 低優先度のイベントとする

以上をまとめると、次のようになります:

@EventHandler
public void onPlayerLogin(PlayerLoginEvent event) {
    // Your code here...
}

リスナの追加

"PlayerListener"として拡張する場合は、"Listener"インタフェースを実装する必要があります。
この時点では、次のような形のメソッドになりそうです:

public class myPlayerListener implements Listener {
    @EventHandler
    public void onPlayerLogin(PlayerLoginEvent event) {
        // Your code here...
    }
}

リスナ内部のメソッドは任意の名前を付けて呼び出する事が可能であり、
"onPlayerLogin()"のような、メソッドの名称が重要ではない事に言及しておきましょう。
「Bukkitは、どのEventを識別可能でどうやってListenするのか?」と不思議に思うかもしれませんが・・・
Bukkitは、あなたが指定したイベントからそれら(識別可能なEventと、そのListenの方法)を読み込みます。
上記の例で言えば、PlayerLoginEventから読み込みます。

Note: 特定のイベントまたはBukkitが登録しないイベントに関しては、手動で指定しなければなりません。(訳が不適切か)

イベントハンドラの設定

イベントハンドラには、様々な内容を指定可能です。
現時点では次のような指定が行えます:

項目名 デフォルト値 概要 指定できる値
EventPriority priority EventPriority.NORMAL リスナに指定する優先度。
  • EventPriority.MONITOR
  • EventPriority.HIGHEST
  • EventPriority.HIGH
  • EventPriority.NORMAL
  • EventPriority.LOW
  • EventPriority.LOWEST
boolean ignoreCancelled false この値にTrueを指定したメソッド場合は、イベントがキャンセルされた際にイベントを受け付けない。
  • true
  • false

イベントの登録

イベントを記述するクラスは、
Listenerインタフェースを実装(implements)し、かつイベントハンドラを含んでいなければなりません。

import org.bukkit.event.Listener;

public class LoginListener implements Listener {
}

イベントの登録処理は、PluginManagerインスタンスのregisterEventsメソッドとして、プラグインとリスナに記述します。

getServer().getPluginManager().registerEvents(Listener, Plugin);

リスナの例

このリスナは、2つのイベントハンドラを含んでいます。
1つは高優先度のもの、もうひとつは通常の優先度のものです。

import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerLoginEvent;
 
public class LoginListener implements Listener {
    @EventHandler
    public void normalLogin(PlayerLoginEvent event) {
        // 何かの処理
    }
 
    @EventHandler(priority = EventPriority.HIGH)
    public void highLogin(PlayerLoginEvent event) {
        // 何かの処理
    }
}

プラグインへのイベントの登録

イベントの登録処理は、リスナとプラグインが必要です。
丁度よい事に、既にLoginListenerを作成していますので、これを利用してLoginPluginを作りましょう!

import org.bukkit.plugin.java.JavaPlugin;

public class LoginPlugin extends JavaPlugin {
    public void onEnable() {
        getServer().getPluginManager().registerEvents(new LoginListener(), this);
    }
}

プラグインへのイベントのリスナとしての登録

メインとなるクラスもイベントを持つ事ができます。
例:

import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerLoginEvent;
 
public class LoginPlugin extends JavaPlugin implements Listener {
    public void onEnable() {
        getServer().getPluginManager().registerEvents(this, this);
    }
 
    @EventHandler
    public void normalLogin(PlayerLoginEvent event) {
        // 何かの処理
    }
}

リスナへのイベントの登録

イベントの登録には複数の方法があります。リスナクラスでイベントを登録する例を記述します。

import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerLoginEvent;
 
public class LoginListener implements Listener {
    public LoginListener(LoginPlugin plugin) {
        plugin.getServer().getPluginManager().registerEvents(this, plugin);
    }
 
    @EventHandler
    public void normalLogin(PlayerLoginEvent event) {
        // Some code here
    }
 
    @EventHandler(priority = EventPriority.HIGH)
    public void highLogin(PlayerLoginEvent event) {
        // Some code here
    }
}

LoginPluginは次のようになります:

import org.bukkit.plugin.java.JavaPlugin;
 
public class LoginPlugin extends JavaPlugin {
    public void onEnable() {
        new LoginListener(this);
    }
}

イベントの自作

Bukkit自体が利用しているイベント記述の仕組みと全く同一の仕組みを利用して、 イベントを自作する事ができます。 この方法は、従来のような、 自作イベントである事に起因する固有のチェックが不要な点、 Bukkitの処理方法をそのまま利用するためにパフォーマンスを犠牲にしないという点で、 従来のイベント自作の方法よりも優れています。

イベントを自作する際は、次の2点に留意して下さい。

  • Eventクラスを継承する必要がある
  • static(静的)なハンドラとして作成する必要がある

staticハンドラは、自作イベント中に次のように記述して下さい。

private static final HandlerList handlers = new HandlerList();
 
public HandlerList getHandlers() {
    return handlers;
}
 
public static HandlerList getHandlerList() {
    return handlers;
}

上記のコードを実際に自作のイベント処理の中に記述する事で、 処理は疎結合となり、実行速度が改善します。

自作イベントの例

import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
 
public class CustomEvent extends Event {
    private static final HandlerList handlers = new HandlerList();
    private String message;
 
    public CustomEvent(String example) {
        message = example;
    }
 
    public String getMessage() {
        return message;
    }
 
    public HandlerList getHandlers() {
        return handlers;
    }
 
    public static HandlerList getHandlerList() {
        return handlers;
    }
}

自作イベントの呼び出し

イベントの呼び出し方法は、従来の方法と同様です:

// イベントのインスタンス化
CustomEvent event = new CustomEvent("Sample Message");
// イベントの実行
Bukkit.getServer().getPluginManager().callEvent(event);
// イベントから得たメッセージの出力処理
Bukkit.getServer().broadcastMessage(event.getMessage());

自作イベントの待ち受け

イベントはどのようにListen(待受,受付などとも表記)すれば良いでしょう? 簡単です。通常のイベントと同様に待ち受けして下さい。

import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;
 
public class CustomListener implements Listener {
    @EventHandler
    public void normalLogin(CustomEvent event) {
        // 何かの処理
    }
}

コマンド

onCommand()メソッド

さて、どのようにイベントを登録しいつ発生するかについて理解しました。 しかし、コマンドを利用して何かを起こしたい場合はどのようなデータ型を利用すればよいのでしょうか?それには、onCommand()メソッドを利用します。 このメソッドは、プレイヤーが文字"/"を入力する度に実行されます。 具体的には、"/do something"とコマンドを実行した場合にonCommand()が呼び出されます。 今のところ、onCommand()はまだプログラムしていないので何も起こらないでしょう。

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

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

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

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

コマンドの設定

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: <plugin name>.basic
      permission-message: You don't have <permission>
  • basic - コマンド名
  • description - コマンドの説明文
  • usage - onCommand()がfalseを返した際に、コマンド実行ユーザに向けて表示されるメッセージの内容。コマンドの使い方を明解に書いてください。
  • permission - 当コマンドの動作に必要なプラグインの設定を、コマンド実行ユーザに知らせるメッセージ。(主に、コマンド実行に必要なpermissionを書きます)
  • permission-message - コマンド実行権限を持たないユーザがコマンドを実行した場合に、その旨を同ユーザに知らせるメッセージ。
Note: ymlファイルには、1個タブを2個のスペースで記述する必要があります。タブ文字は構文エラーとなるため利用できません。

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

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

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

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

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

記述例:

public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
	Player player = null;
	if (sender instanceof Player) {
		player = (Player) sender;
	}

	if (cmd.getName().equalsIgnoreCase("basic")){ // If the player typed /basic then do the following...
		// do something...
		return true;
	} else if (cmd.getName().equalsIgnoreCase("basic2")) {
		if (player == null) {
			sender.sendMessage("this command can only be run by a player");
		} else {
			// do something else...
		}
		return true;
	}
	return false;
}

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

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

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

CommandExecutorのクラス分割

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

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

とっても良い具体例:

MyPlugin.java (the main plugin class):

private MyPluginCommandExecutor myExecutor;
@Override
public void onEnable() {
	// ....
 
	myExecutor = new MyPluginCommandExecutor(this);
	getCommand("basic").setExecutor(myExecutor);
 
	// ...
}
MyPluginCommandExecutor.java:

public class MyPluginCommandExecutor implements CommandExecutor {
 
	private MyPlugin plugin;
 
	public MyPluginCommandExecutor(MyPlugin plugin) {
		this.plugin = plugin;
	}
 
	@Override
	public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
		// ... implementation exactly as before ...
	}
}

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

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

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

堅牢なonCommandの記述

When writing a onCommand, its important that you don't assume any information, such as the sender being a Player. Things to keep in mind:

Make sure the sender is a Player before casting

Using simple code like this makes it possible:

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	if ((sender instanceof Player)) {
           // doSomething
        } else {
           sender.sendMessage(ChatColor.RED + "You must be a player!");
           return false;
        }
        Player player = (Player) sender;
        return false;
}

Check the arguments length

Don't always assume the sender typed the correct amount of arguments.

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	if (args.length > 4) {
           sender.sendMessage(ChatColor.RED + "Too many arguments!");
           return false;
        } 
        if (args.length < 2) {
           sender.sendMessage(ChatColor.RED + "Not enough arguments!");
           return false;
        }
}

When getting another Player by their name, make sure they are online

Sometimes you want to get another player by the name entered by the player. Always make sure the player is online!

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	Player other = (Bukkit.getServer().getPlayer(args[0]));
        if (other == null) {
           sender.sendMessage(ChatColor.RED + args[0] + " is not online!");
           return false;
        }
        return false;
}

If you need to modify a Player currently not online, the OfflinePlayer class provides basic manipulation methods

プラグインのConfiguration/Settings

パーミッション

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

ブロックの操作

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

アイテムの操作

HashMapの応用

Map・Set・Listの応用

データベース

プラグインの配布

ヒントとノウハウ