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」のようなメソッドを直接呼び出すことができる。

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

次回 >>>

Java Basic Swing Level 2〜画面作成の基本的なところ〜

イントロダクション

前回は、ちょこっとだけSwingの概要に触れました。

Java Swing はOracleのチュートリアルもあるのでそちらも参考にしながら学習できます。

JFCとスイング

参考サイトによると、以下のような説明がありました。


JFC は Java Foundation Classes の略で、グラフィカル ユーザー インターフェイス (GUI) を構築し、豊富なグラフィック機能と対話性を Java アプリケーションに追加するための一連の機能を網羅しています。

他にも、JavaFXでも画面作成ができます。こちらはSceneBuilderというツールを使用して画面の作成ができます。

SceneBuilder1

SceneBuilder2

Swingは

画面を作成できるAPIでも、JavaFXと違い、JDKに同梱されているもので、JavaFXよりも細かい部分を操作できる。
※筆者の感想です。

Swingの基本構成

前回記載した様に土台がありその上にコンポーネントを配置します。

  • JFrame, JDialog, JAppletの3クラスが土台になる
  • Jlabel, JPanel, JTextField...etcはラベルやボタンなどの部品
// 土台クラス
JFrame frame = new JFrame("FirstSwing");
// 土台のコンポーネントを載せる部分(位置などは後で指定する)
// コンテナーと呼びます
Container con = frame.getContentPane();
//コンテナの大記載をしています
con.setSize(300, 300);
// コンテナの上にラベルを配置します
con.add(new JLabel("Hello Swing"));
// 土台の上に乗せたものをおきます
frame.setContentPane(con);
// お約束ごとで閉じる時にこのクラスの起動を終了する設定など。。。
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
// 画面を表示する設定→これをFALSEにすると画面が非表示になる
frame.setVisible(true);

ソースはここからダウンロードできます。

Swingのイメージ




上記の図は、参考サイトから失敬しています。この図のように、JFrameの上にJMenubar, Contentpaneの上に黄色のラベルがのっかっていて
左側のように表示されています。

これから作成するアプリも、同様な形で作成されます。

JFrameの使い方

JFrameクラスでは、上の図のようにコンポーネント(画面の部品)を載せる(追加する)のにはContentPaneを使用して追加します。
下のコードでは、フレームにラベルを追加する処理を行っています。

JFrameをインスタンス化

JFrame frame = new JFrame();
Container cont = frame.getContentPane();
cont.add(new JLabel("ラベル1"));

そして、起動時に終了するときの処理「画面を閉じたらプログラムを終了する設定」と
フレームを表示する処理を行っています。
frame.pack()はコンポーネントのサイズにフレームを合わせる処理です。

// お約束ごとで閉じる時にこのクラスの起動を終了する設定など。。。
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
// 画面を表示する設定→これをFALSEにすると画面が非表示になる
frame.setVisible(true);

JFrameにコンポーネントを追加

下のコードは、チュートリアルにあるコードです。

frame.getContentPane().add(yellowLabel, BorderLayout.CENTER);

このコードをわかりやすく書くと下のようになります。全体のコードはこちらにありました。

JFrame frame = new JFrame();

Container con = frame.getContentPane();

JLabel yellowLabel = new JLabel();
yellowLabel.setOpaque(true);
yellowLabel.setBackground(new Color(248, 213, 131));
yellowLabel.setPreferredSize(new Dimension(200, 180));

con.add(yelloLabel);

もっとたくさんの部品を追加したい場合は、JPanelを使用して、JFrame -> JPanel -> そのほかの部品のような関係で部品(コンポーネント)を追加します。

//Create a panel and add components to it.
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(someBorder);
contentPane.add(someComponent, BorderLayout.CENTER);
contentPane.add(anotherComponent, BorderLayout.PAGE_END);

やはり、プログラムを組んだ時の完成図はイメージするしかないので、下のようなイメージを持つとわかりやすいと思います。

サンプルコード

参考サイトはこちらです。

/** An application that requires no other files. */
public class GlassPaneDemo {
    static private MyGlassPane myGlassPane;

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("GlassPaneDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Start creating and adding components.
        JCheckBox changeButton =
                new JCheckBox("Glass pane \"visible\"");
        changeButton.setSelected(false);

        //Set up the content pane, where the "main GUI" lives.
        Container contentPane = frame.getContentPane();
        contentPane.setLayout(new FlowLayout());
        contentPane.add(changeButton);
        contentPane.add(new JButton("Button 1"));
        contentPane.add(new JButton("Button 2"));

        //Set up the menu bar, which appears above the content pane.
        JMenuBar menuBar = new JMenuBar();
        JMenu menu = new JMenu("Menu");
        menu.add(new JMenuItem("Do nothing"));
        menuBar.add(menu);
        frame.setJMenuBar(menuBar);

        //Set up the glass pane, which appears over both menu bar
        //and content pane and is an item listener on the change
        //button.
        myGlassPane = new MyGlassPane(changeButton, menuBar,
                                      frame.getContentPane());
        changeButton.addItemListener(myGlassPane);
        frame.setGlassPane(myGlassPane);

        //Show the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

/**
 * We have to provide our own glass pane so that it can paint.
 */
class MyGlassPane extends JComponent
                  implements ItemListener {
    Point point;

    //React to change button clicks.
    public void itemStateChanged(ItemEvent e) {
        setVisible(e.getStateChange() == ItemEvent.SELECTED);
    }

    protected void paintComponent(Graphics g) {
        if (point != null) {
            g.setColor(Color.red);
            g.fillOval(point.x - 10, point.y - 10, 20, 20);
        }
    }

    public void setPoint(Point p) {
        point = p;
    }

    public MyGlassPane(AbstractButton aButton,
                       JMenuBar menuBar,
                       Container contentPane) {
        CBListener listener = new CBListener(aButton, menuBar,
                                             this, contentPane);
        addMouseListener(listener);
        addMouseMotionListener(listener);
    }
}

/**
 * Listen for all events that our check box is likely to be
 * interested in.  Redispatch them to the check box.
 */
class CBListener extends MouseInputAdapter {
    Toolkit toolkit;
    Component liveButton;
    JMenuBar menuBar;
    MyGlassPane glassPane;
    Container contentPane;

    public CBListener(Component liveButton, JMenuBar menuBar,
                      MyGlassPane glassPane, Container contentPane) {
        toolkit = Toolkit.getDefaultToolkit();
        this.liveButton = liveButton;
        this.menuBar = menuBar;
        this.glassPane = glassPane;
        this.contentPane = contentPane;
    }

    public void mouseMoved(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mouseDragged(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mouseClicked(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mouseEntered(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mouseExited(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mousePressed(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mouseReleased(MouseEvent e) {
        redispatchMouseEvent(e, true);
    }

    //A basic implementation of redispatching events.
    private void redispatchMouseEvent(MouseEvent e,
                                      boolean repaint) {
        Point glassPanePoint = e.getPoint();
        Container container = contentPane;
        Point containerPoint = SwingUtilities.convertPoint(
                                        glassPane,
                                        glassPanePoint,
                                        contentPane);
        if (containerPoint.y < 0) { //we are not in the content pane
            if (containerPoint.y + menuBar.getHeight() >= 0) { 
                //The mouse event is over the menu bar.
                //Could handle specially.
            } else { 
                //The mouse event is over non-system window 
                //decorations, such as the ones provided by
                //the Java look and feel.
                //Could handle specially.
            }
        } else {
            //The mouse event is probably over the content pane.
            //Find out exactly which component it is over.
            Component component = SwingUtilities.getDeepestComponentAt(
                                        container,
                                        containerPoint.x,
                                        containerPoint.y);

            if ((component != null) 
                && (component.equals(liveButton))) {
                //Forward events over the check box.
                Point componentPoint = SwingUtilities.convertPoint(
                                            glassPane,
                                            glassPanePoint,
                                            component);
                component.dispatchEvent(new MouseEvent(component,
                                                     e.getID(),
                                                     e.getWhen(),
                                                     e.getModifiers(),
                                                     componentPoint.x,
                                                     componentPoint.y,
                                                     e.getClickCount(),
                                                     e.isPopupTrigger()));
            }
        }

        //Update the glass pane if requested.
        if (repaint) {
            glassPane.setPoint(glassPanePoint);
            glassPane.repaint();
        }
    }
}

サンプルコード解説

上記のプログラムを解説します。まずはこのようなプログラムを見るときにメインメソッドを探します。
理由は必ずメインメソッドが動くからです。

メインメソッド

public static void main(String[] args) {
    //Schedule a job for the event-dispatching thread:
    //creating and showing this application's GUI.
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            createAndShowGUI();
        }
    });
}

単純にcreateAndShowGUI()を起動しているだけですね。細かいところを言うと、こちらのサイトにあるように「イベントディスパッチスレッドで非同期的に実行させます。」ということなので、重い処理は後から非同期で実行する設定だと思ってもらえれば、OKです。
言葉を変えるならば、非同期で処理を実行するので画面の描画中にほかの処理も残ったリソースで実行してくれる設定をしているということです。

createAndShowGUI()

JFrameを生成して、CheckBoxとMenubarを生成。JButtonも同様に生成。

JFrame frame = new JFrame("GlassPaneDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//Start creating and adding components.
JCheckBox changeButton =
new JCheckBox("Glass pane \"visible\"");
changeButton.setSelected(false);

//Set up the content pane, where the "main GUI" lives.
Container contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout());
contentPane.add(changeButton);
contentPane.add(new JButton("Button 1"));
contentPane.add(new JButton("Button 2"));

//Set up the menu bar, which appears above the content pane.
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("Menu");
menu.add(new JMenuItem("Do nothing"));
menuBar.add(menu);
frame.setJMenuBar(menuBar);

インナークラスである「MyGlassPane」はフィールド変数に定義、最終的には、JFrameにセットしている(setGlassPane())

//Set up the glass pane, which appears over both menu bar
//and content pane and is an item listener on the change
//button.
myGlassPane = new MyGlassPane(changeButton, menuBar,
frame.getContentPane());
changeButton.addItemListener(myGlassPane);
frame.setGlassPane(myGlassPane);

途中「changeButton.addItemListener(myGlassPane);」では、「項目選択イベントが発生すると、リスナー・オブジェクトのitemStateChangedメソッドが呼び出されます。」ので、「項目の選択」を行ったときの処理を行うのは「itemStateChanged()」でありますよ。と設定しています。
ちなみに、ItemListenerのメソッドをitemStateChangedはオーバーライドしています。

class MyGlassPane extends JComponent implements ItemListener { 
    //React to change button clicks.
    public void itemStateChanged(ItemEvent e) {
        setVisible(e.getStateChange() == ItemEvent.SELECTED);
    }
}

他の部分は、マウスリスナー(マウス操作時の処理)を定義しています。

でわでわ。。。

関連ページ一覧

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜

Java Basic Swing〜Javaで画面アプリを作る。〜

イントロダクション

java.swingパッケージを使用した。画面作成をやります。Java SwingはJava開発をするためにインストールしたJDKの中に入っているので外部のライブラリをインストールして。。。Mavenで依存関係を記述して。。Gradleでインポートして。。。などの手間がありません。

このパッケージ(Swingフレームワーク)はクラス→画面コンポーネント(ラベルや、テキストフィールドなど)になるのでクラス・オブジェクトの扱いを理解するのにとても役立ちます。

Swingについて

画面作成用のフレームワークで、どこに何のクラスを使用するか?が決まっています。

Swing is java frame work to make view. And defind components to use part of view.

詳細はドキュメント(英語)を見てください。

https://docs.oracle.com/javase/tutorial/uiswing/components/index.html

JFrameクラスを継承すると。。。

 Swingの部品構成

画面を作る時は以下の順で画面コンポーネント(部品(ボタンなど))を置いていくイメージです。

Swing components based on “JFrame” or “JDialog” I think ... And put on components one.

<イメージ>

画面の1番下に土台を置きます。(JFrame, JDialo, JAppl

そして、土台の上にはボタンやラベルなどを置きます。これらの部品は土台になる部品とは親クラスが違います。※土台になる部品とボタンなどの部品は別物だと思ってくれればOK

実際の実装は今後やります。

I will write a ample code next blog...

サンプル(sample)

// フレーム(土台)
JFrame frame = new JFrame(“土台”);

// 部品を乗せるスペース=コンテナー
Container con = frame.getContentPain();

// パネル(これもコンテナーフレームより上に乗っかるもの)
JPanel panel = new JPanel();
// パネルにコンポーネントを追加
panel.add(new JLabel("ラベル1"));

// コンテナーにパネル(これもコンテナー)を追加する
con.add(panel);

文字表現ですが、下のようにコンポーネント(画面の部品)を追加して画面を作成します。

  1. フレーム
  2. パネル
  3. ラベルやテキストフィールドなど※チェックボックスなどもある

JLabelを表示する

JLabelを表示する、ハローワールド的なプログラムです。
コメントに処理内容を記述していますが、大まかに土台の上にラベルを載せて表示しているというところです。

ラベル以外のコンポーネント(パネルやフレームを含むすべての画面部品のこと)もAPIで用意されています。※ドキュメント自体は英語ですが、Google翻訳で十分に読めるものでした。

public static void main(String[] args) {
    // 土台クラス
    JFrame frame = new JFrame("FirstSwing");
    // 土台のコンポーネントを載せる部分(位置などは後で指定する)
    // コンテナーと呼びます
    Container con = frame.getContentPane();
    //コンテナの大記載をしています
    con.setSize(300, 300);
    // コンテナの上にラベルを配置します
    con.add(new JLabel("Hello Swing"));
    // 土台の上に乗せたものをおきます
    frame.setContentPane(con);
    // お約束ごとで閉じる時にこのクラスの起動を終了する設定など。。。
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.pack();
    // 画面を表示する設定→これをFALSEにすると画面が非表示になる
    frame.setVisible(true);
}

Swingを使用して作成したアプリケーションです。「テキストRPG」といいます。

まとめ

つまるところは、Java Swingを使用すると、TextRPGのようなGUIアプリケーションが作成できます。そして、JavaはほとんどのPCで起動できるのでラズパイなどでも起動できマス。
今回は、画面を作成して文字を表示するためのラベルを使用してみました。HTMLで使用するフォーム部品もSwingで使用することができるのでブラウザではなく単体のアプリケーションとして作成することができます。起動するときは「実行可能JAR」というファイルに変換してやれば、ダブルクリックで起動することができます。

関連ページ一覧

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜

Java Basic Swing 〜ラズパイの画面作成 〜

イントロダクション

前回は、JavaFXでの画面を作成しようと試みましたが、XWindowを使用するという時点でイマイチ。。。ということになり(自分の中で)

java.swingで実装してみようとなりました。

試みとしては、「CUIでのインストールしたRPiに画面をJavaで作成する」というのが今回の目的です。。。一応、これもダメだったらC++で作成します。

予定を変更してNo画面アプリを作成する方向にシフトします。

ちなみにC++でやるときはQtを使用します。※ java.swing.*で出来ない時です。。。

そして、java.swingでの実装はオブジェクト指向の良い勉強になります。→別の機会にやります。

自分はオブジェクト指向の基本をこのパッケージ(java.swing)で理解しました。

Introduction

I had tried create view on RPi using JavaFX. But it not can be, cause we have to execute GUI mode. That's why change plan to create app of no view.

And, studing java.swing.* is good for study java and object-orientation.

I understood object-orientation becouse studied java.swing.*.

サンプル実装

本当に簡単ですが、サンプルコードです。

This is a simple sample of swing program.

package jp.zenryoku.sample.swing;

import javax.swing.*;
import java.awt.*;

public class PracSwingMain extends JFrame {
    public static void main(String[] args) {
        JFrame frame = new JFrame("FirstSwing");
        Container con = frame.getContentPane();
        con.setSize(300, 300);
        con.add(new JLabel("Hello Swing"));
        frame.setContentPane(con);
        frame.pack();
        frame.setVisible(true);

    }
}

実行結果とソースのキャプチャです。※右上の「Hello Swing」という部分がSwingで作成した画面です。

昔のSWINGとは違っている様なので、復習が必要です。。。

Java Swingで動かしてみました(Execute Swing Program)

ダメでした。。。Swingでの実装も以下の様なエラーが出ます。

Rpi3では起動した様なのですが。。。自分のはRpi2。。。

CUI上でGUIアプリは起動できないのか?ちょいと調査します。→ No画面アプリ作成にシフトします。→フレームバッファを調査します。

But I can not see java view. out error as below.

java.awt.HeadlessException: 
No X11 DISPLAY variable was set,
but this program performed an operation which requires it.

Cause of this Exception, it is need setting "Frame Buffer". I think....I will investigate one.

その後(after)

結局は、ラズパイにXFCE4を入れて Swingを起動する方向になりました。

As result... I will try to install XFCE4 on RPi and execute Swing.

http://zenryokuservice.com/wp/2018/09/30/rpi-java-swing〜ラズパイにjava-swingアプリを起動する〜/

[rakuten ids="juju-shop:10000216"]

関連ページ一覧

  1. ラズパイ SSH接続メモ
  2.  ラズパイ Under-voltage detected! 〜エラー対処〜
  3.  ラズパイ(CUI)セットアップ
  4.  RPi Settingup Wifi in CUI ~ラズパイ CUI Wifi接続~
  5. Memos about Settingup RPi ~使用したコマンドメモ~
  6. RPi and JavaFX sample of deployment 〜ラズパイにサンプルデプロイ〜
  7.  RPi JavaFX execution ~ラズパイ JavaFX自動起動~
  8. RPi Install Git 〜ラズパイにGitのインストール〜
  9. RPi Java Swing〜ラズパイにJava Swingアプリを起動する〜※失敗しています。。。」
  10. RPi Maven Install 〜ラズパイでMeven
  11. Install XFCE4 on RPi 〜ラズパイに高速軽量デスクトップインストール〜

 



Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜