Pi4J ラズパイでLチカをしてみる

イントロダクション

ラズパイでのJavaプログラミングを行うのに、BlueJが使えるというかOracleさんが言うにはラズパイ用に出ているようです。
こちらにその記事があります、そして、下のように記述がありました。

バージョン 3.14 から、BlueJ は、学校での基本的なコンピューター プログラミングの教育を促進することを目的とした、クレジット カード サイズのシングル ボード コンピューターであるRaspberry Piを完全にサポートします。BlueJ は、Pi 上での開発とプログラムの実行を可能にする Java 開発環境です。

インストール方法も記述があり、実際にはコマンド一発でインストールできました

Pi4J1でLチカを

ラズパイが世の中に出てから「時間があったら触りたい」と思い続けていたものの、なかなか触れないという状況がありました。
それというのは、「何をどの言語を使用して作ろうか?できればJavaを使いたい」と思っていたためです。

ちょうど、チュートリアルなるものがありました。
記事内のリンクからBlueJプロジェクトをダウンロードして、ラズパイで開けばよいみたいです。
Windowsで開くと、エラーが出ました。GPIO周りのクラスが参照できないエラーでした。

ラズパイで、プロジェクトをダウンロードしてBlueJを開いてみたらビルドエラーがない状態でした。
しかし、重くてキャプチャーなどを残せませんでした。オーバークロックなどいろいろとセットアップが必要です。※調査します。

チュートリアル翻訳と解釈

参照するページはこちら、オラクルで出しているBlueJのサイトにあるものです。

LED を Raspberry Pi に接続

このセクションでは、LED を Raspberry Pi に接続し、BlueJ を使用してオブジェクトを直接操作してオンとオフを切り替えます。このチュートリアルでは、プロジェクトLEDButtonを使用します。このプロジェクトをダウンロードして、Raspberry Pi で実行されている BlueJ で開く必要があります。

材料

  1. LED:
  2. 抵抗 (270Ω ~ 330Ω の任意の値
  3. ブレッドボード(あるとよい)

組み立て方

ブレッドボードなし

LEDは、電流が流れると光る部品です。長い脚と短い脚があります。短い脚 (接地端子) は黒いワイヤに直接接続し、長い脚は抵抗器に接続する必要があります。抵抗は流れる電流を制限し、LED の焼損を防ぐために使用されます。

次の図のように、抵抗器のもう一方の脚をもう一方のワイヤ (赤いワイヤ) に接続する必要があります。

次に、黒のワイヤを Pi の 20 (Ground) とマークされたピンに接続し、赤のワイヤを 22 (GPIO6) とマークされたピンに接続する必要があります。

注: Raspberry Pi モデル B+ を使用している場合は、26 個を超えるピンがあります。ただし、両方のモデル (B および B+) に存在するピンの割り当ては、ここで説明されているものと同じであり、プロジェクトは変更なしで機能するはずです。

回路は次のようになります。

ブレッドボードあり

任意のサイズのブレッドボードが利用できる場合は、それを使用して回路を構築することをお勧めします。LEDの脚を正しい方向に向けることを忘れないでください! この回路を以下の図 4 に示します。

コードについて

BlueJ で、プロジェクト LEDButton を開きます。画面は次のようになります。

上の BlueJ 画面の黄色のボックスはそれぞれ Java クラスです。LED クラスは、Raspberry Pi に接続された実際の LED を表します。

LED、Button、および ButtonListener クラスはすでに作成されており、次の演習で使用できますが、変更しないでください。Controller クラスは、独自のコードを記述する場所です。Button クラスと ButtonListener クラスについては、このチュートリアルのパート 2 で説明します。

新しい LED オブジェクトの作成
コードを書き始める前に、BlueJ を使用して直接制御することで、LED クラスが実際の LED にどのように影響するかを確認します。

開始するには、LED クラスを右クリックし、ポップアップ メニューから次の項目を選択します。

BlueJ は「インスタンスの名前」を尋ねます。提案された名前は今のところ問題ありません。BlueJ ウィンドウの左下に「lED1」という名前の赤い四角形が表示されます。

この長方形の赤いアイコンは、「lED1」オブジェクトを表します。このオブジェクトは、Raspberry Pi に接続された実際の LED の Java 表現です。

LEDの点灯

LED をオンにするには、「lED1」インスタンスを右クリックし、次を選択します。

これで LED が点灯するはずです。(すべての舞台裏の接続が行われるため、最初は少し時間がかかる場合があります)。

図 6 を見ると、gpio 番号を指定して LED も作成できることがわかります。デフォルトの gpio 番号は、LED を接続した gpio 番号 6 であるため、これを行う必要はありませんでした。

ヒント: LED クラスには、使用可能なすべてのメソッドとそれぞれの簡単な説明を示す独自のドキュメントが含まれています。それらを表示するには、LED クラス (黄色のボックス) をダブルクリックするだけで、そのドキュメント (javadoc) が表示されます。

演習

演習 1.1 : LED をオンにしました。今すぐオフにできますか?
演習 1.2 : Controller クラスにコードを記述して、今行ったことを対話的に行うことができます。Controller クラスで、メソッド「void turnLEDOn()」の本体を変更して LED をオンにするメソッドを呼び出し、同じクラスのメソッド「void turnLEDOff()」を同様に LED をオフにするように変更します。
チップ:

Controller クラスを編集するには、「Controller」の黄色のボックスをダブルクリックすると、Controller クラスのソース コードが表示されます。
Controller クラスでは、LED オブジェクトは 'led' と呼ばれます: それはすでに存在しています!
重要:コントローラー クラスに加えた変更をテストする前に、エディターの左上または BlueJ メイン画面の左パネルにある [コンパイル] をクリックしてプロジェクトをコンパイルすることを忘れないでください。

Controller クラスの変更をテストするには、LED クラスで行ったのと同じように、クラスを右クリックして Controller のインスタンスを作成し、次に赤い Controller インスタンスを右クリックして、変更したばかりのメソッドを呼び出します。 .
演習 1.3 : Controller クラスのメソッド "void flash(int time)" の本体を変更して、一定時間 (ミリ秒単位で測定) LED をオンにしてから、LED をオフにします。
チップ:

Controller クラスには、というメソッドがあります。
sleepMillisec(int 時間)
このメソッドは、プログラムを指定されたミリ秒数待機させるために使用できます。
1 ミリ秒は非常に短い時間です。
演習 1.4 : SOS のモールス信号を LED で点滅させるために、メソッド "void flashSOS()" の本体を変更します。
ヒント:

SOS のモールス符号は「. . . - - - . . .」で、ドット (.) は短いフラッシュで、ダッシュ (-) は長いフラッシュです。

前の演習で実装したフラッシュ メソッドを利用します。

演習 1.5 : モールス信号が使用するすべてのパターンを調べ、モールス符号で指定した文字列をフラッシュする Controller クラスの新しいメソッドを作成します。

ラズパイで実行するとき

  1. ラズパイのセットアップを先に行います。
  2. 必要な部品などを用意。
  3. サンプルプロジェクトをダウンロード。
  4. チュートリアルを見ながら作成。

現状は、Windowsで記事を書いているので、後に、ラズパイでの実行した結果を記述します。

Java Swing テキストRPGを作る ~Textの作成を行うゲーム進行の作成~

イントロダクション

前回は、Java Swingを使用して画面作成を行いました。下のような画面です。

この状態は、表示するための領域があるだけですので、これに表示するもの(内容)を出力する部品が必要になります。
もちろん、出力する内容はテキストRPGの「RPG」の部分です。そのためにまずはタイトルを表示することを考えます。

ソースはGithubにアップしています。

RpgProgressRpgStoryクラスの作成

ストーリーを表示するための画面を作成したので、今度はストーリーを出力(文字列を読み込んだり)するクラスを作成します。
まとめると、ゲーム進行を開始、実行するクラスです。

ちなみに、現状のクラスは下のようになっています。

<ゲーム進行の実装後>

RpgStoryクラスの設計

クラスを作成するために、まずは設計を行います。何から考えるか?

1. 目的(役割)を考える

作成するクラスの名前は「RpgStory」という名前に決めました。なのでこのクラスの役割(目的)を考えます。
やりたいことは次の通りです。フローチャートにしてみました。

とりあえず、下のような形で処理を進める形で作りたいと考えています。

  1. ゲーム開始
  2. タイトル表示(これもシーンとして扱う)
  3. 各シーンを表示
      1. Storyの表示
      1. 入力を受ける
      1. 結果の表委
      1. 次のシーンへ移動
  4. Escが押下されたとき、終了シーンに移動したときゲームを終了

こんな感じの処理フローを考えています。これを実現するために頭をひねります。
そして、上記の処理にある。「各シーンのStoryを表示」を実現する役割のクラスが「RpgStoryクラス」になりますので、下のようにクラスを作成します。

ストーリーを表示するまでの処理

まずは、クラスを作成します。Storyや結果などを表示する必要があるのでこのクラスのフィールドにテキストエリアをフィールド変数にセットします。
※RpgStoryというクラスを作成していましたが、クラス名を以下のように変更しました。

package jp.zenryoku.rpg;

import javax.swing.JComponent;
import jp.zenryoku.rpg.utils.FileReaderUtil;
import jp.zenryoku.rpg.utils.StoryBuffer;

import jp.zenryoku.rpg.views.InputSelector;
/**
 * クラス RpgProgress の注釈をここに書きます.
 * テキストRPGのゲーム進行を行う。以下の処理を行う。
 * 
 * 1. ロード済みのストーリー(シーン)を呼び出す。
 * 2. シーンのストーリー表示。
 * 3. 入力受付。
 * 4. 結果の表示。
 * 5. 次のシーンへ移動。
 * 
 * @author (Takunoji)
 * @version (1.0)
 */
public class RpgProgress
{
    /** テキストエリア */
    private RpgTextArea fView;
    /** ストーリーのテキストを保持するバッファ */
    private StoryBuffer fBuf;

    /** コンストラクタ */
    public RpgProgress(RpgTextArea view) {
        // ここに実行するための準備を実装する
        this.fView = view;
        // StoryBufferをセット
        fBuf = StoryBuffer.getInstance();
    }

    /**
     * テキストRPGのシーンを呼び出し実行する。
     * プログラムのメイン処理フローを実装。
     */
    public void run(JComponent comp, int x, int y) {
         // - 1. Storyの表示
         StringBuilder build = FileReaderUtil.readFile("title.txt", true);
         fView.setText(build.toString());
         // - 2. 入力を受ける
         openSelects(comp, x, y);
         // - 3. 結果の表委
         // - 4. 次のシーンへ移動
    }

    /**
     * ユーザーの入力を促すためのポップアップを表示する。
     */
    private void openPopup(JComponent comp, int x, int y) {
        String[] yesNo = new String[] {"start", "continue"};
        InputSelector pop = new InputSelector("First", yesNo);
        pop.show(comp, x, y);
    }
}

そして、ここから考えている処理を実現するための仕組みを考えていきます。そのためにクラスとクラスの関係を作成します。
このクラス同士の関係を作るために必要になる知識が以下のものになります。

クラス関係を作るための知識

「基本」という言葉で丸め込まれることが多いですが、以下のようなものです。

  1. 通常のクラスの書き方
  2. クラスの継承の仕方
  3. インターフェース・クラスの作り方
  4. 抽象クラスの作り方

すでに、クラスの作り方は学んでいるので大丈夫だと思います。が、作り方がわかっても使うのに慣れていないと、「理解した」というところに、たどり着かないので。。。

クラスを使ってみよう(ちょっと脱線)

Javaプログラミングを行うときにはJDKというものを使用してプログラムを使っています。
このJDKにはJavaプログラムを作成した人々が用意しているAPIというものを使うことができます。まずはこのAPIを使い
クラスの扱い方を理解しましょう。今回は、java.io.BufferedReaderクラスを使ってみます。

次は、タイトルを表示するために「Story.txt」ファイルを読み込み、それを表示するプログラムを作成します。

ファイルの読み込み

テキストRPGを起動して、初めにタイトルを表示します。このタイトルはRPGを作成するユーザーが作るものなので「固定」ではなく「動的」に作ります。

「動的」とは

上記の通り「固定」ではなく「動的」という言い方がわかりやすいと思うのですが、

固定的に実装

固定的に実装するというのを、初めのプログラムハローワールドを例にして考えてみます。
ハローワールドのプログラムを思い出してみてください。下のように実装しました。

public class Kotei {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

これは固定的で、出力する文字列が必ず「Hello World」です。

動的に実装

上記のプログラムを動的に変更します。動的になると必ずしも「Hello World」ではない文字列が出力できます。
でわ、動的なプログラムに修正しましょう。

public class Doteki {
    public static void main(String[] args) {
        if (args.length != 0) {
            System.out.println(args[0]);
        } else {
            System.out.println("Hello World");
        }
    }
}

上記のような形で実装すると動的に文字列を表示できます。

このような形で、実装すると表示する文字列を動的に出力することができます。

ファイルを読む形だと?

ファイルを読み込む形にすると、プログラムは全く同じでも、出力する文字列は、ファイルの中身に依存しますので、動的に表示するタイトルを表示することができるというわけです。別な言い方をすると「プログラムの修正をしなくても出力する文字列を変更できる」ということです。

ファイルの読み込み処理

ここで注意が一つあります。本来であれば、ファイルコントロール用のクラスを作るのが、通常のやり方ですが、今回はクラスを作成する一歩前の学習を目的としているので、メソッドを追加する形で実装します。

追加するのは下のようなメソッドです。

    private void readFile(String fileName) throws FileNotFoundException, IOException {
        File f = new File(fileName);
        BufferedReader buf = new BufferedReader(new FileReader(f));
        String line = null;
        while((line = buf.readLine()) != null) {
            System.out.println(line);
        }
        buf.close();
    }

ポイント

throws FileNotFoundException, IOExceptionの部分は「例外を呼び出し元に投げますよ」という意味です。
具体的には、例外があったときに、上記のメソッドを呼び出しているメインメソッドに例外が投げられます。

Scanner scan = new Scanner(System.in);
System.out.print("入力してください: ");
String in = scan.next();

try {
    if (in.equals("test")) {
        System.out.println("*** Testing now! ***");
    } else if ("file".equals(in)) {
        main.readFile("resources/FirstStage.txt");
    }
} catch (FileNotFoundException e) {
    System.out.println("*** No File! ***");
} catch (IOException e) {
    System.out.println("*** What's up!? ***");
}

このコードでは、以下のような処理を行っています。

  1. 標準入力を受けとる
  2. 入力した値が「test」の場合は、「* Testing now! *」を表示する
  3. 入力した値が「file」の場合は、「resources/FirstStage.txt」のファイルを読み込み表示

ここで使用しているメソッドは引数に「resources/ファイル名」を渡しています。つまり、プロジェクト直下にある「resources」フォルダ内にある「FirstStage.txt」ファイルを読み込んで表示します。

このような、「引数にファイル名(パスも含める)」を渡してやれば、ファイルを読み込むようなメソッド、そのような役割を持っているメソッドを作ってやればこのメソッドは、いろんな場面で使用することができます。

このような役割を持たせてメソッドを作ることを練習してできるようになったら、次はクラスにその役割を持たせるように実装します。
メソッドと違いクラスの場合は、もっと大きな役割を持たせることができます。詳細は後々に説明します。まずは、メソッドを作れるようになりましょう。

サンプルの説明

上記に書いた処理を実行していますが、プログラムが動いている部分を見る方が理解が早いと思います。

ファイルの中身を表示する

話をもとに戻します。RpgStoryの処理で「タイトル表示」を行うために「動的に表示したい」のでファイルの中身を表示するように
プログラムを作成したいともいます。

作成した画面に読み込んだファイルの中身を表示します。
先ほどのBufferedReaderクラスを使用して次のようなクラスを作成、付随するクラスを同様に、作成しました。

プログラムの実行結果は次のような感じです。

タイトル表示後は?

タイトルを表示した後は、「A.はじめてやる」のと「B.続きをやる」の2択にします。なので、AとBのどちらかを選択し次の処理を行うようにしたいと思います。

選択をしてもらうのに「ポップアップ」を使用したいと持っています。なので。次のクラスを使用してポップアップを作成します。

そして、このクラスは今回のテキストRPGで使いたいのでテキストRPGようにカスタムしますのでこのクラスを拡張します。

JPopupMenuクラスを拡張する

クラスの継承方法は、「tends XXXXように書きます。作成するクラスは「putSelector」クラスにします。

public class InputSelector extends JPopupMenu { ... }

これで、拡張する準備ができました。
ここから、RpgStoryクラスに、ストーリーを進めていったときの入力を受け取るクラスとして役割を果たします。

JMenuItemクラスも拡張

上記のポップアップメニューに表示する選択肢を表示するためのクラスがJMenuItemクラスです。そして、このクラスがクリックされたときに何かしらのアクションを起こすクラスが、Actionインターフェース・クラスなのですが、扱いが簡単な「AbstractAction」を使用して実装します。

やりたいことは、つぎの操作が行いたいです。

  1. 選択肢を表示する
  2. クリックしたときに次の処理をする

なので、下のように実装します。

public class TitleMenu extends AbstractAction { ... }

これで、AbstractActionに定義され散る「オーバーライドしてね」とされているメソッド「actionPerformed()」をオーバーライドしてやればOKです。
実際には、コンストラクタも作成しました。

public class TitleMenu extends AbstractAction
{
    public TitleMenu() {
        super("titleMenu");
    }

    public TitleMenu(String str) {
        super(str);
    }

   public void actionPerformed(ActionEvent event) {
        System.out.println("Message: ");
   }
}

現状では、クリックされたときに「Message:」を標準出力に出力するだけです。
<クラス図>

処理の内容について
  1. 画面の表示を行う
  2. title.txtを読み込み画面に表示
  3. 初めの選択肢(ポップアップメニュー)を表示
  4. クリックしたらそれぞれの処理を行う(TitleMenuクラス)

現状では、上記のような実装ができています。

この後は、以下の処理を実装したいと思います。

  • 「continue」をクリックしたときには、「未実装です」を表示した後に元のポップアップを表示する。
  • 「start」をクリックしたときには、Story.txtに定義されている文章を表示する。

さてどのような仕組みにしたらよいでしょうか?イメージとしては、初めに記述したように「Scene」を使用してストーリーを展開していきます。
なので、これを忠実に実行するための仕組みを考えなくてはいけません。

次は、これを実現すための仕組みを考えます。
さてさて。。。どうしたものか?

仕様を確認する

今回の開発は、以下の目的があります。

  • 以前作成したものをリメイクする。
  • アプリケーションを動かし、java学習者が学習に使う。
  • ゲームブックのようにゲームを展開する。

なので、なるべく制限を少なく、自由にゲームを作成する事ができるように、。各設定ファイルの拡張性を高めたうえで実装するのが理想的。

実装のポイント

実装する上でのポイントになる部分、ゲームの設定を行うためのファイルと実行するシーンの種類を一覧しました。

設定ファイル一覧(ゲーム作成者用)

ゲームの作成者は以下のファイルの内容を記述する。サンプルゲームにある、これらのファイルには、簡単な設定が書かれているのでそれをカスタムして利用してほしい。※Githubを更新していきます。

ファイル名 記述内容
title.txt ゲームを起動したときの初期表示画面(テキストのみで描画する)
conf.txt ゲームの設定、言葉(単語)の意味を定義、HPやMPは固定だが他の値を追加、使用方法を指定することができる
story_XXX.xml ゲームのストーリーを記述、各ストーリーのシーンを描画するXMLファイル。「XXX」にはシーン番号が入る。 例: story_1.xml
Story.txt ゲームのストーリーを記述、ゲームブックの本体のイメージ
Job.xml 各職業の技、魔法などの習得レベルを記述
Commands.xml 各キャラクターが実行するコマンド
STM.xml 技(Skill, Tech), 魔法(Magic)の効果などを定義
MonseterType.xml モンスターの技、魔法などの習得レベルを記述
Monsters.xml モンスターの名前、HPやMPなどを記述する
Shops.xml ショップの定義、販売しているものと値段のリスト
Items.xml アイテム(武具も含む)の定義、販売しているものと値段のリスト、非売品は値段を「-」にする

Sceneの種類

No シーンタイプ シーンの役割
1 ストーリーシーン XMLファイルに定義されているstoryタグの内容を表示する
2 バトルシーン XMLファイルに定義されているmonsterタグの内容を表示する
3 プレーヤー生成シーン プレーヤーを作成するシーン
4 次のシーンを選択するシーン 次のシーン番号を指定してそのシーンを呼び出す
5 ショップシーン 買い物を行うシーン
6 エフェクトシーン 「お金を拾う」「HPが回復した」などのプレーヤーに対する効果を起こすシーン

それぞれのシーンを実装するのに、前回のシーンでは、テキストファイルからタグをつけてシーンタイプを指定していたため
ユーザーが作成するストーリーに対し大きな制限があった。
これを、XMLファイルに変更して次のように実装する

XMLでのシーン定義

タグでシーンの動きを定義するようにする。

1.storyタグ

「ストーリーシーン」を実行する。
単純にタグ内の文字列を表示する。

2.batlleタグ

「バトルシーン」を実行する。
戦闘開始前の文言、例えば「XXXが現れた!」、を定義。モンスターIDを指定し、Monster.xmlから対象のモンスターを取得する。

3.create

「プレーヤー生成シーン」を実行する。もしくは、プレーヤーを初めから用意しておく。

  1. 名前から性別などを入力して、生成する(設定ファイルにて必要な入力項目を定義するようにする)
  2. プレーヤーの名前から性別、ステータスを定義、職業の類もセットできるようにする。
4.nextタグ

次のシーンを選択するシーンを実行する。
選択肢と次のシーン番号をセットにして表示、選択する。

5.effectタグ

「エフェクトシーン」を実行する。
「conf.txt」で定義しているパラメータをプレーヤーに対して上げたり下げたりする。
例:「プレーヤーは10のダメージを受けた!」、「100円拾った」、「たろう(犬)がなついてきた(効果なし)」など

6.shopタグ

「ショップシーン」を実行する。
ショップIDで指定した店を表示、買い物など利用することができる。「宿屋」などはeffectタグをつけることで何かしらの効果を起こすことができる

XMLの読み込み

DocumentBuilderFactoryを使用してXMLファイルを読みこみ、ゲーム展開に必要なデータ(情報)を取得するようにプログラムを作っていくことにしました。

このクラスは、XMLファイルを読み込んで、Documentというインターフェース(の中身が実装されたもの)を取得できる。というJavaAPIです。

<サンプルコード>

    /**
     * XMLドキュメント(ファイル)を読み込む。
     * @param directory ファイルの配置しているディレクトリ
     * @param fileName ファイル名
     * @return Documentオブジェクト
     * @throws RpgException ファイルの読み込みエラー
     */
    private static Document loadDocumentBuilder(String directory, String fileName) throws RpgException {
        //creating a constructor of file class and parsing an XML files
        Path path = Paths.get(directory, fileName);
        File file = path.toFile();// ("/Monster.xml");
        //an instance of factory that gives a document builder
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        //an instance of builder to parse the specified xml file
        DocumentBuilder db = null;
        Document doc = null;
        try {
            db = dbf.newDocumentBuilder();
            doc = db.parse(file);
            doc.getDocumentElement().normalize();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
            throw new RpgException(MessageConst.ERR_XML_PERSE.toString() + ": " + e.getMessage());
        } catch (SAXException e) {
            e.printStackTrace();
            throw new RpgException(MessageConst.ERR_XML_PERSE.toString() + ": " + e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            throw new RpgException(MessageConst.ERR_IOEXCEPTION.toString() + ": " + e.getMessage());
        }
        return doc;
    }

前回作成したコードですが、これでDocumentを取得して、各タグを取得してい行きます。
下のようなコードでタグを取得します。上のメソッドを呼び出しています。これはMonster.xmlを読み込んだ時のサンプルコードです。

Document doc = loadDocumentBuilder("src/main/resources", "Monsters.xml");

NodeList list = doc.getElementsByTagName("monster");
for (int i = 0; i < list.getLength(); i++) {
    Node node = list.item(i);
    if (node.getNodeType() == Node.ELEMENT_NODE) {
        monsList.add(createMonster(node));
    }
}

XMLから設定情報クラスを生成

XMLファイルの解説は別の記事にて行っております。

上記のようなXMLファイル、つまり設定ファイルをユーザー(ゲームブック作成者)が作成することで、ユーザーは自由に自分の作品(RPGゲームブック)を作成することができるというわけです。ただし、XMLとかフローチャートとか、使い方を覚える必要がありますが。。。

しかし、ここで重要なのは、「動的なアプリケーションを作成する事」ですのでユーザーの定義する情報からアプリケーションの動作をコントロールできるように作ることです。
ちなみに、サンプルで「世界観」を作成してみました。XMLファイルは「Worlds.xml」、読み込みはXMLUtilsクラスです。

Worlds.xml

<?xml version="1.0"?>
<!-- 世界観 -->
<class>
    <world>
        <id>upperWorld</id>
        <!-- name: 世界の名前 -->
        <name>ちきゅう</name>
        <!-- 世界地図(画像ファイル) -->
        <img>https://img.game8.jp/3808388/f49a39a900970874043b2d4c899bce8e.jpeg/show</img>
        <!-- nature: 自然 -->
        <nature>
            <climate>
                <name>寒帯気候(かんたいきこう)</name>
                <creatures>
                    <creature id="tonakai"/>
                    <creature id="arcticfox"/>
                    <cresture id="grizzlybear"/>
                </creatures>
                <!-- 定義は自由に追加してよい -->
            </climate>
            <climate>
                <name>亜寒帯気候(あかんたい きこう)</name>
                <creatures>
                    <creature id="amurleopard"/>
                </creatures>
            </climate>
            <climate>
                <name>温帯気候(おんたいきこう)</name>
                <creatures>
                    <creature id="lynx"/>
                    <creature id="eagle"/>
                </creatures>
            </climate>

            <climate>
                <name>乾燥帯気候(かんそうたい きこう)</name>
                <creatures>
                    <creature>
                        <id>camel</id>
                        <name>ラクダ</name>
                        <discription>ラクダは、楽だ</discription>
                    </creature>
                </creatures>
            </climate>

            <climate>
                <name>熱帯雨林気候(ねったいうりん きこう)</name>
                <discription>./climate/tropical_rainforest_climate.txt</discription>
            </climate>

        </nature>
        <!-- モンスターを含む動植物には食物連鎖 -->
        <!-- 「植物→草食動物→肉食動物」のような書き方でもよい -->
        <food_chain>./FoodChain.md</food_chain>
        <!-- 生息地、生物分布 -->
        <creatures>./Creatures.xml</creatures>
        <!-- 「地域」としてまとめる(地形、天候、四季(雨季と乾季など)) -->
        <regions>
            <region>
                <id>1</id>
                <name>FirstStage</name>
                <discription>アリアハンのある大陸</discription>
            </region>
            <region>
                <id>2</id>
                <name>SecondStage</name>
                <discription>ポルトガのある大陸</discription>
            </region>
            <region>
                <id>1</id>
                <name>ThirdStage</name>
                <discription>ミラミッドのある大陸</discription>
            </region>
        </regions>
        <!-- 魔法(の類)の発動ロジック、効果、特性 -->
        <logic>呪文を唱えて、まほうを発動する。魔法力(MP)を消費する。</logic>
        <!-- civilzation: 世界の文明 -->
        <civilzations>
            <civilzation>
                <id>DOQ</id>
                <discription>ドラ○エ3の文明</discription>
                <charactor></charactor>
                <structures></structures>
                <technology></technology>
                <arts></arts>
            </civilzation>
        </civilzations>
        <!-- 文化 -->
        <cultures>
            <culture>
                <id>ARIAHAN</id>
                <discription>アリアハン周辺の文化</discription>
                <!-- 生活様式全般 -->
                <life_style>中世の生活様式</life_style>
                <!-- 生活習慣 -->
                <habit>士農工商に分かれて生活、進行するのは神様、協会に祈りを捧げる。</habit>
                <!-- 価値観 -->
                <values></values>
                <!-- 世界観 -->
                <view_of_world></view_of_world>
                <!-- 規範 -->
                <norm></norm>
                <!-- 思考様式 -->
                <way_of_thinking></way_of_thinking>
                <!-- 社会制度 -->
                <social_system>封建社会</social_system>
                <!-- 社会構造 -->
                <social_structure>王族→貴族・聖職者→士族→平民(農民、工業人、商人)</social_structure>
                <!-- 組織 -->
                <organization></organization>
            </culture>
            <culture>
                <id>PORUTOGA</id>
                <discription>ポルトガ周辺の文化</discription>
            </culture>
            <culture>
                <id>ROMARIA</id>
                <discription>ロマリア周辺の文化</discription>
            </culture>
            <culture>
                <id>NORNEAL</id>
                <discription>ノアニール周辺の文化</discription>
            </culture>
        </cultures>
    </world>
    <world>
        <id>arefguld</id>
        <!-- 上記の世界の情報を継承する -->
        <inherit>upperWorld</inherit>
        <!-- name: 世界の名前 -->
        <name>アレフガルド</name>
        <!-- 世界地図(画像ファイル)を上書きする(オーバーライド) -->
        <img>https://img.game8.jp/3808505/5ce596169ba7e3a6386f26ca395aed35.jpeg/show</img>
        <!-- 生息地、生物分布 -->
        <creatures>./Arefgulds.xml</creatures>
        <regions>./Regions.xml</regions>
    </world>
</class>

XMLUtilsクラス

まずは、コードを見てください。XMLファイルを読み込んで、各タグを表示するものです。

public static void loadWorlds(String directory, String fileName) {
    // 空オブジェクトのクラスを取得する
    Class instance = null;
    try {
        Document doc = loadDocumentBuilder(directory, fileName);
        doc.normalizeDocument();
        if (isDebug) System.out.println("Root element: " + doc.getDocumentElement().getNodeName());

            NodeList list = doc.getChildNodes();
            int len = list.getLength();
            int level = 0;
            for (int i = 0; i < len; i++) {
                Node node = list.item(i);
                String s = node.getNodeName();
                if (s.startsWith("#") == false) {
                    if (isDebug) {
                        System.out.println("Lv: " + level);
                        System.out.println("名前: " + s);
                    }
                    if (!"class".equals(s) == false) {
                        System.out.println("XMLの初めは<class>タグで開始してください。");
                        System.exit(-1);
                    }
                    instance = new Object().getClass();
                }
                if (node.hasChildNodes()) {
                    level = level + 1;
                    childNodes(node.getChildNodes(), level);
                }
            }

    } catch (RpgException e) {
        e.printStackTrace();
    }
}
private static void childNodes(NodeList list, int level) {
    int len = list.getLength();
    for (int i = 0; i < len; i++) {
        Node node = list.item(i);
        String s = node.getNodeName();
        if (s.startsWith("#") == false) {
            if (isDebug) {
                System.out.print("*** Lv: " + level);
                System.out.print(" TAG名前: " + s);
            }
            printVlue(level, node);
        }
        if (node.hasChildNodes()) {
            level = level + 1;
            if(isDebug) System.out.print(level + " : " + node.getNodeName());
            childNodes(node.getChildNodes(), level);
            level = level - 1;
        }
        System.out.println();
    }
}
private static void printVlue(int level, Node node) {

    //System.out.print("Level: " + level);
    if (node.hasAttributes()) {
        NamedNodeMap map = node.getAttributes();
        int len = map.getLength();
        for (int i = 0; i < len; i++) {
            Node n = map.item(i);
            if (isDebug) System.out.println(" 属性: " + n.getTextContent());
        }
    }
    if (node.hasChildNodes() && level >= 2)  {
        if (!"".equals(node.getFirstChild().getTextContent().trim())) {
            if (isDebug) System.out.println("Node : " + node.getFirstChild().getTextContent());
        }
    }

}

この表示している項目を取得して、各設定クラス(オブジェクト)にセットしアプリケーションをコントロールできるようにしたいと思います。

XMLファイルのロード(読込)

XMLファイルを読み込む処理ができました。テストをしないと、ちゃんと動いているかわからないので、テストしました。
テストの実装方法は、こちらの記事に記載しています。JUnitです。

テストコード

    @Test
    public void testCreateConfig() {
        String[] filesA = new String[] {"Worlds.xml", "Regions.xml", "Creatures.xml"};
        for (String fileName : filesA) {
            try {
                StoryConfig c = (StoryConfig) XmlUtils.createConfig(fileName);
                assertTrue(c instanceof ConfigIF);
            } catch (Exception e) {
               e.printStackTrace();
               fail("file: " + fileName);
            }
        }

        String[] filesB = new String[] {"MonsterType.xml", "Job.xml", "Commands.xml", "STM.xml", "Effects.xml"};
        for (String fileName : filesB) {
            try {
                ConfigIF c = (ConfigIF) XmlUtils.createConfig(fileName);
                assertTrue(c instanceof ConfigIF);
            } catch (Exception e) {
                e.printStackTrace();
                fail("file: " + fileName);
            }
        }

    }

テスト対象メソッド

クラスはXMLUtilsです。

   /**
     * 各種設定ファイルを読み込むときに、ファイル名に対応した
     * クラスを生成して返却する。
     *
     * @param fileName
     * @return ConfigRpptを継承しているクラス
     */
    public static ConfigIF createConfig(String fileName) {
        ConfigIF conf = null;
        switch (fileName) {
            case StoryConfig.WORLD_XML:
                conf = new World();
                break;
            case StoryConfig.NATURE_XML:
                conf = new Nature();
                break;
            case StoryConfig.CREATURE_XML:
                conf = new Creature();
                break;
            case StoryConfig.REGIONS_XML:
                conf = new Region();
                break;
            case StoryConfig.EFFECTS_XML:
                conf = new Effects();
                break;
            case StoryConfig.STM_XML:
                conf = new STM();
                break;
            case StoryConfig.CPMMADS_XML:
                conf = new Command();
                break;
            case StoryConfig.JOB_XML:
                conf = new Job();
                break;
            case StoryConfig.MONSTER_TYPE_XML:
                conf = new MonsterType();
            case StoryConfig.MONSTERS_XML:
                /** TODO-[MonsterクラスはPlayerクラスを継承する] */
                //conf = new Monster();
                break;
            default:
                System.out.println("想定外の設定ファイル名です。 : " + fileName);
                System.exit(-1);
        }
        return conf;
    }

とりあえずは、XMLファイルのロード時に、対象のクラスをインスタンス化する処理を実装しました。

まとめ

ここまで作ったら、文字列が表示できることを確認する必要があります。
つまりは、RpgProgressの処理が動いた後に出力する文字列が画面に表示できないことには始まりませんので。。。
結局、ユーザー入力は、ポップアップメニューを使用することにしました。
以下のような処理順序です。

  1. ゲームを起動する
  2. タイトルの表示
  3. ゲーム開始:この時にRpgProgressを実行して、常に出力する文字列を表示しているテキストエリアに出力する形をとるようにします。

Java Swing テキストRPGを作る ~Swingを使って画面を作る~

イントロダクション

テキストRPGを作成しようと思います。どちらかというとリメイクに近いのですが、前回作成したものが完成していないので。。。
兎にも角にも、Java Swingでの実装になりますので、クラスの扱い方の学習がしやすい、かつ、視覚的にクラス関係を理解できると思います。

IDE(開発ツール)はBlueJを使用しています。

Swingを使って画面を作る

以前、テキストRPGを作成しました。
Gitにアップしてあります。

しかし、これはコマンドプロンプト上で実行するもので「ゲーム」って感じのしないものでした。画面のリロードとかうまくいきません。。。
なので、いろいろと考えた末、Java Swingで実装したらよいと考えなおしました。

余談

実際、筆者はJavaのオブジェクト指向プログラミングという部分をSwingで学びました。つまり、クラスの扱い方を理解しました。
「オブジェクト指向」という言葉を使うと「staticおじさん」や「オブジェクト指向おじさん」よろしく。。。混沌の世界に足を踏み入れることになるので言葉を変えていきたいと思います。

クラスの扱い方を理解する

まとめると、筆者はSwingの実装を通してクラスの扱い方を理解しました。というところを言いたかった次第です。
そして、最近覚えたBlueJを使用して、テキストRPGを作成していきたいと思います。

画面を作る

Swingを使用して、画面を作成していきます。まずは、テキストRPGを実行して「テキスト」を表示する領域が必要になります。

初めのコード

この領域には次の機能が必要になります。

  1. 文字を表示する
  2. 文字をクリア(削除)する

これらを実現するためにプログラム的には、以下のものを使用します。

  • JFrame: アプリの表示する領域フレームを表現するクラス
  • JPanel: フレーム上にコンポーネントを配置するパネル
  • 各種コンポーネント: ラベル(JLabel)、テキストエリア(JTextArea)など

クラス継承について

クラスの継承関係を見てみるとわかりやすいです。

これは、JFrameクラスの親は、Frameクラス、そしてその親は。。。とそれぞれの継承関係を示しています。
つまり、クラスキャストも行うことができるということです。

JFrame frame = new JFrame();
Frame superFrame = (Frame) frame;
superFrame.XXXX;

言葉を変えると、親クラスが必要な時は、上記のようにキャストして使用することができます。
そして、親クラスのメソッドを呼び出すこともできます。

JFrame frame = new JFrame();
frame.addNotify(); // java.awt.Frameのメソッド

コードについて

作成したコードは、下のような表示を行います。どの部分がフレームなのか?も記述しました。

コード

TextRPGMainクラスは、JFrameを継承しているところに注意してください。

package jp.zenryoku.rpg;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JPanel;
import javax.swing.JLabel;
import java.awt.Container;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;

/**
 * クラス TextRPGMain の注釈をここに書きます.
 * 
 * @author (Takunoji)
 * @version (1.0)
 */
public class TextRPGMain extends JFrame
{
    public static void main(String[] args) {
        // JFrameを継承しているのでJFrameクラスのメソッドを使える
        TextRPGMain main = new TextRPGMain();
        main.run("Text RPG");
    }

    public void run(String title) {
        // タイトルをセット
        setTitle(title);
        // 画面の表示位置と画面サイズをセット
        Dimension windowSize = Toolkit.getDefaultToolkit().getScreenSize();
        int xPos = (int) windowSize.getWidth() / 4;
        int yPos = (int) windowSize.getHeight() / 4;
        setBounds(xPos, yPos, xPos * 2, yPos * 2);
        // 画面を閉じたときアプリを終了する設定
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // ラベル
        JLabel titleLabel = new JLabel("Text RPG");
        JTextArea textarea = new JTextArea();
        // テキストエリア
        textarea.setColumns(40);
        textarea.setRows(10);

        // ラベル用のパネル
        JPanel titlePanel = new JPanel();
        titlePanel.add(titleLabel);

        // テキストエリア用のパネル
        JPanel textPanel = new JPanel();
        textPanel.add(textarea);

        // パネルをセットするコンテナ
        Container contentPane = getContentPane();
        // コンテナにパネルをセット
        contentPane.add(titlePanel, BorderLayout.NORTH);
        contentPane.add(textPanel, BorderLayout.CENTER);
        // 表示する設定
        setVisible(true);
    }
}

ちなみに、クラス図で見ると下のようになります。

メインメソッドを持っているクラスのみになります。

次は、クラスの拡張実装を行ってみようと思います。

クラス継承の実装

クラスの継承方法は下のように「extends クラス名」と書くだけです。

public class ChildClass extends ParentClass {
   ....
}

JLabelを拡張する

「拡張」という言葉に戸惑うかもしれません。ズバリ「JLabelを継承して新しいクラスを作成する」という意味です。

新しく「TitleLabel」クラスを追加します。このクラスは上記のTextRPGMainクラスのrun()メソッドで行っている処理を少なくするように実装しています。
別な言い方をすると「タイトルラベルの処理はTitleLabelに任せましょう。というところです。

では、どのようになるのか?というところです。

TitleLabelの実装

  1. TitleLabelクラスを作成します。
  2. JLabelを継承します。
  3. 現状はコンストラクタの実装のみで事足ります。

実際のコードです。

package jp.zenryoku.rpg;

import javax.swing.JLabel;
import java.awt.Dimension;
import java.awt.Color;

/**
 * クラス TitleLabel の注釈をここに書きます.
 * JLabelを拡張して、テキストRPGのタイトルをセットするラベルを作成する。
 * 
 * @author (Takunoji)
 * @version (1.0)
 */
public class TitleLabel extends JLabel
{
    public TitleLabel(String title, Dimension windowSize) {
        super(title);
        int width = (int) windowSize.getWidth() / 4;
        int height = (int) windowSize.getHeight() / 16;
        Dimension labelSize = new Dimension(width, height);
        setOpaque(true);
        setPreferredSize(labelSize);
        setBackground(Color.GREEN);
        setHorizontalAlignment(JLabel.CENTER);
    }
}
  1. JLabelを継承しているので、親クラス(JLabel)のコンストラクタを呼び出します。super(title);
  2. ラベルのサイズ(縦横の幅指定)をします。
  3. ラベルの領域がわかるように、緑色の背景を付けます。

ちなみに、ラベルのサイズは、毎回値を変更するのは、面倒なのでPCの画面サイズに合わせてサイズを変更するように実装しました。

そして、run()メソッドと今回作成したクラスの処理の関係を示します。

TextRPGMain#run()

JLabelを生成して、タイトルをセットしただけで、幅や背景などはセットしていませんでした。

// ラベル
JLabel titleLabel = new JLabel("Text RPG");

なので、この「TitleLabel」クラスを作成していなかったらTextRPGMainクラスにJLabelの処理を書くことになります。
このTextRPGMainクラスにJLabelの処理を書くことがプログラム的に美しくないのでTitleLabelを作成しタイトルラベルのことはこのクラスにコードを書きましょう。という風に考えてプログラムを作りました。

TextRPGMain#run()の修正

ズバリ下のように修正しました。1行です。
JLabel titleLabel = new JLabel("Text RPG");TitleLabel titleLabel = new TitleLabel("Text RPG", windowSize);
になりました。表示の結果は以下の通り
<元々の表示>

<修正後の表示>

次は、プログラム・コードを見てみましょう。
<元々の処理>

public void run(String title) {
    // タイトルをセット
    setTitle(title);
    // 画面の表示位置と画面サイズをセット
    Dimension windowSize = Toolkit.getDefaultToolkit().getScreenSize();
    int xPos = (int) windowSize.getWidth() / 4;
    int yPos = (int) windowSize.getHeight() / 4;
    setBounds(xPos, yPos, xPos * 2, yPos * 2);
    // 画面を閉じたときアプリを終了する設定
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    // ラベル
    JLabel titleLabel = new JLabel("Text RPG");
    // テキストエリア
    JTextArea textarea = new JTextArea();
    textarea.setColumns(40);
    textarea.setRows(10);

    // ラベル用のパネル
    JPanel titlePanel = new JPanel();
    titlePanel.add(titleLabel);

    // テキストエリア用のパネル
    JPanel textPanel = new JPanel();
    textPanel.add(textarea);

    // パネルをセットするコンテナ
    Container contentPane = getContentPane();
    // コンテナにパネルをセット
    contentPane.add(titlePanel, BorderLayout.NORTH);
    contentPane.add(textPanel, BorderLayout.CENTER);
    // 表示する設定
    setVisible(true);
}

<修正後>

public void run(String title) {
    // タイトルをセット
   setTitle(title);
    // 画面の表示位置と画面サイズをセット
    Dimension windowSize = Toolkit.getDefaultToolkit().getScreenSize();
    int xPos = (int) windowSize.getWidth() / 4;
    int yPos = (int) windowSize.getHeight() / 4;
    setBounds(xPos, yPos, xPos * 2, yPos * 2);
    // 画面を閉じたときアプリを終了する設定
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    // ラベル
    TitleLabel titleLabel = new TitleLabel("Text RPG", windowSize);
    // テキストエリア
    JTextArea textarea = new JTextArea();
    textarea.setColumns(40);
    textarea.setRows(10);

    // ラベル用のパネル
    JPanel titlePanel = new JPanel();
    titlePanel.add(titleLabel);

    // テキストエリア用のパネル
    JPanel textPanel = new JPanel();
    textPanel.add(textarea);

    // パネルをセットするコンテナ
    Container contentPane = getContentPane();
    // コンテナにパネルをセット
    contentPane.add(titlePanel, BorderLayout.NORTH);
    contentPane.add(textPanel, BorderLayout.CENTER);
    // 表示する設定
    setVisible(true);
}

こんな感じです。
次は、テキストエリアをタイトルラベルと同じように拡張しようと思います。

JTextAreaの拡張

まずは、現状のクラス作成状況を確認します。

次は、画面の白い部分「テキストエリア」を拡張して文字列の表示領域を作成します。

今回も、テキストエリアを担当するクラスを作成します。ネーミングセンスが問われますが、目的と役割を明確にすることを最優先にするので。。。

RpgTextクラスを作成

RpgTextAreaクラスとします。作成はまずJTextAreaを継承します。

import javax.swing.JTextArea;

/**
 * クラス RpgTextArea の注釈をここに書きます.
 * テキストRPGの表示する文字列をこの領域に出力(描画)する。
 * 背景は黒、イメージはドラ○エのような感じにしたい。
 * 
 * @author (Takunoji)
 * @version (1.0)
 */
public class RpgTextArea extends JTextArea
{
    public RpgTextArea() {

    }
}

そして、テキストの表示を担当するので、メインクラスに書いている次の部分が不要になります。

JTextArea textarea = new JTextArea();
textarea.setColumns(40);
textarea.setRows(10);

同様に、次のようにコードをRpgTextAreaに追加します。

public class RpgTextArea extends JTextArea
{
    public RpgTextArea() {
        setColumns(40);
        setRows(10);
    }
}

そして、TextRPGMain#run()を修正

    public void run(String title) {
        setTitle(title);
        Dimension windowSize = Toolkit.getDefaultToolkit().getScreenSize();
        int xPos = (int) windowSize.getWidth() / 4;
        int yPos = (int) windowSize.getHeight() / 4;
        setBounds(xPos, yPos, xPos * 2, yPos * 2);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        TitleLabel titleLabel = new TitleLabel("Text RPG", windowSize);
        RpgTextArea textarea = new RpgTextArea();

        JPanel titlePanel = new JPanel();
        titlePanel.add(titleLabel);

        JPanel textPanel = new JPanel();
        textPanel.add(textarea);

        Container contentPane = getContentPane();
        contentPane.add(titlePanel, BorderLayout.NORTH);
        contentPane.add(textPanel, BorderLayout.CENTER);

        setVisible(true);
    }

JPanelをRpgTextAreaに修正、不要なコードを削除しました。

この状態でプログラム実行すると下のようになります。

全く変わりません。その代わり、run()メソッドのコードの量は(少しですが)減りました。
ここから、テキストエリアのおしゃれをしていきます。
参照するのはJavaDocのJTextAreaです。
他にも次のクラスを参照します。

Fontクラスを見ると、フォントファイルを指定することでオリジナルのフォントも使えるようです。

<実行結果>

TextAreaのサイズ設定

画面のサイズ指定に関して、文字入力することも考えてPCの画面サイズから文字の数、行の数を設定するようにプログラムを組みました。
理論的なところが、はっきりと決まらなかったのですが、縦横の「~分の~」という形で実装しました。

public class RpgTextArea extends JTextArea
{
    /** コンストラクタ */
    public RpgTextArea(Dimension size) {
        int widthCol = (int) size.getWidth() / 19;
        int heightRow = (int) size.getHeight() / 28;
        System.out.println("width: " + widthCol);
        System.out.println("height: " + heightRow);
        setColumns(widthCol);
        setRows(heightRow);
        ...
    }
    ...
}

実行結果は、下のような形です。

とりあえずは、これで、画面が作成できたのでここでひと段落になります。

まとめ

クラスを継承すると親クラスのメソッドなどは、自分のクラス内のメソッドのように使用することができる。
なので、下のようなコードが書ける。

public class RpgTextArea extends JTextArea
{
    /** コンストラクタ */
    public RpgTextArea(Dimension size) {
        int widthCol = (int) size.getWidth() / 19;
        int heightRow = (int) size.getHeight() / 28;
        System.out.println("width: " + widthCol);
        System.out.println("height: " + heightRow);
        setColumns(widthCol);
        setRows(heightRow);
        // 背景の描画準備
        setOpaque(true);
        // フォントの設定
        setFont(createTextFont());
        // 背景を黒にする
        setBackground(Color.BLACK);
        // 白い文字の設定
        setForeground(Color.WHITE);
        // 白いボーダーの設定
        Border border = BorderFactory.createLineBorder(Color.GREEN);
        setBorder(BorderFactory.createCompoundBorder(border,
            BorderFactory.createEmptyBorder(10, 10, 10, 10)));

        setWrapStyleWord(true);
        setLineWrap(true);
    }
    ....
}

つまるところは、親クラスのpublic, (packaged, )protectedのメソッドを子クラスが使用することができるので「setXXX」のようなメソッドを直接呼び出すことができる。

今回は、コンストラクタのみを使用した形で実装しました。
次は、テキストの表示などを行っていきたいと思います。

次回 >>>

BlueJの使い方~インストール、クラス作成、サンプルプロジェクト~

BlueJの使い方

Oracle製のIDE「BlueJ」を使ってみようと思います。
そもそも、「なんで使ってみようと思ったのか?」に関して、以下の理由があります。
下の動画のように、作成したクラスがクラス図(簡易バージョン)で表現されるところです。
<HelloWorldの実行動画>

他にも、継承関係も表示できる用ですので、クラス間の関係性を説明するときに、自分で確認するときにも便利です。

ライブラリの追加

スタックオーバーフローより
「Tools -> Preferences -> Libraries -> Add file」ということで。

「ツール→カスタマイズ→ライブラリタブ」を開いて、Jarファイルを追加、BlueJを再起動することで使用可能になるようです。

BlueJインストール

こちらのページからダウンロードできます。MSIファイルなので、クリックするだけでインストールできました。

そして、ドキュメント、サンプルプロジェクトもそろっているようです。

そんなわけで、上記の赤丸部分をクリックしてダウンロードした。ファイルの中身を見てみます。

注意点

コンパイルするときには、修正したクラスを全て、コンパイルする必要があります。

サンプルプロジェクト

ダウンロードしたprojects.zipを展開すると、Chapter01~16までプロジェクトが入っていました。
これらの中にあるフォルダが、BlueJで開くべきプロジェクトになります。

Chapter1のプロジェクト(figure)

開いたプロジェクトは下のような形でした。

これに、メインクラスを作成し、メインメソッドを追加して実行しました。

<実行結果>

サンプルプロジェクトを見る

ダウンロードしたZIPファイルを展開したあと、「projects」-> 「chapter01」-> プロジェクトとフォルダを下がっていく形になります。
具体的には、「projects」-> 「chapter01」-> 「home」を開きます。

プロジェクトを開いた結果

ここから、Javaプログラムの解説になります。

Java プログラム

開いたプロジェクトは、下のようなクラスがあります。

  • Picture
  • Circle
  • Square
  • Triangle
  • Person
  • Canvas

クラスの関係について

上記のクラス図から、Circle, Square, Triangleの3クラスがPictureクラスに呼び出されています。

そして、Picture以外のクラスがCanvasクラスを呼び出しています。

なので、ここに「」クラスを作成し、Picture、Personクラスを起動するメインメソッドを実装、起動します。

Pictureクラス

このクラスは、Circle, Square, Triangleの3クラスを呼び出しています。
詳細は、フィールド変数に上記の3クラスを持っています。
そして、コンストラクタで各クラスのインスタンス化を行い。
draw()メソッドで描画を行っています。

なので、メインメソッドでは、インスタンス化とdraw()を呼び出しを行っています。

/**
 * This class represents a simple picture. You can draw the picture using
 * the draw method. But wait, there's more: being an electronic picture, it
 * can be changed. You can set it to black-and-white display and back to
 * colors (only after it's been drawn, of course).
 *
 * This class was written as an early example for teaching Java with BlueJ.
 * 
 * @author  Michael Kölling and David J. Barnes
 * @version 2016.02.29
 */
public class Picture
{
    private Square wall;
    private Square window;
    private Triangle roof;
    private Circle sun;
    private boolean drawn;

    /**
     * Constructor for objects of class Picture
     */
    public Picture()
    {
        wall = new Square();
        window = new Square();
        roof = new Triangle();
        sun = new Circle();
        drawn = false;
    }

    /**
     * Draw this picture.
     */
    public void draw()
    {
        if(!drawn) {
            wall.moveHorizontal(-140);
            wall.moveVertical(20);
            wall.changeSize(120);
            wall.makeVisible();

            window.changeColor("black");
            window.moveHorizontal(-120);
            window.moveVertical(40);
            window.changeSize(40);
            window.makeVisible();

            roof.changeSize(60, 180);
            roof.moveHorizontal(20);
            roof.moveVertical(-60);
            roof.makeVisible();

            sun.changeColor("yellow");
            sun.moveHorizontal(100);
            sun.moveVertical(-40);
            sun.changeSize(80);
            sun.makeVisible();
            drawn = true;
        }
    }

    /**
     * Change this picture to black/white display
     */
    public void setBlackAndWhite()
    {
        wall.changeColor("black");
        window.changeColor("white");
        roof.changeColor("black");
        sun.changeColor("black");
    }

    /**
     * Change this picture to use color display
     */
    public void setColor()
    {
        wall.changeColor("red");
        window.changeColor("black");
        roof.changeColor("green");
        sun.changeColor("yellow");
    }
}

Mainクラス

このクラスは、筆者が作成したクラスです。単純にPictureクラスとPersonクラスをインスタンス化してそれぞれ描画するメソッドを起動しています。

public class Main
{
  public static void main(String[] args) {
      Picture pic = new Picture();
      Person per = new Person();

      pic.draw();
      per.makeVisible();
  }
}

Extension(拡張)

拡張機能に関して、こちらの記事にありました。

BlueJはSwingを使用して作成されているのか。。。拡張するのにSwingを使用して拡張するようです。

次の例では、ユーザーが開いたすべての BlueJ プロジェクトの名前をログに記録する拡張機能を実装しSystem.out 、他の拡張機能の使用方法を示します。インストールすると、次のようにも表示されます。

  • BlueJ ヘルプメニューの「Installed Extensions」パネルにあるエントリ
  • (あまり役に立たない) BlueJ の [ツール] メニューへのメニュー エントリ、および既存のクラスのソース コードにコメントを追加するものを含む、クラスおよびオブジェクト用に表示されるメニューへのエントリ。
  • Tools/Preferences/Extensions パネルのエントリで、お気に入りの色を入力 (および保存) します。
    拡張機能の完全なソース コードはこちらです。

単体テスト

こちらにあるドキュメントを見るとやり方が書いてあるのですが、ちょっと違うようで。。。

テストクラスを作成する

下の図のように、対象クラスを右クリックします。そして、「テストクラスの作成」を選択します。

すると、下のようなクラスが作成されます。
対象にしているクラスはXMLUtilクラスです。

作成したクラスは、下のように表示されます。