★はじめに★
第1話 504iSでカメラを制御する
[JAVA PRESS Vol.29]

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


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



へにへに、この雑誌見て! 地球で、P504iSが発売されたみたい!
504iSというと、iアプリからカメラの制御ができる携帯電話だね、そらみちゃん。
カメラの制御かぁ〜。それって一体、どんなことができるの?
アプリ内で写真を撮ったり、その写真をデータとして利用できたりするみたいだよ。
それじゃあ例えば、自分を撮ってゲームの主人公にするってこともできる!?
ネットではとても配信できないような、あんな姿やこんな姿のそらみちゃんもね。
…無数に現れるへにへにをひたすら狙撃するゲームとか、面白そうかも。
と、とりあえずは普通のパズルゲームでも作ってみようか…(汗)
うんっ。まずは基本的なことから覚えないとね♪


★J2MEとiアプリ★


まずはじめに、連載のタイトルになってる「J2ME」と、iモードで動く小さなアプリ「iアプリ」の関係について説明します。


J2ME
Java言語は、パソコンはもちろん、サーバー、組み込み機器など、様々な用途で使われています。3Dグラフィックスなら「Java 3D」、音声認識なら「Java Speech」というように、用途にが増えるにしたがってAPIの数も膨大になってきました。そこで、Java言語のAPIは3つのエディションに分けられました。 J2MEはPDAや携帯電話など組み込み機器で使われるJava実行環境です。J2EEやJ2SEにはサン・マイクロシステムズからSDK(Software Development Kit)が提供されていますが、J2MEにはありません。これはJ2MEが対象とする組み込み機器は、メモリやCPUの処理能力はもちろん、インターフェースも多種多様なため、1つの開発キットとして提供することができないからです。J2MEでは機器に応じて「コンフィギュレーション」と「プロファイル」を組み合わせて使うことになります。


コンフィギュレーション
J2MEでは処理能力の低い機器でもプログラムが動くように、従来のJ2SEから機能を絞りこんだサブセットを定義します。この定義のことを「コンフィギュレーション」と呼びます。執筆時現在(2002年12月)、以下の2つのコンフィギュレーションがあります。 「J2ME CDC」はカーナビ、セットトップ・ボックスなどの、中程度の処理能力を持つデバイスを対象としているコンフィギュレーションで、Java仮想マシンはJ2SEで使われているのと同じものを使います。

「J2ME CLDC」はPDAや携帯電話などの、処理能力やメモリに制限のあるデバイスを対象としているコンフィギュレーションです。サポートするクラスの数を必要最低限に絞って、APIのサイズを減らして、Java仮想マシンが実行時に使用するメモリを小さくしています。CLDC仕様のプログラムを動作させるためのJava仮想マシンは、キロ(K)のオーダーのメモリで動作することから「K Virtual Machine(KVM)」と呼ばれています。


プロファイル
組み込み機器は、PDAならペン入力、携帯電話ならバイブレーション機能というように、それぞれ特有の機能を持つため、それに対応できるように用途に応じた特殊なAPIが必要になります。このAPIのことを「プロファイル」と呼びます。執筆時現在(2002年12月)、J2ME CLDCには以下の2つのプロファイルがあります。

「MID Profile」はPDAや携帯電話向けのプロファイルです。J-PHONE端末やau端末のJava実行環境は、このプロファイルがベースとなっています。MIDPの仕様にそって作られたJavaアプリのことを「MIDlet」と呼びます。

「DoJa」はiモードに特化したプロファイルです。iモードではMID Profileとは別のプロファイルを採用することにより、iモードの機能を活かせるようにしました。DoJaの仕様にそって作られたJavaアプリのことを「iアプリ」と呼びます。DoJaには503iシリーズで採用しているDoJa-1.0と、504iシリーズで採用されているDoJa-2.0の2つのバージョンがあります。

今回は、504iSシリーズで動くiアプリを作るので、コンフィギュレーションは「J2ME CLDC」、プロファイルは「DoJa-2.0」を使います。


★開発環境を整える★


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


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


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

また、同サイトから開発に役立つドキュメントが入手できるので、いっしょにダウンロードしてください。

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


★はじめてのiアプリ★


それでは、「Hello World!」という文字列を表示するアプリを作ります。



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


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


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

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


HelloWorldクラス
HelloWorldクラスは、プログラムの本体となるクラスです。

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

//文字列の表示(本体)
public class HelloWorld extends IApplication {

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


import
はじめのimportでは、com.nttdocomo.uiパッケージのクラスを使うことを宣言しています。次で説明するIApplicationクラスやDisplayクラスがこのパッケージに含まれています。
HelloWorld.javaの一部
import com.nttdocomo.ui.*;


IApplicationクラス
iアプリの本体となるクラスは、IApplicationクラスを継承します。IApplicationを継承したクラスは、start()メソッドを必ず持つ必要があります。このメソッドはiアプリが起動した時に、1度だけ呼ばれるメソッドです。
HelloWorld.javaの一部
public class HelloWorld extends IApplication {


Displayクラス
DisplayクラスのsetCurrent()メソッドで、実機の画面に表示するキャンバスを指定します。ここでは、次で説明するHelloWorldCanvasクラスのオブジェクトをセットしています。
HelloWorld.javaの一部
Display.setCurrent(new HelloWorldCanvas());


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

//文字列の表示(キャンバス)
class HelloCanvas extends Canvas {

    //描画
    public void paint(Graphics g) {
        g.drawString("Hello World!",0,20);
    }
}


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


Graphicsクラス
文字列を表示するにはGraphicsクラスのdrawString()メソッドを使います。
void drawString(String msg,int x,int y)
text テキスト
x X座標
y Y座標

指定するXY座標は、文字列の左上でなくベースライン左隅の参照点の座標となります。



ここでは、"Hello World!"という文字列を、XY座標(0,20)に表示しています。
g.drawString("Hello World!",0,20);


ビルドとADF設定
iアプリはJARファイルとADFで構成されています。ADFファイルを先にサーバからダウンロードして、そのiアプリが実行可能かどうかチェックしてから、JARファイルをダウンロードする仕組になっています。



ビルド
「iαppliTool」の[ビルド]ボタンを押してください。成功すれば、binディレクトリにJARファイル(C:\iDK\apps\HelloWorld\bin\HelloWorld.jar)が生成されます。


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


「AppName」キーは、端末にダウンロードした時、iアプリ一覧の画面で表示される名前です。「AppClass」キーには、iアプリの本体となるクラスの名前を指定します。「PackageURL」「AppSize」「LastModified」も入力必須項目ですが、一度ビルドすれば自動的に入力されます。その他の設定項目については、「iアプリコンテンツ開発ガイドfor504i(詳細編)」を参照してください。


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


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

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


★カメラの制御★


次に、アプリで写真を撮るプログラム「CameraEx」を作ります。504iSシリーズからアプリでカメラ制御する機能が追加されました。アプリからカメラを起動し、そこで撮った写真データをImageオブジェクトやJPEGバイトデータとして取得できます。単独でカメラを起動して撮影した写真のデータは取得できません。執筆時現在(2002年12月)、P504iS・N504iS・F504iSで使用できます。


このプログラムは、以下の2つのクラスで構成されています。

画像ファイルの用意
iアプリでは、JARファイルに含めた画像ファイルを、読み込んで使うことができます。JARファイルに含めたいファイルは、プロジェクトのresディレクトリ(C:\iDK\apps\CameraEx\res\)に置いてください。iアプリで使用できる画像のファイル形式はGIFです。機種依存ですがJPEGを使用できるものもあります。このアプリでは以下の画像ファイルを使います。

・そらみ&へにへに
-0.gif
-120x120ドット



CameraExクラス
CameraExクラスは、プログラムの本体となるクラスです。
CameraEx.java
import com.nttdocomo.ui.*;

//カメラの制御(本体)
public class CameraEx extends IApplication {

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


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

//カメラの制御(キャンバス)
class CameraCanvas extends Canvas {
    private Camera cam;  //カメラ
    private Image  image;//イメージ

    //コンストラクタ
    CameraCanvas() {
        //イメージの読み込み
        try {
            MediaImage m=MediaManager.getImage(
                "resource:///0.gif");
            m.use();
            image=m.getImage();
        } catch (Exception e) {
        }

        //カメラの初期化
        cam=Camera.getCamera(0);
        cam.setImageSize(getWidth(),getHeight());
        setSoftLabel(SOFT_KEY_2,"カメラ");
    }

    //描画
    public void paint(Graphics g) {
        if (image!=null) g.drawImage(image,0,0);
    }

    //キーイベントの処理
    public void processEvent(int type, int param) {
        if (type==Display.KEY_PRESSED_EVENT &&
            param==Display.KEY_SOFT2) {
            //カメラの制御
            try {
                cam.takePicture();
                if (cam.getNumberOfImages()!=0) {
                    if (image!=null) image.dispose();
                    MediaImage m=cam.getImage(0);
                    m.use();
                    image=m.getImage();
                    repaint();
                }
            } catch (Exception e) {
            }
        }
    }
}


画像ファイルの読み込み
画像ファイルを読み込むにはMediaManagerクラスのgetImage()メソッドを使います。
static MediaImage getImage(String location)
location 読み込み先
返り値 MediaImageオブジェクト

読み込み先は次の書式で指定します。
resource:///ファイル名

返り値としてMediaImageクラスのオブジェクトが返ってきます。MediaImageクラスは、ファイルを読み込み、内部表現形式に変換するクラスです。use()メソッドで読み込み処理を行い、getImage()メソッドでImageクラスのオブジェクトを取得します。今回は、"0.gif"を読み込むので次のようになります。
CameraCanvas.javaの一部
MediaImage m=MediaManager.getImage("resource:///0.gif");
m.use();
image=m.getImage();


イメージの描画
Imageクラスのオブジェクトを、GraphicsクラスのdrawImage()メソッドに渡すことで、イメージを描画することができます。
void drawImage(Image image,int x,int y)
image 表示するイメージ
x X座標
y Y座標

XY座標は、イメージの左上の座標です。今回は、座標(0,0)にイメージを描画するので次のようになります。
if (image!=null) g.drawImage(image,0,0);


ソフトキーとソフトラベル
実機の画面下に表示されるメニューを実行するためのキーを「ソフトキー」と呼びます。画面の左下にあるソフトキー1と、画面の右下にあるソフトキー2の2種類があります。メニューに表示される文字列を「ソフトラベル」と呼びます。このソフトラベルを変更するには、CanvasクラスのsetSoftLabel()メソッドを使います。
void setSoftLabel(int key,String caption)
key ソフトキーの種類
label 表示する文字列

ソフトキーの種類には、Canvasクラスの持つソフトキー定数を指定します。
ソフトキー定数
Canvas.SOFT_KEY_1 ソフトキー1
Canvas.SOFT_KEY_1 ソフトキー2

表示する文字列は全角2文字(半角4文字)です。
今回、ソフトキー2のソフトラベルに"カメラ"という文字列を指定するので次のようになります。
CameraCanvas.javaの一部
setSoftLabel(SOFT_KEY_2,"カメラ");


キーイベント
CanvasクラスのprocessEvent()メソッドをオーバーライドすることによって、キーイベントを取得することができます。キーが押されたり、離されたりするたびに、実機からこのメソッドが呼ばれます。
void processEvent(int type,int param)
type キーイベント
param キー
typeには次のキーイベント定数が渡されます。
キーイベント定数
Display.KEY_PRESSED_EVENT キーが押された時に発生するイベント
Display.KEY_RELEASED_EVENT キーが離された時に発生するイベント

paramには次のキー定数が渡されます。
キー定数
Display.KEY_SELECT 選択キー
Display.KEY_UP 上キー
Display.KEY_DOWN 下キー
Display.KEY_LEFT 左キー
Display.KEY_RIGHT 右キー
Display.KEY_SOFT1 ソフトキー1
Display.KEY_SOFT2 ソフトキー2
Display.KEY_0 0キー
Display.KEY_1 1キー
Display.KEY_2 2キー
Display.KEY_3 3キー
Display.KEY_4 4キー
Display.KEY_5 5キー
Display.KEY_6 6キー
Display.KEY_7 7キー
Display.KEY_8 8キー
Display.KEY_9 9キー
Display.KEY_ASTERISK *キー
Display.KEY_POUND #キー

今回は、ソフトキー2を押した時にカメラの制御を行っています。
CameraCanvas.javaの一部
if (type==Display.KEY_PRESSED_EVENT &&
    param==Display.KEY_SOFT2) {
    //カメラの制御
    ‥中略‥
    }
}


カメラの初期化
カメラを制御するには、com.nttdocomo.opt.uiパッケージにあるCameraクラスを使います。はじめに、CameraクラスのgetCamera()メソッドでCameraクラスのオブジェクトを取得します。取得済みのカメラを再取得することはできません。
Camera getCamera(int id)
id カメラID

カメラIDは、複数のカメラを持つ端末でどのカメラを制御するかを指定するものです。P504iSは2つのカメラを持ち、0は背面カメラ、1は前面カメラです。N504iSとF504iSはカメラ1つだけなので0のみを使用します。

次に、CameraクラスのsetImageSize()メソッドで、撮影する写真の画像サイズを指定します。
void setImageSize(int width,int height)
width 幅(ドット)
height 高さ(ドット)

実際にどの画像サイズで撮れるかは機種依存で、ここで指定した値に近いものが選択されます。
今回は、カメラIDを0、サイズを120x120ドットに指定するので次のようになります。
CameraCanvas.javaの一部
cam=Camera.getCamera(0);
cam.setImageSize(getWidth(),getHeight());

また、CameraクラスのsetAttribute()メソッドで、連写モード・画質・マイク音量を指定できます。設定可能かどうかは機種依存です。
void setAttribute(int attr,int value)
attr 属性
value 値

属性
DEV_CONTINUOUS_SHOT(連写モード) ATTR_CONTINUOUS_SHOT_OFF(OFF)
ATTR_CONTINUOUS_SHOT_ON (ON)
DEV_QUALITY(画質) ATTR_QUALITY_HIGH (高)
ATTR_QUALITY_STANDARD(標準)
ATTR_QUALITY_LOW (低)
DEV_SOUND(マイク音量) ATTR_VOLUME_MIN (最小)
ATTR_VOLUME_MAX (最大)


カメラの制御
CameraクラスのtakePicture()メソッドを呼ぶと、カメラを起動します。そこで、写真を撮るかキャンセルすると、アプリに制御が戻ります。写真を撮ったかどうかは、getNumberOfImage()メソッドで取った枚数を取得し、0以上かどうかを判定します。CameraクラスのgetImage()メソッドでMediaImageオブジェクトが、getInputStream()メソッドでJPEGバイナリデータが取得できます。Cameraクラスで撮った写真のイメージデータは、次にtakePicture()メソッドかdisposeImages()メソッドを呼んだ時に破棄されます。
void getImage(int index)
index 写真のインデックス
void getInputStream(int index)
index 写真のインデックス

写真のインデックスは、連写モードで撮影した時に何枚目の写真を取得するかを指定するものです。今回は連写モードを使ってないので先頭の0を指定します。
cam.takePicture();
if (cam.getNumberOfImages()!=0) {
    if (image!=null) image.dispose();
    MediaImage m=cam.getImage(0);
    m.use();
    image=m.getImage();
    repaint();
}
repaint()メソッドを呼ぶと、実機からpaint()メソッドが呼ばれて、キャンバスの再描画を行います。

また、Java言語のオブジェクトは、基本的に参照が切れればガーベージコレクションで自動的にメモリが解放されますが、iアプリのImageオブジェクトはdispose()メソッドを呼ばなければ解放されません。忘れないように注意してください。


★写真パズルを作る★


それでは、本題の「写真パズル」を作ります。5x5の25ピースに分割してバラバラになった絵を、もとの1枚の絵に戻すゲームです。操作方法は方向キーで空いている場所にピースをつめるだけです。絵がもと通りになればクリアです。ソフトキー1でピースをバラバラにできます。ソフトキー2でカメラモードに遷移し、そこで撮影した写真をパズルの絵柄として使えます。
  


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

画像ファイルの用意
このゲームでは以下の2枚の画像ファイルを使います。resディレクトリ(C:\iDK\apps\PhotoPuzzle\res\))に置いてください。

・そらみ&へにへに
-0.gif
-120x120


・フレーム
-1.gif
-132x136



PhotoPuzzleクラス
PhotoPuzzleクラスは、プログラムの本体となるクラスです。
PhotoPuzzle.java
import com.nttdocomo.ui.*;

//写真パズル(本体)
public class PhotoPuzzle extends IApplication {

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


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

//写真パズル(キャンバス)
class PhotoCanvas extends Canvas {
    private Random  rand =new Random(); //乱数
    private Camera  cam;                //カメラ
    private Image[] image=new Image[2]; //イメージ
    private Image[] piece=new Image[25];//ピース
    private int[]   data =new int[25];  //データ

    //コンストラクタ
    PhotoCanvas() {
        int i;
        MediaImage m;

        //カメラの初期化
        cam=Camera.getCamera(0);
        cam.setImageSize(120,120);
        setSoftLabel(SOFT_KEY_1,"スタート");
        setSoftLabel(SOFT_KEY_2,"カメラ");

        //イメージの生成
        try {
            for (i=24;i>=0;i--) {
                piece[i]=Image.createImage(24,24);
            }
            for (i=1;i>=0;i--) {
                m=MediaManager.getImage("resource:///"+i+".gif");
                m.use();
                image[i]=m.getImage();
            }
        } catch (Exception e) {
        }
        makePiece(image[0]);
        repaint();
    }

    //ピースの生成
    private void makePiece(Image image) {
        int i;
        Graphics g;
        for (i=24;i>=0;i--) {
            data[i]=i;
            g=piece[i].getGraphics();
            g.drawImage(image,
                -24*(i%5)-(image.getWidth()-120)/2,
                -24*(i/5)-(image.getHeight()-120)/2);
        }
    }

    //ピースの移動
    private void movePiece(int param) {
        int i;
        for (i=24;i>=0;i--) {
            if (data[i]==24) break;
        }
        if (param==Display.KEY_UP && i/5<4) {
            data[i]=data[i+5];
            data[i+5]=24;
        } else if (param==Display.KEY_DOWN && i/5>0) {
            data[i]=data[i-5];
            data[i-5]=24;
        } else if (param==Display.KEY_LEFT && i%5<4) {
            data[i]=data[i+1];
            data[i+1]=24;
        } else if (param==Display.KEY_RIGHT && i%5>0) {
            data[i]=data[i-1];
            data[i-1]=24;
        }
    }

    //描画
    public void paint(Graphics g) {
        int i;
        if (image==null) return;

        //結果
        boolean complete=true;
        for (i=24;i>=0;i--) {
            if (data[i]!=i) complete=false;
        }

        //描画
        g.lock();
        g.drawImage(image[1],0,0);
        g.setColor(g.getColorOfName(g.BLACK));
        for (i=24;i>=0;i--) {
            if (complete || data[i]!=24) {
                g.drawImage(piece[data[i]],6+24*(i%5),8+24*(i/5));
            }
            if (!complete) {
                g.drawRect(6+24*(i%5),8+24*(i/5),23,23);
            }
        }
        g.unlock(true);
    }

    //キーイベントの処理
    public void processEvent(int type,int param) {
        int i;
        if (type==Display.KEY_PRESSED_EVENT) {
            //スタート
            if (param==Display.KEY_SOFT1) {
                for (i=99;i>=0;i--) {
                    movePiece(Display.KEY_LEFT+(rand.nextInt()>>>1)%4);
                }
            }
            //カメラ
            else if (param==Display.KEY_SOFT2) {
                try {
                    cam.takePicture();
                    if (cam.getNumberOfImages()!=0) {
                        MediaImage m=cam.getImage(0);
                        m.use();
                        Image photo=m.getImage();
                        makePiece(photo);
                        photo.dispose();
                    }
                } catch (Exception e) {
                }
            }
            //ピースの移動
            else {
                movePiece(param);
            }
            repaint();
        }
    }
}


ピースイメージとピースデータ
今回作るゲームは縦5枚で横5枚の計25ピースのパズルです。ピースイメージは、piece配列に格納します。ピースがどのようにならんでいるかというピースデータは、data配列に格納します。X座標は「インデックス%5」、Y座標は「インデックス/5」で計算します。
インデックスと座標
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
15 16 17 18 19
20 21 22 23 24

data配列のインデックスはピースの位置、data配列の値はピースの絵柄に対応します。"data[0]"の値が"4"の時、絵柄の右上のピースが、現在左上にあることを示します。また、配列の値が24(右下のピース)の時、そこを空き領域として周囲のピースをつめることができます。


ピースの生成
ピースの生成はmakePiece()メソッドで行っています。
void makePiece(Image image)
image カメラで取得したイメージ

カメラで取得したイメージをずらして描画することにより、25個の小さなピースイメージに分割しています。
PhotoCanvas.javaの一部
int i;
Graphics g;
for (i=24;i>=0;i--) {
    data[i]=i;
    g=piece[i].getGraphics();
    g.drawImage(image,
        -24*(i%5)-(image.getWidth()-120)/2,
        -24*(i/5)-(image.getHeight()-120)/2);
}

また、pieceオブジェクト自体はコンストラクタで生成しています。Imageクラスのオブジェクトを生成するには、ImageクラスのcreateImage()メソッドを使います。
Image createImage(int width,int height)
width 幅
height 高さ
返り値 Imageクラスのオブジェクト


ピースの移動
ピースの移動はmovePiece()メソッドを作って行っています。方向キーで操作したり、ソフトキー1でバラバラにする時に使います。
void makePiece(int param)
param 方向キー
paramに方向キーの定数が渡された時、空きピースに周囲のピースを詰めるという処理を行っています。例をあげると、引数がDisplay.UPの時、空きピースと空きピースの下のピースを入れ替えています。空きピースが一番下の行の時は、何も行いません。
PhotoCanvas.javaの一部
int i;
for (i=24;i>=0;i--) {
    if (data[i]==24) break;
}
if (param==Display.KEY_UP && i/5<4) {
    data[i]=data[i+5];
    data[i+5]=24;
} else if (param==Display.KEY_DOWN && i/5>0) {
    data[i]=data[i-5];
    data[i-5]=24;
} else if (param==Display.KEY_LEFT && i%5<4) {
    data[i]=data[i+1];
    data[i+1]=24;
} else if (param==Display.KEY_RIGHT && i%5>0) {
    data[i]=data[i-1];
    data[i-1]=24;
}


描画
描画はpaint()メソッドで行います。描画する前にパズルが完成しているかどうかを調べます。配列のインデックスと中身が全て同じ時、絵は完成したことになります。
PhotoCanvas.javaの一部
boolean complete=true;
for (i=24;i>=0;i--) {
    if (data[i]!=i) complete=false;
}

次にフレームとピースを描画しますが、パズルが完成していない時は、ピースの枠を表示し、配列の値が24(右下のピース)を空き領域として描画しないようにします。
g.lock();
g.drawImage(image[1],0,0);
g.setColor(g.getColorOfName(g.BLACK));
for (i=24;i>=0;i--) {
    if (complete || data[i]!=24) {
        g.drawImage(piece[data[i]],6+24*(i%5),8+24*(i/5));
    }
    if (!complete) {
        g.drawRect(6+24*(i%5),8+24*(i/5),23,23);
    }
}
g.unlock(true);

描画する時、そのままでは1つ1つ描画しているところが見えてしまいます。そこで「ロック・アンロック」と呼ばれる、描画命令をまとめて画面に反映させる機能を使います。使い方は、描画処理を行う前後でGraphicsクラスのlock()メソッドとunlock()メソッドを呼ぶだけです。lock()メソッドを呼ぶと、unlock()メソッドが呼ばれるまで、描画命令が画面に反映されなくなります。unlock()メソッドの引数は、強制的にフラッシュするかどうかなので、trueにしてください。


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

今回は、100回4方向のキー定数をランダムに指定することで、パズルをバラバラにしています。
PhotoCanvas.javaの一部
for (i=99;i>=0;i--) {
    movePiece(Display.KEY_LEFT+(rand.nextInt()>>>1)%4);
}

キー定数 実値
Display.KEY_LEFT 0x10
Display.KEY_RIGHT 0x12
Display.KEY_UP 0x11
Display.KEY_DOWN 0x13


★おわりに★


次回は、FOMAの2051シリーズの新機能を使ったゲームを作る予定です。お楽しみに。




−戻る−


(C)Npaka/Sehira, 2003-2004