★はじめに★
最終話 900iシリーズで動くロールプレイングゲームを作る
[JAVA PRESS Vol.36]

ゲーム:ファイナルそらみクエスト
対応端末:F900i・N900i・P900i・SH900i・D900i


 テキスト 布留川 英一 (ん・ぱか工房)
 イラスト つきみの せひら (せひらねっと)



う〜ん、今月で地球留学も終わりかぁ。
長いようで短かったなぁ…。
そらみちゃん、感傷に浸るのもいいけど、課題の卒業制作はできてるの?
あ"〜! そういえばまだ何も考えてなかったよ。
…このあいだ作ったアドベンチャーゲームでいいかな?
も〜、手を抜いちゃダメでしょうに!
そらみちゃんがいかに地球の携帯テクノロジーを学んだか、留学の成果として提出するものなんでしょ〜?
はぁ〜い…。それじゃあドラクエやFFも遊べる本気のFOMA、『900iシリーズ』で動くゲームを作ろっか♪
基本的な機能は505iシリーズと一緒だけど、実行ファイルの容量が100K、スクラッチパッドの容量が400Kに大幅アップしたんだ。
パケット代が定額制のパケ・ホーダイもついに登場するし、通信しまくりなネットゲーも作れるね!
定額といっても料金けっこうかかるし、ユーザー限定しちゃうから、まずはスタンドアロンゲームがいいんじゃない?
それじゃあ最後の総決算だし、究極のRPGを作ろうよ。
名付けて『ファイナルそらみクエスト』!
名前がベタすぎだよ、そらみちゃん…。


★FOMA 900iシリーズ★


FOMA
「FOMA」は、NTTドコモが2001年10月からサービスを開始したIMT-2000方式による携帯電話サービスです。アナログ携帯電話を第1世代、デジタル携帯電話(PCD方式)を第2世代、FOMAは第3世代と位置付けられています。
IMT-2000方式の特徴としては、 などが挙げられます。
また、通信料金もPDC端末より安く、「パケットパック」というオプション料金を払うことで、通信料金を最大10分の1にできます。さらに、6月1日からは「パケ・ホーダイ」と呼ばれる月額3900円の定額制サービスもはじまる予定です。


900iシリーズ
900iシリーズは「本気のFOMA」と言われてます。900i以前のFOMA端末はPDC端末の後追いで作っていたため、最新機能は常にPDC端末の方が先に搭載され、魅力が薄くなっていました。今回の900iがはじめて機能的にPDC端末を追い越した端末となります。
900iシリーズの特徴としては、 の5つが挙げられます。

iアプリのパワーアップ
900iシリーズでは、JavaのAPI仕様として「DoJa-3.5」が採用されています。API自体は505iシリーズで採用されてる「DoJa-3.0」とほぼ同じですが、容量的には実行ファイル(JARファイル)のサイズが30Kバイトから100Kバイト、スクラッチパッドのサイズが200Kバイトから400Kバイトと、大幅に拡張されました。これにより、ロールプレイングゲームやアドベンチャーゲームなど大容量を必要とするゲームも遊べるようになりました。
iアプリサイズの拡張
DoJa1.0 DoJa2.0 DoJa3.0 DoJa3.5
実行ファイルサイズ 10Kバイト 30Kバイト 30Kバイト 100Kバイト
スクラッチパッドサイズ 10Kバイト程度 100Kバイト 200Kバイト 400Kバイト
画面サイズ 120x130ドット程度 132x144ドット程度 240x260ドット程度 240x260ドット程度
フォントサイズ 12ドット程度 12ドット 12ドット,16ドット程度,24ドット 12ドット,16ドット程度,24ドット
HTTP送信データ 5Kバイト程度 5Kバイト 10Kバイト 80Kバイト
HTTP受信データ 10Kバイト程度 10Kバイト 20Kバイト 150Kバイト
赤外線送受信データ × 30Kバイト 100Kバイト 100Kバイト
「程度」は機種依存により多少値が上下する項目に付けています。

Flashサイズの拡張
900iシリーズでは、Flashファイルのサイズも20Kバイトから100Kバイトと、大幅に拡張されました。20Kバイト制限はかなりに厳しかったため、505iシリーズでは用途が限られていましたが、今後はいろいろな場面で使われるようになるでしょう。
Flashサイズの増加
505iシリーズ 900iシリーズ
Flashファイルサイズ 20Kバイト 100Kバイト

デコメール
「デコメール」は携帯電話用の「HTMLメール」のことです。メール本文の背景や文字の色、大きさや動きを変えることができます。パソコン上では悪意のあるスクリプトを含むメールが作れるため嫌われているHTMLメールですが、携帯の世界で普及するかどうか興味深いところです。

着モーション
「着モーション」は「iモーション」で電話着信時を知らせる機能のことです。iモーションは、FOMA向けの動画配信サービスのことで、300Kバイト/40秒の動画を再生できます。携帯で撮影したムービーをそのまま着モーションとして指定することもできます。

キャラ電
「キャラ電」は、TV電話で会話する時、自分の代わりに好きなキャラクターを使って電話に出る機能のことです。ボタン操作でアクションさせて、自分の気持ちを相手に伝えることもできます。キャラ電のキャラクターを自作するツールもドコモのサイトで公開されているので、興味ある人は作ってみると良いでしょう。


★開発環境を整える★


今回のゲームを作るのに必要な開発ツールは次の3つです。どれも無償で入手できます。

Java 2 SDK, Standard Edition Version 1.3(JDK1.3以降)
パソコン上で動くJavaアプリを作るための開発キットです。サン・マイクロシステムズのサイトで入手できます。次に説明するiαppli Development Kitを実行するのに必要なので、それより先にインストールします。インストーラの指示に従ってインストールしてください。

iαppli Development Kit for DoJa 3.5
900iシリーズ用のiアプリを作るための開発キットです。NTTドコモのサイトで入手できます。ボタン1つでビルド(コンパイル+検証+JAR作成)やエミュレータでの実行ができます。インストーラの指示に従ってインストールしてください。Microsoft Windows 2000/XPで動作保証しています。
同サイトから開発に役立つドキュメントが入手できるので、いっしょにダウンロードしてください。
CLDC API Documentation, V1.0(Japanese)
CLDCのAPIリファレンスはNTTドコモのサイトで入手できるドキュメントの中には含まれないので、サン・マイクロシステムズのサイトから入手してください。


★ロールプレイングゲームを作る★


今回は大作ということで、iアプリ開発の基礎は省略して、いきなりゲームを作るところからはじめます。基礎から覚えたい人は、第1回(JavaPress Vol.28)で解説しているので、そちらを参照してください。

それでは、本題のロールプレイングゲーム「ファイナルそらみクエスト」を作ります。プレイヤーは勇者そらみとなり、城に住む魔王ヘニヘニを退治しに行くゲームです。このゲームには「マップ画面」と「戦闘画面」の2つの場面が存在します。「マップ画面」では方向キーでそらみを移動させます。「平地」を歩くとたまにピーチャンが出現して戦闘になります。「家」に戻ると体力が回復します。「城」へ行くとヘニヘニが出現して戦闘になります。「木」が立っているところは進めません。

LV(レベル)
そらみの強さ。敵を一定数倒すとアップ。

HP(ヒットポイント)
そらみの体力/最大体力。0になるとゲームオーバー。

操作方法
「戦闘画面」ではそらみと敵が交互に攻撃し、体力が先になくなった方が負けとなります。決定キーでセリフ進行、1キー「攻撃」と2キー「逃げる」でコマンド選択を行います。「攻撃」を選んだ時は、戦闘を開始し、勝った時は経験値を取得します。経験値がたまるとレベルが上がり、そらみの攻撃力と防御力がアップします。「逃げる」を選んだ時は、一定確率で逃げられますが、失敗した時は敵の攻撃を一方的に受けます。

操作方法


プロジェクトの作成
「iαppli Development Kit」をインストールすると、スタートメニューに[プログラム|iDK3.5|iappliTool for DoJa3.5(FOMA)]が追加されているので、それを選択してください。「iαppli Development Kit」の核となるツール「iαppliTool」が起動します。


[プロジェクト新規作成]ボタンを押すと、新規作成ダイアログが開くので、プロジェクト名に"SoramiQuest"と入力します。


[作成]ボタンを押すと、「iαppli Development Kit」のルートにあるappsディレクトリの下にSoramiQuestディレクトリ(C:\iDK3.5\apps\SoramiQuest)が生成されます。さらにsその下に次の3つのディレクトリが生成されます。

画像ファイルの準備
このゲームに必要な画像ファイルは、次の8枚です。resディレクトリに置いてください。

・平地
0.gif
48x48


・家
1.gif
48x48


・城
2.gif
48x48


・木
3.gif
48x48


・そらみ
4.gif
48x48


・ピーチャン
5.gif
120x120


・ヘニヘニ
6.gif
120x120


・エンディング
7.gif
200x160



プログラムファイルの作成
今回のプログラムは、次の2つのクラスで構成されています。 プログラムファイルをテキストエディタで作成し、srcディレクトリに置いてください。「iαppli Development Kit」には、標準のエディタは付属してないので、自分にあったエディタを用意してください。


SoramiQuestクラス
SoramiQuestクラスは、プログラムの本体となるクラスです。SoramiCanvasオブジェクトを生成し、 DisplayクラスのsetCurrent()メソッドに引数として渡しています。最後にSoramiCanvasのexe()メソッドを呼んでいます。
SoramiQuest.java
import com.nttdocomo.ui.*;

//そらみクエスト(本体)
public class SoramiQuest extends IApplication {

    //アプリの開始
    public void start() {
        SoramiCanvas c=new SoramiCanvas();
        Display.setCurrent(c);
        c.exe();
    }
}


SoramiCanvasクラス
SoramiCanvasクラスは、キャンバスとなるクラスです。
SoramiQuest.java
import com.nttdocomo.ui.*;
import java.util.*;

//そらみクエスト(キャンバス)
class SoramiCanvas extends Canvas {
    //システム定数
    final static int//シーン
        S_MAP    =0,//マップ
        S_APPEAR =1,//出現
        S_COMMAND=2,//コマンド
        S_ATTACK =3,//攻撃
        S_DEFENCE=4,//防御
        S_ESCAPE =5;//逃げる
    final static int[][] MAP={//マップ
        {3,3,3,3,3,3,3,3,3,3},
        {3,1,0,0,0,0,3,0,0,3},
        {3,0,0,0,0,0,3,3,0,3},
        {3,0,3,3,3,3,3,3,0,3},
        {3,0,0,3,0,0,0,3,0,3},
        {3,3,0,3,0,3,3,3,0,3},
        {3,0,0,3,0,0,0,0,0,3},
        {3,0,3,3,0,3,0,3,3,3},
        {3,0,0,0,0,3,0,0,2,3},
        {3,3,3,3,3,3,3,3,3,3}};

    //そらみ定数
    final static int[] SO_MAXHP  ={0,30,50,70};//最大体力
    final static int[] SO_ATTACK ={0,5,10,30}; //攻撃力
    final static int[] SO_DEFENCE={0,0,5,10};  //守備力
    final static int[] SO_EXP    ={0,0,3,6};   //必要経験値

    //敵定数
    final static String[] EN_NAME={"ピーチャン","ヘニヘニ"};//名前
    final static int[] EN_MAXHP  ={10,50};          //最大体力
    final static int[] EN_ATTACK ={10,26};          //攻撃力
    final static int[] EN_DEFENCE={0,16};           //守備力
    final static int[] EN_EXP    ={1,99};           //取得経験値

    //システム
    int      init=S_MAP;         //初期化
    int      scene;              //シーン
    int      key;                //キー
    Graphics g    =getGraphics();//グラフィック
    Image[]  image=new Image[8]; //イメージ
    Random   rand =new Random(); //乱数

    //そらみ
    int soX  =1; //X座標
    int soY  =2; //Y座標
    int soLV =1; //レベル
    int soHP =30;//体力
    int soEXP=0; //経験値

    //敵
    int enType;//種類
    int enHP;  //体力

    //実行
    void exe() {
        try {
            //フォント指定
            g.setFont(Font.getFont(Font.SIZE_MEDIUM));

            //イメージ読み込み
            for (int i=0;i<8;i++) {
                MediaImage m=MediaManager.getImage(
                    "resource:///"+i+".gif");
                m.use();
                image[i]=m.getImage();
            }

            //無限ループ
            while (true) tick();
        } catch (Exception e) {}
    }

    //時間経過
    void tick() throws Exception {
        int i,j,k;

        //初期化
        if (init>=0) {
            scene=init;
            init =-1;
            key  =-200;
        }

        //マップ
        if (scene==S_MAP) {
            //操作
            i=0;
            if (key==Display.KEY_UP) {
                if (MAP[soY-1][soX]<=2) {soY--;i=1;}
            } else if (key==Display.KEY_DOWN) {
                if (MAP[soY+1][soX]<=2) {soY++;i=1;}
            } else if (key==Display.KEY_LEFT) {
                if (MAP[soY][soX-1]<=2) {soX--;i=1;}
            } else if (key==Display.KEY_RIGHT) {
                if (MAP[soY][soX+1]<=2) {soX++;i=1;}
            }

            //戦闘・回復
            if (i==1) {
                if (MAP[soY][soX]==0 && (rand.nextInt()>>>1)%10==0) {
                    enType=0;init=S_APPEAR;}
                if (MAP[soY][soX]==1) soHP=SO_MAXHP[soLV];
                if (MAP[soY][soX]==2) {enType=1;init=S_APPEAR;}
            }

            //描画
            g.lock();
            for (j=-3;j<=3;j++) {
                for (i=-3;i<=3;i++) {
                    k=3;
                    if (0<=soX+i && soX+i<MAP[0].length &&
                        0<=soY+j && soY+j<MAP.length) {
                        k=MAP[soY+j][soX+i];
                    }
                    g.drawImage(image[k],96+48*i,96+48*j);
                }
            }
            g.drawImage(image[4],96,96);
            drawStatus();
            g.unlock(true);
        }
        //出現
        else if (scene==S_APPEAR) {
            //初期化
            enHP=EN_MAXHP[enType];

            //フラッシュ
            Thread.sleep(300);
            for (i=0;i<6;i++) {
                if (i%2==0) {
                    g.setColor(g.getColorOfName(g.BLACK));
                } else {
                    g.setColor(g.getColorOfName(g.WHITE));
                }
                g.fillRect(0,0,240,240);
                Thread.sleep(100);
            }

            //メッセージ
            drawBattle(EN_NAME[enType]+"があらわれた");
            waitSelect();
            init=S_COMMAND;
        }
        //コマンド
        else if (scene==S_COMMAND) {
            drawBattle("1.攻撃  2.逃げる");
            key=-200;
            while (init==-1) {
                if (key==Display.KEY_1) init=S_ATTACK;
                if (key==Display.KEY_2) init=S_ESCAPE;
                Thread.sleep(100);
            }
        }
        //攻撃
        else if (scene==S_ATTACK) {
            //メッセージ
            drawBattle("そらみの攻撃");
            Thread.sleep(1000);

            //フラッシュ
            for (i=0;i<10;i++) {
                if (i%2==0) {
                    g.setColor(g.getColorOfName(g.BLACK));
                    g.fillRect(60,45,120,120);
                } else {
                    g.drawImage(image[5+enType],60,45);
                }
                Thread.sleep(100);
            }

            //ダメージ計算
            i=SO_ATTACK[soLV]-EN_DEFENCE[enType]+
                (rand.nextInt()>>>1)%10;
            if (i<= 1) i= 1;
            if (i>=99) i=99;

            //メッセージ
            drawBattle(i+"ダメージ与えた!");
            waitSelect();

            //体力計算
            enHP-=i;
            if (enHP<=0) enHP=0;

            //勝利
            init=S_DEFENCE;
            if (enHP==0) {
                //メッセージ
                drawBattle(EN_NAME[enType]+"を倒した");
                waitSelect();

                //経験値計算
                soEXP+=EN_EXP[enType];
                if (soLV<3 && SO_EXP[soLV+1]<=soEXP) {
                    soLV++;
                    drawBattle("レベルアップした");
                    waitSelect();
                }

                //エンディング
                if (enType==1) {
                    g.lock();
                    g.setColor(g.getColorOfName(g.BLACK));
                    g.fillRect(0,0,240,240);
                    g.drawImage(image[7],20,40);
                    g.unlock(true);
                    waitSelect();
                    SoramiQuest.getCurrentApp().terminate();
                }
                init=S_MAP;
            }
        }
        //敵攻撃
        else if (scene==S_DEFENCE) {
            //メッセージ
            drawBattle(EN_NAME[enType]+"の攻撃");
            Thread.sleep(1000);

            //フラッシュ
            for (i=0;i<10;i++) {
                if (i%2==0) {
                    g.setColor(g.getColorOfName(g.WHITE));
                    g.fillRect(0,0,240,240);
                } else {
                    g.lock();
                    g.setColor(g.getColorOfName(g.BLACK));
                    g.fillRect(0,0,240,240);
                    g.drawImage(image[5+enType],60,45);
                    drawStatus();
                    drawBattle(EN_NAME[enType]+"の攻撃");
                    g.unlock(true);
                }
                Thread.sleep(100);
            }

            //ダメージ計算
            i=EN_ATTACK[enType]-SO_DEFENCE[soLV]+
                (rand.nextInt()>>>1)%10;
            if (i<= 1) i= 1;
            if (i>=99) i=99;

            //メッセージ
            drawBattle(i+"ダメージ受けた!");
            waitSelect();

            //体力計算
            soHP-=i;
            if (soHP<=0) soHP=0;

            //敗北
            init=S_COMMAND;
            if (soHP==0) {
                drawBattle("そらみは力尽きた");
                waitSelect();
                SoramiQuest.getCurrentApp().terminate();
            }
        }
        //逃げる
        else if (scene==S_ESCAPE) {
            //メッセージ
            drawBattle("そらみは逃げ出した");
            waitSelect();

            //失敗
            init=S_MAP;
            if (enType==1 || (rand.nextInt()>>>1)%100<=10) {
                drawBattle(EN_NAME[enType]+"は回り込んだ");
                waitSelect();
                init=S_DEFENCE;
            }
        }

        //後処理
        key=-200;
        Thread.sleep(100);
    }

    //イベント処理
    public void processEvent(int type,int param) {
        if (type==Display.KEY_PRESSED_EVENT) key=param;
    }

    //決定キー待ち
    void waitSelect() throws Exception {
        key=-200;
        while (key!=Display.KEY_SELECT) Thread.sleep(100);
    }

    //戦闘画面の描画
    void drawBattle(String message) {
        int color=(soHP!=0)?g.BLACK:g.RED;
        g.lock();
        g.setColor(g.getColorOfName(color));
        g.fillRect(0,0,240,240);
        drawStatus();
        if (enHP>=0) g.drawImage(image[5+enType],60,45);
        g.setColor(g.getColorOfName(g.WHITE));
        g.fillRect(5-2,175-2,230+4,60+4);
        g.setColor(g.getColorOfName(color));
        g.fillRect(5,175,230,60);
        g.setColor(g.getColorOfName(g.WHITE));
        g.drawString(message,12,200);
        g.unlock(true);
    }

    //ステータスの描画
    void drawStatus() {
        int color=(soHP!=0)?g.BLACK:g.RED;
        g.setColor(g.getColorOfName(g.WHITE));
        g.fillRect(5-2,5-2,230+4,30+4);
        g.setColor(g.getColorOfName(color));
        g.fillRect(5,5,230,30);
        g.setColor(g.getColorOfName(g.WHITE));
        g.drawString("そらみ LV"+soLV+" HP"+soHP+
            "/"+SO_MAXHP[soLV],12,30);
    }

    //描画
    public void paint(Graphics g) {}
}


シーンの遷移
このゲームには次の6つのシーンがあります。現在のシーンはscene変数で保持しています。次に遷移するシーンはinit変数で保持しています。遷移しない時はinitは-1を保持しています。
SoramiCanvas.javaの一部
final static int//シーン
    S_MAP    =0,//マップ
    S_APPEAR =1,//出現
    S_COMMAND=2,//コマンド
    S_ATTACK =3,//攻撃
    S_DEFENCE=4,//防御
    S_ESCAPE =5;//逃げる
SoramiCanvas.javaの一部
int      init=S_MAP;         //初期化
int      scene;              //シーン



マップデータ
マップ画面の木や家の配置はMAP配列で保持しています。
SoramiCanvas.javaの一部
final static int[][] MAP={//マップ
    {3,3,3,3,3,3,3,3,3,3},
    {3,1,0,0,0,0,3,0,0,3},
    {3,0,0,0,0,0,3,3,0,3},
    {3,0,3,3,3,3,3,3,0,3},
    {3,0,0,3,0,0,0,3,0,3},
    {3,3,0,3,0,3,3,3,0,3},
    {3,0,0,3,0,0,0,0,0,3},
    {3,0,3,3,0,3,0,3,3,3},
    {3,0,0,0,0,3,0,0,2,3},
    {3,3,3,3,3,3,3,3,3,3}};

数値の意味は次の通りです。
0:平地
1:家
2:城
3:木(移動不可)

実際のマップ全域は次のようになります。


そらみがマップ上のどの位置にいるかは、soXとsoYで保持しています。初期位置は家の下である(1,2)です。
SoramiCanvas.javaの一部
int soX  =1;          //X座標
int soY  =2;          //Y座標


そらみパラメータ
そらみはレベルによって最大体力、攻撃力・防御力の値が変化します。各レベルの最大体力をSO_MAXHP、攻撃力をSO_ATTACK、防御力をSO_DEFENCEで保持しています。また、各レベルごとの必要経験値はSO_EXPで保持しています。

今回は最大レベルを3とし、そこまでの情報を保持しています。
SoramiCanvas.javaの一部
final static int[] SO_MAXHP  ={0,30,50,70};//最大体力
final static int[] SO_ATTACK ={0,5,10,30}; //攻撃力
final static int[] SO_DEFENCE={0,0,5,10};  //守備力
final static int[] SO_EXP    ={0,0,3,6};   //必要経験値

そらみのレベルはsoLV、体力はsoHP、経験値はsoEXPで保持しています。
SoramiCanvas.javaの一部
int soLV =1; //レベル
int soHP =30;//体力
int soEXP=0; //経験値


敵パラメータ
敵は種類によって名前、最大体力、攻撃力・防御力、取得経験値の値が変化します。各種類名前をEN_NAME、最大体力をEN_MAXHP、攻撃力をEN_ATTACK、防御力をEN_DEFENCE、取得経験値をEN_EXPで保持しています。

敵の種類はenType、体力はenHPで保持しています。
SoramiCanvas.javaの一部
int enType;//種類
int enHP;  //体力

敵の種類は次の通りです。
0:ピーチャン
1:ヘニヘニ


乱数
乱数を使うには、java.utilパッケージにあるRandomクラスを使います。使い方は、Randomオブジェクトを生成して、nextInt()メソッドを呼ぶだけです。これで、int型の最小値から最大値(-2147483648〜2147483647)の乱数を取得することができます。取得した乱数を>>>演算子を使って、1ビットだけ符号なしビットシフトを行えば、最上位ビットが0になるので、正数の乱数(0〜2147483647)を取得することができます。さらに、正数の乱数を任意の値で割ったあまりを求めれば、0から任意の正数未満の乱数を取得することができます。
SoramiCanvas.javaの一部
Random   rand =new Random(); //乱数


無限ループ
SoramiCanvasのexe()メソッドでは、フォント指定やイメージ読み込みといった初期化処理を行ったあと、無限ループでtick()メソッドを繰り返し呼んでいます。ゲームの中核となる処理は、この無限ループで行っています。
SoramiCanvas.javaの一部
void exe() {
    try {
        //フォント指定
        g.setFont(Font.getFont(Font.SIZE_MEDIUM));

        //イメージ読み込み
        for (int i=0;i<8;i++) {
            MediaImage m=MediaManager.getImage(
                "resource:///"+i+".gif");
            m.use();
            image[i]=m.getImage();
        }

        //無限ループ
        while (true) tick();
    } catch (Exception e) {}
}


マップ(S_MAP)
方向キーによってそらみが移動します。移動先の足元が「平地」の時は1/10の確率でピーチャンを出現、「家」の時は体力を回復、「城」の時はヘニヘニを出現させています。
SoramiCanvas.javaの一部
if (scene==S_MAP) {
    //操作
    i=0;
    if (key==Display.KEY_UP) {
        if (MAP[soY-1][soX]<=2) {soY--;i=1;}
    } else if (key==Display.KEY_DOWN) {
        if (MAP[soY+1][soX]<=2) {soY++;i=1;}
    } else if (key==Display.KEY_LEFT) {
        if (MAP[soY][soX-1]<=2) {soX--;i=1;}
    } else if (key==Display.KEY_RIGHT) {
        if (MAP[soY][soX+1]<=2) {soX++;i=1;}
    }

    //戦闘・回復
    if (i==1) {
        if (MAP[soY][soX]==0 && (rand.nextInt()>>>1)%10==0) {
            enType=0;init=S_APPEAR;}
        if (MAP[soY][soX]==1) soHP=SO_MAXHP[soLV];
        if (MAP[soY][soX]==2) {enType=1;init=S_APPEAR;}
    }

    //描画
    g.lock();
    for (j=-3;j<=3;j++) {
        for (i=-3;i<=3;i++) {
            k=3;
            if (0<=soX+i && soX+i<MAP[0].length &&
                0<=soY+j && soY+j<MAP.length) {
                k=MAP[soY+j][soX+i];
            }
            g.drawImage(image[k],96+48*i,96+48*j);
        }
    }
    g.drawImage(image[4],96,96);
    drawStatus();
    g.unlock(true);
}


出現(S_APPEAR)
敵体力に最大体力を代入した後、画面全体をフラッシュさせます。そして、「○○があらわれた」のメッセージを表示後、「コマンド」に遷移します。
SoramiCanvas.javaの一部
else if (scene==S_APPEAR) {
    //初期化
    enHP=EN_MAXHP[enType];

    //フラッシュ
    Thread.sleep(300);
    for (i=0;i<6;i++) {
        if (i%2==0) {
            g.setColor(g.getColorOfName(g.BLACK));
        } else {
            g.setColor(g.getColorOfName(g.WHITE));
        }
        g.fillRect(0,0,240,240);
        Thread.sleep(100);
    }

    //メッセージ
    drawBattle(EN_NAME[enType]+"があらわれた");
    waitSelect();
    init=S_COMMAND;
}


コマンド(S_COMMAND)
「1.攻撃  2.逃げる」のメッセージを表示後、1キーを押した時は「攻撃」に、2キーを押した時は「逃げる」に遷移します。
SoramiCanvas.javaの一部
else if (scene==S_COMMAND) {
    drawBattle("1.攻撃  2.逃げる");
    key=-200;
    while (init==-1) {
        if (key==Display.KEY_1) init=S_ATTACK;
        if (key==Display.KEY_2) init=S_ESCAPE;
        Thread.sleep(100);
    }
}


攻撃(S_ATTACK)
「そらみの攻撃」のメッセージを表示後、敵画像をフラッシュさせます。そして、ダメージを計算し、ダメージ分だけ敵の体力を減らします。
ダメージの計算式は
そらみ攻撃力-敵防御+乱数(0〜9)
です。

敵の体力が0になった時、「○○を倒した」のメッセージを表示後、マップへ遷移します。敵がヘニヘニの時は、エンディング画像を表示後、アプリを終了させます。
SoramiCanvas.javaの一部
else if (scene==S_ATTACK) {
    //メッセージ
    drawBattle("そらみの攻撃");
    Thread.sleep(1000);

    //フラッシュ
    for (i=0;i<10;i++) {
        if (i%2==0) {
            g.setColor(g.getColorOfName(g.BLACK));
            g.fillRect(60,45,120,120);
        } else {
            g.drawImage(image[5+enType],60,45);
        }
        Thread.sleep(100);
    }

    //ダメージ計算
    i=SO_ATTACK[soLV]-EN_DEFENCE[enType]+
        (rand.nextInt()>>>1)%10;
    if (i<= 1) i= 1;
    if (i>=99) i=99;

    //メッセージ
    drawBattle(i+"ダメージ与えた!");
    waitSelect();

    //体力計算
    enHP-=i;
    if (enHP<=0) enHP=0;

    //勝利
    init=S_DEFENCE;
    if (enHP==0) {
        //メッセージ
        drawBattle(EN_NAME[enType]+"を倒した");
        waitSelect();

        //経験値計算
        soEXP+=EN_EXP[enType];
        if (soLV<3 && SO_EXP[soLV+1]<=soEXP) {
            soLV++;
            drawBattle("レベルアップした");
            waitSelect();
        }

        //エンディング
        if (enType==1) {
            g.lock();
            g.setColor(g.getColorOfName(g.BLACK));
            g.fillRect(0,0,240,240);
            g.drawImage(image[7],20,40);
            g.unlock(true);
            waitSelect();
            SoramiQuest.getCurrentApp().terminate();
        }
        init=S_MAP;
    }
}


防御(S_DEFENCE)
「○○の攻撃」のメッセージを表示後、画面全体をフラッシュさせます。そして、ダメージを計算し、ダメージ分だけそらみの体力を減らします。
ダメージの計算式は
敵攻撃力-そらみ防御+乱数(0〜9)
です。

そらみの体力が0になった時は、「そらみは力尽きた」のメッセージを表示後、アプリを終了させます。
SoramiCanvas.javaの一部
else if (scene==S_DEFENCE) {
    //メッセージ
    drawBattle(EN_NAME[enType]+"の攻撃");
    Thread.sleep(1000);

    //フラッシュ
    for (i=0;i<10;i++) {
        if (i%2==0) {
            g.setColor(g.getColorOfName(g.WHITE));
            g.fillRect(0,0,240,240);
        } else {
            g.lock();
            g.setColor(g.getColorOfName(g.BLACK));
            g.fillRect(0,0,240,240);
            g.drawImage(image[5+enType],60,45);
            drawStatus();
            drawBattle(EN_NAME[enType]+"の攻撃");
            g.unlock(true);
        }
        Thread.sleep(100);
    }

    //ダメージ計算
    i=EN_ATTACK[enType]-SO_DEFENCE[soLV]+
        (rand.nextInt()>>>1)%10;
    if (i<= 1) i= 1;
    if (i>=99) i=99;

    //メッセージ
    drawBattle(i+"ダメージ受けた!");
    waitSelect();

    //体力計算
    soHP-=i;
    if (soHP<=0) soHP=0;

    //敗北
    init=S_COMMAND;
    if (soHP==0) {
        drawBattle("そらみは力尽きた");
        waitSelect();
        SoramiQuest.getCurrentApp().terminate();
    }
}


逃げる(S_ESCAPE)
「そらみは逃げ出した」のメッセージを表示後、成功時は「マップ」へ、失敗時は「防御」へ遷移します。失敗する確率は、ピーチャンの時10/100、ヘニヘニの時100/100です。
SoramiCanvas.javaの一部
else if (scene==S_ESCAPE) {
    //メッセージ
    drawBattle("そらみは逃げ出した");
    waitSelect();

    //失敗
    init=S_MAP;
    if (enType==1 || (rand.nextInt()>>>1)%100<=10) {
        drawBattle(EN_NAME[enType]+"は回り込んだ");
        waitSelect();
        init=S_DEFENCE;
    }
}


キーイベント
CanvasクラスのprocessEvent()メソッドをオーバーライドすることによって、キーイベントを取得することができます。今回は、押されたキーの種類をkey変数に代入するという処理を行っています。何も押されていない状態の時は、keyに-200を指定しています。-200というのはキー定数と競合しない値の中から適当に選んだ値です。
SoramiCanvas.javaの一部
public void processEvent(int type,int param) {
    if (type==Display.KEY_PRESSED_EVENT) key=param;
}


決定キー待ち
waitSelect()メソッドでは、決定キーを押すまでループし続けて、次に移行しないようにする処理を行っています。戦闘画面でよく使います。
SoramiCanvas.javaの一部
void waitSelect() throws Exception {
    key=-200;
    while (key!=Display.KEY_SELECT) Thread.sleep(100);
}


ADF設定
「iαppliTool」の[ADF設定]ボタンを押すと、ADF設定ダイアログが開きます。各項目に情報を入力して[設定]ボタンを押すと、ADF(C:\iDK3.5\apps\SoramiQuest\bin\SoramiQuest.jam)に内容が反映されます。今回は、「AppName」キーに"ファイナルそらみクエスト"、「AppClass」キーに"SoramiQuest"を入力してください。

「AppName」キーは、端末にダウンロードした時、iアプリ一覧の画面で表示される名前です。「AppClass」キーには、iアプリの本体となるクラスの名前を指定します。「PackageURL」「AppSize」「LastModified」も入力必須項目ですが、一度ビルドすれば自動的に入力されます。


また、今回のゲームは画面サイズ240x240用に作っています。ADF設定の「DrawArea」に"240x240"を指定してください。DrawAreaはアプリが前提としている画面サイズを指定するところで、前提としている画面サイズより大きな画面の端末で実行する時、画面中央に表示されるようになります。



エミュレータでの実行
「iαppliTool」の[起動]ボタンを押すと、パソコン上でiアプリを実行することができます。実行を終了するには、携帯端末の終了キーを押します。また、メニュー[端末]で、エミュレータのビューを変更することができます。



実機での実行
iアプリをネットに公開してダウンロードできるようにするには、JARファイルとADFの他に、ダウンロード用HTMLが必要です。HTMLの書式は以下のようになります。
SoramiQuest.htm
<OBJECT DECLARE ID="SoramiQuest" DATA="SoramiQuest.jam" TYPE="application/x-jam"></OBJECT>
ファイナルそらみクエストを<A IJAM="#SoramiQuest" HREF="error.htm">ダウンロード</A>


「OBJECTタグ」は、iアプリに対応するADFを参照するために使われます。OBJECTタグのID属性は、Aタグから参照される名前を指定します。この名前はHTMLファイル内で一意であれば何でもOKです。DATA属性には、ADFの位置を示すURLを指定します。

「Aタグ」は、ダウンロード対象のアプリケーションに対応するOBJECTタグを参照するために使用します。ユーザがこのAタグで囲まれた文字を選択することで、iアプリのダウンロードを開始します。AタグのIJAM属性には、OBJECTタグのID属性に指定した名前を指定します。HREF属性には、iアプリが実行できない端末でアクセスした際に表示するHTMLファイルのURLを指定します。
error.htm
▼−端末エラー−▼<BR>
iアプリ対応のiモードでアクセスしてね<BR>

SoramiQuest.jar、SoramiQuest.jam、SoramiQuest.htm、error.htmの4つのファイルを、FTPソフトでネットにアップロードすれば完成です。iモードからSoramiQuest.htmにアクセスしてiアプリをダウンロードしてください。


★おわりに★


最終回記念に「そらみ着ボイス」を配布中です。携帯にセットすれば、メールや電話の着信をそらみが声で教えてくれるよ。900i/505iシリーズと504iシリーズ、ボーダフォン・au端末対応しています。

http://npaka.net/sorami/i/




−戻る−


(C)Npaka/Sehira, 2003-2004