★はじめに★
第2話 新生FOMAによるシューティングゲームの作成
[JAVA PRESS Vol.29]

ゲーム:シューティングゲーム
対応端末:・N2051・F2051・P2102V


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



ねぇねぇ、へにへに〜。FOMAって、第3世代の携帯電話とか言われてるけど、もの凄い勢いで人気無いよね?
ピー!! そらみちゃんはもう少し穏やかな物腰で発言してください。…でも実際のところ、FOMAよりも504iシリーズの方が小さくて性能良いしね…。
えっ? FOMAは次世代携帯のはずなのに、どうして現行機の504iより性能が悪いの?
まだ各メーカーが端末を作り慣れていないし、ネットワークが新しい分、回線の整備も行き届いてないみたいなんだよ。
うーん、iアプリの性能が504iと同じなら、パケット通信料は10分の1で済むし普及しそうなのにねぇ…。
…ふっふっふ、そらみちゃん。504i並のiアプリ処理能力を持ったFOMAが先日発売されたのは知ってるかい?
あ、それってもしかして、N2051とかF2051とか…P2102Vなんかのこと?どうでもいいけど、中身がNのP端末なんて、あたしゃ認めないよ!!
うん。とりあえずそらみちゃんのマニア心は横に置いておくとして、ようやく504i用のアプリも動くようになって、動画も綺麗になったし、これからはFOMAの逆襲がはじまりそうだね。
(…最近は505iシリーズの噂もちらほら聞くけど、とりあえず黙っとこ…)


★FOMA02仕様モデル★


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

しかし、初期のFOMAは、 と、第2世代である503i・504iシリーズより性能が悪かったため、普及しませんでした。

これを打破すべく2003年に投入されたのが、F2051・N2051・P2102Vといった次世代のFOMA端末です。これらは「FOMA02仕様モデル」と呼ばれ、プロファイルとして504iシリーズのDoJa-2.0を拡張したDoJa-2.1が採用され、iアプリの処理能力は504iシリーズ並になり、iモーションの画質も格段に良くなりました。また、通話エリアも関東・甲信越で人口カバー率約95%、全国の人口カバー率約82%と、ようやく実用に耐える端末になりました。


FOMA02仕様モデルの拡張機能
FOMA02仕様モデルの拡張機能には次のようなものがあります。

iモーション
「iモーション」は、FOMA向けの動画配信サービスです。今まで100Kバイト/最大15秒から、300Kバイト/最大40秒となり、画質も格段に良くなりました。動画フォーマットは映像コーデック(圧縮/展開技術)は「MPEG-4」、音声コーデックは「AMR」と変わりませんが、ファイルフォーマットが従来の「ASF」から「MP4」に変更されました。MP4はQuickTimeのファイルフォーマットを基本として策定されたファイルフォーマットで、MPEG標準とされています。FOMA02仕様モデルでは、ASFは再生できないので注意してください。

この新しいiモーションを再生できるのは、FOMA02仕様モデルと、新しいバージョンのQuickTimeをインストールしたパソコンとなります。QuickTimeは従来からMP4に対応していますが、音声コーデックがAMRであるiモーションは再生できません。次期バージョンで対応するとのことですが、執筆時現在(2003年1月)まだリリースされていません。

映像コーデック 音声コーデック ファイルフォーマット
ドコモ新FOMA MPEG-4 AMR MP4
ドコモ旧FOMA MPEG-4 AMR ASF
KDDIムービー MPEG-4 QCELPまたはMP3 MP4
J-PHONEムービー写メール Nancy AMR Nancy


ポインティングデバイス
ポインティングデバイスは、パソコンのマウスのように、カーソルを移動させることで座標位置を伝えることができる入力デバイスです。執筆時現在(2003年1月)、携帯で利用可能なポインティングデバイスは、N2051で採用されている「ニューロポインター」のみです。


携帯電話の個体識別情報の取得
アプリから携帯電話の個体識別情報を取得することができます。個体識別情報には以下の2種類があり、同じ値が複数の個体に割り当てられることはありません。

スクラッチパッドとHTTP送受信データのサイズ拡大
スクラッチパッドとは、アプリごとに割り当てられるデータ保存領域のことです。スクラッチパッドの最大サイズは今まで10〜100Kバイトでしたが、FOMA02仕様モデルは200Kバイトまで引き上げられました。また、HTTP通信時の送受信データの最大サイズは今まで送信5Kバイト、受信10Kバイトでしたが、FOMA02仕様モデルは送信80Kバイト、受信150Kバイトまで引き上げられました。

503iシリーズ 504iシリーズ FOMA02仕様モデル
スクラッチパッドサイズ 10Kバイト 100Kバイト 200Kバイト
送信データサイズ 5Kバイト 5Kバイト 80Kバイト
受信データサイズ 10Kバイト 10Kバイト 150Kバイト


メロディフォーマットの追加
サウンドデータのフォーマットは今まで「iメロディ形式」が標準でしたが、FOMA02仕様モデルでは「SMF(Standard MIDI File)」が標準となりました。今後発売される端末は「iメロディ形式」をサポートしない可能性があります。


オプションAPIと拡張APIの実装状況
FOMA02仕様モデルのiアプリオプションAPIとiアプリ拡張APIの実装状況は次の通りです。

オプションAPIと拡張APIの実装状況
オプションAPIと拡張APIの実装状況
F2051 N2051 P2102V
携帯電話組み込みの効果音を鳴らす
複数のサウンドを同時に鳴らす      
イメージマップを使う  
描画クリッピングを行う      
ピクセルを操作する      
イメージの回転・反転・拡大縮小を行う      
ラスタオペレーションを行う      
スプライト機能を使う  
カラーパレットを変更する      
サウンドエフェクトを使う  
待ち受け画像を変更する      
サブディスプレイにイメージを表示する  
3Dポリゴンを表示する(高レベル)
3Dポリゴンを表示する(低レベル)      
2Dアニメーションを表示する      
静止画の撮影
動画の撮影
ポインティングデバイス    


★開発環境を整える★


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


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


iαppli Development Kit for DoJa2.1 (FOMA) Ver.1.00
iアプリを作るための開発キットです。FOMA02仕様モデルの新機能にも対応しました。NTTドコモのサイトで入手できます。ボタン1つでビルド(コンパイル+検証+JAR作成)やエミュレータでの実行ができます。インストーラの指示に従ってインストールしてください。Microsoft Windows 98 Second Edition/2000で動作します。

また、同サイトから開発に役立つドキュメントが入手できるので、いっしょにダウンロードしてください。
Japanese documentation for the J2ME CLDC
CLDCのAPIリファレンスはNTTドコモのサイトで入手できるドキュメントの中には含まれないので、サン・マイクロシステムズのサイトから入手してください。


★iモーションの再生★


はじめに、ボタンを押した時にiモーションを再生するプログラム「VisualPresenterEx」を作ります。


このプログラムは以下の2つのクラスで構成されています。 「iαppli Development Kit」のエミュレータでは、iモーションの再生機能をサポートしてないので、動作テストは実機で行ってください。


動画ファイルの準備
このプログラムでは、1つの動画ファイルを使います。プロジェクトのresディレクトリ(C:\iDKDoJa2.1FOMA\apps\VisualPresenterEx\res)に置いてください。

・サンプル
sample.3gp


iモーションの動画ファイル(*.3gp)を生成するには、次の2つの方法があります。 執筆時現在(2003年1月)、iモーションの動画ファイルに変換する方法は公開されていないので、FOMA端末で動画を撮影することにします。メールに添付できる動画ファイルのサイズは最大100Kバイトです。今回の動画ファイルはJARファイルに含めます。JARファイルのサイズ制限は504iシリーズと同じ30Kバイトなので、動画ファイルのサイズは20Kバイト程度の短いものにしてください。


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

//iモーションの再生(本体)
public class VisualPresenterEx extends IApplication {

    //アプリの開始
    public void start() {
        Display.setCurrent(new VisualPresenterPanel());
    }
}


VisualPresenterPanelクラス
VisualPresenterPanelクラスは、パネルとなるクラスです。
VisualPresenterPanel.java
import com.nttdocomo.ui.*;

//iモーションの再生(パネル)
class VisualPresenterPanel extends Panel
    implements ComponentListener {
    private static Button          button;//ボタン
    private static VisualPresenter vp;    //ビジュアルプレゼンター

    //コンストラクタ
    VisualPresenterPanel() {
        //ボタン
        button=new Button("動画の再生");
        add(button);
        setComponentListener(this);
        try {
            //動画ファイルの読み込み
            MediaImage m=MediaManager.getImage(
                "resource:///sample.3gp");
            m.use();

            //ビジュアルプレゼンター
            vp=new VisualPresenter();
            add(vp);
            vp.setImage(m);
        } catch (Exception e) {
        }
    }

    //コンポーネントアクションイベント
    public void componentAction(Component c,int type,int param) {
        //iモーションの再生
        if (type==BUTTON_PRESSED && c==button) {
            try {
                vp.play();
            } catch (Exception e) {
            }
        }
    }
}


パネル
「パネル」はラベルやテキストボックスなどの「コンポーネント(部品)」を貼り付けて使うものです。キャンバスが画面に文字列やイメージを描画したい時に使うのに対し、パネルはHTMLブラウザのフォームのようなインターフェイスを作りたい時に使います。パネルとなるクラスはPanelクラスを継承します。
VisualPresenterPanel.javaの一部
class VisualPresenterPanel extends Panel

「DoJa-2.1プロファイル」で使えるコンポーネントは9種類あります。
ラベル 1行のテキストを表示する。
イメージラベル イメージを表示する。
ボタン ボタンによって入力する。
イメージボタン イメージを貼り付けることができるボタン。
アンカーボタン HTMLのアンカー風のボタン。
テキストボックス 文字列を入力する。
リストボックス 各種選択リストによって入力する。
ティッカー スクロールする文字列を表示する。
ビジュアルプレゼンター 動画を再生する。


ボタン
Buttonクラスのオブジェクトを作り、パネルに追加します。Buttonクラスには2種類のコンストラクタがあります。
Button()
Button(String label)
label:ボタン上に表示する文字列

パネルにコンポーネントを追加するにはadd()メソッドを使います。
void add(Component c)
c:コンポーネント

今回は、"動画の再生"というボタンを作るので以下のようになります。
VisualPresenterPanel.javaの一部
button=new Button("動画の再生");
add(button);


ボタンが押した時に処理を行う時には、コンポーネントリスナーを使います。コンポーネントリスナーは、ボタンを押したことや、テキストボックスの中身が変更したことなど、イベント情報を受信するためのリスナーです。

はじめに、VisualPresenterPanelクラス自身にコンポーネントリスナーのインタフェースを実装します。Java言語では、インタフェースを実装していることを示すために、implementsキーワードの後にインタフェース名を記述します。
VisualPresenterPanel.javaの一部
implements ComponentListener {

次に、PanelクラスのsetComponentListener()メソッドで、イベントが発生した時、そのことをどこへ送信すれば良いかを指定します。今回はVisualPresenterPanelクラス自身がコンポーネントリスナーになるのでthisをセットします。
VisualPresenterPanel.javaの一部
setComponentListener(this);

最後にcomponentAction()メソッドを実装します。実機はイベントが発生した時、このメソッドを呼びます。
void componentAction(Component source, int type, int param)
source:イベントが発生したコンポーネント
type:イベント種別
param:パラメータ

sourceはイベントが発生したコンポーネントのオブジェクト、typeはイベント種別でComponentListenerインタフェースが持つイベント種別定数が渡されます。
イベント種別定数
BUTTON_PRESSED ボタンが押された時に発行するイベント
TEXT_CHANGED テキスト入力が確定した時に発行するイベント
SELECTION_CHANGED リストの選択が変化した時に発行するイベント

paramはパラメータで、意味はコンポーネントごとに異なります。今回は、ボタンを押した時にiモーションを再生するので以下のようになります。
VisualPresenterPanel.javaの一部
public void componentAction(Component c,int type,int param) {
    //iモーションの再生
    if (type==BUTTON_PRESSED && c==button) {
        try {
            vp.play();
        } catch (Exception e) {
        }
    }
}


動画ファイルの読み込み
動画ファイルを読み込むには、「MediaManagerクラス」を使います。MediaManagerクラスは、動画ファイルを読み込むためのgetImage()メソッドを持っています。
static MediaImage getImage(String location)
location:読み込み先
戻り値:MediaImageオブジェクト

読み込み先は以下のフォーマットで指定します。
resource:///ファイル名

動画データをリソースでなく、スクラッチパッドから読み込む場合は、以下のフォーマットで指定します。
scratchpad:///0;pos=位置

戻り値として「MediaImageクラス」のオブジェクトが返ってきます。MediaImageクラスは、ファイルを読み込み、内部表現形式に変換するクラスです。use()メソッドで読み込み処理を行います。

今回は、"sample.3gp"を読み込むので以下のようになります。
MediaImage m=MediaManager.getImage("resource:///sample.3gp");
m.use();


ビジュアルプレゼンター
ビジュアルプレゼンターは、動画を再生するコンポーネントです。「iモーション」の他に、全機種で「GIFアニメーション」、N504iとN504iSで「E-アニメーター」を再生できます。

VisualPresenterクラスのオブジェクトを作り、パネルに追加し、VisualPresenterクラスのsetImage()メソッドで動画データをセットします。
VisualPresenterPanel.javaの一部
vp=new VisualPresenter();
add(vp);
vp.setImage(m);

再生するにはVisualPresenterクラスのplay()メソッドを呼びます。
VisualPresenterPanel.javaの一部
vp.play();


★ポインティングデバイスの使用★


次に、ポインティングデバイスのカーソルが示している座標を表示するプログラム「PointingDeviceEx」を作ります。


このプログラムは以下の2つのクラスで構成されています。 執筆時現在(2003年1月)、ポインティングデバイスが使用できるのはN2051のみです。


PointingDeviceExクラス
PointingDeviceExクラスは、プログラムの本体となるクラスです。PointingDeviceCanvasオブジェクトを生成し、 DisplayクラスのsetCurrent()メソッドに引数として渡しています。最後にPointingDeviceCanvasのexe()メソッドを呼んでいます。

PointingDeviceEx.java
import com.nttdocomo.ui.*;

//ポインティングデバイスの使用(本体)
public class PointingDeviceEx extends IApplication {

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


PointingDeviceCanvasクラス
PointingDeviceCanvasクラスは、キャンバスとなるクラスです。exe()メソッドでは無限ループを行い、100ミリ秒ごとにポインティングデバイスのカーソルの座標を描画しています。
PointingDeviceCanvas.java
import com.nttdocomo.ui.*;
import com.nttdocomo.opt.ui.*;

//ポインティングデバイスの使用(キャンバス)
class PointingDeviceCanvas extends Canvas {

    //実行
    void exe() {
        //Graphicsオブジェクトの取得
        Graphics g=getGraphics();

        //ポインティングデバイスの使用
        PointingDevice.setEnabled(true);
        while (true) {
            //描画
            g.lock();
            g.setColor(g.getColorOfName(g.WHITE));
            g.fillRect(0,0,getWidth(),getHeight());
            g.setColor(g.getColorOfName(g.BLACK));
            g.drawString("X座標>"+PointingDevice.getX(),0,20);
            g.drawString("Y座標>"+PointingDevice.getY(),0,40);
            g.drawString("ポインティング>"+PointingDevice.isEnabled(),0,60);
            g.unlock(true);

            //スリープ
            try {
                Thread.sleep(100);
            } catch (Exception e) {
            }
        }
    }

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


Graphicsオブジェクトの取得
画面に図形や文字列を描画するにはGraphicsオブジェクトを操作します。Graphicsオブジェクトはpaint()メソッドの引数として渡されますが、CanvasクラスのgetGraphics()メソッドで明示的に取得することもできます。
PointingDeviceCanvas.javaの一部
Graphics g=getGraphics();


ポインティングデバイス
ポインティングデバイスを操作するにはcom.nttdocomo.opt.uiパッケージのPointingDeviceクラスを使います。ポインティングデバイスを使用するかどうかを指定するにはsetEnabled()メソッド、使用しているかどうかを取得するにはisEnabled()メソッドを使います。
static void setEnabled(boolean flag)
flag:ポインティングデバイスを使用するかどうか
static boolean isEnabled()
戻り値:ポインティングデバイスを使用しているかどうか

今回は、使用するので以下のようになります。
PointingDeviceCanvas.javaの一部
PointingDevice.setEnabled(true);

また、ポインティングデバイスのカーソルのX座標を取得するにはgetX()メソッド、Y座標を取得するにはgetY()メソッドを使います。
static int getX()
戻り値:ポインティングデバイスのX座標
static int getY()
戻り値:ポインティングデバイスのY座標

今回は、ポインティングデバイスの座表を描画するので以下のようになります。
PointingDeviceCanvas.javaの一部
g.drawString("X座標>"+PointingDevice.getX(),0,20);
g.drawString("Y座標>"+PointingDevice.getY(),0,40);


★シューティングゲームを作る★


それでは、本題の「シューティングゲーム」を作ります。落ちてくる隕石をレーザーで破壊し、地球を守るゲームです。操作方法はポインティングデバイス(未対応機種は方向キー)で照準の移動、選択キーでレーザーの発射です。隕石が地球に落ちるとゲームオーバーです。どれだけ長い時間守れたかがスコアとなります。ゲームオーバー時には、ソフトキー1によってリトライできます。

  

このゲームは以下の2つのクラスで構成されています。

画像ファイルの用意
このゲームでは、5枚の画像ファイルを使います。resディレクトリ(C:\iDKDoJa2.1FOMA\apps\ShootingGame\res)に置いてください。

・宇宙船
-0.gif
-16x16


・照準
-1.gif
-20x16


・隕石
-2.gif
-16x16


・爆発
-3.gif
-24x24


・ゲームオーバー
-4.gif
-86x16



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

//シューティングゲーム(本体)
public class ShootingGame extends IApplication {

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


ShootingCanvasクラス
ShootingCanvasクラスは、キャンバスとなるクラスです。exe()メソッドでは無限ループを行い、100ミリ秒ごとに状態の遷移と再描画を行っています。
ShootingCanvas.java
import com.nttdocomo.ui.*;
import com.nttdocomo.opt.ui.*;
import java.util.*;

//シューティングゲーム(キャンバス)
class ShootingCanvas extends Canvas {
    //定数
    private static int S_PLAY=0,S_GAMEOVER=1;     //シーン
    private static int E_NONE=-1,E_DOWN=0,E_BOM=1;//敵

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

    //照準
    private static boolean pointing;//ポインティング
    private static int     targetX; //X座標
    private static int     targetY; //Y座標

    //星
    private static int[] starX=new int[10];//X座標
    private static int[] starY=new int[10];//Y座標

    //隕石
    private static int[] enState=new int[8];//状態
    private static int[] enX    =new int[8];//X座標
    private static int[] enY    =new int[8];//Y座標

    //処理
    void exe() {
        int i;
        long sleepTime=0;
        MediaImage m;

        //ポインティングデバイス
        try {
            PointingDevice.setEnabled(true);
            pointing=true;
        } catch (Exception e) {
            pointing=false;
        }
        try {
            //初期化
            g=getGraphics();
            for (i=4;i>=0;i--) {
                m=MediaManager.getImage("resource:///"+i+".gif");
                m.use();
                image[i]=m.getImage();
            }

            //メインループ
            while (true) {
                tick();
                while (System.currentTimeMillis()<sleepTime+100);
                sleepTime=System.currentTimeMillis();
            }
        } catch (Exception e) {
        }
    }

    //時間経過
    private void tick() {
        int i,j;

        //初期化
        if (init>=0) {
            scene=init;
            init =-1;
            //プレイ
            if (scene==S_PLAY) {
                for (i=9;i>=0;i--) {
                    starX[i]=(rand.nextInt()>>>1)%getWidth();
                    starY[i]=(rand.nextInt()>>>1)%getHeight();
                }
                for (i=7;i>=0;i--) enState[i]=E_NONE;
                targetX=getWidth()/2;
                targetY=getHeight()/2;
                score  =0;
                setSoftLabel(SOFT_KEY_1,"");
            }
            //ゲームオーバー
            else if (scene==S_GAMEOVER) {
                setSoftLabel(SOFT_KEY_1,"リトライ");
            }
        }

        //背景
        g.lock();
        g.setColor(g.getColorOfName(g.BLACK));
        g.fillRect(0,0,getWidth(),getHeight());
        g.setColor(g.getColorOfName(g.WHITE));
        for (i=9;i>=0;i--) {
            g.fillRect(starX[i],starY[i],1,1);
            starY[i]+=10;
            if (starY[i]>getHeight()) {
                starX[i]=(rand.nextInt()>>>1)%getWidth();
                starY[i]=-(rand.nextInt()>>>1)%100;
            }
        }
        g.setColor(g.getColorOfRGB( 50, 50,255));
        g.fillRect(0,getHeight()-9,getWidth(),3);
        g.setColor(g.getColorOfRGB(100,100,255));
        g.fillRect(0,getHeight()-6,getWidth(),3);
        g.setColor(g.getColorOfRGB(150,150,255));
        g.fillRect(0,getHeight()-3,getWidth(),3);

        //隕石
        j=(rand.nextInt()>>>1)%100;
        for (i=7;i>=0;i--) {
            //出現
            if (enState[i]==E_NONE && j<5) {
                enX[i]    =(rand.nextInt()>>>1)%getWidth();
                enY[i]    =-16;
                enState[i]=E_DOWN;
                j=99;
            }
            //下
            if (enState[i]==E_DOWN) {
                g.drawImage(image[2],enX[i]-8,enY[i]-8);
                enY[i]++;
                if (score>300) enY[i]++;
                if (score>600) enY[i]++;
                if (enY[i]>getHeight()+8) enState[i]=E_NONE;
            }
            //爆発
            else if (enState[i]==E_BOM) {
                g.drawImage(image[3],enX[i]-12,enY[i]-12);
                enState[i]=E_NONE;
            }
        }

        //プレイ
        if (scene==S_PLAY) {
            //ゲームオーバーチェック
            for (i=7;i>=0;i--) {
                if (enState[i]!=E_NONE && enY[i]>getHeight()-18) {
                    init=S_GAMEOVER;
                }
            }

            //スコア
            score++;

            //レーザー
            j=getKeypadState();
            if ((1<<Display.KEY_SELECT&j)!=0) {
                g.setColor(g.getColorOfRGB(160,230,255));
                g.drawLine(targetX,getHeight()-28,targetX,targetY);
                g.drawLine(targetX,getHeight()-29,targetX,targetY+1);
                for (i=7;i>=0;i--) {
                    if (enState[i]==E_DOWN) {
                        if (targetX-8<=enX[i] && enX[i]<=targetX+8 &&
                            targetY-8<=enY[i] && enY[i]<=targetY+8) {
                            enState[i]=E_BOM;
                        }
                    }
                }
            }
            //ポインティングデバイス操作
            else if (pointing) {
                targetX=PointingDevice.getX();
                targetY=PointingDevice.getY();
            }
            //キー操作
            else {
                if ((1<<Display.KEY_UP&j)!=0 && targetY>12) targetY-=8;
                if ((1<<Display.KEY_DOWN&j)!=0 && targetY<getHeight()-12) targetY+=8;
                if ((1<<Display.KEY_LEFT&j)!=0 && targetX>12) targetX-=8;
                if ((1<<Display.KEY_RIGHT&j)!=0 && targetX<getWidth()-12) targetX+=8;
            }

            //照準
            g.drawImage(image[1],targetX-10,targetY-8);
        }
        //ゲームオーバー
        else {
            g.drawImage(image[4],(getWidth()-86)/2,40);
        }

        //スコア
        g.setColor(g.getColorOfName(g.WHITE));
        g.drawString(String.valueOf(score),4,14);

        //宇宙船
        g.drawImage(image[0],targetX-8,getHeight()-30);
        g.unlock(true);
    }

    //キーイベント
    public void processEvent(int type, int param) {
        if (type ==Display.KEY_PRESSED_EVENT &&
            param==Display.KEY_SOFT1 &&
            scene==S_GAMEOVER) {
            init=S_PLAY;
        }
    }

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


ポインティングデバイスの対応機種判定
ポインティングデバイスを使用可能に指定した時、例外が発生するかどうかで、対応機種かどうかの判定を行っています。使用可能な時はpointing変数にtrue、使用不可能な時はfalseを指定します。
ShootingCanvas.javaの一部
try {
    PointingDevice.setEnabled(true);
    pointing=true;
} catch (Exception e) {
    pointing=false;
}


スリープ
100ミリ秒に1回処理を行うために必要なスリープを行っています。
while (System.currentTimeMillis()<sleepTime+100);
sleepTime=System.currentTimeMillis();

System.currentTimeMillis()メソッドは、ミリ秒で表される現在の時間(現在時刻と協定世界時のUTC1970年1月1日午前0時との差)を返すメソッドです。端末によっては精度が良くない端末もあります。sleepTime変数にスリープ完了した時の時間を指定し、次のスリープ時には、その時間から100ミリ秒経つまで空のループを行って処理を止めています。


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


隕石の状態遷移
隕石には以下の3つの状態があります。現在の状態はenState配列で保持しています。8つの隕石の状態を保持するので、配列の長さは8です。


キー状態の取得
キー状態を取得するには、CanvasクラスのgetKeypadState()メソッドを使います。processEvent()メソッドでは、キーを押したというイベントが発生するたびに、任意の処理を行っていました。それに対し今回のgetKeypadState()メソッドは、現在どのキーが押されているかという情報を取得して、それに対応した処理を行います。
int getKeypadState()
戻り値:キー状態

戻り値の各ビットの位置がDisplayクラスで定義されているキー定数に対応します。 ビットとキーの対応は、1<<(Displayクラスで定義されているキーの値) となります。 押された状態になっているキーに対応するビットは1に、 押されていない状態になっているキーに対応するビットは、0になります。

キー定数 意味
KEY_0 0キー 0
KEY_1 1キー 1
KEY_2 2キー 2
KEY_3 3キー 3
KEY_4 4キー 4
KEY_5 5キー 5
KEY_6 6キー 6
KEY_7 7キー 7
KEY_8 8キー 8
KEY_9 9キー 9
KEY_ASTERISK *キー 10
KEY_POUND #キー 11
KEY_LEFT 左キー 16
KEY_UP 上キー 17
KEY_RIGHT 右キー 18
KEY_DOWN 下キー 19
KEY_SELECT 選択キー 20
KEY_SOFT1 ソフトキー1 21
KEY_SOFT2 ソフトキー2 22


2進数に直してみると分かりやすいでしょう。
2キーを押す →4 4を2進数にすると 00000100
3キーを押す →8 8を2進数にすると 00001000
2キーと3キー同時押し→12 12を2進数にすると 00001100


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


★おわりに★


次回は、「J2ME Personal Profile for Zaurus」を使って、「Zaurus SL-C700」で動くゲームを作る予定です。お楽しみに。




−戻る−


(C)Npaka/Sehira, 2003-2004