★はじめに★
第5話 ゲームボーイアドバンスで動くMIDP2.0のゲームを作る
[JAVA PRESS Vol.32]

ゲーム:アクションゲーム
対応端末:ゲームボーイアドバンス・ゲームボーイアドバンスSP


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



へにへに〜、今回はボーダフォンのゲーム作ってみない?J-SH53って機能満載でちょっとカッコイイから、ついうっかり契約してきちゃった。SH53で拡張された3Dグラフィックスや音声認識の機能ってすっごいし、シーマンみたいなの作れるんじゃない?
う〜ん、 せっかくそらみちゃんがやる気まんまんなところ残念だけど、J-SH53の新機能を使うために必要な開発ツールは一般公開されてないんだよね…。
ケチくさっ! ボーダフォンはJ-PHONEのときからしてそんな姿勢だから、一般ユーザーが作った無料面白アプリもなかなか増えやしないんだよね。メールアドレスも無理やり変えられちゃうし、解約してやるっ!!
まぁまぁ、そらみちゃん落ち着いて。そこはかとないプリティフェイスが台無しだよ。気分転換がてら、今回はMIDP2.0のゲームでも作ってみないかい?
MIDP2.0って…、ボーダフォンやauで使われてるJavaの仕様はMIDP1.0だよね?作ってみても実際に携帯端末で動かせないとつまんないよぉ…。
ふっふっふ、実はMIDP2.0を動かせる携帯端末があるんだよ…。そらみちゃんもよく知ってるこの端末!
うわぁ、ゲームボーイアドバンス!?…と、ちょっぴりアングラな香り漂う、不格好に本体から突き出たカートリッジ…。
めっ! そらみちゃん、そんなこと言っちゃだめ!
普通のゲームボーイ用ソフトだってアドバンスに挿したら不格好に突き出るでしょ!?これは「JEMBlazer」といって、ゲームボーイアドバンスでMIDP2.0のアプリを実行できる
凄いカートリッジなんだよ!
とりあえず、ボーダフォンよりは面白そうだし、やってみよ〜♪
・・・・・・(汗)


★MIDP2.0の新機能★


MIDP2.0とは
「MIDP(Mobile Information Device Profile)1.0」は、ボーダフォンやauの携帯電話をはじめとする多くの携帯端末で採用されているJavaのAPI仕様です。携帯電話だけでなくPDAまでも視野に入れて設計されている仕様で、Palmでも動きます。しかし、携帯端末の進化ははやく、MIDP1.0の仕様だけではアプリ作成には不十分となり、結果としてキャリア(ボーダフォンやauなどの通信事業者)ごとの拡張APIが乱立するようになりました。そこで、端末依存のAPIを使わなくてもすむように、MIDP1.0を拡張して作られたものが「MIDP2.0」です。2003年8月現在、MIDP2.0のアプリが実行できる端末は、ゲームボーイアドバンスのみです。


MIDP2.0の新機能
MIDP2.0の新機能には、次のようなものがあります。

★開発環境を整える★


今回のゲームを作るのに必要な開発ツールは次の4つです。

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

J2ME Wireless Toolkit 2.0
MIDP2.0仕様のJavaアプリを作るための開発キットです。サン・マイクロシステムズのサイトで入手できます。インストーラの指示に従ってインストールします。

JEMBlazer
MIDP2.0仕様のJavaアプリをゲームボーイアドバンスで動かすためのツール郡です。Java仮想マシンが載った「JEMBlazerカートリッジ」や、MIDP2.0仕様のJavaアプリ(JARファイルとJADファイル)をゲームボーイアドバンス用の実行ファイル(BRLファイル)に変換するツール「MIDletBuilder」などが含まれています。JEMBlazerのサイトで購入でき、価格は$226です。インストーラの指示に従ってインストールしてください。


MMCカードリーダー/ライター
「MMC(MultiMediaCard)」は、携帯音楽プレーヤーなどでよく使われるメモリカードです。JEMBlazerはMMCカード経由でアプリをパソコンからゲームボーイアドバンスに移すので、パソコンからMMCカードの読み書きを行う「MMCカードリーダー/ライター」が必要となります。パソコンショップ等で購入してください。


★文字列を表示する★


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

プロジェクトの作成
「J2ME Wireless Toolkit」がインストールできたら、Windowsのスタートメニューに「プログラム - J2ME Wireless Toolkit 2.0 - KToolbar」が追加されているので、それを選択してください。「J2ME Wireless Toolkit」の核となるツール「KToolbar」が起動します。


[New Project]ボタンを押すと、「New Projectダイアログ」が開くので、「Project Name」と「MIDlet Class Name」に"HelloWorld"と入力します。


「MIDlet Class Name」はアプリで一番はじめに実行されるクラスの名前です。[Create Project]ボタンを押すと、「J2ME Wireless Toolkit」のルートにあるappsディレクトリの下に「Project Name」と同じ名前のディレクトリ(今回は"HelloWorld")が生成されます。さらにその下に以下の4つのディレクトリが生成されています。 アプリの属性を設定する「Settings for project "HelloWorld"ダイアログ」が開きますが、今回は要ないので、そのまま[OK]ボタンを押して閉じてください。


HelloWorldクラス
HelloWorldクラスは、プログラムの本体となるクラスです。
HelloWorld.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

//HelloWorld(本体)
public final class HelloWorld extends MIDlet {

    //コンストラクタ
    public HelloWorld() {
        Display.getDisplay(this).setCurrent(new HelloCanvas());
    }

    //アプリの開始
    public void startApp() {
    }

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

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


import
はじめの"import"というのはパッケージを使う時に使う予約語で、ここでは「javax.microedition.lcduiパッケージ」と「javax.microedition.midletパッケージ」を使うことを宣言しています。次で説明する「MIDletクラス」や「Displayクラス」がこれらのパッケージに含まれています。
HelloWorld.javaの一部
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;


MIDletクラス
アプリの本体となるクラスは、「MIDletクラス」を継承します。
HelloWorld.javaの一部
public class HelloWorld extends MIDlet {

MIDletを継承したクラスは、以下の3つのメソッドを必ず持つ(オーバーライドする)必要があります。 今回は何も行わないので全て空です。


Displayクラス
DisplayクラスのgetDisplay()メソッドでDisplayクラスのオブジェクトを取得し、そのオブジェクトのsetCurrent()メソッドで、実機の画面に表示するキャンバス等を指定します。キャンバスとは文字列や絵などのグラフィックを表示するためのものです。
static Display getDisplay(MIDlet midlet)
midlet:MIDletクラスのオブジェクト
戻り値:Displayクラスのオブジェクト
void setCurrent(Displayable displayable)
displayable:画面に表示するもの(キャンバス等)

今回は、次で説明するHelloCanvasクラスのオブジェクトをセットしています。
HelloWorld.javaの一部
Display.getDisplay(this).setCurrent(new HelloCanvas());


HelloCanvasクラス
HelloCanvasクラスは、キャンバスとなるクラスです。
HelloCanvas.java
import javax.microedition.lcdui.*;

//HelloWorld(キャンバス)
class HelloCanvas extends Canvas {

    //描画
    public void paint(Graphics g) {
        g.setColor(255,255,255);
        g.fillRect(0,0,getWidth(),getHeight());
        g.setColor(0,0,0);
        g.drawString("Hello World!",0,0,g.LEFT|g.TOP);
    }
}


Canvasクラス
キャンバスとなるクラスは、Canvasクラスを継承します。Canvasを継承したクラスは、paint()メソッドを必ず持つ(オーバーライドする)必要があります。paint()メソッドは、アプリ起動時や、再描画が必要な時に呼ばれます。このpaint()メソッドに渡されるGraphicsクラスを操作することにより、画面に文字列や絵などを表示します。
void paint(Graphics g)
g:グラフィックス


Graphicsクラス
Graphicsクラスには、画面に文字列や絵などの表示を行なうためのメソッドが用意されています。

色を指定するには、setColor()メソッドを使います。このメソッドを使った後、それ以降の文字列や図形の描画時に、指定された色が適用されます。
void setColor(int red,int green,int blue)
red :赤(0〜255)
green:緑(0〜255)
blue :青(0〜255)

画面を塗り潰すには、fillRect()メソッドを使います。引数には、矩形の左上のXY座標と、幅・高さを指定します。
void fillRect(int x,int y,int width,int height)
x :X座標
y :Y座標
width :幅
height:高さ

文字列を表示するのでdrawString()メソッドを使います。
void drawString(String str,int x,int y,int align)
str :表示する文字列
x :X座標
y :Y座標
align:配置

strには表示する文字列を指定します。ただし、使用できるのは英数字のみで、日本語を指定すると文字化けします。
align引数には配置定数を指定します。指定した座標に文字列の左上を配置したい時は"Graphics.LEFT|Graphics.UP"を指定します。
配置定数
Graphics.TOP
Graphics.BOTTOM
Graphics.LEFT
Graphics.RIGHT
Graphics.HCENTER 水平中央
Graphics.VCENTER 垂直中央

今回は、画面全体を白で塗り潰した後、黒で"Hello World!"という文字を表示するので、以下のようになります。
HelloCanvas.javaの一部
g.setColor(255,255,255);
g.fillRect(0,0,getWidth(),getHeight());
g.setColor(0,0,0);
g.drawString("Hello World!",0,0,g.LEFT|g.TOP);


ビルドとエミュレータでの実行
プログラムファイルをsrcディレクトリに置いて、[Build]ボタンを押してコンパイルし、メニューの[Project - Package - CreatePackage]でJARファイルを生成します。「Deviceコンボボックス」でデバイスを選んで[Run]ボタンを押すとエミュレータで実行できます。

ゲームボーイアドバンスのエミュレータで実行したい時は、"C:\aJile\JEMBlazer\Emulator\wtklib\devices\JEMBlazer"ディレクトリを"C:\WTK20\wtklib\devices"ディレクトリの下にコピーして、KToolbarを起動しなおしてください。DeviceコンボボックスにJEMBlazerが追加されているので、それを選択して[Run]ボタンを押します。ただし、実機とはフォントサイズが若干違い、ABLRボタンも効かないので注意してください。
画面サイズ LARGEフォント MEDIUMフォント SMALLフォント
ゲームボーイアドバンス 240x141 7x13 7x13 7x13
エミュレータ 240x139 7x20 6x16 5x12

  


ゲームボーイアドバンスで実行する
ゲームボーイアドバンスで実行するには、JARファイルとJADファイルをゲームボーイアドバンス用の実行ファイル「BRLファイル(*.brl)」に変換する必要があります。「JEMBlazer」に含まれている「MIDletBuilder」を使ってこの変換を行います。「MIDletBuilder」の実行ファイル(C:\aJile\MIDletBuilder\MIDletBuilder.exe)をダブルクリックして起動してください。

最初に、次の3種類のターゲットから作成したい実行ファイル形式を選択します。 「JEMBlazerデスクトップ」は、JEMBlazerカートリッジに複数のアプリを入れて、その中から選択して起動するための管理ツールです。今回はこのJEMBlazerデスクトップを使うので、メニューの[Target - JEMBlazerDesktop」を選択してください。

次に「JAD File」に変換対象となるJADファイルを、「Output Directory」に変換後のBRLファイルの出力先ディレクトリを指定します。[Browse...]ボタンを使って指定してください。



最後に、メニューの「File - Build」を選択します。成功すれば出力先ディレクトリに「HelloWorld.brl」が生成されます。
「HelloWorld.brl」が生成できたらMMCに書き込み、ゲームボーイアドバンスにJEMBlazerカートリッジを、JEMBlazerカートリッジにMMCを挿し込みます。そして、ゲームボーイアドバンスの電源をONにすると、JEMBlazerデスクトップが起動します。HelloWorldアイコンが表示されるので選択すれば、HelloWorldが実行されます。


★絵を表示する★


次に、絵を表示するプログラム「ImageEx」を作ります。今回のプログラムは、以下の2つのクラスで構成されています。 「Project Name」「MIDlet Class Name」に"ImageEx"を指定して、プロジェクトを作成してください。



画像ファイルの用意
このプログラムでは1枚の画像ファイルを使います。この画像ファイルをImageExディレクトリの下のresディレクトリに置いてください。MIDPで扱える画像形式はPNG-8形式です。


・そらみ

-sorami.png
-140x140



ImageExクラス
ImageExクラスは、プログラムの本体となるクラスです。コンストラクタでキャンバスの生成だけを行っています。
ImageEx.java
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

//絵を表示する(本体)
public class ImageEx extends MIDlet {

    //コンストラクタ
    public ImageEx() {
        Display.getDisplay(this).setCurrent(new ImageCanvas());
    }

    //アプリの開始
    public void startApp() {
    }

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

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


ImageCanvasクラス
ImageCanvasクラスは、キャンバスとなるクラスです。
ImageCanvas.java
import javax.microedition.lcdui.*;

//絵を表示する(キャンバス)
class ImageCanvas extends Canvas {
    private Image image;//イメージ

    //コンストラクタ
    public ImageCanvas() {
        try {
            image=Image.createImage("/sorami.png");
        } catch (Exception e) {
        }
    }

    //描画
    public void paint(Graphics g) {
        g.setColor(255,255,255);
        g.fillRect(0,0,getWidth(),getHeight());
        g.drawImage(image,getWidth()/2,getHeight()/2,
            g.VCENTER|g.HCENTER);
    }
}


Imageクラス
ImageクラスのcreateImage()メソッドで画像ファイルを読み込みます。ファイル名には「/sorami.gif」のように頭に"/"をつけてください。
ImageCanvas.javaの一部
image=Image.createImage("/sorami.png");


drawImage()メソッド
GraphicsクラスのdrawImage()メソッドでイメージをキャンバスに描画します。
void drawImage(Image image,int x,int y,int anchor)
image :表示するイメージ
x :X座標
y :Y座標
anchor:配置

引数anchorはdrawString()メソッドと同じです。


★ボタン操作を行う★


次に、押されたボタンの名前をキャンバスに表示するプログラム「KeyEventEx」を作ります。今回のプログラムは、以下の2つのクラスで構成されています。 「Project Name」「MIDlet Class Name」に"KeyEvent"を指定して、プロジェクトを作成してください。



KeyEventExクラス
KeyEventExクラスは、プログラムの本体となるクラスです。コンストラクタでキャンバスの生成だけを行っています。
KeyEventEx.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

//ボタン操作を行う(本体)
public final class KeyEventEx extends MIDlet {

    //コンストラクタ
    public KeyEventEx() {
        Display.getDisplay(this).setCurrent(
            new KeyEventCanvas());
    }

    //アプリの開始
    public void startApp() {
    }

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

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


KeyEventCanvasクラス
KeyEventCanvasクラスは、キャンバスとなるクラスです。
KeyEventCanvas.java
import javax.microedition.lcdui.*;

//ボタン操作を行う(キャンバス)
class KeyEventCanvas extends Canvas
    implements CommandListener {
    private Command soft1;     //ソフトキー1
    private Command soft2;     //ソフトキー2
    private String  output="?";//出力データ

    //コンストラクタ
    KeyEventCanvas() {
        soft1=new Command("SOFT1",Command.SCREEN,1);
        soft2=new Command("SOFT2",Command.SCREEN,2);
        addCommand(soft1);
        addCommand(soft2);
        setCommandListener(this);
    }

    //描画
    public void paint(Graphics g) {
        g.setColor(255,255,255);
        g.fillRect(0,0,getWidth(),getHeight());
        g.setColor(0,0,0);
        g.drawString(output,0,0,g.LEFT|g.TOP);
    }

    //キープレスイベント
    public void keyPressed(int keyCode) {
        //ゲームアクション
        int action=getGameAction(keyCode);
        if (action==UP)     output="UP BUTTON";
        if (action==DOWN)   output="DOWN BUTTON";
        if (action==LEFT)   output="LEFT BUTTON";
        if (action==RIGHT)  output="RIGHT BUTTON";
        if (action==FIRE)   output="START BUTTON";
        if (action==GAME_A) output="A BUTTON";
        if (action==GAME_B) output="B BUTTON";
        if (action==GAME_C) output="R BUTTON";
        if (action==GAME_D) output="L BUTTON";

        //再描画
        repaint();
    }

    //コマンドイベント
    public void commandAction(Command c,Displayable s) {
        //ソフトキー
        if (c==soft1) output="SOFT1";
        if (c==soft2) output="SOFT2";

        //再描画
        repaint();
    }
}


キーイベント
キーイベントは、ゲームボーイアドバンスのボタンを操作した時に発生するイベントです。ボタンを押した時にはkeyPressed()メソッド呼ばれ、ボタンを離した時はkeyReleased()メソッドが呼ばれます。
public void keyPressed(int keyCode)
keyCode:キーコード
public void keyReleased(int keyCode)
keyCode:キーコード

キーコードをCanvasクラスのgetGameAction()メソッドに渡して得られる値「ゲームアクション」によって、どのボタン押されたかを判断します。
int getGameAction(int keyCode)
keyCode:キーコード
戻り値 :ゲームアクション

ゲームアクション
Canvas.UP 上ボタン
Canvas.DOWN 下ボタン
Canvas.LEFT 左ボタン
Canvas.RIGHT 右ボタン
Canvas.FIRE スタートボタン
Canvas.GAME_A Aボタン
Canvas.GAME_B Bボタン
Canvas.GAME_C Rボタン
Canvas.GAME_D Lボタン

KeyEventCanvas.javaの一部
int action=getGameAction(keyCode);
if (action==UP) output="UP BUTTON";
if (action==DOWN) output="DOWN BUTTON";
if (action==LEFT) output="LEFT BUTTON";
if (action==RIGHT) output="RIGHT BUTTON";
if (action==FIRE) output="START BUTTON";
if (action==GAME_A) output="A BUTTON";
if (action==GAME_B) output="B BUTTON";
if (action==GAME_C) output="R BUTTON";
if (action==GAME_D) output="L BUTTON";

ただし、ゲームボーイアドバンスのエミュレータでは、ABLRボタンが効かないので注意してください。


コマンドの生成
画面下端に表示されるメニューを「コマンド」と呼びます。コマンドを生成するには、Commandクラスを使います。
Command(String label,int type,int priority)
label :ソフトラベル
type :コマンドタイプ
priority:優先度
labelには、メニューとして表示する文字列を指定します。
typeには、コマンドタイプ定数を指定します。コマンドタイプはコマンドの種別を識別するためだけのもので、BACKを指定したら前画面に戻るというわけではありません。基本的にCommand.SCREENを指定すれば良いです。
コマンドタイプ定数
Command.BACK バック
Cinnabd.CANCEL キャンセル
Command.HELP ヘルプ
Command.ITEM アイテム
Command.SCREEN スクリーン
Command.STOP ストップ

priorityには、優先度を指定します。優先度の順番に応じてソフトキーの配置が変わり、
ゲームボーイアドバンスでは、小さい値のものから右下・左下の順に配置されます。ただし、エミュレータでは、右下にまとめて表示されます。

今回は、ラベルが"SOFT1"、コマンドタイプがCommand.SCREEN、優先度が1のソフトキー1と、ラベルが"SOFT12"、コマンドタイプがCommand.SCREEN、優先度が2のソフトキー2を生成するので、以下のようになります。
KeyEventCanvas.javaの一部
soft1=new Command("SOFT1",Command.SCREEN,1);
soft2=new Command("SOFT2",Command.SCREEN,2);


コマンドの追加と削除
コマンドを画面に追加するにはDisplayableクラスのaddCommand()メソッドを、削除するにはremoveCommand()メソッドを使います。CanvasクラスはDisplayableクラスを継承しているので、この2つのメソッドを持っています。
void addCommand(Command command)
command:Commandクラスのオブジェクト
void removeCommand(Command command)
command:Commandクラスのオブジェクト

今回は、Commandオブジェクトのsoft1とsoft2をキャンバスに追加するので、以下のようになります。
KeyEventCanvas.javaの一部
addCommand(soft1);
addCommand(soft2);


コマンドイベント
「セレクトボタン+Bボタン」で優先順位1番目のコマンド、「セレクトボタン+Aボタン」で優先順位2番目のコマンドが実行され、CommandListenerのcommandAction()メソッドを呼びます。

今回は、KeyEventCanvasクラス自身にcommandAction()メソッド持たせるので、CommandListenerインタフェースを実装し、DisplayableクラスのsetCommandListener()メソッドでコマンドイベントの通知先を自身に指定します。
KeyEventCanvas.javaの一部
class KeyEventCanvas extends Canvas
implements CommandListener {

KeyEventCanvas.javaの一部
setCommandListener(this);

commandAction()メソッドの書式は次の通りです。
void commandAction(Command c,Displayable s)
c:イベント発生元となるCommandオブジェクト
s:イベント発生元となるDisplayableオブジェクト
cにはイベント発生元となるCommandオブジェクト、sにはイベント発生元となるDisplayableオブジェクトが渡されます。

今回は、「セレクトボタン+Bボタン」で"SOFT1"、「セレクトボタン+Aボタン」で"SOFT2"とキャンバスに表示するので、以下のようになります。
KeyEventCanvas.javaの一部
if (c==soft1) output="SOFT1";
if (c==soft2) output="SOFT2";


★アクションゲームを作る★


それでは、本題の「アクションゲーム」を作ります。スタートボタンを押すとゲームが開始します。そらみは強制的に右に移動するので、Aボタン(または上ボタン)でジャンプしながら、下に落ちないように進んでください。どれだけ進むことができたかがスコアとなります。このゲーム以下の2つのクラスで構成されています。 「Project Name」「MIDlet Class Name」に"ActinGame"を指定して、プロジェクトを作成してください。

  


画像ファイルの用意
このゲームでは以下の4枚の画像ファイルを使います。ActionGameディレクトリの下のresディレクトリに置いてください。

・そらみA
-0.png
-30x33


・そらみB
-1.png
-30x33


・地面
-2.png
-30x30


・ゲームオーバー
-3.png
-86x16



ActionGameクラス
ActionGameクラスは、プログラムの本体となるクラスです。コンストラクタで、キャンバスとスレッドの生成を行っています。
ActionGame.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

//アクションゲーム(本体)
public class ActionGame extends MIDlet {
    //コンストラクタ
    public ActionGame() {
        //キャンバス
        ActionCanvas canvas=new ActionCanvas();
        Display.getDisplay(this).setCurrent(canvas);

        //スレッド
        Thread thread=new Thread(canvas);
        thread.start();
    }

    //アプリの開始
    public void startApp() {
    }

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

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


ActionCanvasクラス
ActionCanvasクラスは、キャンバスとなるクラスです。
ActionCanvas.java
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
import java.util.Random;

//横スクロールアクションゲーム(キャンバス)
class ActionCanvas extends GameCanvas
    implements Runnable {
    //シーン
    private final static int
        S_TITLE   =0,//タイトル
        S_PLAY    =1,//プレイ
        S_GAMEOVER=2;//ゲームオーバー

    //システム
    private int      init=S_TITLE;      //初期化
    private int      scene;             //シーン
    private int      score;             //スコア
    private int      event;             //イベント
    private Image[]  image=new Image[4];//イメージ
    private Graphics g;                 //グラフィクス
    private Random   rand=new Random(); //乱数

    //そらみ
    private int     soramiY; //Y座標
    private int     jumpPow; //ジャンプ力
    private boolean jumpAble;//ジャンプ可

    //地面
    private int   mapDX;           //X相対位置
    private int[] mapH=new int[10];//高さ

    //コンストラクタ
    ActionCanvas() {
        super(false);
    }

    //メインループ
    public void run() {
        try {
            //初期化
            g=getGraphics();
            for (int i=0;i<4;i++) {
                image[i]=Image.createImage("/"+i+".png");
            }

            //メインループ
            while (true) {
                tick();
                Thread.sleep(60);
            }
        } catch (Exception e) {
        }
    }

    //処理
    private void tick() throws Exception {
        int i;

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

            //タイトル・プレイ
            if (scene==S_TITLE || scene==S_PLAY) {
                score   =0;
                soramiY   =getHeight()-63;
                jumpPow =0;
                jumpAble=true;
                mapDX    =0;
                for (i=0;i<10;i++) mapH[i]=1;
            }
            event=-999;
        }

        //背景
        int red  =35;
        int green=100;
        int blue =200;
        for (i=0;i<15;i++) {
            g.setColor(red,green,blue);
            g.fillRect(0,10*i,getWidth(),10);
            red  +=20;if (red  >255) red  =255;
            green+=20;if (green>255) green=255;
            blue +=20;if (blue >255) blue =255;
        }

        //そらみ
        i=1;
        if (jumpAble) i=score%2;
        g.drawImage(image[i],45,soramiY,g.LEFT|g.TOP);

        //地面
        for (i=0;i<mapH.length;i++) {
            g.drawImage(image[2],mapDX+i*30,
                getHeight()-mapH[i]*30,g.LEFT|g.TOP);
        }

        //スコア
        g.setColor(0,0,0);
        g.drawString(""+score,5,3,g.LEFT|g.TOP);

        //プレイ
        if (scene==S_PLAY) {
            //スコアアップ
            score++;

            //衝突判定
            if (soramiY>getHeight()-33-mapH[2]*30) {
                init=S_GAMEOVER;
                jumpAble=false;
            }
            //上移動
            else if (jumpPow>=0) {
                soramiY-=jumpPow*2;
                jumpPow--;
            }
            //下移動
            else {
                soramiY+=6;
                jumpAble=false;
                if (mapH[2]!=0 &&
                    soramiY>getHeight()-33-mapH[2]*30) {
                    soramiY=getHeight()-33-mapH[2]*30;
                    jumpAble=true;
                }
            }
            //右移動
            mapDX-=3;
            if (mapDX==-30) {
                mapDX=0;

                //地面の高さを左にシフト
                System.arraycopy(mapH,1,mapH,0,9);

                //地面の高さを指定
                i=(rand.nextInt()>>>1)%6;
                if (mapH[8]==0) {
                    mapH[9]=mapH[7];
                } else if (i==0) {
                    mapH[9]=0;
                } else if (i==1) {
                    mapH[9]=mapH[8]+1;
                    if (mapH[9]>4) mapH[9]=4;
                } else if (i==2) {
                    mapH[9]=mapH[8]-1;
                    if (mapH[9]<1) mapH[9]=1;
                }
            }

            //イベント
            i=getKeyStates();
            if (jumpAble) {
                if ((i&(UP_PRESSED|GAME_A_PRESSED))!=0) {
                    jumpAble=false;
                    jumpPow =9;
                    event=-999;
                }
            } else {
                if ((i&(UP_PRESSED|GAME_A_PRESSED))==0) {
                    jumpPow=-10;
                }
            }
        }

        //ゲームオーバー
        if (scene==S_GAMEOVER) {
            //そらみ落下
            if (soramiY<getHeight()+33) soramiY+=12;

            //ゲームオーバー表示
            g.drawImage(image[3],getWidth()/2,30,g.HCENTER|g.TOP);
        }

        //画面に反映
        flushGraphics();
    }

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

    //キープレスイベント
    public void keyPressed(int keyCode) {
        if (scene!=S_PLAY && getGameAction(keyCode)==FIRE) init=S_PLAY;
    }
}


シーン
このゲームには以下の4つのシーンがあります。現在のシーンはscene変数で保持しています。次に遷移するシーンはinit変数で保持しています。遷移しない時はinitは-1を保持しています。


GameCanvasクラス
ActionCanvasクラスはGameCanvasクラスを継承しています。GameCanvasクラスはCanvasクラスを拡張したクラスで、「ダブルバッファリング」や「キー状態の取得」などのゲームに役立つ機能が使用できます。

「ダブルバッファリング」は、画面と同じサイズのイメージを用意し、必要なものをこのイメージに描画してから、実際の画面に一変に反映させるという処理で、画面のチラツキを防ぎます。GameCanvasクラスのgetGraphics()メソッドでGraphicsオブジェクトを取得し、それに必要な描画を行い、最後にflushGraphics()メソッドを呼んで実際の画面に反映します。
ActionCanvas.javaの一部
g=getGraphics();

ActionCanvas.javaの一部
flushGraphics();

「キー状態の取得」は、現在どのキーが押されているかを調べる機能です。「キーイベント」や「コマンド」がボタンを操作した時に処理を行っていたのに対し、「キー状態の取得」では現在どのキーが押されているかによって、それに応じた処理を行います。GameCanvasクラスのgetKeyStates()メソッドを使います。
int getKeypadState()
戻り値:キー状態

戻り値の各ビットの位置がGameCanvasクラスで定義されているキー定数に対応します。

キー定数
GameCanvas.UP_PRESSED 上ボタン
GameCanvas.DOWN_PRESSED 下ボタン
GameCanvas.LEFT_PRESSED 左ボタン
GameCanvas.RIGHT_PRESSED 右ボタン
GameCanvas.FIRE_PRESSED スタートボタン
GameCanvas.GAME_A_PRESSED Aボタン
GameCanvas.GAME_B_PRESSED Bボタン
GameCanvas.GAME_C_PRESSED Rボタン
GameCanvas.GAME_D_PRESSED Lボタン

特定のキーが押されているかどうかは、getKeyStates()メソッドで取得した値と「GameCanvasクラスで定義されているキー定数」の値を&で計算して0になるかどうかでわかります。

今回のプログラムは、Aボタンか上ボタンを押している時、ジャンプ処理を行うので以下のようになります。

ActionCanvas.javaの一部
if ((i&(UP_PRESSED|GAME_A_PRESSED))!=0) {


スクロール処理
地面がスクロールする処理は、mapDX変数と、mapH配列で行っています。mapH配列には10個分の地面の高さの情報が格納されています。右から左へ地面1つ分(30ドット)スクロールしては、次の地面の高さをランダムに指定するという処理を繰り返しています。
ActionCanvas.javaの一部
mapDX-=3;
if (mapDX==-30) {
    mapDX=0;

    //地面の高さを左にシフト
    System.arraycopy(mapH,1,mapH,0,9);

    //地面の高さを指定
    i=(rand.nextInt()>>>1)%6;
    if (mapH[8]==0) {
        mapH[9]=mapH[7];
    } else if (i==0) {
        mapH[9]=0;
    } else if (i==1) {
        mapH[9]=mapH[8]+1;
        if (mapH[9]>4) mapH[9]=4;
    } else if (i==2) {
        mapH[9]=mapH[8]-1;
       if (mapH[9]<1) mapH[9]=1;
    }
}


ジャンプ処理
そらみのジャンプ処理は、jumpAble変数と、jumpPow変数で行っています。jumpAble変数はジャンプできる状態にあるかどうかを示します。そらみがのすぐ下に地面がある時はtrue、ない時はfalseを保持します。jumpPow変数はジャンプ力を示します。Aボタン(または上ボタン)が押された時jumpAbleがtrueなら、jumpPowに9がセットされ、上昇しながら1ずつ減っていき、0以下になったら下降しはじめます。ボタンを離した時にはjumpPowに-10がセットされ、ただちに下降しはじめます。
ActionCanvas.javaの一部
i=getKeyStates();
if (jumpAble) {
    if ((i&(UP_PRESSED|GAME_A_PRESSED))!=0) {
        jumpAble=false;
        jumpPow =9;
        event=-999;
    }
} else {
    if ((i&(UP_PRESSED|GAME_A_PRESSED))==0) {
        jumpPow=-10;
    }
}


★おわりに★


次回は、Phase2.5の新機能を使ったEZアプリ(Java)を作る予定です。お楽しみに。



−戻る−


(C)Npaka/Sehira, 2003-2004