見出し画像

昔を懐かしみたい人ための携帯プログラミング講座 - CEDEC 2000/J2ME MIDP/PDA Profile

【注意】この記事は2000年にホームページに載せてた記事の修正版です。

2000年9月、ゲーム開発者向けのカンファレンス「CEDEC 2000」にて、年末に発売されるJava搭載のiモード 502iシリーズについての発表がありました。
・1つのアプリサイズは10KB。
・データ保存領域(スクラッチパッド)のサイズがアプリ毎に5KB。
・通信できるのはダウンロードしたサーバーのみ
という制限があるとのことです。

さらに9月下旬の「東京ゲームショウ2000秋」にて、Java搭載iモード向けのネットワークRPG「サムライ ロマネスク」が発表されました。戦国時代の侍の人生をテーマにした、最大50万人のプレーヤーが同時に参加できるRPGで、Java搭載のiモードの発売と同時にサービス開始予定とのことです。Javaと携帯電話の組み合わせによって、ゲームの世界がどう変わっていくか楽しみです。

そこで今回は、リリースされたばかりの「J2ME MIDP Early Access 1.0」を使って携帯電話用の対戦格闘ゲームを作ります。残念ながらJava搭載した携帯電話はまだ発売されていないので、開発キットに付属するエミュレータで実行します。

1. J2ME MIDPとPDA Profile

J2MEは、機器に応じてコンフィギュレーションとプロファイルの組み合わせを選んで利用します。J2MEでは処理能力の低い機器でもJavaプログラムが動くように、従来のJ2SEから機能を絞りこんでサブセットを定義します。この定義のことを「コンフィギュレーション」と呼びます。現在以下の2つのコンフィギュレーションが提案されています。

・J2ME CLDC(J2ME Connected, Limited Device Configuration)
・J2ME CDC(J2ME Connected Device Configuration)

「J2ME CLDC」はPalmやiモードなどの処理能力やメモリに制限のあるデバイスを対象としているコンフィギュレーションで、JVMのサブセットである「KVM」を使います。
「J2ME CDC」はカーナビ、セットトップ・ボックスなどの中程度の処理能力を持つデバイスを対象としているコンフィギュレーションで、JVMのフルセット(従来のJVM)を使います。

また、組み込み機器はそれぞれ特有の機能を持つため、それに対応できるように用途に応じた特殊なAPIが必要になります。このAPIのことを「プロファイル)」と呼びます。現在以下の2つのプロファイルが提案されています。

・J2ME MIDP(J2ME Mobile Information Device Profile)
・PDA Profile

J2ME MIDP」は、iモードのような携帯電話を想定したプロファイルです。「MIDlet」という形式のアプリを作ってネット上に公開することにより、AppletのようにJava搭載の携帯電話でダウンロードして実行できるようになります。
PDA Profile」(リンク切れ)はPalmなどのPDA向けのプロファイルで、標準化活動が7月下旬に始まりました。前回までPalmOS用ゲームを作るのに使っていたkjavaパッケージは、廃止されました。

2. 開発環境の準備

今回のゲーム開発には、「J2ME MIDP Early Access 1.0」と「J2SE Version 1.2.2以降」を使います。どちらも無償で入手することができます。

・J2ME MIDP Early Access 1.0
「J2ME MIDP Early Access 1.0」は、Javaで携帯電話用アプリケーションを作るための開発キットです。この中には「J2ME CLDC」のAPIも含まれているので別途CLDCを入手する必要はありません。
インストールは、JDC(Java Developer Connection)のサイト(リンク切れ)からダウンロードして解凍するのみになります。

・J2SE Version 1.2.2以降
「J2ME MIDP」で開発を行う際には、「J2SE Version 1.2.2以降」も必要です。コンパイルする時のjavacなどはJ2SEを利用します。

インストールが完了したら両開発環境のbinディレクトリにパスを通します。
C:¥java¥にインストールする時のパスは以下のようになります。

set path=c:¥java¥jdk1.2.2¥bin¥;c:¥java¥j2me_cldc¥bin;%path%

3. 文字列を表示する

「Hello World!」という文字列を表示するプログラムを作ります。このプログラムは以下の2つのクラスで構成されています。

・StringEx.java:本体
・StringCanvas.java:キャンバス

◎StringExクラス
StringExクラスは、文字列を表示するプログラムの本体となるクラスです。

【StringEx.java】

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

//文字列の表示:本体
public class StringEx extends MIDlet
   implements CommandListener {
   private Command      cmdExit;//終了コマンド
   private StringCanvas canvas; //キャンバス

   //コンストラクタ
   public StringEx() {
       cmdExit = new Command("Exit", Command.SCREEN, 2);
       canvas  = new StringCanvas();
   }

   //アプリケーションの開始
   public void startApp() {
       canvas.addCommand(cmdExit);
       canvas.setListener(this);
       Display.getDisplay(this).setCurrent(canvas);
   }

   //アプリケーションの一時停止
   public void pauseApp() {
   }

   //アプリケーションの終了
   public void destroyApp(boolean unconditional) {
   }

   //コマンドアクション
   public void commandAction(Command command, Displayable screen) {
       //終了する
       if (command == cmdExit) {
           destroyApp(true);
           notifyDestroyed();
       }
   }
}

・パッケージ
J2ME MIDPには以下の4つのパッケージがあります。

・javax.microedition.midlet:MIDlet本体
・javax.microedition.lcdui:ユーザーインターフェイス
・javax.microedition.rms:ファイルの読み書き
・javax.microedition.io:ネットワーク

今回は上の2つのパッケージだけを利用します。

MIDletクラス
「J2ME MIDP」で作るアプリの本体は、MIDletクラスを継承します。これを継承したクラスは、以下の3つのメソッドをオーバーライドする必要があります。

・startApp(): アプリの開始時に呼ばれる。
・pauseApp(): アプリの一時停止時に呼ばれる。
・destroyApp(): アプリの終了時に呼ばれる。

CommandクラスとCommandListenerインターフェイス
画面左下のExitメニューを選択すると、アプリが終了します。このメニューはCommandクラスとCommandListenerインターフェイスを使って、以下の手順で作成します。

(1)終了コマンドの生成
(2)キャンバスに終了コマンドとコマンドリスナを追加
(3)コマンドリスナの実装

まずCommandクラスで、終了コマンドを作ります。

cmdExit = new Command("Exit", Command.SCREEN, 2);

コンストラクタの引数は以下の通りです。

Command(String label, int commandType, int priority)
    label:画面に表示される文字列
    commandType:コマンドタイプ
        SCREEN(アプリケーションで定義する命令)
        OK(OKコマンド)
        CANCEL(キャンセルコマンド)
        STOP(ストップコマンド)
        BACK(バックコマンド)
    priority:優先順位(小さいほど優先)

優先順位が1番高いコマンドは左下のメニューに、2番目に高いコマンドは右下のメニューに表示されます。コマンドが3つ以上ある時には右下のメニューが「Menu」になり、それを選択するとコマンド一覧が表示されるようになります。

キャンバスへの終了コマンドとコマンドリスナの追加は、以下のように行います。

canvas.addCommand(cmdExit);
canvas.setListener(this);

StringExクラス自身をコマンドリスナとしてキャンバスに追加したので、このクラスにCommandListenerインターフェイスをつけて、commandAction()メソッドを実装する必要があります。

void commandAction(Command command, Displayable screen)
    command:コマンド
    screen: イベントが発生した画面

これでExitメニューが表示され、それが選択されるとcommandAction()メソッドが呼ばれるようになります。

このメソッドでは、destroyApp()メソッドとnotifyDestroyed()メソッドを呼んで、アプリケーションの終了処理を行っています。

・Displayクラス
Displayクラスで携帯電話のディスプレイに表示するキャンバスを指定しています。

Display.getDisplay(this).setCurrent(canvas);

StringCanvas
StringCanvasクラスは、文字列を表示するキャンバスです。

【StringCanvas.java】

import javax.microedition.lcdui.*;

//文字列の表示:キャンバス
public class StringCanvas extends Canvas {

   //コンストラクタ
   public StringCanvas() {
   }

   //文字列の表示
   public void paint(Graphics g) {
       g.drawString("Hello World!", 0, 0, g.LEFT|g.TOP);
   }
}

・Canvasクラス
StringCanvasクラスは、Canvasクラスを継承してpaint()メソッドをオーバーライドしています。このpaint()メソッドに渡されるGraphicsオブジェクトをによって画面を行ます。

・Graphicsクラス
Graphicsクラスには画面に文字列や絵の表示を行なうためのメソッド群が用意されています。ここでは文字列を表示するdrawString()メソッドを使います。

g.drawString("Hello World!", 0, 0, g.LEFT|g.TOP);
public void drawString(String str, int x, int y, int anchor)
    str:表示する文字列
    x:X座標
    y:Y座標
    anchor:軸とする場所
        HCENTER(水平中央)
        VCENTER(垂直中央)
        TOP(上端)
        BOTTOM(下端)
        LEFT(左端)
        RIGHT(右端)
        BASELINE(ベースライン)

ancherは指定したXY座標に文字列のどの位置を置くかを設定します。例えば左上を軸にする時には"g.LEFT|g.TOP"、中央を軸にする時に"g.HCENTER|g.VCENTER"を指定します。

コンパイルと検証
ソースコードの準備ができたら「javac」でコンパイルします。

javac -g:none -bootclasspath c:¥javamidp-ea1libmidp.jar *.java

「-bootclasspath」オプションで、開発キットのlibディレクトリ内にあるmidp.jarを指定します。「-g:none」オプションは付けるとデバッグ情報が書き込まれないぶんクラスファイルのサイズが小さくなります。

次に「preverify」でクラスファイルを検証します。J2SEでは実行時にクラスが安全かどうかチェックしてますが、処理能力が低い組み込み機器ではかなりの負担になってしまいます。
そこでJ2ME CLDCでは実行前に「preverify」によって、スタックマップ情報をクラスファイルに追加し、実行時の負荷を減らしています。

ここで注意しなければならない点はclasspathオプションでmidp.jarを指定しても、うまく認識しません。midp.jarを解凍して、classpathに解凍先ディレクトリを指定すると認識します。

jar xvf midp.jar

preverifyのコマンドは以下の通りです。

preverify -classpath .;c:¥java¥midp-ea1¥lib¥ -d . StringEx
preverify -classpath .;c:¥java¥midp-ea1¥lib¥ -d . StringCanvas 

dオプションで出力先をカレントディレクトリに指定してるので、検証前のクラスファイルに上書きされる形で検証済みクラスファイルが出力されます。

プログラムを実行する
検証済みクラスファイルができたらエミュレータ「midp.exe」で実行します。

midp -classpath .;c:¥javamidp-ea1libmidp.jar StringEx

「midp.exe」は標準4階調モノクロで画面サイズが96x100のエミュレータです。実行前に

set SCREEN_DEPTH=8
set ENCODING=SJIS 

を入力すれば8bitカラーやSJIS対応になります。

4. 絵を表示する

絵を表示するプログラムを作ります。
このプログラムは以下の2つのクラスで構成されています。

・ImageEx.java:本体
・ImageCanvas.java:キャンバス

◎画像ファイルの用意
MIDPで読み込み可能なファイル形式はJPEGとGIFです。プログラムと同じディレクトリにsample.gifを置きます。

【sample.gif】

◎ImageExクラス
ImageExクラスは、画像を表示するプログラムの本体となるクラスです。
クラス名が違うだけでStringExクラスとほぼ同じです。

【ImageEx.java】

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

//画像の表示:本体
public class ImageEx extends MIDlet
   implements CommandListener {
   private Command     cmdExit;//終了コマンド
   private ImageCanvas canvas; //キャンバス

   //コンストラクタ
   public ImageEx() {
       cmdExit = new Command("Exit", Command.SCREEN, 2);
       canvas  = new ImageCanvas();
   }

   //アプリケーションの開始
   public void startApp() {
       canvas.addCommand(cmdExit);
       canvas.setListener(this);
       Display.getDisplay(this).setCurrent(canvas);
   }

   //アプリケーションの一時停止
   public void pauseApp() {
   }

   //アプリケーションの終了
   public void destroyApp(boolean unconditional) {
   }

   //コマンドアクション
   public void commandAction(Command command, Displayable screen) {
       //終了する
       if (command == cmdExit) {
           destroyApp(false);
           notifyDestroyed();
       }
   }
}

◎ImageCanvasクラス
ImageCanvasクラスは、画像を表示するキャンバスです。

import javax.microedition.lcdui.*;

//画像の表示:キャンバス
public class ImageCanvas extends Canvas {

   //コンストラクタ
   public ImageCanvas() {
   }

   //絵の表示
   public void paint(Graphics g) {
       Image image = Image.createImage("/sample.gif");
       g.drawImage(image, 0, 0, g.TOP|g.LEFT);
   }
}

・Imageクラス
ImageクラスのcreateImage()メソッドで画像ファイルを読み込みます。

Image image = Image.createImage("/sample.gif"); 

ファイル名には「/sample.gif」のように頭に"/"を付ける必要があります。

・drawImage()
GraphicsクラスのdrawImage()で読み込んだ画像を画面に描画します。

g.drawImage(image, 0, 0, g.TOP|g.LEFT);
void drawImage(Image image, int x, int y, int anchor)  
    image:表示するイメージ
    x:X座標
    y:Y座標
    anchor:軸とする場所
        HCENTER(水平中央)
        VCENTER(垂直中央)
        TOP(上端) 
        BOTTOM(下端)
        LEFT(左端)
        RIGHT(右端)
        BASELINE(ベースライン)

anchorはdrawString()メソッドと同じで、指定したXY座標に文字列のどの位置を置くかを設定します。

◎プログラムを実行する
コンパイルと検証を行なうコマンドは以下の通りです。今回は「set SCREEN_DEPTH=8」を指定し、カラーで表示するようにしています。

set SCREEN_DEPTH=8
midp -classpath .;c:¥java¥midp-ea1¥lib¥midp.jar ImageEx

5. ゲームを作る

携帯電話で動く格闘ゲームを作ります。
プレーヤーが右のキャラ、コンピュータが左のキャラを操作して戦います。左右のフットワークとパンチを駆使して、敵を倒します。パンチを受けるたびに画面上方にある体力ゲージが減り、なくなると負けになります。

このゲームは以下の4つのクラスで構成されています。
・Fighterクラス:格闘するキャラ
・GameCanvasクラス:キャンバス
・Asuka12クラス:本体
・Comクラス:敵思考ルーチン

◎画像ファイルの用意
このゲームで使う画像ファイルは以下の10枚です。プログラムと同じディレクトリに置いてください。

【girl0.gif】

【girl1.gif】

【girl2.gif】

【girl3.gif】

【girl4.gif】

【boy0.gif】

【boy1.gif】

【boy2.gif】

【boy3.gif】

【boy4.gif】

◎Fighterクラス
格闘家を表すクラスです。コンストラクタの引数playerは、プレイヤーのキャラの時はtrue、コンピュータが操作するキャラの時はfalseを指定します。

【Fighter.java】

import javax.microedition.lcdui.*;

//格闘ゲーム:格闘家
public class Fighter {
   public final static int
       NULL   = -1,//なし
       LEFT   =  0,//左へ移動
       RIGHT  =  1,//右へ移動
       PUNCH  =  2,//パンチ
       DAMAGE =  3,//ダメージ
       DOWN   =  4;//ダウン

   private boolean player;  //プレイヤーかどうか
   private Image   image[]; //イメージ
   private int     x;       //X座標
   private int     command; //コマンド
   private int     state;   //状態
   private int     anime;   //アニメ
   private int     hp;      //HP
   private Fighter opponent;//対戦相手


   //コンストラクタ
   public Fighter(boolean player) {
       this.player = player;
       String name = (player) ? "girl" : "boy";
       image = new Image[5];
       for (int i = 0;i < 5;i++) {
           image[i] = Image.createImage("/"+name+i+".gif");
       }
       init();
   }

   //初期化
   public void init() {
       x        = (player) ? 70 : 2;
       command  = NULL;
       state    = NULL;
       anime    = 0;
       hp       = 40;
   }

   //対戦相手のセット
   public void setOpponent(Fighter opponent) {
       this.opponent = opponent;
   }

   //コマンドのセット
   public void setCommand(int command) {
       this.command = command;
   }

   //X座標を得る
   public int getX() {
       return x;
   }

   //状態を得る
   public int getState() {
       return state;
   }

   //HPを得る
   public int getHP() {
       return hp;
   }

   //ダメージ
   public synchronized void damage() {
       if (state == DAMAGE || state == DOWN) return;
       anime = 0;
       hp -= 8;
       if (hp > 0) {
           command = state = DAMAGE;
       } else {
           command = state = DOWN;
           hp = 0;
       }
   }

   //左へ移動
   private void left(int val) {
       x -= val;
       if (x < -6) x = -6;
       if (player && (x - opponent.getX()) < 18) {
           x = opponent.getX() + 18;
       }
   }

   //右へ移動
   private void right(int val) {
       x += val;
       if (x > 78) x = 78;
       if (!player && (opponent.getX() - x) < 18) {
           x = opponent.getX() - 18;
       }
   }

   //描画
   public synchronized void paint(Graphics g) {
       //animeが0の時はcommandを実行
       if (anime == 0) {
           state = command;
           if (command == PUNCH || command == DAMAGE) command = NULL;
       }

       //キャラの消去
       int width = (state == DOWN) ? 24: 22;
       g.setColor(181, 255, 165);
       g.fillRect(x, 51, width, 15);
       g.setColor(255, 255, 165);
       g.fillRect(x, 66, width, 10);

       //左へ移動
       int imageNum = 0;
       if (state == LEFT) {
           left(4);
           imageNum = anime;
           anime = (anime >= 1) ? 0 : anime+1;
       }
       //右へ移動
       else if (state == RIGHT) {
           right(4);
           imageNum = anime;
           anime = (anime >= 1) ? 0 : anime+1;
       }
       //パンチ
       else if (state == PUNCH) {
           if (anime < 2) {
               if (player) {
                   left(6);
               } else {
                   right(6);
               }
               imageNum = 2;
           }
           anime = (anime >= 2) ? 0 : anime+1;
       }
       //ダメージ
       else if (state == DAMAGE) {
           if (player) {
               right(4);
           } else {
               left(4);
           }
           imageNum = 3;
           anime = (anime >= 2) ? 0 : anime+1;
       }
       //ダウン
       else if (state == DOWN) {
           if (player) {
               right(6);
           } else {
               left(6);
           }
           imageNum = 4;
           anime = (anime >= 1) ? 1 : anime+1;
       }
       //その他
       else {
           anime = 0;
       }

       //キャラの描画
       g.drawImage(image[imageNum], x, 51, g.TOP|g.LEFT);
   }
}

・stateとanimeとcommand

下図を見てください。変数「state」で示す状態には、以下の6種類があります。

・NULL(なし)
・LEFT(左へ移動)
・RIGHT(右へ移動)、
・PUNCH(パンチ)
・DAMAGE(ダメージ)
・DOWN(ダウン)

各状態で2コマ以上のアニメーションを行ないます。今どのコマなのかは変数「anime」で指定します。

また、ユーザーからキャラに命令してもアニメーションが、終わるまでは次の動作に移りません。命令を変数「command」で保持しておいて、アニメーションが終わってからその命令を実行します。

・damage()
パンチがヒットした時に呼ばれ、アニメーション中でもそれを中止して、体力ゲージがまだある時は状態をDAMAGEに、ない時はDOWNに移します。

・paint()
現在表示されているキャラを消去して、stateとanimeの値から次の位置と状態を計算してから、再びキャラを描画するという処理をしています。このメソッドはGameCanvasクラスから一定時間おきに呼ばれます。また、animeが0の時はcommandを見て次の状態に移るという処理も行っています。

◎GameCanvasクラス
ゲーム用のキャンバスのクラスです。

【GameCanvas.java】

import javax.microedition.lcdui.*;

//格闘ゲーム:キャンバス
public class GameCanvas extends Canvas {
   public final static int
       TITLE    =  0,//タイトル
       PLAY     =  1,//プレイ中
       WIN0     =  2,//1P勝利
       WIN1     =  3;//2P勝利

   private int scene = TITLE;               //シーン
   private boolean init = true;             //画面の初期化
   private Fighter girl = new Fighter(true);//プレーヤーのキャラ
   private Fighter boy = new Fighter(false);//コンピュータのキャラ
   private Com com = new Com();             //敵思考ルーチン

   //コンストラクタ
   public GameCanvas() {
       girl.setOpponent(boy);
       boy.setOpponent(girl);
   }

   //スタート
   public void start() {
       if (scene == PLAY) return;
       init = true;
       scene = PLAY;
       girl.init();
       boy.init();
   }

   //描画
   public synchronized void paint(Graphics g) {
       if (init) {
           paintBack(g);
           init = false;
       }
       if (scene == PLAY) {
           com.control(boy, girl);
           checkHit();
       } else {
           girl.setCommand(girl.NULL);
           boy.setCommand(boy.NULL);
       }
       boy.paint(g);
       girl.paint(g);
       paintHP(g);
       paintMessage(g);
   }

   //背景の描画
   private void paintBack(Graphics g) {
       //空
       g.setColor(181, 255, 255);
       g.fillRect(0, 17, 96, 28);

       //山
       for (int i = 0;i < 4;i++) {
           g.setColor(255, 222, 255);
           g.fillRoundRect(i*24, 28, 24, 12, 18, 10);
           g.setColor(107, 107, 82);
           g.drawRoundRect(i*24, 28, 24, 12, 18, 10);
       }
       g.setColor(255, 222, 255);
       g.fillRect(0, 34, 96, 8);

       //道
       g.setColor(181, 255, 165);
       g.fillRect(0, 42, 96, 40);
       g.setColor(255, 255, 165);
       g.fillRect(0, 66, 96, 12);

       //パネル
       g.setColor(255, 181, 82);
       g.fillRect(0, 0, 95, 20);
       g.fillRect(0, 82, 95, 17);
       g.setColor(107, 107, 82);
       g.drawRect(0, 0, 95, 20);
       g.drawRect(0, 82, 95, 17);
       g.setColor(255, 255, 255);
       g.fillRect(2, 2, 91, 16);

       //名前プレート
       g.setColor(107, 107, 82);
       g.setFont(Font.getFont(Font.FACE_SYSTEM,
           Font.STYLE_PLAIN, Font.SIZE_SMALL));
       g.drawString("SINJI", 2, 10, g.LEFT|g.TOP);
       g.drawString("ASUKA", 93, 10, g.RIGHT|g.TOP);
       g.setFont(Font.getDefaultFont());
   }

   //体力ゲージの描画
   private void paintHP(Graphics g) {
       g.setColor(255, 255, 255);
       g.fillRect(2, 3, 91, 6);
       g.setColor(222, 214, 255);
       g.fillRect(2, 3, boy.getHP(), 6);
       g.fillRect(93-girl.getHP(), 3, girl.getHP(), 6);
   }

   //メッセージの描画
   private void paintMessage(Graphics g) {
       g.setColor(255, 30, 30);
       if (scene == TITLE) {
           g.drawString("ASUKA 12%", 48, 45, g.HCENTER|g.VCENTER);
       } else if (scene == WIN0) {
           g.drawString("ASUKA WIN!", 48, 45, g.HCENTER|g.VCENTER);
       } else if (scene == WIN1) {
           g.drawString("SINJI WIN!", 48, 45, g.HCENTER|g.VCENTER);
       }
   }

   //当たり判定
   private void checkHit() {
       int len = Math.abs(girl.getX() - boy.getX());
       if (len < 20) {
           if (girl.getState() == Fighter.PUNCH &&
               boy.getState() == Fighter.PUNCH) {
               girl.damage();
               if (girl.getHP() == 0) scene = WIN1;
           } else if (girl.getState() == Fighter.PUNCH) {
               boy.damage();
               if (boy.getHP() == 0) scene = WIN0;
           } else if (boy.getState() == Fighter.PUNCH) {
               girl.damage();
               if (girl.getHP() == 0) scene = WIN1;
           }
       }
   }

   //キープレスイベント
   protected synchronized void keyPressed(int keyCode) {
       if (scene != PLAY) return;
       int action = getGameAction(keyCode);
       if (action == LEFT) {
           girl.setCommand(Fighter.LEFT);
       } else if (action == RIGHT) {
           girl.setCommand(Fighter.RIGHT);
       } else if (action == DOWN) {
           girl.setCommand(Fighter.PUNCH);
       }
   }

   //キーリリース
   protected synchronized void keyReleased(int keyCode) {
       if (scene != PLAY) return;
       int action = getGameAction(keyCode);
       if (action == LEFT) {

           girl.setCommand(Fighter.NULL);
       } else if (action == RIGHT) {
           girl.setCommand(Fighter.NULL);
       }
   }
}

・paint()
背景、キャラ、体力ゲージ、メッセージの描画を行います。このメソッドはAsuka12クラスから一定時間おきによばれます。

・paintBack()
ゲーム開始時に、Graphicsクラスのメソッドを使って全ての背景を描画します。色を設定するには、setColor()メソッドを使います。これを行った後drawRect()メソッドなどの図形描画メソッドを使うと、その色で描画されるようになります。

g.setColor(181, 255, 255);
g.fillRect(0, 17, 96, 28); 

setColor()メソッドの引数はJ2SEとはちょっと違ってRGB色の値を直接設定します。

public void setColor(int red, int green, int blue)
    red:赤(0255)
    green:緑(0255)
    blue:青(0255)


使用するフォントを変更するには、FontクラスのgetFont()メソッドでフォントを生成し、それをGraphicsクラスのsetFont()メソッドでセットします。

g.setFont(Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL));
public static Font getFont(int face, int style, int size)
    face:フェイス
        FACE_SYSTEM(システム)
        FACE_MONOSPACE(モノスペース)
        FACE_PROPORTIONAL(プロポーショナル)
    style:スタイル
        STYLE_PLAIN(プレーン)
        STYLE_BOLD(ボールド)
        STYLE_ITALIC(イタリック)
    size:サイズ
        SIZE_SMALL(小)
        SIZE_MEDIUM(中)
        SIZE_LARGE(大)

・keyPress()とkeyRelese()
ボタンを押したり離したりした時の処理は、Canvasクラスが持つ以下のメソッドをオーバーライドして追加します。

・keyPressed():ボタンを押したときに呼ばれる。
・keyReleased():ボタンを離した時に呼ばれる。
・keyRepeated():ボタンを押し続けた時に呼ばれ続ける。

これらのメソッドに引数として渡されるキーコードを直接調べて、どのボタンが押されたのかを判定してもよいのですが、getGameAction()というゲーム用のメソッドが用意されているので、これを利用することにします。

public int getGameAction(int keyCode)
    keyCode:キーコード
    返り値:ゲームアクション
        UP(上)
        DOWN(下)
        LEFT(左)
        RIGHT(右)
        FIRE(ミサイル発射)
        GAME_A(ゲームA)
        GAME_B(ゲームB)
        GAME_C(ゲームC)
        GAME_D(ゲームD)

getGameAction()メソッドにキーコードを入れることで、Canvasクラスが持っているゲームアクション定数に変換することができます。これによって、どのボタンが押されたかを判定する処理は以下のようになります。

int action = getGameAction(keyCode);
if (action == LEFT) {
    girl.setCommand(Fighter.LEFT);
} else if (action == RIGHT) {
    girl.setCommand(Fighter.RIGHT);
} else if (action == DOWN) {
    girl.setCommand(Fighter.PUNCH);
}

◎Asuka12クラス
Asuka12クラスは、ゲームの本体となるクラスです。スレッドで一定時間おきにGameCanvasクラスのrepaint()メソッドを呼んでいます。スレッドの使い方はJ2SEと同じです。repaint()メソッドを呼ぶとそこからpaint()メソッドも呼ばれます。

【Asuka12.java】

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

//格闘ゲーム:本体
public class Asuka12 extends MIDlet
   implements CommandListener, Runnable {
   private GameCanvas canvas;  //キャンバス
   private Thread     thread;  //スレッド
   private boolean    active;  //動作中
   private Command    cmdStart;//スタートコマンド
   private Command    cmdExit; //終了コマンド

   //コンストラクタ
   public Asuka12() {
       cmdStart = new Command("Start", Command.SCREEN, 2);
       cmdExit  = new Command("Exit", Command.SCREEN, 3);
       canvas = new GameCanvas();
       canvas.addCommand(cmdStart);
       canvas.addCommand(cmdExit);
       canvas.setListener(this);
       Display.getDisplay(this).setCurrent(canvas);
   }

   //アプリの開始
   public void startApp() {
       active = true;
       thread = new Thread(this);
       thread.start();
   }

   //アプリの一時停止
   public void pauseApp() {
       active = false;
   }

   //アプリの終了
   public void destroyApp(boolean b) {
   }

   //コマンドアクション
   public void commandAction(Command c, Displayable s) {
       if (c == cmdStart) {
           canvas.start();
       } else if (c == cmdExit) {
           destroyApp(false);
           notifyDestroyed();
       }
   }

   //一定時間ごとの処理
   public void run() {
       while (active) {
           try {
               thread.sleep(50);
           } catch (InterruptedException e) {
           }
           canvas.repaint();
       }
   }
}

・Comクラス
対戦相手となる敵キャラの思考ルーチンのクラスです。
コンピュータはキャラの状態に応じて

・パンチ
・右に移動
・左に移動
・何もしない

という4つの命令のうち、その時点で的確だと思うものをキャラにセットします。命令を決定するまでの手順は以下の通りです。

(1)優先度の値の用意
(2)キャラの状態を見て優先度の値を増減
(3)最終的な優先度の値を見て命令を決定

【Com.java】

import java.util.Random;

//格闘ゲーム:敵思考ルーチン
public class Com {
   private Random rand = new Random();//乱数

   //操作
   public void control(Fighter self, Fighter opponent) {
       //優先度の値の用意
       int punch = 0;
       int move  = 0;

       //2人のキャラの距離を見て優先度の値を増減
       int len = Math.abs(self.getX()-opponent.getX());
       if(len < 20) {
           punch += rand(0, 5);
           move  += rand(-5, 2);
       } else if(len<25) {
           punch += rand(0, 3);
           move  += rand(-4, 5);
       } else if(len<30) {
           punch += rand(-2, 1);
           move  += rand(-3, 5);
       } else {
           punch += rand(-10, 1);
           move  += rand(-2, 5);
       }

       //現在のキャラの状態を見て優先度の値を増減
       if (self.getState() == self.LEFT) {
           move += 2;
       } else if (self.getState() == self.RIGHT) {
           move -= 2;
       }

       //最終的な優先度の値を見て命令を決定
       if (punch > 0) {
           self.setCommand(self.PUNCH);
       } else if (move < 0) {
           self.setCommand(self.LEFT);
       } else if (move > 0) {
           self.setCommand(self.RIGHT);
       } else {
           self.setCommand(self.NULL);
       }
   }

   //min以上max以下のランダム値生成
   private int rand(int min, int max) {
       return ((rand.nextInt() >>> 1) % (max-min))+min;
   }
}

・優先度の値の用意
まずはじめに、命令の種類ごとに、優先度の値を用意します。ここでは以下の2つを用意しました。

・punch(値が大きいほどパンチしたくなる)
・move(値が大きいほど右へ移動したくなり、小さいほど左へ移動したくなる)


・キャラの状態を見て優先度の値を増減
次にキャラの状態に応じて、この優先度の値を増減させます。チェック項目は以下の2つです。

・2人のキャラの距離
・現在のキャラの状態

2人のキャラの距離は、近いほどパンチやフットワークを行う優先度を高くして、遠ければ優先度を低くしています。また、現在のキャラの状態を見て、右へ移動中の時は右に移動する優先度を高くし、左へ移動中の時は左へ移動する優先度を高くしています。

・最終的な優先度の値を見て命令を決定
全てのチェック項目で優先度の増減を行ったら、最終的な優先度の値の大きさを見て命令を決定します。

・punchが1以上:パンチ
・moveが1以上:右へ移動
・moveが-1以下:左へ移動
・その他:何もしない

◎ゲームを実行する
コンパイルと検証を行なうコマンドは以下の通りです。

javac -bootclasspath c:¥java¥midp-ea1¥lib¥midp.jar *.java
preverify -classpath .;c:¥java¥midp-ea1¥lib¥ -d . Asuka12
preverify -classpath .;c:¥java¥midp-ea1¥lib¥ -d . GameCanvas
preverify -classpath .;c:¥java¥midp-ea1¥lib¥ -d . Fighter
preverify -classpath .;c:¥java¥midp-ea1¥lib¥ -d . Com

検証済みクラスができた、ら以下のコマンドでゲームを実行します。

set SCREEN_DEPTH=8
midp -classpath .;c:¥java¥midp-ea1¥lib¥midp.jar Asuka12

6. ネット上に公開する

ゲームが完成したのでネット上に公開して、エミュレータでダウンロードして実行してみましょう。MIDletをネット上に公開するには、JARファイルとJADファイルを生成する必要があります。

JARファイルを生成する
「JARファイル(Java Archive)」は、複数のファイルを 1つのファイルにまとめるためのプラットフォームから独立したファイル形式です。格闘ゲームではクラスファイルも画像ファイルも全て、カレントディレクトリにあるのでコマンドは以下の通りです。

jar cf Asuka12.jar *.class *.gif 

これでAsuka12.jarが生成されます。

JADファイルを生成する
「JADファイル(Java Application Descriptor)」は、J2MEのバージョンやファイルサイズなどのアプリケーションの情報を保持するテキストファイルです。JARファイルをダウンロードしてJ2MEのバージョンが対応してなかったり、メモリ不足だったりしてして、動かないとなるとその間の通信料が無駄使いになってしまいます。
そこでMIDPでは先にJADファイルを読み込んで、実行可能かどうかチェックしてから、JARファイルをダウンロードするようにしています。

格闘ゲーム用のJADファイル「Asuka12.jad」は以下の通りです。

【Asuka12.jad】

MIDlet-Name: Asuka12
MIDlet-Version: 1.0.0
MIDlet-Vendor: H.Furukawa
MIDlet-Description: MIDlet Asuka12
MIDlet-Info-URL: http://www.people.or.jp/~npaka/kvm/javapress/asuka12/
MIDlet-Jar-URL: http://www.people.or.jp/~npaka/kvm/javapress/asuka12/Asuka12.jar
MIDlet-Jar-Size:    8323
MicroEdition-Profile: MIDP-1.0
MicroEdition-Configuration: CLDC-1.0
MIDlet-1: Asuka12,, Asuka12

テキストエディタで作成します。MIDlet-Info-URLとMIDlet-Jar-URLには、アップロード先のアドレスを指定してください。

エミュレータでJADファイルにアクセスする
Asuka12.jarとAsuka12.jadができたら、この2つのファイルをネット上にアップロードしてください。エミュレータでJADファイルにアクセスするには、transientオプションにネット上に公開したJADファイルのURLを指定します。

midp -classpath .;c:¥java¥midp-ea1¥lib¥ -transient http://www.people.or.jp/~npaka/Asuka12.jad 

成功すればローカルで実行しているのと同じようにゲーム画面が表示されます。


この記事が気に入ったらサポートをしてみませんか?