CodeZine(コードジン)

特集ページ一覧

アドベンチャーゲームに活用できるフラグ管理のやり方

簡易インタプリタとフラグを利用したゲームの作成

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/09/15 12:00

ゲームでは、その進行状況を「フラグ」と呼ばれるもので管理します。本稿では、シンプルなアドベンチャーゲームを作成することで、このフラグ管理の方法について解説します。

はじめに

 ゲームでは、その進行状況を「フラグ」と呼ばれるもので管理します。フラグとは「flag」、つまり「旗」のことで、ONまたはOFFどちらかの状態を表すものです。例えば「王様と会話したかどうか?」で発生するイベントを変えたいときなどは、王様と会話したときに、フラグがONになるよう(俗に「フラグを立てる」といいます)設定しておき、イベント発生時に、そのフラグをチェックして、ONならばイベントA、OFFならばイベントBのように処理を分岐します。主に、アドベンチャーゲームや、ロールプレイングゲームなどで用いられる手法ですね。今回はそのフラグ管理の方法を解説してみたいと思います。

 サンプルプログラムは「ミステリールーム」。家に隠されたダイヤモンドを探し出す、ミステリーアドベンチャーです。さあ、あなたはこの家の謎を解いて、クリアすることが出来るでしょうか?

 ちなみに元ネタは、パソコンゲームの名作「ミステリーハウス」(株式会社マイクロキャビン、1982年)。このサンプルを見て懐かしさを覚える人は、かなり古くからのパソコンユーザーだと思います。

対象ユーザー

 ゲームプログラムに興味がある方を対象とします。また、グラフィックとシナリオデータを用意するだけで、別のゲームにすることも可能ですので、一度ゲームを作ってみたいという人にもオススメします。

必要な環境

 アプレットの実行には、Javaを動作させることの出来るブラウザーが必要です。もし、お使いのブラウザーでアプレットが動かない場合は、Javaソフトウェアの無料ダウンロードから、最新のJava Runtime Environment(JRE)を入手してください。

 また、コンパイルにはJ2SE Development Kit(JDK) 1.4以上が必要です。

画面構成と遊び方

ミステリールーム
ミステリールーム

 部屋の様子が表示されるのがメイン画面。ここをマウスでクリックするだけで進んでいきます。その下にはメッセージが表示され、取得したアイテムは右側に表示されます。

 右下には現在の位置と方角が出ますので、部屋を移動する際の参考にしてください。

 怪しいものがあればどんどんクリックして構いませんが、場合によってはゲームオーバーになるかもしれませんので、ご注意を! 見事ダイヤモンドを入手すれば、ゲームクリアです。

クラスの構造

 このプログラムは以下の5つのクラスより構成されています。

クラス名概要
MysteryGameこのゲームの土台であるアプレット。
GamePanelメイン画面の描画、およびマウスイベントを担当。
Scenarioフラグを管理し、ゲームの進行を制御。
Messageクリックされたときに表示されるメッセージデータ。
Sceneメイン画面に表示される画像のデータ。

 ポイントとなるのは、ゲームの流れを全て外部ファイル(「message.csv」と「scene.csv」)に記述しているということです。MessageクラスとSceneクラスは、それぞれ同じ名前の外部ファイルに対応しています。

 マウスがクリックされると、Scenarioクラスが条件にマッチするMessageオブジェクトとSceneオブジェクトを検索する、という流れでゲームが進行します。

 では、ゲームデータの構成を中心に、全体の流れを見ていきましょう。

メッセージの構造

 Messageクラスは、マウスをクリックしたときに表示されるメッセージとその情報を管理します。「message.csv」に記述されているメッセージデータ1件が、Messageオブジェクト1つに対応します。

 「message.csv」は、カンマ区切りのCSV形式になっており、各要素は、左から順番に以下のような構成になってます。

順番内容詳細
1シーン名後述のSceneクラスで定義されたシーン名。
2~5クリック範囲クリックで反応する範囲。 X座標、Y座標、幅、高さの順に指定。
6表示条件このメッセージが表示されるための条件文。
7後処理メッセージを表示した後の処理コマンド。
8次のシーン名次に移るシーンの名前。
9メッセージ表示されるメッセージ。

 例えば、カギを発見するときのデータは以下のようになってます。

シーン名xywidthheight条件処理次シーンメッセージ
ROOM1W52951123!keyKEYカギを発見した!このカギはどこで使うのだろう?
KEY00240160key&+カギROOM1Wカギを手に入れた。

 これは、ROOM1W(第1の部屋 西)のシーンで、座標(52, 95)から高さ11、幅23の範囲(花瓶の花)をクリックしたとき、keyフラグが立ってなかったら、「カギを発見~」とのメッセージを出し、KEY(カギ発見)のシーンに推移するという意味になります。

 また、KEYシーンでは、座標(0, 0)から高さ240、幅160(つまり画面のどこでも)クリックした場合に、keyフラグをセットし、所持アイテムに「カギ」を追加して、ROOM1Wシーンに戻る、ということになります。

 方向転換も、アイテムの取得も、次の部屋への移動も、すべてこの流れの中で行っていることを理解しておいてください。

 「GamePanel.java」にdebugというクラス変数がありますが、これをtrueにすると、画面にクリック範囲が表示されます。シーンごとに用意されたメッセージの様子が分かりますので、一度試して欲しいと思います。

debugモード画面
debugモード画面

シーンの構造

 Sceneクラスは各シーンごとの画像とその情報を管理します。「scene.csv」に記述されているシーンデータ1件が、Sceneオブジェクト1つに対応します。「scene.csv」もCSV形式で、以下のような構成なってます。

順番内容詳細
1シーン名前述のMessageクラスにおける「シーン名」「次のシーン名」で使用する名前。
2表示条件この画像が表示されるための条件文。
3ファイル名画像のファイル名。
4位置情報現在地を表す部屋の名前と方向。

 たとえば、第2の部屋で北を向いたときのデータは以下のようになってます。

シーン名条件ファイル名位置情報
ROOM2Nchair&trapdoorroom2n3.gif第2の部屋 北
ROOM2Nchairroom2n2.gif第2の部屋 北
ROOM2Nroom2n1.gif第2の部屋 北

 これは、ROOM2N(第2の部屋 北)のシーンで、chairフラグとtrapdoorフラグがONの場合は「room2n3.gif」を表示し、charフラグのみがONの場合は「room2n2.gif」、何もフラグが立ってないときは「room2n1.gif」を表示することなります。グラフィックの変化で、椅子を移動させた様子を表すのに利用しています。

アプレットでのファイル読み込み
 アプレットでファイルを読み込む際には、相対パスではなくて、URLで指定しないとセキュリティに引っかかってしまいます。一度ストリームをオープンすれば、後は通常のファイルと同じように読み込むことが出来ますので、オープンの仕方さえ気をつけておけば大丈夫です。
「Scenario.java」より抜粋
URL scnUrl = new URL(applet.getDocumentBase(), SCENE_DATA);
BufferedReader br = new BufferedReader(
    new InputStreamReader(scnUrl.openStream()));
 ちなみに、セキュリティ上の観点から、アプレットと同一サーバーにあるファイルしか読めないようになってます。
アプレットでの画像読み込み
 アプレットで画像を読み込む場合は、AppletgetImageメソッドで読み込むのが一般的ですが、このメソッドは非同期に画像を読み込むので、その後java.awt.MediaTrackerで読み込みの終了を監視する必要があります。
 しかし、Swingベースで作成している場合は、ImageIconを利用することで、このMediaTrackerの処理を省略できます。ImageIconはコンストラクターの中で、MediaTrackerを使って、読み込みが終了するまで待ってくれるからです。
「GamePanel.java」より抜粋
URL imgUrl = new URL(applet.getDocumentBase(), "image/"+imageName);
ImageIcon image = new ImageIcon(imgUrl);
sceneImage = image.getImage();
 ちなみに、ImageIconで読み込む場合も、上記のセキュリティの問題から、
 画像ファイルをURLで指定する必要があります。

簡易インタプリタの実装

 フラグの状況をチェックする条件文や、メッセージ表示後の処理は、いわばゲームの内容そのものであり、このデータをいかに組み上げるかで、ゲームの面白さが大きく左右されます。難易度、演出、ゲーム性に関わる部分であるため、ゲーム制作の最後まで修正を繰り返します。

 ですから、プログラム本体とは切り離して管理する方が、メンテナンスの上でも望ましいことになります。

 そこで、一般的には、ごく簡単なインタプリタ言語を実装し、フラグのチェックや命令を別ファイルに記述する方法が用いられます。今回のプログラムでは、Scenarioクラスがその言語の解析と実行を担っています。

 では、そのインタプリタ言語を解析するメソッドを見ていきましょう。

条件文の解析

public boolean check(String condition) {
    // チェック条件がない場合はtrueを返す
    if (condition == null || condition.equals("")) {
        return true;
    }
    
    // 条件の解析
    String[] cnd = condition.split("&");
    for (int i = 0; i < cnd.length; i++) {
        char c = cnd[i].charAt(0);
        Boolean f;
        switch (c) {
            // フラグがOFFでtrue
            case '!':
                // 先頭の1文字除去
                String flag = cnd[i].substring(1);
                // フラグを取り出してチェック
                f = (Boolean)(flagMap.get(flag));
                if (f != null && f.booleanValue() == true) {
                    return false;
                }
                break;
            // フラグがONでtrue
            default:
                // フラグを取り出してチェック
                f = (Boolean)(flagMap.get(cnd[i]));
                if (f == null || f.booleanValue() == false) {
                    return false;
                }
                break;
        }
    }
    
    // 全ての条件を満たしていた場合
    return true;
}

 checkメソッドは、ゲームデータに記述された条件文を解析し、フラグと照合した上でマッチするかどうかを判定するメソッドです。条件文は以下のように記述します。

[コマンド]フラグ名&[コマンド]フラグ名&...

 区切り文字は「&」で、コマンドには以下の記号が用意されています。

コマンド意味
!フラグがOFFの場合True
なしフラグがONの場合True

 例えば、条件文が「clock&!chair」の場合、clockフラグがONになっていて、かつchairフラグがOFFの場合に、Trueを返します。

 メッセージの条件文と、シーンの条件文は同じ文法になってます。複数の条件がマッチする場合は、最初にマッチしたデータを採用する仕様になってます。ゲームの進行において後に表示されるメッセージを先に記述することになりますので、データを用意する際に気をつけてください。

後処理コマンドの説明

public void exec(String command) {

    // 後処理がない場合はすぐに戻る
    if (command == null || command.equals("")) {
        return;
    }

    // 後処理の解析
    String[] cmd = command.split("&");
    for (int i = 0; i < cmd.length; i++) {
        char c = cmd[i].charAt(0);
        switch (c) {
            // アイテム取得
            case '+':
                // 先頭の1文字除去
                String item = cmd[i].substring(1);
                // アイテムを追加
                applet.addItem(item);
                break;
            case '@':
                // フラグとアイテムをクリアしてゲームを初期化
                flagMap = new HashMap();
                applet.clearItem();
                break;
            default:
                // フラグをONにする
                flagMap.put(cmd[i], new Boolean(true));
                break;
        }
    }
}

 execメソッドは、記述されたコマンド文を解析し、指定された処理を行うメソッドです。これも条件文と同様に、「&」を区切り文字としてコマンドを記述していきます。

記号コマンド
+アイテムを追加する。
@ゲームを初期化する。
なしフラグを立てる。

 例えば、「key&+カギ」というコマンドは、keyフラグをONにし、アイテムリストに「カギ」を追加するという意味を持ちます。

 ちなみに、フラグはフラグ名をキーとするHashMapで管理しています。ですので、基本型のbooleanではなくて、参照型のBooleanを利用してます(Java 5.0ならば、この辺の処理をもっと簡単に記述することが可能でしょう)。

最後に

 このゲームは、データを外部ファイルに記述してますので、プログラムを変更することなく、ストーリーを変えることが可能です。ですので、可愛い女の子の画像を用意して、会話文を用意すれば、恋愛アドベンチャーにも変わります。

 でも、実際にゲームのデータを作るのは、意外と難しいかもしれません。ちょっとフラグの管理を間違えただけで、ゲームがそれ以上進まなくなったりします。俗に言う「ハマリ」ですね。ただ、動いたときの感動はひとしおですので、ぜひ挑戦してもらいたいと思います。

 また、より複雑なインタプリタ言語を実装するという方向性もあります。今回は、条件の論理演算にANDしか用意しませんでしたが、ORもあれば表現の幅がさらに広がることでしょう。また、後処理でも、音を鳴らすためのコマンドの実装などが考えられます。

 このサンプルを元に、それぞれのアプローチでゲーム作りに取り組んでもらいたいと思います。

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 安永ノリカズ(ヤスナガノリカズ)

    株式会社リバーヒルソフトに7年間勤務した後、フリーのゲームクリエイターとして独立。デジタルハリウッド福岡校にて非常勤Java講師を始める。現在、講師業務をしながら、のんびりとゲームを制作中。Webサイト:安永ノリカズのゲーム制作&Javaサンプル集

All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5