4.1 フィールド制御の概要
フィールドでの移動を制御するプログラムを作成します。キャラクター画面を移動してマップの中を探索します。マップの制御について機能をまとめてみました。
マップのイメージを表示する
キャラクターのイメージを表示する
キャラクターを上に動かす
キャラクターを下に動かす
キャラクターを左に動かす
キャラクターを右に動かす
キャラクターが画面端を超えた場合マップを切り替える
キャラクターが画面の最上位で動いてマップが切り替わった場合はキャラクターのy座標を画面最下位にする
キャラクターが画面の最下位で動いてマップが切り替わった場合はキャラクターのy座標を画面最上位にする
キャラクターが画面の最左位で動いてマップが切り替わった場合はキャラクターのx座標を画面最右位にする
キャラクターが画面の最右位で動いてマップが切り替わった場合はキャラクターのx座標を画面最左位にする
ワープポイントに入るとマップが切り替わる。
ワープポイントの移動では、キャラクターの座標は変更しない
セレクトボタンを押すと宝箱を調べる
セレクトボタンを押すと人と話す
セレクトボタンを押すとイベントが発生する
マップのフィールド
フィールドには、キャラクターが移動できるものがある
フィールドには、キャラクターが移動できないものがある
移動できるフィールド
通常フィールド(平原や道)
ワープポイント
強制戦闘フィールド(通行可能)
イベントフィールド
移動できないフィールド
通行不可フィールド(岩や川)
強制戦闘フィールド(通行不可能)
人
宝箱
4.2 マップの描画とキャラクターの移動 ver1(グラフィックスの制御)
マップには、キャラクターの移動などグラフィックスを操作と「移動できるか出来ないか」やイベントの判定などフィールドによって処理を行う操作が必要になります。
グラフィックスの操作は、フィールドの描画・キャラクターイメージの描画・キャラクター移動時の描画です。決まりさえ決めてしまえば以外に簡単です。携帯電話の画面サイズは120×120を想定しています。フィールドの描画には、120×120のイメージを作成して描画する方法とパズルのようにある画像イメージを組み合わせて描画する方法があります。今回は、後者の方を使用します。最初から120×120のフィールドイメージを使用するほうが簡単なのですが一つのイメージのサイズが1000kbyteを超えてしまいます。保存領域も考えるとなかなか難しいところがあります。504iの様にスクラッチパッドが30k使えたとしてもかなりの領域を占有されてしまいます。
画像イメージを組み合わせて描画する方法を図にすると以下のような感じになります。画面をある幅と高さに分割して升目(セル)を作成するのです。そして、そのマス(セル)に画像を貼り付けていくのです。画像の幅・高さは升目の幅・高さと同じにしなければなりません。絵の貼り付ける座標ですが、x座標は「貼り付けるマス×マスの幅」、y座標は「貼り付けるマス×マスの高さ」で求められます(マス目を以降、セルと呼びます)。

画面を分割したセルを利用して画面を制御するには、二次元配列を使用すれば簡単に実現できます。例えば、要素が[9][9]の二次元配列を生成すれば、9行9列の表ができます。具体的には以下のような配列を宣言すれば良いのです。最初の要素がy座標を表し、後の要素がx座標を表します。後は、要素にデータを設定してイメージなどの描画に使用すれば良いのです。
例 フィールドの情報
private int mapdat[9][9];
mapdat[y座標][x座標]

図 二次元配列の要素とフィールドデータの対応

セルの制御は、二次元配列で管理できます。では、セルにはどのようなデータが必要なのでしょうか。マップに必要なのは、「どこにどの画像を貼り付けるか」という画像データとフィールドの通行に関わるフィールド制御のデータです。画像データ・フィールドデータを一つの二次元配列で制御するのは難しいのでイメージはイメージ用の二次元配列、フィールドデータはフィールド用の二次元配列に分けて管理しましょう。Mapクラスを定義してフィールドにイメージ用の二次元配列とフィールドデータ用の二次元配列を定義します。これで、Mapクラスを利用すれば簡単にマップ情報の制御ができます。
例 フィールド情報を格納するクラス(Mapクラス)
public class Map{
//イメージ用のデータ
public byte mapi[][]=new byte[9][9];
//通行状態のデータ
public byte mapd[][]=new byte[9][9];
}
イメージ用のデータ利用の仕方は簡単です。0〜任意の値の数値を代入しておきます。フィールドイメージはImageクラスを配列で宣言しておいてインデックスに配列の値を設定すればよいのです。
例 描画処理の手順
Image fimg[]=new Image[2];
fimgにイメージのセットする処理
Map mapdat=new Map();
マップクラスのフィールドにデータ設定する処理
g.drawImage(fimg[mapdat.mapi[i][j]],x,y);
フィールドデータは、イベントに対応する値を決めといてその値をもとに制御します。イメージほど単純ではありませんが、イベント内容とイベントに対応する値の決まりを決めてしまえば難しくはありません。まずは、大まかな仕組みを抑えましょう。
サンプルプログラムを作成して、動作を確認しましょう。iappliToolを起動してMapFieldCntというプロジェクトを作成します。以降、フィールドの操作はこのプロジェクトを使用します。ADF設定では、AppNameの属性に「MapFieldCnt」、AppClassの属性に「App」と設定します。また、「LastModified」の項目には、年月日を半角スペースで区切って入力します。
プロジェクトが作成できたら以下のイメージを「res」フォルダに保存してください。右クリックの「名前を付けて画像を保存」でダウンロードする事が出来ます。
リソースファイル
攻撃:![]()
上向き:![]()
左向き:![]()
下向き:![]()
右向き;![]()
敵のイメージ1:![]()
敵のイメージ2:![]()
フィールドイメージ1:![]()
フィールドイメージ2:![]()
図 保存したリソースファイル

リソースの準備が出来たらソースファイルを作成・保存します。戦闘画面(AppBattleCnt ver3)で作成したソースファイルを「src」フォルダにコピーします。コピーするソースファイルは、App.java・BattCnt.java・Cmn.java・Hero.java・Item.java・MainCav.java・Monster.javaです。この内、BattCnt.javaはまだ利用しませんのでコピーし無くても結構です。また、Item.java・Monster.javaファイルに変更は加えません。以降、処理をする為、他のファイルに修正が加わります。
図 コピーしたソースファイル

準備が出来たらソースファイルの修正と作成を行います。Cmnクラスに追加フィールドとメソッドがあります。フィールドが追加されたので初期化メソッド(InitCmn)に命令が追加されています。MainCavクラスは、フィールドテスト用に書き換えてあります。
新たに作成するソースファイルは、MapクラスのソースファイルとMapFieldCntクラスのソースファイルです。Mapクラスがマップのデータを格納して、MapFieldCntクラスが制御を行います。MapFieldCntクラスがMapクラスを利用するのです。
修正・追加ソースファイル
| Cmn.java |
| import com.nttdocomo.ui.*; import java.util.*; public class Cmn{ //********************************************************************************** //* 画面表示関係 開始 * //********************************************************************************** //************************画面表示関係 フィールドデータ群 開始******************** ・・・・・省略・・・・・ //キャンバスの幅と高さ public static int cvsWdh; public static int cvsHgt; //************************画面表示関係 フィールドデータ群 終了******************** //****************************初期処理メソッド************************************** //初期処理 public static void InitCmn(Canvas maincvs,Image dmg){ ・・・・・省略・・・・・ //キャンバスの幅と高さを設定 cvsHgt=c.getWidth(); cvsWdh=c.getHeight(); } //********************************************************************************** //************************画面表示関係 メソッドデータ群 開始******************** //---------------------------------余白の描画 開始----------------------------- public static void drSpc(){ g.setColor(claWht); if(cx>0){ g.fillRect(0,0,cx,cvsHgt); g.fillRect(cx+130,0,cx,cvsHgt); } if(cy>0){ g.fillRect(0,0,cvsWdh,cy); g.fillRect(0,cy+130,cvsWdh,cy); } } //---------------------------------余白の描画 終了----------------------------- //************************画面表示関係 メソッドデータ群 終了******************** //********************************************************************************** //* 画面表示関係 終了 * //********************************************************************************** } |
| Map.java |
| public class Map{ //大マップのインデックス int mapno; //Mapのインデックス int idx; //イメージ用のデータ byte mapi[][]=new byte[9][9]; //通行状態のデータ byte mapd[][]=new byte[9][9]; } |
| MapFieldCnt.java |
| import com.nttdocomo.ui.*; public class MapFieldCnt{ private Image[] fimg;//フィールドのイメージを格納 private Map mapdat=new Map();//マップクラスのインスタンス private Monster[] mms;//ヒーロークラスのインスタンスを格納 private Hero mhr;//ヒーロークラスのインスタンスを格納 private int cha_x;//キャラクターのx座標 private int cha_y;//キャラクターのy座標 private int startX;//イメージ描画開始のx座標 private int startY;//イメージ描画開始のy座標 //キャラクターの向き、0前、1右、2後ろ、3左 private int cha_drc; //コンストラクタ public MapFieldCnt(Hero sHero,Monster[] aryMnt){ mhr=sHero; mms=aryMnt; //描画座標の設定 startX=Cmn.cx-7; startY=Cmn.cy-7; //テスト用の設定 cha_drc=0; cha_x=1; cha_y=1; fimg=new Image[2]; MediaImage mdi = null; try{ mdi = MediaManager.getImage("resource:///Green.gif"); mdi.use(); fimg[0]= mdi.getImage(); mdi = MediaManager.getImage("resource:///Hatake.gif"); mdi.use(); fimg[1]= mdi.getImage(); }catch(Exception e){} for(int i=0;i<=8;i++){ for(int j=0;j<=8;j++){ mapdat.mapi[i][j]=0; mapdat.mapd[i][j]=0; if(i==0 || i==8){ mapdat.mapi[i][j]=1; mapdat.mapd[i][j]=1; } if(j==0 || j==8){ mapdat.mapi[i][j]=1; mapdat.mapd[i][j]=1; } } } mapdat.mapi[0][5]=1; mapdat.mapi[8][5]=1; mapdat.mapi[5][0]=1; mapdat.mapi[5][8]=1; drGround(true);//フィールドの描画 } //----------------マップ制御 開始----------------------// //フィールドの描画 //引き数 キャラクターの描画の有無 private void drGround(boolean stflg){ int i,j; Cmn.g.lock(); //フィールドを描画 for(i=0;i<=8;i++){ for(j=0;j<=8;j++){ Cmn.g.drawImage(fimg[mapdat.mapi[i][j]],startX+(j*16),startY+(i*16)); } } //フラグがtrueの場合は、キャラクターも描画 if(stflg==true) Cmn.g.drawImage(mhr.image[cha_drc],startX+(cha_x*16),startY+(cha_y*16)); //余白の描画 Cmn.drSpc(); Cmn.g.unlock(true); } //キャラクター移動部分の描画 //引き数 移動先の座標 private void drPic(int sx,int sy){ Cmn.g.drawImage(fimg[mapdat.mapi[cha_y][cha_x]],startX+(cha_x*16),startY+(cha_y*16)); Cmn.g.drawImage(fimg[mapdat.mapi[sy][sx]],startX+(sx*16),startY+(sy*16)); } //キャラクターの横移動を描画 //引き数 移動先のx座標 private void mvX(int sx){ int i,j=0; //4ピクセルづつ描画(4回の繰り返しで16ピクセル移動) for(i=0;i<=3;i++){ if(cha_x>sx){//画面上に移動した場合 j-=4; } else{//画面下に移動した場合 j+=4; } //描画ロック Cmn.g.lock(); //移動エリアのフィールドを描画 drPic(sx,cha_y); //キャラクターを描画 Cmn.g.drawImage(mhr.image[cha_drc],startX+(cha_x*16)+j,startY+(cha_y*16)); //画面端への移動の場合は、余白部を描画 if(sx==0 || sx==8 || cha_x==0 || cha_x==8){ Cmn.drSpc();//余白の描画 } //描画ロックの解除 Cmn.g.unlock(true); } //キャラクター座標の更新 cha_x=sx; } //キャラクターの縦移動を描画 //引き数 移動先のy座標 private void mvY(int sy){ int i,j=0; //4ピクセルづつ描画(4回の繰り返しで16ピクセル移動) for(i=0;i<=3;i++){ if(cha_y>sy){//画面上に移動した場合 j-=4; } else{//画面下に移動した場合 j+=4; } //描画ロック Cmn.g.lock(); //移動エリアのフィールドを描画 drPic(cha_x,sy); //キャラクターを描画 Cmn.g.drawImage(mhr.image[cha_drc],startX+(cha_x*16),startY+(cha_y*16)+j); //画面端への移動の場合は、余白部を描画 if(sy==0 || sy==8 || cha_y==0 || cha_y==8){ Cmn.drSpc();//余白の描画 } //描画ロックの解除 Cmn.g.unlock(true); } //キャラクター座標の更新 cha_y=sy; } //キャラクターの移動処理 //戻り値、1:ゲームオーバー、2:セーブ、3:マップデータのリフレッシュ // 4:次のシナリオへ public int MoveCnt(){ //キープレスイベント int flg=0; while(true){ switch(Cmn.gKey()){ //上に移動する case Display.KEY_UP: //キャラクターの向きを0(上を表す値)にする cha_drc=0; //テスト用の処理 通行可能なフィールドの場合の判定とキャラクターの描画 if(mapdat.mapd[cha_y-1][cha_x]!=1 && cha_y > 1){ mvY(cha_y-1); } //イベント判定メソッドでイベントの判定 if((flg=echk(mapdat.mapd[cha_y][cha_x]))!=0) return flg; //画面端にを超えた場合 if(cha_y==0){ cha_y=8; mvY(cha_y-1); } break; //下に移動する case Display.KEY_DOWN: //キャラクターの向きを2(下を表す値)にする cha_drc=2; //テスト用の処理 通行可能なフィールドの場合の判定とキャラクターの描画 if(mapdat.mapd[cha_y+1][cha_x]!=1 && cha_y < 7){ mvY(cha_y+1); } //イベント判定メソッドでイベントの判定 if((flg=echk(mapdat.mapd[cha_y][cha_x]))!=0) return flg; //画面端にを超えた場合 if(cha_y==8){ cha_y=0; mvY(cha_y+1); } cha_drc=1; break; //右に移動する case Display.KEY_RIGHT: //キャラクターの向きを1(右を表す値)にする cha_drc=1; //テスト用の処理 通行可能なフィールドの場合の判定とキャラクターの描画 if(mapdat.mapd[cha_y][cha_x+1]!=1 && cha_x < 7){ mvX(cha_x+1); } //イベント判定メソッドでイベントの判定 if((flg=echk(mapdat.mapd[cha_y][cha_x]))!=0) return flg; //画面端にを超えた場合 if(cha_x==8){ cha_x=0; mvX(cha_x+1); } break; //左に移動する case Display.KEY_LEFT: //キャラクターの向きを3(左を表す値)にする cha_drc=3; //テスト用の処理 通行可能なフィールドの場合の判定とキャラクターの描画 if(mapdat.mapd[cha_y][cha_x-1]!=1 && cha_x > 1){ mvX(cha_x-1); } //イベント判定メソッドでイベントの判定 if((flg=echk(mapdat.mapd[cha_y][cha_x]))!=0) return flg; //画面端にを超えた場合 if(cha_x==0){ cha_x=8; mvX(cha_x-1); } break; } }//キープレスイベント終了 } private int echk(int fdat){//イベントのチェック //何も無ければ、0 //ゲームオーバー、1 //次のシナリオ、2 return 0; } //----------------マップ制御 終了----------------------// } |
| MainCav.java |
| import com.nttdocomo.ui.*; public class MainCav extends Canvas{ //コンストラクタ public MainCav(){ Image img; MediaImage Mimage; //メディアイメージインターフェース型の変数宣言 //イメージの読み込み try{ Mimage = MediaManager.getImage("resource:///attack.gif"); Mimage.use(); img = Mimage.getImage(); }catch(Exception e){ System.out.println(e); System.out.println("例外が発生しました。処理を終了します。"); return; } //共通処理にデータを設定 Cmn.InitCmn(this,img); } //描画 public void paint(Graphics g) {} //プログラムの開始 public void stApp (){ String strTest; int i; Image resImage; MediaImage Mimage; Hero thisHero = new Hero(); Monster[] dbMonster = new Monster[2]; dbMonster[0] = new Monster(); dbMonster[1] = new Monster(); //モンスター情報の設定 dbMonster[0].setData("ツライム(01000200201000500101,薬草,傷を回復,30100010)"); dbMonster[1].setData("みど〜り(01000500501000500205,燃える草,燃えやすい草,40060010)"); //イメージの読み込み try{ //モンスタークラスのイメージ設定 Mimage = MediaManager.getImage("resource:///enemy1.gif"); Mimage.use(); dbMonster[0].image = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///enemy2.gif"); Mimage.use(); dbMonster[1].image = Mimage.getImage(); //Heroクラスのイメージ設定 Mimage = MediaManager.getImage("resource:///chr_up.gif"); Mimage.use(); thisHero.image[0] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_right.gif"); Mimage.use(); thisHero.image[1] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_down.gif"); Mimage.use(); thisHero.image[2] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_left.gif"); Mimage.use(); thisHero.image[3] = Mimage.getImage(); }catch(Exception e){ System.out.println(e); System.out.println("例外が発生しました。処理を終了します。"); return; } //主人公情報の設定 thisHero.name="権兵衛太郎大輔"; thisHero.hp=15; //HP thisHero.mhp=15; //マックスHP thisHero.mp=20; //MP thisHero.mmp=20; //マックスMP thisHero.attack=5; //攻撃力 thisHero.defense=15;//防御力 thisHero.maney=150; //お金 thisHero.expoint=0;//経験値 thisHero.lve=1; thisHero.nlve=10; thisHero.slv=3; for(i=0;i<=5;i++) thisHero.myItem[i].setData("薬草,傷を回復,30100010"); thisHero.myItem[3].setData("燃える草,燃えやすい草,40060010"); thisHero.myItem[4].setData("デバッグ剣,デバッグ用の剣,10200010"); thisHero.myItem[5].setData("デバッグ防具,デバッグ用の防具,20100010"); thisHero.myItem[6].setData("木の棒,落ちていた木の棒,10020010"); thisHero.myItem[7].setData("ただの服,普段着,20020010"); //フィールド制御の開始 MapFieldCnt myMapFdCnt=new MapFieldCnt(thisHero,dbMonster); myMapFdCnt.MoveCnt(); } } |
図 ソースファイル作成後のファイル構成

ビルドを行って実行してみましょう。ボタンを押すとキャラクターが上下左右に動きます。フィールド処理の中心であるMapFieldCntクラスの説明を行う前に、Cmnクラスの追加したフィールドとメソッドを説明しましょう。
フィールドの制御にも戦闘画面と同じ問題があります。それは、画面サイズが130×130とは限らないということです。戦闘画面では、背景を塗りつぶすだけでしたがフィールドだと少し手を加えなければなりません。余白を描画するメソッドが必要になります。そして、余白を描画するには、画面の幅と高さが必要になります。この画面の幅と高さを格納するのがcvsWdh・cvsHgtフィールドです。
定義 cvsWdh・cvsHgtフィールド
//キャンバスの幅と高さ
public static int cvsWdh;
public static int cvsHgt;
処理 InitCmnメソッドで画面の幅と高さを格納
public static void InitCmn(Canvas maincvs,Image
dmg){
・・・・・省略・・・・・
//キャンバスの幅と高さを設定
cvsHgt=c.getWidth();
cvsWdh=c.getHeight();
}
余白を実際に描画するのは、Cmnクラスに追加した
drSpcメソッドです。処理は以下のようになっています。
Cmnクラス 追加メソッド
public static void drSpc(){
g.setColor(claWht);
if(cx>0){
g.fillRect(0,0,cx,cvsHgt);
g.fillRect(cx+130,0,cx,cvsHgt);
}
if(cy>0){
g.fillRect(0,0,cvsWdh,cy);
g.fillRect(0,cy+130,cvsWdh,cy);
}
}
cx・cyフィールドは、サイズが130を超えた場合に「超えたサイズ÷2」の値を格納しています。0以上のデータが格納されている場合は、画面サイズが130を超えているということです。fillPolygonメソッドを使用したくなりますが、130を超える対象が幅と高さが両方超えるとは限りません。ここは、fillRectメソッドで処理を行っています。
cxを例にとって説明しましょう。cxが0以上の場合は、画面幅が130を超えた事になります。cxは「超えたサイズ÷2」の値を格納していますからこの値が余白のサイズに利用できます。
g.fillRect(0,0,cx,cvsHgt);
余白があった場合は、最初に左側の余白を描画します。fillRectの描画開始の座標が、0(x座標),0(y座標)なのは問題ありませんね。次に、塗りつぶす幅ですがcxを指定します。塗りつぶす高さは、画面の高さを指定すれば余白部を塗りつぶす事が出来ます。
図 余白の塗りつぶしエリア

後は、同じように座標を右側のエリアに変えて塗りつぶし処理を行います。右側のエリアは、「画面が130を超えたサイズ÷2」に130を加算すれば求められます。つまり、「cx+130」です。縦のエリアも同じ要領で処理を行います。以上が、Cmnクラスに追加されたフィールド・メソッドです。
MainCavクラスの内容は、説明するまでも無いでしょう。Heroクラスの生成とMonsterクラスの生成を行っています。フィールドの画面描画にHeroクラスのimageフィールドを利用する為、リソースからイメージを取得しています。Heroクラスのimageフィールドに対応するイメージは以下のようになっています。要素0が上向き、1が右向き、2が下向き、3が左向きです。これは、ボタンが押された時に向く方向です。
図 要素とイメージの対応
image[0] =chr_up.gif
image[1] =chr_right.gif
image[2] =chr_down.gif
image[3] =chr_left.gif
では、今回の処理の中心となるMapFieldCntクラスを説明します。MapFieldCntクラスは、Mapクラスに格納されたデータを元にフィールドの描画や通行・イベントの判定・処理を行います。フィールドのデータを格納するMapクラスは以下の内容になっています。
public class Map{
//大マップのインデックス
int mapno;
//Mapのインデックス
int idx;
//イメージ用のデータ
byte mapi[][]=new byte[9][9];
//通行状態のデータ
byte mapd[][]=new byte[9][9];
}
配列部のフィールドの意味は、最初に説明したとおりです。大マップとMapのインデックスがフィールドとして定義されています。今回は利用しません。フィールドは、マップクラスを複数組み合わせて構成されます。Mapクラスは、ジグソーパズルのピースのような存在です。Mapのインデックスとは、ピース単位のインデックスです。大マップのインデックスは、ピースを組み合わせて作成される大きなフィールド(マップ)のインデックスです。どのピースがどのフィールドに含まれるか藩閥する為のインデックスです。働きは、処理が複雑になるにつれてわかってきます。
処理の中心となるMapFieldCntクラスの説明を行います。イメージの描画は、Cmnクラスのフィールド(g)を利用して描画を行います。描画処理は、複雑なように見えますが仕組みを考えれば難しくはありません。まずは描画の仕組みを意識しながら読んでください。メソッドの役割を以下にまとめてみました。処理が複数のメソッドに渡っているのは「キャラクターの移動」の処理だけです。
マップ全体の描画:drGround(boolean stflg)
キャラクターの移動:mvX(int sx)・mvY(int sy) ・drPic(int sx,int
sy)
イベントの判定:echk(int fdat)
4.2.2 MapFieldCntクラスのフィールド
フィールドは以下のようなフィールドを定義しています。役割は、コメントで記述している内容そのものです。
private Image[] fimg;//フィールドのイメージを格納
private Map mapdat=new Map();//マップクラスのインスタンス
private Monster[] mms;//ヒーロークラスのインスタンスを格納
private Hero mhr;//ヒーロークラスのインスタンスを格納
private int cha_x;//キャラクターのx座標
private int cha_y;//キャラクターのy座標
private int startX;//イメージ描画開始のx座標
private int startY;//イメージ描画開始のy座標
//キャラクターの向き、0前、1右、2後ろ、3左
private int cha_drc;
ここでは、描画のテストですのでMapクラスは一つだけです。実際は、マップフィールドは配列で持つことになります。キャラクターの描画座標は、cha_x・cha_yフィールドに持ちます。この値を変化させる事でキャラクターの描画位置を変えます。cha_drcフィールドは、キャラクターの向きです。Heroクラスのimage配列の要素に対応した値を格納します。
4.2.3 MapFieldCntクラスのコンストラクタ
コンストラクタでは、テストデータの初期設定を行っています。Heroクラスの設定・Monsterクラスの設定・描画座標の初期設定・イメージの設定とmapdat(Mapクラスのインスタンス)のフィールドにデータを設定しています。
//描画座標の設定
startX=Cmn.cx-7;
startY=Cmn.cy-7;
描画座標が、画面の描画開始位置から-7をした値を設定しています。これは、後で説明が出てきます。以降は、全てテストデータですので処理の働きだけを確認してください。Mapクラスのフィールドにデータを設定しているのはfor文の処理です。内容は気にしないで下さい。この処理でMapクラスのフィールドは、以下の図のようなデータ構成になります。フィールドデータでは、0:通行可能・1:通行不可と想定しています。また、イメージでは1の部分に畑、0に草のイメージが表示されます。コンストラクタでは、各設定が終わるとdrGroundメソッドでマップ全体を描画します。

4.2.4 全体マップの描画:drGroundメソッド
画面にマップ全体の描画を行うのは、drGroundメソッドです。boolean型の引数を指定します。引数の値がtrueであればキャラクターのイメージも描画します。falseであればキャラクターのイメージは描画されません。引数の値を操作する事で背景のみの描画・背景+キャラクターの描画を制御できます。
//フィールドの描画
//引き数 キャラクターの描画の有無
private void drGround(boolean stflg){
int i,j;
Cmn.g.lock();
//フィールドを描画
for(i=0;i<=8;i++){
for(j=0;j<=8;j++){
Cmn.g.drawImage(fimg[mapdat.mapi[i][j]],startX+(j*16),startY+(i*16));
}
}
//フラグがtrueの場合は、キャラクターも描画
if(stflg==true) Cmn.g.drawImage(mhr.image[cha_drc],startX+(cha_x*16),startY+(cha_y*16));
//余白の描画
Cmn.drSpc();
Cmn.g.unlock(true);
}
描画は繰り返し処理をネストして実現しています。最初のfor文がmapi配列の最初の要素を表します(mapi
[][])。そして、ネストしたfor文が後者の要素を表します(mapi
[][])。以下の図のような感じで要素の参照が推移していくのは想像できますね。

イメージの描画はdrawImageメソッドを使用します。描画の始点は、startX・startYフィールドの値です。描画位置は、繰り返し処理のインデックスを参照して値を求めています。キャラクターを含めてイメージのサイズは、16ですからインデックスに16を乗算してやれば座標が求められます。そして、その値に、描画開始座標のstartX・startYフィールドの値を加算すれば求める事が出来ます。
//フィールドを描画
for(i=0;i<=8;i++){
for(j=0;j<=8;j++){
Cmn.g.drawImage(fimg[mapdat.mapi[i][j]],startX+(j*16),startY+(i*16));
}
}
フィールドの画面描画には注意点があります。drawImageメソッドの最初に引数にjの値を指定している事です。
Cmn.g.drawImage(fimg[mapdat.mapi[i][j]],startX+(j*16),startY+(i*16));
jはネストしたfor文のループカウンタです。mapdat配列の後の要素を表します。つまり、x座標をあらわします。以下のように記述すると座標が逆になってしまいます。
間違い
Cmn.g.drawImage(fimg[mapdat.mapi[i][j]],startX+(i*16),startY+(j*16));
少し矛盾するような気がします。これは、mapdat[y座標][x座標]、drawImage(x座標,
y座標)というようにmapi配列の座標を表す構成がdrawImageメソッドの引数と逆のために起こります。mapi配列の要素をdrawImageメソッドに合うように修正しなければなりません。
それならば、「mapi配列の要素をdrawImageメソッドに合わせてやればよい」という意見もあるでしょう。次の章になりますがその場合は、座標データを作成する時に見た目でマップが想像しづらくなります。プログラムは、書いてしまえば終わりですがマップデータは常に作成し続けます。皆さんがマップ作成をする回数を考えると見た目でマップが想像できたほうが良いと思います。そのためにmapdat[y座標][x座標]という構成にしてあります。
次に、表示位置の計算です。「(i*16)」の16という値はイメージの「辺」を表しています。パズルのように組み合わせてマップフィールドを作るためにイメージのサイズを決めなければなりません。当マニュアルでは「16×16」のサイズを使用します。当マニュアルでは、処理のための描画エリアと携帯電話の画面に表示されるエリアの大きさを変えてあります。例えば、Cmnクラスのフィールドcx,xyが0の時、コンスタラクタでstartX・startYにはそれぞれ-7が格納される事になります。
//描画座標の設定
startX=Cmn.cx-7;
startY=Cmn.cy-7;
この例で、mapi[0][0]の描画座標を求めてみましょう。x座標は「startX+(j*16)」ですから「-7+(0*16)」で「-7」になります。y座標は、「startY+(i*16)」ですから「-7+(0*16)」で「-7」になります。つまり、x,y座標-7から描画される訳です。

フィールドの移動では、最終的に複数のマップをキャラクターが移動します。その時、画面端で移動した場合、キャラクターが画面の先に消えていくように描画したいためにエリアのサイズを変えたのです。その方が、移動しているという感じがでます。これは、好みの問題です。
以上がマップ描画の仕組みです。座標の値などを変えて試してみると働きが再確認できるでしょう。マップが描画されてしまえば、後は、余白の描画と引数を判定してキャラクターを描画します。
//フラグがtrueの場合は、キャラクターも描画
if(stflg==true) Cmn.g.drawImage(mhr.image[cha_drc],startX+(cha_x*16),startY+(cha_y*16));
キャラクターの描画も仕組みは同じです。ただし、こちらはcha_x・cha_yを元に座標を求めます。cha_x・cha_yフィールドは、座標のピクセルと格納するのではなくセルの位置を格納します。この方が、キャラクター移動の管理しやすいのです。
最後に注意点として、マップ描画の際には9×9で81回の描画処理を行います。これは結構な回数です。画面のちらつきの防止のために描画はGraphicsクラスのlockメソッドを使用して一気に描画するようにしましょう。
4.2.4 コマンドボタンでのキャラクターの移動
キャラクターのマップ移動は、MoveCntメソッドが制御します。MoveCntメソッドはキーイベントの種類によってキャラクターを移動させたり、イベントの判定を行います。今回は、キャラクターの移動が主要な機能です。MoveCntメソッドで方向キーのイベントが発生すると、移動描画メソッドを呼び出してキャラクターの移動を描画します。
MoveCntメソッドの処理は簡単です。無限ループに、switch文を記述します。switch文の式には、Cmn.gKeyメソッドを指定します。これは、今までも利用していますので問題は無いでしょう。押されたキーを取得できたら、後は、caseで処理を判定するだけです。
MoveCntメソッド処理概要
public int MoveCnt(){
//キープレスイベント
int flg=0;
while(true){
switch(Cmn.gKey()){
//上に移動する
case Display.KEY_UP:
処理
//下に移動する
case Display.KEY_DOWN:
処理
//右に移動する
case Display.KEY_RIGHT:
処理
//左に移動する
case Display.KEY_LEFT:
処理
}
}
}
方向キーが押された時、キャラクターが移動する際の描画はmvXメソッド・mvYメソッド・drPicメソッドが処理を行います。mvYメソッドは縦への描画・mvXメソッドは横への描画をそれぞれ受け持ちます。drPicメソッドは、mvXメソッド・mvYメソッドの中で使用されます。もちろん、これらのメソッドは、MoveCntメソッドから利用されます。
図 キャラクター移動の処理の流れ(メソッド単位)
MoveCntメソッド→mvXメソッド(内部で利用:drPicメソッド)
MoveCntメソッド→mvYメソッド(内部で利用:drPicメソッド)
上キーが押された場合の処理を例に、キャラクター移動の処理を確認しましょう。MoveCntメソッドで上キーが押されると「case
Display.KEY_UP:」のケースで処理が行われます。
//上に移動する
case Display.KEY_UP:
//キャラクターの向きを0(上を表す値)にする
cha_drc=0;
//テスト用の処理 通行可能なフィールドの場合の判定とキャラクターの描画
if(mapdat.mapd[cha_y-1][cha_x]!=1 &&
cha_y > 1){
mvY(cha_y-1);
}
//イベント判定メソッドでイベントの判定
if((flg=echk(mapdat.mapd[cha_y][cha_x]))!=0)
return flg;
//画面端にを超えた場合
if(cha_y==0){
cha_y=8;
mvY(cha_y-1);
}
break;
上ボタンが押された場合、移動先の座標(cha_y-1:現在座標-1)の値を参照します。今回は1は移動不可なので1でなければ移動処理を行います(1以外ならmvYメソッドの実行)。
キーイベントでキャラクターが移動する時はmvY
・mvX メソッドで制御します。mvYが縦移動 ・mvXが横移動です。今回は上キーを押された場合ですので、縦移動のmvY
メソッドを例に処理の仕組みを説明しましょう。
mvYメソッド
private void mvY(int sy)
mvY メソッドの引数syには、移動先のセルが渡されます。つまり、上ボタンが押されたらcha_y
-1(現在の座標-1)、下ボタンの場合はcha_y
+1(現在の座標+1)が渡されます。今回は、cha_y
-1が渡される事になります。減算に違和感を感じている方は、いるでしょうか?。上と言うと加算の間隔に陥ります。画面のx,y座標の開始座標は、0,0(x,y)です。この場合は下に行くとy座標は加算されます。上に行くと座標は減算されます。図を思い浮かべて考えてみてください。
mvY メソッドの処理は、以下のようになっています。
//キャラクターの縦移動を描画
//引き数 移動先のy座標
private void mvY(int sy){
int i,j=0;
//4ピクセルづつ描画(4回の繰り返しで16ピクセル移動)
for(i=0;i<=3;i++){
if(cha_y>sy){//画面上に移動した場合
j-=4;
}
else{//画面下に移動した場合
j+=4;
}
//描画ロック
Cmn.g.lock();
//移動エリアのフィールドを描画
drPic(cha_x,sy);
//キャラクターを描画
Cmn.g.drawImage(mhr.image[cha_drc],startX+(cha_x*16),startY+(cha_y*16)+j);
//画面端への移動の場合は、余白部を描画
if(sy==0 || sy==8 || cha_y==0
|| cha_y==8){
Cmn.drSpc();//余白の描画
}
//描画ロックの解除
Cmn.g.unlock(true);
}
}
移動処理はfor文を利用します。キャラクターの移動には、移動座標に一気に描画する方法もあります。今回はfor文を使用して4ピクセルづつイメージを移動させます。イメージサイズは16ですから16/4=4で、4回処理を繰り返します。
処理は簡単です。任意の変数j(インデックス)を4づつ加算(減算)してキャラクター描画の座標に加算(減算)すれば良いのです。キャラクターの描画は一気に行っても構いません。ただ、キャラクターが細かく動いてくれた方がユーザーは「動いている」と感じやすいので操作時のストレスが減ります。これは、好みの問題です。
キャラクターを描画するには、どちらに動くのか判定しなければなりません。上方向なのか、下方向なのかの判定です。これは、cha_y(現在座標)と引き数sy(移動先座標)の値を比較してすればよいのです。
if(cha_y>sy){
j-=4;
}
else{
j+=4;
}
cha_y(現在座標)がsy(移動座標)より大きければ上に移動だと分かりますね(cha_y>sy)。この式で分岐して、jを加算するか減算するかを決めます。移動座標が決まったらいよいよ、イメージを描画します。画面のちらつきを抑える為に、描画にロックをかけます。描画のロックを行っている間の処理が描画処理です。
//移動エリアのフィールドを描画
drPic(cha_x,sy);
//キャラクターを描画
Cmn.g.drawImage(mhr.image[cha_drc],startX+(cha_x*16),startY+(cha_y*16)+j);
//画面端への移動の場合は、余白部を描画
if(sy==0 || sy==8 || cha_y==0 || cha_y==8){
Cmn.drSpc();//余白の描画
}
drPic メソッドは現在座標と移動先のイメージの再描画を行います。キャラクターの移動には現在座標と移動先のイメージを描画すれば済みます。一々、全マップを再描画していると処理の負担が増えて待ち時間が発生してストレスになります。処理速度の速い機種だと問題ないでしょうが、今回は現在と移動先の再描画で処理しています。drPic
メソッドの処理に問題は無いでしょう。現在、座標に対応するMapクラスの要素と移動先に対応するMapクラスの要素を参照してイメージを描画するだけです。
drPicメソッド
//キャラクター移動部分の描画
//引き数 移動先の座標
private void drPic(int sx,int sy){
Cmn.g.drawImage(fimg[mapdat.mapi[cha_y][cha_x]],startX+(cha_x*16),startY+(cha_y*16));
Cmn.g.drawImage(fimg[mapdat.mapi[sy][sx]],startX+(sx*16),startY+(sy*16));
}
キャラクターの描画は問題が無いと思いますが、次の余白部の描画の分岐が問題です。今回は、マップが一つだけですが、通常の処理だとキャラクターが画面端で移動するとマップが切り替わります。130×130の画面サイズの場合は問題ありません。しかし、画面サイズが130×130を超えている場合、キャラクターが画面端や画面端から移動してくるときに余白の処理を行わないと移動位置だけ突起のように表示されてしまいます。余白の描画判定を忘れないようにしましょう。
for文の処理の流れをまとめて見ましょう。移動位置の判定(jの更新)→drPicメソッド(フィールドの描画)→キャラクターの描画→余白の描画(分岐で処理)という処理になります。
メソッドの最後に座標(cha_y)を更新します。移動の描画処理が終われば、cha_y(現在座標)の値は必要ありません。現在座標を更新して終了します。
cha_y=sy;
もちろん更新を忘れると、永遠に座標から移動することが出来ませんので気をつけてください。これで終了です。mvXメソッドも処理の仕組みは同じです。座標の処理が違うだけです。
drPicメソッドについて補足的に注意を行います。drPicメソッドは現在のフィールドと移動先のフィールドの描画を行います。移動先の座標は、引数で渡します。処理は、難しいところはありません。cha_y・cha_xの値を参照して現在座標のイメージを描画する命令と引数sx・syを参照して移動先の座標のイメージを描画するだけです。ケアレスミスをしやすいのはこのメソッドの呼び出し時です。
x座標の移動:drPic(sx, cha_y);
y座標の移動:drPic(cha_x,sy);
x座標を移動する時は、y座標を操作する必要はありませんからcha_yをそのまま渡します。y座標を移動する時は、x座標を操作する必要はありませんからcha_xをそのまま渡します。
マップの描画処理はこれで終わります。働きが納得できない場合は、わざとメソッドの値を変えて実行してみると仕組みが実感できます。
mvYメソッドの余白の処理を変える
if(sy==0 || sy==8 || cha_y==0 || cha_y==8)→if(sy==0
|| sy==8)
mvYメソッドの余白の処理を変える
drPic(cha_x,sy); →drPic(cha_x, cha_y);
mvYメソッドの余白の処理を変える
cha_y=sy;→コメント化して座標を更新しない
4.3 マップの描画とキャラクターの移動 ver2(マップクラスの生成とイベントの判定)
マップクラスの使い方とフィールド・キャラクターの描画は分かったと思います。ここからより具体的に操作の仕方を考えていきましょう。マップフィールドには、以下のようなイベントが発生します。
マップのフィールドの種類
小マップのマップの移動:次のマップに移動する時の処理
大マップのマップの移動:次のマップに移動する時の処理
シナリオの実行:シナリオ処理に移行
通常フィールド:ランダムに戦闘が発生する
強制戦闘フィールド(通行可能):通行できるが必ず戦闘が発生する
通行禁止フィールド:通行できない
強制戦闘フィールド(通行不可能):通行できない上に移動しようとすると先頭が発生する
「小マップのマップの移動」・「大マップのマップの移動」・「シナリオの実行」について補足しましょう。マップはインターネット上のテキストファイルより取得します。インターネットからの1回のロードで作成できるマップは20マップです。20マップを超えるとインターネットに接続してデータをロードしなければなりません。また、1回のデータロードは20マップですが管理する単位は10マップとします。つまり、「小マップのマップの移動」とは1マップの移動です。「大マップのマップの移動」とは、次の十枚のマップに移動するのです。そして、20を超えるとシナリオ処理に移行して新たなシナリオイベントを発生するのでフィールドの制御が終了します。具体的な処理は後の章で説明します。まずは、マップの移動時の処理とフィールド上のイベントの取得方法を考えていきましょう。
インターネット上のマップデータは以下のような図の形式になります。全て半角文字です。最初の行がフィールドのイメージデータ(mapi)、次の行がフィールドの制御データ(mapd)です。二行で一つのマップデータを構成しています。後は、これが連続しているだけです。10マップだと20行あることになります。

行だと分かりづらいので、最初の行をマップの行単位(9文字)で分割してみましょう。すると以下の図のようになります。

では、この一行(81)にまとめられたデータを数値に変換してマップクラスのフィールドに設定するメソッドを作成しましょう。まずは、文字列の数値への変換です。簡単そうですが、少し制約があります。10など二桁の値を表現したい時です。文字列では「1」と「0」になります。テキストデータを2バイトずつ変換していけば良いように思われます。しかし、それでは、一マップのデータが162バイトになるので、20マップあたり3240バイト掛かります。パケット通信料を考えると避けたいものです。そこで、「1・2・3・・・9」と続けて「a(10)・b(11)・c(12)・・・・・z(35)」とアルファベットも数値として変換しましょう。これで、一バイトで35までの値が表現できます。これだけあれば十分でしょう。
ルールが決まったので、Cmnクラスに変換制御メソッドを作成しましょう。以下のメソッドを「共通の制御」部に作成してください。処理は簡単でswich文で制御するだけです。charデータをもらうとその文字に対応する整数を返します。0〜9・a〜z以外の値の場合は100を返します。
| Cmn.java |
| import com.nttdocomo.ui.*; import java.util.*; public class Cmn{ ・・・・・省略・・・・・ //---------------------------文字数値変換 開始------------------------------------- public static int cData(char sdc){ switch (sdc){ case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': return 10; case 'b': return 11; case 'c': return 12; case 'd': return 13; case 'e': return 14; case 'f': return 15; case 'g': return 16; case 'h': return 17; case 'i': return 18; case 'j': return 19; case 'k': return 20; case 'l': return 21; case 'm': return 22; case 'n': return 23; case 'o': return 24; case 'p': return 25; case 'q': return 26; case 'r': return 27; case 's': return 28; case 't': return 29; case 'u': return 30; case 'v': return 31; case 'w': return 32; case 'x': return 33; case 'y': return 34; case 'z': return 35; default: return -1; } } //---------------------------文字数値変換 終了------------------------------------- //********************************************************************************** //* 共通処理 終了 * //********************************************************************************** } |
これで、文字列の数値への変換が出来ました。次に、テキストデータを分割してマップクラスのフィールドに値を設定するメソッドを作成します。このメソッドは、Mapクラスに定義します。
| Map.java |
| public class Map{ //大マップのインデックス int mapno; //Mapのインデックス int idx; //イメージ用のデータ byte mapi[][]=new byte[9][9]; //通行状態のデータ byte mapd[][]=new byte[9][9]; //---------------------------マップの生成 開始--------------------------------------- //マップのデータを設定 public void setData(String idat,String mdat){ if(idat==null || mdat==null) return; int k=0; idat=idat.trim(); mdat=mdat.trim(); if(idat.length()<81 || mdat.length()<81) return; for(int i=0;i<=8;i++){ for(int j=0;j<=8;j++){ mapi[i][j]=(byte)(Cmn.cData(idat.charAt(k))); mapd[i][j]=(byte)(Cmn.cData(mdat.charAt(k))); k++; } } } //---------------------------マップの生成 終了--------------------------------------- } |
MapクラスのsetDataメソッドでは、引数にイメージデータの文字列(idat)とフィールドデータの文字列(mdat)を受け取ります。処理は、文字列から一文字づつ取り出して数値変換を行い、フィールドに設定するだけです。仕組みさえ分かってしまえば簡単です。
最初のfor文は行を表します。Mapクラスの最初の要素ですね。そして、ネストした次のfor文は、列を表します。後は、ループインデックスを要素に当てはめます。取り出す文字のインデックスは、変数kを加算する事で処理を行います。
smap.mapi[i][j]=(byte)(cData(idat.trim().charAt(k)));
これで、以下の図のようにデータが設定されていく事になります。

データ設定の命令は簡単です。StringクラスのcharAtメソッドを使用して文字を取り出せば良いのです。charAtメソッドは引数に指定した数値をインデックスとして文字を取り出します。取り出した文字はcDataメソッドに送って数値に変換します。注意点としてcDataメソッドの戻り値を(byte)でキャストする事と、取り出す文字のインデックスkを更新してください。変数kがそのままだと最初の値を取り出し続けることになります。
マップのフィールドの設定の処理が出来ました。しかし、設定するデータに対して決まりを決めなければ制御できません。マップの制御では、Mapクラスの配列を10作成して要素のインデックスを切り替えることによりマップを切り替えます。その為、フィールドの値0〜9はマップのインデックスに使用しましょう。例えば、下の図(例1)のようにフィールドデータ(mapd)が1のセルのところに移動した場合はMapクラスの要素を1にします。これならば、画面端に出た場合以外にも、ワープゾーンのようにフィールド途中でマップが切り替わる場合も処理が楽です(例2)。

当マニュアルでは10マップが大きな一単位ですが、人の好みもあると思いますので念のため0〜19までが要素のインデックスとします。
20以降を決めましょう。20は、次の10枚のマップへの移動とします。20になるとマップ配列のデータを書き換えて新たな10枚のマップを作成します。
21は、シナリオイベントへの移行とします。つまり、マップの制御を終了してネットに接続して新たなデータを取得するのです。つまり、MapCntクラスの制御が終了します。
22は、移動は出来きますが強制戦闘が発生します。23は通行可能で戦闘が発生しません。24は、ランダムで戦闘が発生します。25は、通行不可能です。26は、通行不可能な上に移動しようとすると戦闘が生します。26以降は人のデータに対応します。26以降はセレクトボタンが押されたら処理するイベントなのでここでは考えません。0〜26(p)のデータを以下にまとめます。
フィールドデータ一覧
0〜19(0〜j):マップのインデックス
20(k):次の大マップへ移動
21(l):次のシナリオイベントへ移行
22(m):通行可能 強制戦闘
23(n):通行可能 戦闘未発生
24(o):通行可能 ランダムで戦闘発生
25(p):通行不可能
26(q):通行不可能 強制戦闘発生
このデータ構成を考えると24以降が通行可能フィールドです。25以降は通行不可能フィールドになります。このデータの中でもフィールドデータによく使用するのは「通行可能 ランダムで戦闘発生」・「通行不可能」のフィールドでしょう。その為、oとpを振り分けました。キーボードで隣同士なのでフィールドデータが作りやすいですし形が違うので識別が楽です。k・l、m・nも隣同士ですが、oとpを振り分ける事で「24以下ならば移動処理」という通行可能・不可能の判定を簡単にすることが出来ます。
以下のように、MapFieldCntクラスとMainCavクラスを書き換えて実際の動作を確認してみましょう。MapFieldCntクラスのキャラクター描画処理のメソッドは、修正の必要はありません。マップクラスが配列になっていますので、フィールド描画を行うdrGround・drPicには微修正があります。また、修正部は太字で強調してあります。MainCavクラスは、表示の関係上、修正部は青で強調してあります。
補足
マップデータは文字配列で定義しています。記述が面倒な場合は以下のリンクで文字配列の宣言部を記述したテキストファイルを参照できます。コピーして利用してください。
マップデータの宣言ファイルへのリンク
| MapFieldCnt.java |
| import com.nttdocomo.ui.*; public class MapFieldCnt{ ・・・・・省略・・・・・ private int midx=0;//マップのインデックス ・・・・・省略・・・・・ //コンストラクタ public MapFieldCnt(Hero sHero,Monster[] aryMnt,Map[] mymap,Image[] fdimg){ //テスト用の設定 cha_x=5; cha_y=5; //Heroクラスを設定 mhr=sHero; //Monsterクラスを設定 mms=aryMnt; //Mapクラスを設定 mapdat=mymap; //フィールドのイメージ郡を設定 fimg=fdimg; //描画座標の設定 startX=Cmn.cx-7; startY=Cmn.cy-7; cha_drc=0; } //フィールドの描画 //引き数 キャラクターの描画の有無 private void drGround(boolean stflg){ int i,j; Cmn.g.lock(); //フィールドを描画 for(i=0;i<=8;i++){ for(j=0;j<=8;j++){ if(mapdat[midx].mapi[i][j]<10) Cmn.g.drawImage(fimg[mapdat[midx].mapi[i][j]],startX+(j*16),startY+(i*16)); } } //フラグがtrueの場合は、キャラクターも描画 if(stflg==true) Cmn.g.drawImage(mhr.image[cha_drc],startX+(cha_x*16),startY+(cha_y*16)); //余白の描画 Cmn.drSpc(); Cmn.g.unlock(true); } //キャラクター移動部分の描画 //引き数 移動先の座標 private void drPic(int sx,int sy){ Cmn.g.drawImage(fimg[mapdat[midx].mapi[cha_y][cha_x]],startX+(cha_x*16),startY+(cha_y*16)); Cmn.g.drawImage(fimg[mapdat[midx].mapi[sy][sx]],startX+(sx*16),startY+(sy*16)); } ・・・・・省略・・・・・ //キャラクターの移動処理 //戻り値、1:ゲームオーバー、2:セーブ、3:マップデータのリフレッシュ // 4:次のシナリオへ public int MoveCnt(){ //キープレスイベント int flg=0; drGround(true); while(true){ switch(Cmn.gKey()){ //上に移動する case Display.KEY_UP: cha_drc=0; //通行とイベントの処理 if(mapdat[midx].mapd[cha_y-1][cha_x]<=24){ mvY(cha_y-1); if((flg=echk(mapdat[midx].mapd[cha_y][cha_x]))!=0) return flg; } else{ if((flg=echk(mapdat[midx].mapd[cha_y-1][cha_x]))!=0) return flg; } //画面端にを超えた場合 if(cha_y==0){ cha_y=8; mvY(cha_y-1); } break; //下に移動する case Display.KEY_DOWN: cha_drc=2; //通行とイベントの処理 if(mapdat[midx].mapd[cha_y+1][cha_x]<=24){ mvY(cha_y+1); if((flg=echk(mapdat[midx].mapd[cha_y][cha_x]))!=0) return flg; } else{ if((flg=echk(mapdat[midx].mapd[cha_y+1][cha_x]))!=0) return flg; } //画面端にを超えた場合 if(cha_y==8){ cha_y=0; mvY(cha_y+1); } cha_drc=1; break; //右に移動する case Display.KEY_RIGHT: cha_drc=1; //通行とイベントの処理 if(mapdat[midx].mapd[cha_y][cha_x+1]<=24){ mvX(cha_x+1); if((flg=echk(mapdat[midx].mapd[cha_y][cha_x]))!=0) return flg; } else{ if((flg=echk(mapdat[midx].mapd[cha_y][cha_x+1]))!=0) return flg; } //画面端にを超えた場合 if(cha_x==8){ cha_x=0; mvX(cha_x+1); } break; //左に移動する case Display.KEY_LEFT: cha_drc=3; //通行とイベントの処理 if(mapdat[midx].mapd[cha_y][cha_x-1]<=24){ mvX(cha_x-1); if((flg=echk(mapdat[midx].mapd[cha_y][cha_x]))!=0) return flg; } else{ if((flg=echk(mapdat[midx].mapd[cha_y][cha_x-1]))!=0) return flg; } //画面端にを超えた場合 if(cha_x==0){ cha_x=8; mvX(cha_x-1); } break; } }//キープレスイベント終了 } private int echk(int fdat){//イベントのチェック //何も無ければ、0 //ゲームオーバー、1 //次のシナリオ、2 if(fdat<=19){//マップが次のマップを示している場合、次のマップへ移動 midx=fdat; if(cha_y>0 && cha_y<8 && cha_x>0 && cha_x<8) drGround(true); else drGround(false); return 0; } //移動以外の処理 switch(fdat){ case 20://k:次の大マップへ移動する Cmn.battleMsg("次の大マップ"+ Integer.toString(fdat)); drGround(true); return 0; case 21://l:シナリオイベント処理 Cmn.battleMsg("次のシナリオ"+ Integer.toString(fdat)); drGround(true); return 0; case 22://m:通行可能 強制戦闘が発生(無条件) Cmn.battleMsg("強制戦闘(移動可能)"+ Integer.toString(fdat)); drGround(true); return 0; case 23://n:予備のケース(使用しない) Cmn.battleMsg("移動可能(予備)"+ Integer.toString(fdat)); drGround(true); return 0; case 24://o:移動可能 ランダムで戦闘が発生 //Cmn.battleMsg("移動可能"); //drGround(true); return 0; case 25://p:通常の移動不可能 分岐で記述の必要は無し //Cmn.battleMsg("移動不可能"); //drGround(true); return 0; case 26://q:通行不可能 強制戦闘が発生(無条件) Cmn.battleMsg("強制戦闘(移動不可)"+ Integer.toString(fdat)); drGround(true); return 0; } return 0; } //----------------マップ制御 終了----------------------// } |
| MainCav.java |
| import com.nttdocomo.ui.*; public class MainCav extends Canvas{ //コンストラクタ public MainCav(){ Image img; MediaImage Mimage; //メディアイメージインターフェース型の変数宣言 //イメージの読み込み try{ Mimage = MediaManager.getImage("resource:///attack.gif"); Mimage.use(); img = Mimage.getImage(); }catch(Exception e){ System.out.println(e); System.out.println("例外が発生しました。処理を終了します。"); return; } //共通処理にデータを設定 Cmn.InitCmn(this,img); } //描画 public void paint(Graphics g) {} //プログラムの開始 public void stApp (){ String strTest; int i; Image resImage; MediaImage Mimage; Hero thisHero = new Hero(); Monster[] dbMonster = new Monster[2]; dbMonster[0] = new Monster(); dbMonster[1] = new Monster(); //モンスター情報の設定 dbMonster[0].setData("ツライム(01000200201000500101,薬草,傷を回復,30100010)"); dbMonster[1].setData("みど〜り(01000500501000500205,燃える草,燃えやすい草,40060010)"); //イメージの読み込み try{ //モンスタークラスのイメージ設定 Mimage = MediaManager.getImage("resource:///enemy1.gif"); Mimage.use(); dbMonster[0].image = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///enemy2.gif"); Mimage.use(); dbMonster[1].image = Mimage.getImage(); //Heroクラスのイメージ設定 Mimage = MediaManager.getImage("resource:///chr_up.gif"); Mimage.use(); thisHero.image[0] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_right.gif"); Mimage.use(); thisHero.image[1] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_down.gif"); Mimage.use(); thisHero.image[2] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_left.gif"); Mimage.use(); thisHero.image[3] = Mimage.getImage(); }catch(Exception e){ System.out.println(e); System.out.println("例外が発生しました。処理を終了します。"); return; } //主人公情報の設定 thisHero.name="権兵衛太郎大輔"; thisHero.hp=15; //HP thisHero.mhp=15; //マックスHP thisHero.mp=20; //MP thisHero.mmp=20; //マックスMP thisHero.attack=5; //攻撃力 thisHero.defense=15;//防御力 thisHero.maney=150; //お金 thisHero.expoint=0;//経験値 thisHero.lve=1; thisHero.nlve=10; thisHero.slv=3; for(i=0;i<=5;i++) thisHero.myItem[i].setData("薬草,傷を回復,30100010"); thisHero.myItem[3].setData("燃える草,燃えやすい草,40060010"); thisHero.myItem[4].setData("デバッグ剣,デバッグ用の剣,10200010"); thisHero.myItem[5].setData("デバッグ防具,デバッグ用の防具,20100010"); thisHero.myItem[6].setData("木の棒,落ちていた木の棒,10020010"); thisHero.myItem[7].setData("ただの服,普段着,20020010"); //マップクラスの生成 int j=0; Map[] mymap=new Map[10]; //マップのフィールドデータ String mapString[]={"001333100001333100001333100333333333001333100001333100001333100001333100000000000", "pp11111pppplllllppppmmmmmpp4ooooooo34ooooooo3ppoooooppppoooooppppooooopppppqqqppp", "111333111111333111111333111111333111111333111111333111111333111111333111111333111", "pp22222ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp00000pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp55555ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp11111pp", "111333111111333111111333111111333111111333111111333111111333111111333111111333111", "ppppppppppoooooopppoooooopp0ooooo0pp0poooooppppoooooppppoooooppppoooooppppppppppp", "111333111111333111111333111111333111111333111111333111111333111111333111111333111", "pppppppppppoooooppppoooooopppoo0ooo0ppooooop0ppoooooppppoooooppppoooooppppppppppp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp66666ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp22222pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp77777ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp55555pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp88888ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp66666pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp99999ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp77777pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "ppkkkkkppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp88888pp"}; for(i=0;i<=mapString.length-1;i+=2){ mymap[j]=new Map(); mymap[j].mapno=0; mymap[j].idx=j; mymap[j].setData(mapString[i],mapString[i+1]); j++; } Image[] fimg=new Image[4]; try{ Mimage = MediaManager.getImage("resource:///tree.gif"); Mimage.use(); fimg[0]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///Hatake.gif"); Mimage.use(); fimg[1]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///ie.gif"); Mimage.use(); fimg[2]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///Green.gif"); Mimage.use(); fimg[3]= Mimage.getImage(); }catch(Exception e){} MapFieldCnt myMapFdCnt=new MapFieldCnt(thisHero,dbMonster,mymap,fimg); myMapFdCnt.MoveCnt(); } } |
追加リソース
フィールドイメージ:![]()
フィールドイメージ:![]()
ソースの修正と追加リソースの保存が済んだらビルドを行って実行してみましょう。今回のプログラムでは、キャラクターがマップを進んでいきます。最初の画面でうろうろすると「次のシナリオ」などのメッセージが出現します。これは、具体的なイベント処理の変わりにメッセージを表示するようにしているからです。とりあえずイベントの判定が出来ているということはお分かりになるでしょう。
今回、マップデータである文字列は配列データとして定義しました。MainCavでMapクラスの配列を生成して、文字配列を二つ一組でMapクラスのsetDataメソッドに渡します。後は、Mapクラスのフィールドを設定してくれます。マップのデータ作成は、おまけとしてツールを用意しています。少しは手間が省けるはずです。詳細は、ゲーム作成の勉強が済んだ後です。
//マップクラスの生成
int j=0;
Map[] mymap=new Map[10];
//マップのフィールドデータ
String mapString[]={"
省略
};
for(i=0;i<=mapString.length-1;i+=2){
mymap[j]=new Map();
mymap[j].idx=j;
mymap[j].setData(mapString[i],mapString[i+1]);
j++;
}
注意点としては、必ず「new Map();」でインスタンスを生成してください。「mapdat=new
Map[10];」の命令では、配列の要素数を確定しただけでインスタンスを生成した訳ではありません。次の処理で、フィールドのイメージを追加している部分は問題ないでしょう。
MapFieldCntクラスの変更点と追加フィールド・メソッドの説明をしましょう。小マップの移動を制御する為のフィールドmidxを追加しました。この値操作してマップを切り替えます。
private int midx=0;//マップのインデックス
修正メソッドとして、コンストラクタとdrGround・drPic・MoveCntメソッドを修正しています。drGround・drPicの修正部は、マップクラスのフィールドを参照する命令が「mapdat.mapd・・・]」から「mapdat[要素].mapd・・・]」と配列の要素を指定して参照するようになっているだけです。特に問題は無いでしょう。
コンストラクタのキャラクター座標の設定は、テストの為の設定です。コンストラクタでは、引き数の数が増えました。マップクラスの配列、フィールドイメージの配列です。コンストラクタでは、これらの引き数を内部のフィールドに設定しています。
//コンストラクタ
public MapFieldCnt(Hero sHero,Monster[] aryMnt,Map[]
mymap,Image[] fdimg){
//テスト用の設定
cha_x=5;
cha_y=5;
//Heroクラスを設定
mhr=sHero;
//Monsterクラスを設定
mms=aryMnt;
//Mapクラスを設定
mapdat=mymap;
//フィールドのイメージ郡を設定
fimg=fdimg;
//描画座標の設定
startX=Cmn.cx-7;
startY=Cmn.cy-7;
cha_drc=0;
}
MoveCntメソッドの修正点は、イベントの判定処理が加わっています。上キーを押した場合の処理を例に説明しましょう。上キーを押した場合の処理は以下の内容です。
//上に移動する
case Display.KEY_UP:
cha_drc=0;
//通行とイベントの処理
if(mapdat[midx].mapd[cha_y-1][cha_x]<=24){
mvY(cha_y-1);
if((flg=echk(mapdat[midx].mapd[cha_y][cha_x]))!=0)
return flg;
}
else{
if((flg=echk(mapdat[midx].mapd[cha_y-1][cha_x]))!=0)
return flg;
}
//画面端にを超えた場合
if(cha_y==0){
cha_y=8;
mvY(cha_y-1);
}
break;
修正された部分は、「通行とイベントの処理」の部分です。マップのフィールドの種類が決まりました。この項の最初に説明したね。24以下は、通行が出来るフィールドです。通行できる場合はmvY(mvX)でキャラクターの移動を描画します。これは、前回と同じです。しかし、今回はセントなどのイベントも判定しなければなりません。イベントの判定は、echkメソッドに任せます。echkメソッドは、引き数にフィールドのデータを渡すとイベントに対応した処理を行います。戦闘が発生した場合は、echkメソッド内で処理します。戦闘に負けるとゲームオーバーですから処理結果戻り値があります。echkメソッドの内容は後述します。
echkメソッド
int echk(int)メソッド
マップの移動処理の継続は、0の時です。それ以外は、次のシナリオへ移行・ゲームオーバーなどを表しますのでマップ制御を抜ける事になります。その為、戻り値が0以外ならreturnでメソッドを終了します。flg変数にechkメソッドの戻り値を格納するのは、イベントによって処理を変えなければならないからです。次のシナリオへ移行とゲームオーバーでは、次の処理が変わってきます。その為、イベントの種類をMoveCntメソッドを利用する側に伝えなければなりません。
また、今回は「移動できなくて強制戦闘が発生する」フィールドがあります。例え移動できなくてもechkメソッドでの判定が必要です。移動できない場合は、移動先の要素の値を渡します。間違えやすいので注意して下さい。
if((flg=echk(mapdat[midx].mapd[cha_y-1][cha_x]))!=0) return flg;
以上が、フィールドの移動処理の変更点です。次にイベントを判定するechkメソッドの説明をします。今回は、echkメソッドに具体的な分岐処理を記述しました。今回は、マップの切り替えとイベントによってメッセージを出すだけです。本番では、この分岐に具体的な処理を記述します。テストの起動で色々とメッセージが出てきたと思います。echkメソッドが正しく処理できている証拠です。
private int echk(int fdat){//イベントのチェック
//何も無ければ、0
//ゲームオーバー、1
//次のシナリオ、2
if(fdat<=19){//マップが次のマップを示している場合、次のマップへ移動
midx=fdat;
if(cha_y>0 &&
cha_y<8 && cha_x>0 &&
cha_x<8) drGround(true);
else drGround(false);
return 0;
}
//移動以外の処理
switch(fdat){
case 20://k:次の大マップへ移動する
Cmn.battleMsg("次の大マップ"+
Integer.toString(fdat));
drGround(true);
return 0;
case 21://l:シナリオイベント処理
Cmn.battleMsg("次のシナリオ"+
Integer.toString(fdat));
drGround(true);
return 0;
case 22://m:通行可能 強制戦闘が発生(無条件)
Cmn.battleMsg("強制戦闘(移動可能)"+
Integer.toString(fdat));
drGround(true);
return 0;
case 23://n:予備のケース(使用しない)
Cmn.battleMsg("移動可能(予備)"+
Integer.toString(fdat));
drGround(true);
return 0;
case 24://o:移動可能 ランダムで戦闘が発生
//Cmn.battleMsg("移動可能");
//drGround(true);
return 0;
case 25://p:通常の移動不可能 分岐で記述の必要は無し
//Cmn.battleMsg("移動不可能");
//drGround(true);
return 0;
case 26://q:通行不可能 強制戦闘が発生(無条件)
Cmn.battleMsg("強制戦闘(移動不可)"+
Integer.toString(fdat));
drGround(true);
return 0;
}
return 0;
}
echkメソッドの処理といっても戦闘の処理などは記述してありません。マップ制御に関わるイベントのみであとはメッセージが表示されます。処理は簡単です。if文とswitch文で分岐を行っているだけです。これからケースによって処理を記述することになります。switch文の分岐は、メッセージを出すだけなので説明は行いません。引き数が19以下だった場合のマップ移動処理を説明します。
if(fdat<=19){//マップが次のマップを示している場合、次のマップへ移動
midx=fdat;
if(cha_y>0 && cha_y<8
&& cha_x>0 && cha_x<8)
drGround(true);
else drGround(false);
return 0;
}
ここでは、midxのインデックスに渡された引数の値を代入します。要は、移動先のマップのインデックスを現在のマップインデックスに書き換えているのです。そして、フィールド全体を描画します。これで、マップが切り替わります。マップの書き換えには、二つの分岐があります。画面端で移動するしない場合は、フィールド全体の描画後にキャラクターを描画します。これは、ワープゾーンなどで移動した場合の処理です。画面端で移動した場合は、フィールドだけを描画します。後は、MoveCntメソッドでmvY(mvX)を利用してキャラクターの移動を描画します。
4.3 マップの描画とキャラクターの移動 ver3(人との会話イベントの判定)
フィールド制御の最後にフィールド上の人との会話のイベントを考えます。これは、フィールド上の人の隣でセレクトボタンを押すと会話が出来ると言った処理です。人を表すクラスとしてPersonクラスを定義します。この処理では、データ構成が少し複雑になります。Personクラスを考える前に文字列分解のメソッドをCmnクラスに定義しておきましょう。
Cmnクラス 追加メソッド(decStr:文字列分解)
| Cmn.java |
| public class Cmn{ ・・・・・省略・・・・・ //********************************************************************************** //* 共通処理 開始 * //********************************************************************************** ・・・・・省略・・・・・ //----------------------------文字列分解 開始-------------------------------- //文字列を分解 public static String[] decStr(String sendStr , char c){ int myi,myj,ind; String[] retStr; if (sendStr.equals("") || sendStr.charAt(sendStr.length()-1) != c){ sendStr+=c; } //分割できる数を算出 ind = 0; myi = sendStr.indexOf(c); while(myi>=0){ ind++; myi = sendStr.indexOf(c,myi+1); } //分割した文字列を格納する配列を生成 retStr = new String[ind]; //分割した文字列を格納する処理 ind = 0; myj = 0; myi = sendStr.indexOf(c); while(myi>=0){ retStr[ind] = sendStr.substring(myj,myi); ind++; myj = myi + 1; myi = sendStr.indexOf(c,myj); } return retStr; } //----------------------------文字列分解 終了-------------------------------- //********************************************************************************** //* 共通処理 終了 * //********************************************************************************** } |
decStrメソッドは、文字列を分解するメソッドです。引き数に、文字列と分割文字を渡すと分割文字で文字列を分解します。処理結果は、文字配列で返します。このメソッドは、当マニュアルのクラス偏で作成したメソッドと同じです。
処理は、文字列に任意の文字が何回出現するか調べてその回数を元に文字列配列(String[])を作成します。次に、文字列を分解しながら配列の要素に文字列を代入していきます。そして、戻り値で分割した文字列配列を返します。
public static String[] decStr(String sendStr
, char c)
decStrメソッド戻り値は、分割した文字列を格納する文字列配列です。引数には、分割する文字列と分割する時の任意の文字です。
例「E」で分割:decStr("ABCDEABC",'E')
例のような場合は、「E」をキーに文字を分割します。結果は、ABCD・ABCの文字に分割されます。
int myi,myj,youso;
String[] retStr;
作業に使用するインデックスとデータを格納する配列を宣言しています。
if (sendStr.equals("") || sendStr.charAt(sendStr.length()-1)
!= c){
sendStr+=c;
}
分割する文字列の最後に任意の文字列が存在しない場合、任意の文字を文字列の最後に結合します。文字列が最初の例のように「ABCDEABC」の場合、最後にEを結合して「ABCDEABCE」を使用して処理を行います。これは、分割処理上、必要な処理です。
youso = 0;
myi = sendStr.indexOf(c);
配列の要素確定に使用する変数yousoを初期化します。次に、送られてきた文字列の中で最初に出現する任意の文字のインデックスをmyiに代入します。
while(myi>=0){
youso++;
myi = sendStr.indexOf(c,myi+1);
}
次に、最初に出現した任意の文字のインデックスから次に出現する任意の文字の位置を調べます。indexOfメソッドは調べる文字列が出現しない場合は-1を返します。0以下になるまでループを繰り返して、その間、yousoを+1指定きますので分割する文字列数が求められます。
retStr = new String[youso];
youso = 0;
myj = 0;
文字列配列の要素をyousoで確定します。また、インデックスを初期化します。
myi = sendStr.indexOf(c);
ここからが文字列を分割する処理です。要素を調べた時のように最初に任意の文字が出現するインデックスを調べます。
while(myi>=0){
retStr[youso] = sendStr.substring(myj,myi);
youso++;
myj = myi + 1;
myi = sendStr.indexOf(c,myj);
}
繰り返しの条件は出現回数を調べる時と同じです。文字の切り出しは、substringメソッドを使用します。myjは0が代入されていますから0からmyiの値の位置まで分割します。注意点として、substringメソッドは指定した切り出し位置の-1の部分を切り出します。文字列「ABCDEABCE」を例に取ると「E」の最初のインデックス「4」です。最初は、以下のような指定になります。
substring (0,4)
「ABCDE」と取り出されそうですが、指定したエンドインデックス-1ですので4-1で0〜3までの文字が切り出されます。

次に「myj = myi + 1」と開始インデックスを更新します。例では、4+1で5の値がmyiに代入されます。

「myi = sendStr.indexOf(c,myj);」で5以降にEが出現する文字を調べます。これでmyiに8が代入されます。次のループでsubstring
(5,8)となりますから5〜7(8-1)の文字列が切り出されます。そして、次のループで-1となって終了です。
return retStr;
最後に分割した文字列を格納してある文字列配列を返します。以上が文字列分解の処理です。
では、Personクラスのデータ構成を考えましょう。セレクトボタンを押した時に、そこに人が存在するかわからなければなりません。その為に人が存在するマップと座標がわからなければなりません。重要なキャラクターとの会話では、イメージを描画してやるとよいでしょう。会話の種類として、単なる会話、アイテムが貰える、戦闘がはじまる。ボスとの戦闘などがあります。取りあえず、今回はこの4種類を想定します。また、アイテムなどを取得するとメッセージ内容が変わります。Personクラスに必要なデータを考えると以下のようなデータ構成になります。
Personクラスに必要なデータ
座標
イメージ
会話の種類
イベント前の会話
イベント後の会話
イベントに必要なデータ
以上のデータを踏まえてPersonクラスを定義すると以下のようになります。各フィールドの働きは、コメントの内容の通りです。
Personクラスのフィールド構成
public class Person{
int mapno;//大マップのインデックス
int idx;//Mapクラスのインデックス
int x;//x座標 マップクラスの配列に対応
int y;//y座標 マップクラスの配列に対応
int flg;//イベントが発生したかのフラグ
Image image=null;//イメージ
String befor_msg;//イベント前のメッセージ
String after_msg;//イベント後のメッセージ
String event_data;//モンスターやアイテム情報
}
Personクラスの情報もテキストファイルから取得します。Personクラスのネット上のデータは以下のようになります。このデータを文字列分解してフィールドに設定します。
Personクラスのデータサンプル
NOMAL(00000106)(魔物がすみついてからこの村は・・・)
ITEM-PRESENT(00000206)(よろしければ、お使いください/薬草,傷を回復,30100010/もう渡す物は・・・)
ENEMY-BATTLE(00000306)(力を試してみるかい?。/さすがだね。)
BOSS-BATTLE(00000406)(ぐはははは、よくきたな!。|今楽にしてやる!!/親分(01000200201000500101,薬草,傷を回復,30100010)/なんとか倒せた。)
Personクラスのネット上のデータ構成は、会話の種類・座標データ・会話のデータの3部から構成されます。を説明すると以下のようになります。
Personクラスのネット上のデータ構成
会話の種類(座標データ)(メッセージデータ)
図 Personクラスのネット上のデータ構成

会話の種類には、通常の会話、アイテムを貰える会話、戦闘が発生する会話、ボスとの戦闘の会話の4種類です。それぞれの会話に対応するするキーワードは以下のようになります。
会話の種類とキーワード
通常の会話:NOMAL
アイテムを貰える会話:ITEM-PRESENT
戦闘が発生する会話:ENEMY-BATTLE
ボスとの戦闘:BOSS-BATTLE
今回は、アイテムの売買は処理に入れておりません。商品の売買を行いたければ、Personクラスの種類にアイテム売買の種類を付け加えて拡張すればよいでしょう。
文字列データの中心にある括弧は、座標部のデータです。座標部のデータは、2文字で一つのデータを表します。
図 座標データ構成

次に、最後の括弧がメッセージデータになります。メッセージデータには、通常のNOMALの場合は1メッセージで済みますが、他のケースは複数の情報が必要になります。
各会話で必要なデータ
通常の会話(NOMAL):会話のメッセージ
アイテムを貰える会話(ITEM-PRESENT):イベント前の会話、アイテムを渡した後の会話(イベント後の会話)、アイテム情報
戦闘が発生する会話(ENEMY-BATTLE):イベント前の会話、モンスターを倒した後の会話(イベント後の会話)
ボスとの戦闘BOSS-BATTLE():イベント前の会話、戦闘終了後の会話(イベント後の会話)、ボスモンスター情報
必要なデータ場複数ある場合は、「/」(半角スラッシュ)で各データを区切ります。メッセージデータ部の構成は以下のような構成になります。
メッセージ部の構成
最初のメッセージ/アイテム・モンスター情報/イベント後のメッセージ
図 最初のメッセージとイベント後のメッセージ

図 最初のメッセージとアイテム・ボスモンスター情報とイベント後のメッセージ

如何でしょうか?。Personのデータ構成は少し複雑です。これらのデータ分解してフィールドにセットするメソッドはPersonクラスに定義します。このメソッドを定義したPersonクラスは以下のようになります。
| Person.java |
| import com.nttdocomo.ui.*; public class Person{ int mapno;//大マップのインデックス int idx;//Mapクラスのインデックス int x;//x座標 マップクラスの配列に対応 int y;//y座標 マップクラスの配列に対応 int flg;//イベントが発生したかのフラグ Image image=null;//イメージ String befor_msg;//最初に話し掛けた場合のメッセージ String after_msg;//イベント後のメッセージ String event_data;//モンスターやアイテム情報 public void setData(String sdata){ //余計なデータを削除 sdata=sdata.trim(); //データ部を格納する変数 String wstr; //データ分割文字を格納する配列 String[] sarry; //データの分割文字 char c=')'; //種類のフラグをエラー(-1)に設定 flg=-1; //文字列分解に利用するインデックス int i=0; //座標データ部の分解 wstr=sdata.substring(sdata.indexOf('(')+1,sdata.indexOf(')')); mapno=Cmn.cla(wstr.substring(0,2)); idx=Cmn.cla(wstr.substring(2,4)); x=Cmn.cla(wstr.substring(4,6)); y=Cmn.cla(wstr.substring(6)); //データ部の取り出し処理 i=sdata.indexOf(c)+2;//座標部の)を算出 //データ部を取り出し wstr=sdata.substring(i,sdata.lastIndexOf(c)); if(sdata.indexOf("NOMAL(")!=-1){ flg=0; after_msg=wstr; return; } //分割文字の変更 c='/'; //文字列分解処理 sarry=Cmn.decStr(wstr,c); if(sdata.indexOf("ITEM-PRESENT(")!=-1 || sdata.indexOf("BOSS-BATTLE(")!=-1){ if(sarry.length!=3) return; if(sdata.indexOf("BOSS-BATTLE(")!=-1) flg=3; else flg=1; befor_msg=sarry[0]; event_data=sarry[1]; after_msg=sarry[2]; } else if(sdata.indexOf("ENEMY-BATTLE(")!=-1){ if(sarry.length!=2) return; befor_msg=sarry[0]; after_msg=sarry[1]; flg=2; } } } |
メソッドの説明の前にflgフィールドの働きを説明しましょう。flgフィールドはメッセージの種類を格納します。メッセージの種類に対応するflgの値は以下のようになります。
会話の種類とflgフィールドの値
通常の会話(NOMAL):0
アイテムを貰える会話(ITEM-PRESENT):1
戦闘が発生する会話(ENEMY-BATTLE):2
ボスとの戦闘(BOSS-BATTLE):3
メッセージとして処理しない:-1
では、setDataメソッドの説明に移ります。setDataメソッドでは、最初に処理に必要な変数の宣言が行われています。以下のアイテム取得メッセージを元に文字列が分解される処理を確認していきましょう。
ITEM-PRESENT(00000206)(よろしければ、お使いください/薬草,傷を回復,30100010/もう渡す物は・・・)
最初に座標データの処理です。最初の「(」から最初にあらわれる「)」までを切り出してwstr(String)に格納します。この時、wstrには「00000206」が格納されています。
//座標データ部の分解
wstr=sdata.substring(sdata.indexOf('(')+1,sdata.indexOf(')'));
座標データ部をwstr(String)に格納格納できたら2文字づつ分割して、mapno・idx・x・yフィールドに代入します。数値への変換は、Cmnクラスのclaメソッドを利用します。
mapno=Cmn.cla(wstr.substring(0,2));
idx=Cmn.cla(wstr.substring(2,4));
x=Cmn.cla(wstr.substring(4,6));
y=Cmn.cla(wstr.substring(6));
「00000206」の文字列を2文字づつ分割すると00・00・02・06と分解されますからmapno・idx・x・yフィールドには0・0・2・6が代入されています。
座標部の次はメッセージデータ部を切り出します。
//データ部の取り出し処理
i=sdata.indexOf(c)+2;//座標部の)を算出
最初に「)」が現れるインデックスに2を加算します。最初に「)」が現れる位置は、以下の部分ですね。つまり、座標部の「)」です。このインデックスに+2しますから「よろしければ・・・」の「よ」のインデックスが変数iに格納されます。つまり、メッセージデータの最初のインデックスが格納されます。
ITEM-PRESENT(00000206)(よろしければ、お使いください/薬草,傷を回復,30100010/もう渡す物は・・・)
後は、変数iから「)」が最後に現れる位置を取得します。文字列の最後に現れる「)」を探すのでlastIndexOfメソッドを利用します。
//データ部を取り出し
wstr=sdata.substring(i,sdata.lastIndexOf(c));
注意点としては、モンスター情報はデータの中に「)」が含まれます。lastIndexOfメソッドを利用しないとモンスター情報までデータが切られてイベント後のメッセージが取得できません。かならず、lastIndexOfメソッドで最後に現れる「)」の位置を取得します。以上の処理でwstr(String)には、「よろしければ、お使いください/薬草,傷を回復,30100010/もう渡す物は・・・」が格納されています。処理したメッセージがNOMALのメッセージの場合は、このwstr(String)に格納されているデータをafter_msgフィールドに代入します。そして、NOMALメッセージを表す0をflgフィールドに代入してて終了します。メッセージ内容の判定は、indexOfメソッドで判定します。注意点としては、アフターメッセージでメッセージを代入しています。イベントが発生しないのでイベント前のメッセージは存在しないからです。
if(sdata.indexOf("NOMAL(")!=-1){
flg=0;
after_msg=wstr;
return;
}
サンプルは、当然ながら「NOMAL(」の文字列は見つからないので、この制御に入る事はありません。メッセージに「NOMAL(」が無ければ、メッセージの内容が複数に分かれていることになりますからメッセージデータ(wstr)を「/」で文字列分解します。分解した文字列は、sarry(String[])に格納します。処理は簡単で、CmnクラスのdecStrメソッドを利用するだけです。
//分割文字の変更
c='/';
//文字列分解処理
sarry=Cmn.decStr(wstr,c);
分解後文字列
sarry[0]=よろしければ、お使いください
sarry[1]=薬草,傷を回復,30100010
sarry[2]=もう渡す物は・・・
文字列の分解が済んだらフィールドに対応するも文字列を代入します。アイテムやボスモンスターのイベントの場合は、文字列は最初のメッセージ・アイテム(ボスモンスター)情報・イベント後のメッセージに分けられるのでそれぞれの要素をフィールドに代入します。後は、会話の種類を判定してflgに値を代入します。「ENEMY-BATTLE」イベントの場合は、分解される文字列は最初のメッセージ・イベント後のメッセージなので対応するフィールドに要素を代入します。簡単に言ってしまえば、分解される文字列数で分岐して代入する要素を操作しているのです。
if(sdata.indexOf("ITEM-PRESENT(")!=-1
|| sdata.indexOf("BOSS-BATTLE(")!=-1){
if(sarry.length!=3) return;
if(sdata.indexOf("BOSS-BATTLE(")!=-1)
flg=3;
else flg=1;
befor_msg=sarry[0];
event_data=sarry[1];
after_msg=sarry[2];
}
else if(sdata.indexOf("ENEMY-BATTLE(")!=-1){
if(sarry.length!=2) return;
befor_msg=sarry[0];
after_msg=sarry[1];
flg=2;
}
アイテム取得イベントは、宝箱の取得にも利用できます。「薬草を見つけた/薬草,傷を回復,30100010/空っぽだった」と言うようなメッセージ部にしておけば宝箱の判定が出来ます。
厳密には、今回作成した会話の処理は会話情報ではありません。イベント情報といったほうが良いかもしれません。直感的に分かりやすいように人との会話と説明しました。また、利用するクラスもPersonクラスです。当マニュアルの仕様なので、気に入らなければプログラムの仕組みが分かったら自分好みに書き換えて下さい。
では、Personクラスを利用できるようにMapFieldCntクラスを書き換えましょう。Personクラスを格納する配列mpsnをフィールドに定義します。Personクラスのデータはコンストラクタを利用して取得します。コンストラクタの引き数に、Personクラスの配列が加わります。また、コンスタラクタでは、mpsnフィールドにコンストラクタで渡されたPersonクラスの配列の代入処理が加わります。他に、MoveCntメソッドにセレクトボタンを押した時の処理が追加されます。会話イベントは、pschkメソッドを追加して処理します。修正追加部は、太字で強調してあります。
Cmnクラスには、Personクラスの会話とイメージ描画を行う fieldMsg・ drawPersonメソッドの追加があります。人との会話は、フィールドで行うため130×130のエリアにメッセージエリアとイメージを描画しなければなりません。その為の座標やサイズを調整してあります。基本的な仕組みは、戦闘画面の処理と同じです。
MapFieldCntクラスを利用するMainCavクラスにも変更があります。Personクラスのデータを作成する命令が追加されました。MapFieldCntクラスのコンストラクタの引き数にこのデータを渡します。追加命令は、青文字で強調してあります。
また、Personのイメージデータ用のリソースがありますのでリソースフォルダに保存してください。
補足
今回もマップデータは文字配列で定義しています。記述が面倒な場合は以下のリンクで文字配列の宣言部を記述したテキストファイルを参照できます。コピーして利用してください。
マップデータの宣言ファイルへのリンク
| Cmn.java |
| import com.nttdocomo.ui.*; import java.util.*; public class Cmn{ ・・・・・省略・・・・・ //************************画面表示関係 メソッドデータ群 開始******************** ・・・・・省略・・・・・ //フィールド画面でのメッセージ //引数 文字列 public static void fieldMsg(String smg){ //メッセージの描画 Cmn.msgWrite(smg,fmx,fmy,128,3,0,0); } //フィールド画面でのイメージ描画 //引数 文字列 public static void drawPerson(Image sImage){ if(sImage==null) return; int xp=cx+((130-sImage.getWidth())/2); int yp=cy+((130-(fnthgt*3)-sImage.getHeight())/2); //イメージの描画 g.drawImage(sImage,xp,yp); } ・・・・・省略・・・・・ //************************画面表示関係 メソッドデータ群 終了******************** ・・・・・省略・・・・・ } |
| MapFieldCnt.java |
| import com.nttdocomo.ui.*; public class MapFieldCnt{ ・・・・・省略・・・・・ public Person[] mpsn;//パーソンクラスの配列 ・・・・・省略・・・・・ //コンストラクタ public MapFieldCnt(Hero sHero,Monster[] aryMnt,Map[] mymap,Image[] fdimg,Person[] mympsn){ ・・・・・省略・・・・・ //Personクラスを格納 mpsn=mympsn; ・・・・・省略・・・・・ } ・・・・・省略・・・・・ //キャラクターの移動処理 //戻り値、1:ゲームオーバー、2:セーブ、3:マップデータのリフレッシュ // 4:次のシナリオへ public int MoveCnt(){ //キープレスイベント int flg=0; drGround(true); //System.out.println("フィールド描画終了"); while(true){ switch(Cmn.gKey()){ case Display.KEY_SELECT: if(pschk()==1) return 1; break; //上に移動する case Display.KEY_UP: ・・・・・省略・・・・・ } }//キープレスイベント終了 } ・・・・・省略・・・・・ private int pschk(){//人のイベントチェック //何も無ければ、0 //ゲームオーバー、1 //次のシナリオ、2 //パーソン情報が無ければ終了 if(mpsn==null) return 0; //利用する変数の宣言 int pwx=cha_x,pwy=cha_y,i=0,flg=0; //向きによって話し掛けている座標を算出 switch(cha_drc){ case 0://前向き pwy-=1; break; case 1://右向き pwx+=1; break; case 2://後ろ向き pwy+=1; break; case 3://左向き pwx-=1; break; } //人の(イベントの)存在を検索 while(i<=mpsn.length){ if(mpsn[i].mapno == mapdat[midx].mapno & mpsn[i].idx == mapdat[midx].idx & mpsn[i].x == pwx & mpsn[i].y == pwy){ break; } i++; if(i>=mpsn.length) return flg; } //エラーフラグの判定 if(mpsn[i].flg==-1) return flg; //イメージの描画 if(mpsn[i].flg!=3) Cmn.drawPerson(mpsn[i].image); switch(mpsn[i].flg){ case 0://通常メッセージ Cmn.fieldMsg(mpsn[i].after_msg); break; case 1://アイテムのゲット Cmn.fieldMsg(mpsn[i].befor_msg); //アイテムを受け取ったらflgを通常メッセージにする if(mhr.setItem(mpsn[i].event_data)==true) mpsn[i].flg=0; break; case 2://戦闘処理 //モンスター情報が無ければreturn if(mms==null) return flg; //メッセージの描画 Cmn.fieldMsg(mpsn[i].befor_msg); //戦闘処理 flg=mybattle.CntBattle(mhr,mms[Cmn.rdInt(mms.length-1)]); //フィールド画面の描画 drGround(true); Cmn.drawPerson(mpsn[i].image); //判定後のメッセージ描画 if(flg==0){ Cmn.fieldMsg(mpsn[i].after_msg); mpsn[i].flg=0; } break; case 3://ボス戦闘処理 //モンスタークラスの作成 Monster bossmons=new Monster(); bossmons.setData(mpsn[i].event_data); bossmons.image=mpsn[i].image; //メッセージの描画 Cmn.fieldMsg(mpsn[i].befor_msg); //戦闘処理 flg=mybattle.CntBattle(mhr,bossmons); //フィールド画面の描画 drGround(true); //判定後のメッセージ描画 if(flg==0){ Cmn.fieldMsg(mpsn[i].after_msg); mpsn[i].flg=-1; mapdat[midx].mapi[pwy][pwx]=0; mapdat[midx].mapd[pwy][pwx]=22; } break; } drGround(true); return flg; } } |
| MainCav.java |
| import com.nttdocomo.ui.*; public class MainCav extends Canvas{ //コンストラクタ public MainCav(){ Image img; MediaImage Mimage; //メディアイメージインターフェース型の変数宣言 //イメージの読み込み try{ Mimage = MediaManager.getImage("resource:///attack.gif"); Mimage.use(); img = Mimage.getImage(); }catch(Exception e){ System.out.println(e); System.out.println("例外が発生しました。処理を終了します。"); return; } //共通処理にデータを設定 Cmn.InitCmn(this,img); } //描画 public void paint(Graphics g) {} //プログラムの開始 public void stApp (){ String strTest; int i; Image resImage; MediaImage Mimage; Hero thisHero = new Hero(); Monster[] dbMonster = new Monster[2]; dbMonster[0] = new Monster(); dbMonster[1] = new Monster(); //モンスター情報の設定 dbMonster[0].setData("ツライム(01000200201000500101,薬草,傷を回復,30100010)"); dbMonster[1].setData("みど〜り(01000500501000500205,燃える草,燃えやすい草,40060010)"); //イメージの読み込み try{ //モンスタークラスのイメージ設定 Mimage = MediaManager.getImage("resource:///enemy1.gif"); Mimage.use(); dbMonster[0].image = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///enemy2.gif"); Mimage.use(); dbMonster[1].image = Mimage.getImage(); //Heroクラスのイメージ設定 Mimage = MediaManager.getImage("resource:///chr_up.gif"); Mimage.use(); thisHero.image[0] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_right.gif"); Mimage.use(); thisHero.image[1] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_down.gif"); Mimage.use(); thisHero.image[2] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_left.gif"); Mimage.use(); thisHero.image[3] = Mimage.getImage(); }catch(Exception e){ System.out.println(e); System.out.println("例外が発生しました。処理を終了します。"); return; } //主人公情報の設定 thisHero.name="権兵衛太郎大輔"; thisHero.hp=15; //HP thisHero.mhp=15; //マックスHP thisHero.mp=20; //MP thisHero.mmp=20; //マックスMP thisHero.attack=5; //攻撃力 thisHero.defense=15;//防御力 thisHero.maney=150; //お金 thisHero.expoint=0;//経験値 thisHero.lve=1; thisHero.nlve=10; thisHero.slv=3; for(i=0;i<=5;i++) thisHero.myItem[i].setData("薬草,傷を回復,30100010"); thisHero.myItem[3].setData("燃える草,燃えやすい草,40060010"); thisHero.myItem[4].setData("デバッグ剣,デバッグ用の剣,10200010"); thisHero.myItem[5].setData("デバッグ防具,デバッグ用の防具,20100010"); thisHero.myItem[6].setData("木の棒,落ちていた木の棒,10020010"); thisHero.myItem[7].setData("ただの服,普段着,20020010"); //マップクラスの生成 int j=0; Map[] mymap=new Map[10]; //マップのフィールドデータ String mapString[]={"001333100001333100001333100333333333001333100033333100044443100001333100000000000", "pp11111pppplllllppppmmmmmpp4ooooooo34ooooooo3poooooopppppppooppppooooopppppqqqppp", "111333111111333111111333111111333111111333111111333111111333111111333111111333111", "pp22222ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp00000pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp55555ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp11111pp", "111333111111333111111333111111333111111333111111333111111333111111333111111333111", "ppppppppppoooooopppoooooopp0ooooo0pp0poooooppppoooooppppoooooppppoooooppppppppppp", "111333111111333111111333111111333111111333111111333111111333111111333111111333111", "pppppppppppoooooppppoooooopppoo0ooo0ppooooop0ppoooooppppoooooppppoooooppppppppppp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp66666ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp22222pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp77777ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp55555pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp88888ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp66666pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp99999ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp77777pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "ppkkkkkppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp88888pp"}; for(i=0;i<=mapString.length-1;i+=2){ mymap[j]=new Map(); mymap[j].mapno=0; mymap[j].idx=j; mymap[j].setData(mapString[i],mapString[i+1]); j++; } Image[] fimg=new Image[5]; try{ Mimage = MediaManager.getImage("resource:///tree.gif"); Mimage.use(); fimg[0]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///Hatake.gif"); Mimage.use(); fimg[1]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///ie.gif"); Mimage.use(); fimg[2]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///Green.gif"); Mimage.use(); fimg[3]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///hito1.gif"); Mimage.use(); fimg[4]= Mimage.getImage(); }catch(Exception e){} //Person情報の生成 Person[] myPerson=new Person[4]; //普通の人 myPerson[0]=new Person(); myPerson[0].setData("NOMAL(00000106)(魔物がすみついてからこの村は・・・)"); myPerson[1]=new Person(); myPerson[1].setData("ITEM-PRESENT(00000206)(よろしければ、お使いください/薬草,傷を回復,30100010/もう渡す物は・・・)"); myPerson[2]=new Person(); myPerson[2].setData("ENEMY-BATTLE(00000306)(力を試してみるかい?。/さすがだね。)"); myPerson[3]=new Person(); myPerson[3].setData("BOSS-BATTLE(00000406)(ぐはははは、よくきたな!。|今楽にしてやる!!/親分(01000200201000500101,薬草,傷を回復,30100010)/なんとか倒せた。)"); try{ Mimage = MediaManager.getImage("resource:///person.gif"); Mimage.use(); myPerson[0].image= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///boss.gif"); Mimage.use(); myPerson[3].image= Mimage.getImage(); }catch(Exception e){} MapFieldCnt myMapFdCnt=new MapFieldCnt(thisHero,dbMonster,mymap,fimg,myPerson); myMapFdCnt.MoveCnt(); } } |
追加リソース
Personイメージ:
Personイメージ:![]()
フィールドイメージ:![]()
ソースファイルの保存とリソースファイルの保存が出来たらビルドして実行しましょう。人のイメージにキャラクターを向けてセレクトボタンを押すと会話が始まります。アイテムなどをもらうと、次に話し掛けても貰えません。
では、処理を解説します。MainCavでは、Personクラスのデータ構成で利用したサンプルデータを利用してPersonクラスを作成しました。イメージは、データが有るものとないものがあります。Personデータは、mapno=0、idx=0、と大マップ0の最初のマップにPersonが存在する事になります。
MoveCntのコンストラクタの処理に問題は無いでしょう。Person情報をフィールドに代入しています。MoveCntメソッドには、セレクトボタンの分岐が加わりました。単に、pschkメソッドを呼び出しているだけです。会話では戦闘が発生しますからゲームオーバー(1)が考えられます。戻り値が、1の場合は1を返してメソッドを終了します。
switch(Cmn.gKey()){
case Display.KEY_SELECT:
if(pschk()==1) return
1;
drGround(true);
break;
会話の処理を行うpschkメソッドを説明します。今回の処理の中心となる部分です。
private int pschk(){//人のイベントチェック
//何も無ければ、0
//ゲームオーバー、1
//次のシナリオ、2
//パーソン情報が無ければ終了
if(mpsn==null) return 0;
//利用する変数の宣言
int pwx=cha_x,pwy=cha_y,i=0,flg=0;
//向きによって話し掛けている座標を算出
switch(cha_drc){
case 0://前向き
pwy-=1;
break;
case 1://右向き
pwx+=1;
break;
case 2://後ろ向き
pwy+=1;
break;
case 3://左向き
pwx-=1;
break;
}
//人の(イベントの)存在を検索
while(i<=mpsn.length){
if(mpsn[i].mapno == mapdat[midx].mapno
&
mpsn[i].idx == mapdat[midx].idx
&
mpsn[i].x == pwx
&
mpsn[i].y == pwy){
break;
}
i++;
if(i>=mpsn.length) return
flg;
}
//エラーフラグの判定
if(mpsn[i].flg==-1) return flg;
//イメージの描画
if(mpsn[i].flg!=3) Cmn.drawPerson(mpsn[i].image);
switch(mpsn[i].flg){
case 0://通常メッセージ
Cmn.fieldMsg(mpsn[i].after_msg);
break;
case 1://アイテムのゲット
Cmn.fieldMsg(mpsn[i].befor_msg);
//アイテムを受け取ったらflgを通常メッセージにする
if(mhr.setItem(mpsn[i].event_data)==true)
mpsn[i].flg=0;
break;
case 2://戦闘処理
//モンスター情報が無ければreturn
if(mms==null) return
flg;
//メッセージの描画
Cmn.fieldMsg(mpsn[i].befor_msg);
//戦闘処理
flg=mybattle.CntBattle(mhr,mms[Cmn.rdInt(mms.length-1)]);
//フィールド画面の描画
drGround(true);
Cmn.drawPerson(mpsn[i].image);
//判定後のメッセージ描画
if(flg==0){
Cmn.fieldMsg(mpsn[i].after_msg);
mpsn[i].flg=0;
}
break;
case 3://ボス戦闘処理
//モンスタークラスの作成
Monster bossmons=new
Monster();
bossmons.setData(mpsn[i].event_data);
bossmons.image=mpsn[i].image;
//メッセージの描画
Cmn.fieldMsg(mpsn[i].befor_msg);
//戦闘処理
flg=mybattle.CntBattle(mhr,bossmons);
//フィールド画面の描画
drGround(true);
//判定後のメッセージ描画
if(flg==0){
Cmn.fieldMsg(mpsn[i].after_msg);
mpsn[i].flg=-1;
mapdat[midx].mapi[pwy][pwx]=0;
mapdat[midx].mapd[pwy][pwx]=22;
}
break;
}
drGround(true);
return flg;
}
最初に処理に必要な変数を宣言しています。座標を操作するため、現在の座標をpwx・pwyに代入します。
int pwx=cha_x,pwy=cha_y,i=0,flg=0;
キャラクターが人に話し掛けるとき、座標が隣接しているにしても向きによって話し掛けられるか変わってきます。キャラクターの現在地を原点にすると向きによって以下のように「会話対象がいるか」検索する座標が変わってきます。
図 キャラクターの向きと座標

向きのよって座標が違う為、向きにより処理する座標を算出します。この算出処理が最初のswich文です。
//向きによって話し掛けている座標を算出
switch(cha_drc){
case 0://前向き
pwy-=1;
break;
case 1://右向き
pwx+=1;
break;
case 2://後ろ向き
pwy+=1;
break;
case 3://左向き
pwx-=1;
break;
}
話し掛た座標が算出できたら、その座標にPersonクラスが存在するか検索します。Mapクラスの大マップのインデックス(mapno)・Mapクラス単位のインデックス(idx)・算出したx座標(pwx)・算出したy座標(pwy)の4つのデータとPersonクラスの大マップのインデックス(mapno)・Mapクラス単位のインデックス(idx)・x座標(x)・y座標(y)を付き合わせます。この4つのデータが同じであるPersonが存在する事になります。処理は簡単で、現在のマップの4つのデータとPersonクラスの配列mpsnを要素ごと付き合わせます。一致するデータが有る場合は、変数iに格納されています。また、全てのPersonクラスのデータをつき合わせて該当データが無い場合はメソッドを終了します。
//人の(イベントの)存在を検索
while(i<=mpsn.length){
if(mpsn[i].mapno == mapdat[midx].mapno
&
mpsn[i].idx == mapdat[midx].idx
&
mpsn[i].x == pwx &
mpsn[i].y == pwy){
break;
}
i++;
if(i>=mpsn.length) return flg;
}
Personクラスの検索が終わると、該当するPersonクラスのflgを確認します。flgが-1の場合は、エラーデータですのでメソッドを終了します。
//エラーフラグの判定
if(mpsn[i].flg==-1) return flg;
flgを確認したらPersonクラスのイメージを描画します。Personクラスにイメージが存在しない場合は、Personクラスのimageフィールドにnullが設定されています。CmnクラスのdrawPersonメソッドでは、引き数で渡されたImageクラスの参照がnullの場合は処理しませんのでPersonクラスのフィールドをそのまま渡します。
//イメージの描画
if(mpsn[i].flg!=3) Cmn.drawPerson(mpsn[i].image);
次の、分岐がそれぞれのメッセージに対応した処理です。最初に、case0の通常メッセージの処理を確認しましょう。
case 0://通常メッセージ
Cmn.fieldMsg(mpsn[i].after_msg);
通常メッセージでは、Personクラスのafter_msgフィールドのデータをCmnクラスのfieldMsgメソッドに渡すだけです。つまり、メッセージを描画して終了です。これで、分岐処理を抜けます。メソッドの最後に「drGround(true);」の命令で画面を再描画してメッセージを消します。画面の再描画は、全ての処理に必要ですのでメソッドの最後に定義してあります。次に、case1のアイテムがもらえるメッセージを確認します。
case 1://アイテムのゲット
Cmn.fieldMsg(mpsn[i].befor_msg);
//アイテムを受け取ったらflgを通常メッセージにする
if(mhr.setItem(mpsn[i].event_data)==true)
mpsn[i].flg=0;
アイテムでは、イベント前のメッセージをCmnクラスのfieldMsgメソッドに渡して描画します。次に、HeroクラスのsetItemメソッドにevent_dataのデータを渡します。この場合、event_dataフィールドにはアイテム情報の文字列が入っています。アイテムを受け取ったらPersonクラスのflgフィールドを0にします。これで、次に話し掛けるとcase0の処理になるのでafter_msgフィールドのメッセージが描画されます。では、次に戦闘処理のケースを見ていきましょう。
case 2://戦闘処理
//モンスター情報が無ければreturn
if(mms==null) return flg;
//メッセージの描画
Cmn.fieldMsg(mpsn[i].befor_msg);
//戦闘処理
flg=mybattle.CntBattle(mhr,mms[Cmn.rdInt(mms.length-1)]);
//フィールド画面の描画
drGround(true);
Cmn.drawPerson(mpsn[i].image);
//判定後のメッセージ描画
if(flg==0){
Cmn.fieldMsg(mpsn[i].after_msg);
mpsn[i].flg=0;
}
このケースは、会話後、戦闘が発生します。戦闘に利用するモンスターは、フィールドで遭遇する通常のモンスターと使用します。戦闘のモンスターは、rdIntメソッドで取得します。戦闘が終了したら画面を再描画してイベント後のメッセージを描画します。もし、戦闘に負けた場合は、イベント後のメッセージは表示しません。ここでも、戦闘終了後はPersonクラスのflgを0に設定します。最後にボスモンスターの処理を見ていきましょう。
case 3://ボス戦闘処理
//モンスタークラスの作成
Monster bossmons=new Monster();
bossmons.setData(mpsn[i].event_data);
bossmons.image=mpsn[i].image;
//メッセージの描画
Cmn.fieldMsg(mpsn[i].befor_msg);
//戦闘処理
flg=mybattle.CntBattle(mhr,bossmons);
//フィールド画面の描画
drGround(true);
//判定後のメッセージ描画
if(flg==0){
Cmn.fieldMsg(mpsn[i].after_msg);
mpsn[i].flg=-1;
mapdat[midx].mapi[pwy][pwx]=0;
mapdat[midx].mapd[pwy][pwx]=22;
}
ボスモンスターのモンスター情報は、event_dataフィールドから取得します。モンスタークラスを独自に生成してevent_dataフィールドのデータを渡してやります。後は、戦闘があったケースと同じです。ただし、モンスターのイメージはPersonクラスのイメージを利用します。つまり、ボスとの戦闘のケースでは、イメージはモンスターデータ用ですのでフィールドに描画する事はありません。処理の分岐の前に、以下のような命令がありました。これは、ボスのデータを格納しているPersonクラスのイメージを描画しない為の制御なのです。
//イメージの描画
if(mpsn[i].flg!=3) Cmn.drawPerson(mpsn[i].image);
戦闘終了後は、フラグによって判定を行います。戦闘が買った場合は、Personクラスのflgに-1を設定します。これで、次に話し掛けてもイベントは発生しません。また、マップクラスのイメージ描画も通常フィールドのデータに変えて、通行情報も通行可能にします。ボスとの戦闘は、あるシナリオの区切りとなります。ボスと戦闘を行うと、次のシナリオへ移動するケースが殆どだと考えますのでフィールドデータも通常フィールドに戻す訳です。
会話の処理を想定した処理は、以上です。次は、いよいよフィールド制御のクラスを仕上げていきましょう。
4.4 マップの描画とキャラクターの移動 ver4(完成版)
いよいよ、マップの制御と描画を仕上げていきます。修正部分があるのは、MapFieldCntとMainCavクラスです。MainCavクラスは、難しい修正はありません。例のごとく、修正部を青色にしてあります。
MapFieldCntクラスには、モンスター数・フィールドイメージの数・モンスターとのエンカウント率を表すフィールドが追加されています。コンストラクタでは、これらのフィールドのデータをセットする命令が付け加えられているのとテスト用の設定を削除しました。また、イメージの数をフィールドで持つのでフィールド描画に関わるdrGround・ drPicメソッドに変更があります。修正点は太字で強調してあります。
| MapFieldCnt.java |
| import com.nttdocomo.ui.*; public class MapFieldCnt{ ・・・・・省略・・・・・ public int ect=0;//モンスターのエンカウント率 public int ficnt=0;//フィールドのイメージ数 public int mscnt=0;//モンスター数 //コンストラクタ public MapFieldCnt(Hero sHero,Monster[] aryMnt,Map[] mymap,Image[] fdimg,Person[] mympsn){ //Heroクラスを設定 mhr=sHero; //Monsterクラスを設定 mms=aryMnt; if(aryMnt!=null) mscnt=mms.length; //Mapクラスを設定 mapdat=mymap; //フィールドのイメージ郡を設定 fimg=fdimg; if(fimg!=null) ficnt=fimg.length; //Personクラスを格納 mpsn=mympsn; //戦闘制御クラスを格納 mybattle=new BattCnt(); //描画座標の設定 startX=Cmn.cx-7; startY=Cmn.cy-7; } //フィールドの描画 //引き数 キャラクターの描画の有無 private void drGround(boolean stflg){ int i,j; Cmn.g.lock(); //フィールドを描画 for(i=0;i<=8;i++){ for(j=0;j<=8;j++){ if(mapdat[midx].mapi[i][j]<ficnt) Cmn.g.drawImage(fimg[mapdat[midx].mapi[i][j]],startX+(j*16),startY+(i*16)); } } //フラグがtrueの場合は、キャラクターも描画 if(stflg==true) Cmn.g.drawImage(mhr.image[cha_drc],startX+(cha_x*16),startY+(cha_y*16)); //余白の描画 Cmn.drSpc(); Cmn.g.unlock(true); } //キャラクター移動部分の描画 //引き数 移動先の座標 private void drPic(int sx,int sy){ if(mapdat[midx].mapi[cha_y][cha_x]<ficnt){ Cmn.g.drawImage(fimg[mapdat[midx].mapi[cha_y][cha_x]],startX+(cha_x*16),startY+(cha_y*16)); } if(mapdat[midx].mapi[sy][sx]<ficnt){ Cmn.g.drawImage(fimg[mapdat[midx].mapi[sy][sx]],startX+(sx*16),startY+(sy*16)); } } ・・・・・省略・・・・・ //キャラクターの移動処理 //戻り値、1:ゲームオーバー、2:セーブ、3:マップデータのリフレッシュ // 4:次のシナリオへ public int MoveCnt(){ //キープレスイベント int flg=0; drGround(true); //System.out.println("フィールド描画終了"); while(true){ switch(Cmn.gKey()){ ・・・・・省略・・・・・ //データのセーブ case Display.KEY_SOFT2: return 2; //アプリケーションの終了 case Display.KEY_SOFT1: IApplication.getCurrentApp().terminate(); //キャラクターのステータスを描画 case Display.KEY_POUND: Cmn.drawStatus(mhr); drGround(true); break; //アイテムを利用 case Display.KEY_ASTERISK: this.useSb(); drGround(true); break; } }//キープレスイベント終了 } private int echk(int fdat){//イベントのチェック //戻り値、1:ゲームオーバー、2:セーブ、3:マップデータのリフレッシュ // 4:次のシナリオへ if(fdat<=19){//マップが次のマップを示している場合、次のマップへ移動 midx=fdat; if(cha_y>0 && cha_y<8 && cha_x>0 && cha_x<8) drGround(true); else drGround(false); return 0; } //移動以外の処理 switch(fdat){ case 20://k:次の大マップへ移動する Cmn.battleMsg("次の大マップ"+ Integer.toString(fdat)); midx=0; if(cha_y==0) cha_y=7; else if(cha_y==8) cha_y=1; else if(cha_x==0) cha_x=7; else if(cha_x==8) cha_x=1; return 3; case 21://l:シナリオイベント処理 Cmn.battleMsg("次のシナリオ"+ Integer.toString(fdat)); drGround(true); return 4; case 22://m:通行可能 強制戦闘が発生(無条件) Cmn.battleMsg("強制戦闘(移動可能)"+ Integer.toString(fdat)); fdat=mybattle.CntBattle(mhr,mms[Cmn.rdInt(mscnt)]); if(fdat==0) drGround(true); return fdat; case 23://n:予備のケース(モンスターとエンカウントしない) Cmn.battleMsg("移動可能(予備)"+ Integer.toString(fdat)); drGround(true); return 0; case 24://o:移動可能 ランダムで戦闘が発生 //Cmn.battleMsg("移動可能"); //drGround(true); fdat=0; if(ect<=0) return fdat; if(Cmn.rdInt(ect)==0){ fdat=mybattle.CntBattle(mhr,mms[Cmn.rdInt(mscnt)]); if(fdat==0) drGround(true); } return fdat; case 25://p:通常の移動不可能 分岐で記述の必要は無し //Cmn.battleMsg("移動不可能"); //drGround(true); return 0; case 26://q:通行不可能 強制戦闘が発生(無条件) Cmn.battleMsg("強制戦闘(移動不可)"+ Integer.toString(fdat)); fdat=mybattle.CntBattle(mhr,mms[Cmn.rdInt(mscnt)]); if(fdat==0) drGround(true); return fdat; } return 0; } ・・・・・省略・・・・・ private void useSb(){ //kが種類を格納、pが効果の値を格納 int a=0,i=0,k,p; //表示メッセージを格納 String tmsg; //戦闘メッセージ中に描画するカーソルの座標 int mxp=Cmn.dmx+Cmn.cx; int mxy=Cmn.dmy+Cmn.fnthgt+Cmn.cy; //選択を描画 Cmn.battleMsg(" アイテム| 特技"); if(Cmn.chsCnt(mxp+5,mxy,0,2)==0){ if((a=Cmn.subCnt(mhr.myItem,6))>5) return; } else{ if((a=Cmn.subCnt(mhr.special,mhr.slv))>5) return; } drGround(true); if(i==0){//「道具」の処理 //アイテム名・種類・効果のポイントを格納 tmsg=mhr.myItem[a].name; k=mhr.myItem[a].kind; p=mhr.myItem[a].eftpnt; } else{//「特技」の処理 //残りのMPが消費MPを下回る場合は、終了 if(mhr.special[a].price>mhr.mp){ Cmn.battleMsg("MPが足りないっス"); return; } //特技名・種類・効果のポイントを格納 tmsg=mhr.special[a].name; k=mhr.special[a].kind; p=mhr.special[a].eftpnt; } //道具・特技を使った旨のメッセージを作成 tmsg+="を使った|"; switch (k){ case 3://回復 //HPが満杯の場合、 if(mhr.mhp==mhr.hp){ Cmn.battleMsg("回復の必要は無いっス"); return; } //HPを回復、回復後のHPが最大HPを越えたら修正 if((mhr.hp+=p)>mhr.mhp) mhr.hp=mhr.mhp; //道具。特技の利用メッセージを描画 Cmn.battleMsg(tmsg); //効果のメッセージを描画 Cmn.battleMsg(Integer.toString(p)+"回復した"); //消費の処理 if(i==0){//道具を利用した場合 //利用アイテムの削除 mhr.lostItem(a); } else{//特技を使った場合 //MPの減算 mhr.mp-=mhr.special[a].price; } return; default: Cmn.battleMsg("今は、使えないっス"); return; } } } |
| MainCav.java |
| import com.nttdocomo.ui.*; public class MainCav extends Canvas{ //コンストラクタ public MainCav(){ Image img; MediaImage Mimage; //メディアイメージインターフェース型の変数宣言 //イメージの読み込み try{ Mimage = MediaManager.getImage("resource:///attack.gif"); Mimage.use(); img = Mimage.getImage(); }catch(Exception e){ System.out.println(e); System.out.println("例外が発生しました。処理を終了します。"); return; } //共通処理にデータを設定 Cmn.InitCmn(this,img); } //描画 public void paint(Graphics g) {} //プログラムの開始 public void stApp (){ String strTest; int i; Image resImage; MediaImage Mimage; Hero thisHero = new Hero(); Monster[] dbMonster = new Monster[2]; dbMonster[0] = new Monster(); dbMonster[1] = new Monster(); //モンスター情報の設定 dbMonster[0].setData("ツライム(01000200201000500101,薬草,傷を回復,30100010)"); dbMonster[1].setData("みど〜り(01000500501000500205,燃える草,燃えやすい草,40060010)"); //イメージの読み込み try{ //モンスタークラスのイメージ設定 Mimage = MediaManager.getImage("resource:///enemy1.gif"); Mimage.use(); dbMonster[0].image = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///enemy2.gif"); Mimage.use(); dbMonster[1].image = Mimage.getImage(); //Heroクラスのイメージ設定 Mimage = MediaManager.getImage("resource:///chr_up.gif"); Mimage.use(); thisHero.image[0] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_right.gif"); Mimage.use(); thisHero.image[1] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_down.gif"); Mimage.use(); thisHero.image[2] = Mimage.getImage(); Mimage = MediaManager.getImage("resource:///chr_left.gif"); Mimage.use(); thisHero.image[3] = Mimage.getImage(); }catch(Exception e){ System.out.println(e); System.out.println("例外が発生しました。処理を終了します。"); return; } //主人公情報の設定 thisHero.name="権兵衛太郎大輔"; thisHero.hp=15; //HP thisHero.mhp=15; //マックスHP thisHero.mp=20; //MP thisHero.mmp=20; //マックスMP thisHero.attack=5; //攻撃力 thisHero.defense=15;//防御力 thisHero.maney=150; //お金 thisHero.expoint=0;//経験値 thisHero.lve=1; thisHero.nlve=10; thisHero.slv=3; for(i=0;i<=5;i++) thisHero.myItem[i].setData("薬草,傷を回復,30100010"); thisHero.myItem[3].setData("燃える草,燃えやすい草,40060010"); thisHero.myItem[4].setData("デバッグ剣,デバッグ用の剣,10200010"); thisHero.myItem[5].setData("デバッグ防具,デバッグ用の防具,20100010"); thisHero.myItem[6].setData("木の棒,落ちていた木の棒,10020010"); thisHero.myItem[7].setData("ただの服,普段着,20020010"); //マップクラスの生成 int j=0; Map[] mymap=new Map[10]; //マップのフィールドデータ String mapString[]={"001333100001333100001333100333333333001333100033333100044443100001333100000000000", "pp11111pppplllllppppmmmmmpp4ooooooo34ooooooo3poooooopppppppooppppooooopppppqqqppp", "111333111111333111111333111111333111111333111111333111111333111111333111111333111", "pp22222ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp00000pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp55555ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp11111pp", "111333111111333111111333111111333111111333111111333111111333111111333111111333111", "ppppppppppoooooopppoooooopp0ooooo0pp0poooooppppoooooppppoooooppppoooooppppppppppp", "111333111111333111111333111111333111111333111111333111111333111111333111111333111", "pppppppppppoooooppppoooooopppoo0ooo0ppooooop0ppoooooppppoooooppppoooooppppppppppp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp66666ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp22222pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp77777ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp55555pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp88888ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp66666pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "pp99999ppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp77777pp", "2a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a12a13332a1", "ppkkkkkppppoooooppppoooooppppoooooppppoooooppppoooooppppoooooppppooooopppp88888pp"}; for(i=0;i<=mapString.length-1;i+=2){ mymap[j]=new Map(); mymap[j].mapno=0; mymap[j].idx=j; mymap[j].setData(mapString[i],mapString[i+1]); j++; } Image[] fimg=new Image[5]; try{ Mimage = MediaManager.getImage("resource:///tree.gif"); Mimage.use(); fimg[0]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///Hatake.gif"); Mimage.use(); fimg[1]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///ie.gif"); Mimage.use(); fimg[2]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///Green.gif"); Mimage.use(); fimg[3]= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///hito1.gif"); Mimage.use(); fimg[4]= Mimage.getImage(); }catch(Exception e){} //Person情報の生成 Person[] myPerson=new Person[4]; //普通の人 myPerson[0]=new Person(); myPerson[0].setData("NOMAL(00000106)(魔物がすみついてからこの村は・・・)"); myPerson[1]=new Person(); myPerson[1].setData("ITEM-PRESENT(00000206)(よろしければ、お使いください/薬草,傷を回復,30100010/もう渡す物は・・・)"); myPerson[2]=new Person(); myPerson[2].setData("ENEMY-BATTLE(00000306)(力を試してみるかい?。/さすがだね。)"); myPerson[3]=new Person(); myPerson[3].setData("BOSS-BATTLE(00000406)(ぐはははは、よくきたな!。|今楽にしてやる!!/親分(01000200201000500101,薬草,傷を回復,30100010)/なんとか倒せた。)"); try{ Mimage = MediaManager.getImage("resource:///person.gif"); Mimage.use(); myPerson[0].image= Mimage.getImage(); Mimage = MediaManager.getImage("resource:///boss.gif"); Mimage.use(); myPerson[3].image= Mimage.getImage(); }catch(Exception e){} MapFieldCnt myMapFdCnt=new MapFieldCnt(thisHero,dbMonster,mymap,fimg,myPerson); //モンスターのエンカウント率を0に設定 myMapFdCnt.ect=0; //キャラクターの座標を設定 myMapFdCnt.cha_x=5; myMapFdCnt.cha_y=5; //キャラクターの向きを設定 myMapFdCnt.cha_drc=0; //マップ移動の制御開始 while((i=myMapFdCnt.MoveCnt())!=0){ System.out.println("処理の戻り値:"I+nteger.toString(i)); } } } |
今回の修正点を一覧にすると以下のようになります。
モンスター数・フィールドイメージの数・モンスターとのエンカウント率を表すフィールドが追加
コンストラクタの修正
drGroundの修正
drPicの修正
MoveCntの修正
echkの修正
useSbの追加
フィールドやコンストラクタの修正部に、難しい処理はありません。また、イメージ描画に関わるdrGround・drPicメソッドも描画の判定処理が変わっただけです。詳しくは、マップのフィールドデータにフィールドイメージの数(ficnt)を超えた値が設定されていた場合は描画処理を行わないようにしています。
MoveCntメソッドには、セーブ・アプリの終了・キャラクターのステータス描画・アイテムの利用を行う処理が追加されました。ソフトキーの2がデータのセーブ処理です。データのセーブは、MapFieldCntを利用するクラスで行いますのでここではセーブに対応する2を返してメソッドを終了します。ソフトキーの1は、アプリケーションの終了です。IApplicationクラスのgetCurrentAppで起動しているアプリケーションを取得します。戻り値は、IApplicationクラスです。起動もとのアプリケーションを取得したらterminateメソッドでアプリケーションを終了します。ステータスの描画は、戦闘画面で作成したメソッドです。特に問題は無いでしょう。アイテム利用は、useSbメソッドを定義して処理はメソッドに任せます。
//データのセーブ
case Display.KEY_SOFT2:
return 2;
//アプリケーションの終了
case Display.KEY_SOFT1:
IApplication.getCurrentApp().terminate();
//キャラクターのステータスを描画
case Display.KEY_POUND:
Cmn.drawStatus(mhr);
drGround(true);
break;
//アイテムを利用
case Display.KEY_ASTERISK:
this.useSb();
drGround(true);
break;
フィールドの処理では、アイテムを利用できなければなりません。その為に、useSbメソッドを定義してあります。戦闘制御で作成したuseSbメソッドと基本構造は同じです。フィールドで利用するので回復アイテムしか利用できませんし、利用した後のモンスターの攻撃はありません。このメソッドは、自分で解読できると思いますので説明は省かせていただきます。自分で、処理を確認してみてください。
フィールド上のイベントを判定するechkメソッドには、マップの移動移行の制御文に具体的な処理が記述されました。各ケース毎に処理を説明します。各ケースに記述されているCmnのbattleMsgメソッドは、テストの確認用ですのでテストが済んだらコメント化するか削除してください。
//移動以外の処理
switch(fdat){
case 20://k:次の大マップへ移動する
Cmn.battleMsg("次の大マップ"+
Integer.toString(fdat));
midx=0;
if(cha_y==0) cha_y=7;
else if(cha_y==8) cha_y=1;
else if(cha_x==0) cha_x=7;
else if(cha_x==8) cha_x=1;
return 3;
ケース20の処理は、大マップの移動処理です。次の10マップ(Mapクラス単位)一組のマップに移動します。画面端で移動した場合は、座標を修正します。mvX・mvYメソッドを実行しない為、この処理で座標を修正しているのです。後は、3を返してメソッドを終了します。
case 21://l:シナリオイベント処理
Cmn.battleMsg("次のシナリオ"+
Integer.toString(fdat));
drGround(true);
return 4;
ケース21は、次のシナリオへ移動します。フィールド制御の終わりを表しますので4を返してメソッドを終了します。
case 22://m:通行可能 強制戦闘が発生(無条件)
Cmn.battleMsg("強制戦闘(移動可能)"+
Integer.toString(fdat));
fdat=mybattle.CntBattle(mhr,mms[Cmn.rdInt(mscnt)]);
if(fdat==0) drGround(true);
return fdat;
ケース22は、通行可能ですが強制戦闘が発生します。CmnクラスのrdIntメソッドで、モンスタークラスの要素を決めますのでランダムで遭遇モンスターが決まる事になります。乱数の取得には、モンスター数を表すmscntを指定します。会話イベントのモンスターバトルもmscntを利用した命令に書き換えられます。
case 23://n:予備のケース(モンスターとエンカウントしない)
Cmn.battleMsg("移動可能(予備)"+
Integer.toString(fdat));
drGround(true);
return 0;
ケース23は、予備のケースです。何も利用しません。取りあえずモンスターに遭遇しないケースになります。
case 24://o:移動可能 ランダムで戦闘が発生
fdat=0;
if(ect<=0) return fdat;
if(Cmn.rdInt(ect)==0){
fdat=mybattle.CntBattle(mhr,mms[Cmn.rdInt(mscnt)]);
if(fdat==0) drGround(true);
}
return fdat;
ケース24は、ランダムで戦闘が発生します。最もよく利用されるフィールドです。遭遇率を格納したectフィールドを元にCmnクラスのrdIntメソッドで判定します。ectフィールドの値が少ないほどエンカウント率(遭遇率)が高くなります。
case 25://p:通常の移動不可能 分岐で記述の必要は無し
return 0;
ケース25は、移動できないフィールドです。特に処理はありません。
case 26://q:通行不可能 強制戦闘が発生(無条件)
Cmn.battleMsg("強制戦闘(移動不可)"+
Integer.toString(fdat));
fdat=mybattle.CntBattle(mhr,mms[Cmn.rdInt(mscnt)]);
if(fdat==0) drGround(true);
return fdat;
ケース26は、通行でき無い上に強制戦闘が発生します。道を間違えるとペナルティとして戦闘を発生させるなどの時に利用します。処理的に、問題は無いでしょう。
以上が、フィールド移動の処理です。次回は、フィールド制御に関わるデータをネットワークから取得する仕組みを作成していきます。
独り言
Personクラスは、Mapクラスに管理させるのも手です。今回は、当マニュアルでは別々に管理しました・・・。Mapクラスの中にPersonが存在するので前者の方が良いような気がします。ただ、判定処理などをMapクラスに記述したくなかったのです。Mapクラスが使うヒープのサイズを大きくしたくなかったのです。
あと、メッセージの制御も本とはもう少し複雑でした。簡単に説明する為に機能を限定しましたがお好みで拡張するのが良いでしょう。因みに、商人を考慮に入れた場合は以下のような命令が必要になると思います。バグがあるかもしれないので、あくまで参考としてみてくださいね。アイテムの情報は、「:」(全角)で区切っています。
商人のデータ
MERCHANT(00000206)(特に何も無いね/薬草,傷を回復,30100010:凄い薬草,傷を回復,30100010:高級な薬草,傷を回復,30100010:/そういや、山には凄いお宝があるとか・・・)
商人のメッセージケース(case 4を想定)
case 4
String[] cstr={"買う","売る","雑談","やめる"};
String[] sarry;
int j,mc=0,sc=0;
//文字列分解処理
sarry=Cmn.decStr(mpsn[i].event_data,':');
Item[] commodity = new Item[6];
for(j=0;j<=6;j++) commodity[j]=new
Item();
for(j=0;j<=sarry.length-1;j++) commodity[j].setData(sarry[j]);
//行動選択ウィンドウのカーソル座標
int cxp=Cmn.dcx+3;
int cyp=Cmn.dcy+Cmn.fnthgt;
//画面の塗りつぶし(戦闘画面のサイズを利用)
Cmn.cleBf();
Cmn.battleMsg("いらっしゃいませ。");
Cmn.tSlp(500);
while(true){
//選択ウィンドウの描画
Cmn.cleBf();
Cmn.battleMsg("何にしますか?。");
switch (mc=Cmn.chsCnt(cxp,cyp,mc,4)){
case 0://買う
if((sc=Cmn.subCnt(commodity,6))>5)
break;
if(mhr.maney<commodity[sc].price){
Cmn.battleMsg("お金が足りないようだね");
break;
}
if(mhr.setItem(sarry[sc])==false){
Cmn.battleMsg("アイテムが一杯だったかい");
break;
}
mhr.maney-=commodity[sc].price;
Cmn.battleMsg("まいどう!!");
break;
case 1://売る
if((sc=Cmn.subCnt(mhr.myItem,6))>5)
break;
mhr.lostItem(sc);
Cmn.battleMsg("確かに買わせて貰ったよ");
mc=0;
break;
case 2://雑談
Cmn.battleMsg(mpsn[i].befor_msg);
mc=0;
break;
case 3://やめる
Cmn.battleMsg(mpsn[i].after_msg);
drGround(true);
return 0;
}
}