2.1 ゲームの概要!!
ゲーム作成の本題に入る前に、これから作成するプログラムの方向性であるロールプレイングゲームの仕様をお話したいと思います。
プログラムの構成ですが、当マニュアルはオブジェクト指向を利用してロールプレイングゲームを作るものではありません。iアプリのアプリケーションサイズが大きくなったとは言え、やはりパソコンと比べるとまだまだ制約が多いというのが実際のところでしょう。クラスを沢山作るとやはり30kでは、収まりきれません。JAVAはオブジェクト指向プログラミングの言語です。いくらオブジェクト指向を取り入れてアプリを作成しても低機能では仕方ありません。アプリを使う側は、それがオブジェクト指向に則ろうがそうでなかろうが構わないでしょう。システム屋は、オブジェクト指向に則って作って同じ業種の人からは「すごい」といわれるかもしれません。しかし、使う側は、オブジェクト指向で低機能のアプリより、オブジェクト指向ではなくて高機能なアプリを望むでしょう。オブジェクト指向にこだわるよりロールプレイングゲームとして楽しくなるような機能を少しでも多く提供した方がユーザーの為になります。当マニュアルでは、アプリのサイズを最小にする為、クラスファイルを極力作らない事を目的とします。しかし、一つのファイルに全てまとめてしまうと分かりづらいのも確かです。説明しやすいレベルでクラスファイルを作っていきます。基本的には、静的クラスにメソッドを追加していきます。その為、違和感のある処理になることがありますのでご了承ください。また、プログラムを「作りながら憶えていく」ように書かれています。基本のソースファイルに足りない機能を追加していくような感じで説明します。最初に分析と設計を行ってプログラムをするわけではありません。仕事でプログラムをしている方も違和感があるを思います。対象は、あくまで「休日などに趣味でプログラムをしたい」・「プログラムを作ってみたい」レベルの方ですのでプロの方が当マニュアルを読んでも為にはならにと思います。ご了承ください。
では、ロールプレイングゲームは、どのようなゲームでしょうか?。キャラクターが町で話をして頼みを聞いたりして、モンスターを倒しながら成長していきます。ここで、大まかな流れを考えましょう。他の見事やある事件を解決しなければ次に進めないなど問題が発生します。これをイベントと呼びます。次にマップ移動、マップ移動中に戦闘、イベント解決、そして、イベント発生・・・というようにイベントとマップ移動(+戦闘)が繰り返されます。
そこで、制御が必要な物はイベント制御・マップ移動制御・戦闘制御という事になります。この3つの制御ごとにプログラムを作成していきましょう。
2.2 戦闘画面の作成 多角形の描画
戦闘を考える前に、戦闘を行う画面が無ければ話になりません。戦闘画面を作成しましょう。戦闘画面は、モンスターイメージ・HPを表示する部分・行動選択部分・サブ選択部分・メッセージ表示部分から構成されます。モンスターのイメージ表示は、後回しにして他の部分を作成しましょう。下は、戦闘の選択とHP描画、サブ選択を表示した画面です。この様な描画エリアをなんと呼んだらよいか分かりませんが、当マニュアルではウィンドウと呼びましょう。

行動選択ウィンドウには、「戦う・特技(魔法)・道具(アイテム)・逃げる」といった戦闘時に選択できる行動を表示します。メッセージウィンドウには、「???攻撃! ?ポイントのダメージを与えた」といったメッセージを表示します。HP表示ウィンドウにはその名前の通り、HPやMPの表示を行います。サブ選択ウィンドウには、特技(魔法)・道具(アイテム)を選択した時に使用できる特技(魔法)一覧や道具(アイテム)一覧を表示します。行動選択部分・サブ選択部分に黄色系の色で塗りつぶされているところがあります。これは、選択している部分を表します。今、何が選択されているかわからなければ困りますね。
この選択ウィンドウは、他の用途にも使用できます。例えば、武器の購入など買い物を行う時です。行動選択ウィンドウに「話す・買う・売る・出る」などの買い物に関わる行動を表示して、サブ選択ウィンドウに商品一覧を表示するのです。
画面を描画するには、Graphicsクラスを利用します。GraphicsクラスのfillRectメソッドである領域を塗りつぶしてその領域にdrawRectメソッドで外枠を描画するといった処理です。ただ、fillRectメソッド・drawRectメソッドでは四角形の描画になってしまいます。描画する部分に「角」付いてしまいます。多角形を描画するfillPolygonメソッド・drawPolylineメソッドを利用すれば、多少処理が複雑になりますが見た目がやわらかいウィンドウを描画する事が出来ます。当マニュアルでは、fillPolygonメソッド・drawPolylineメソッドを利用してウィンドウを作り上げていきます。
戦闘画面は、iappliToolにBattleCanvasというプロジェクトを作成して利用しましょう。プロジェクトを作成して以下のプログラムファイルを作成してください。ADF設定の「AppClass」には「App」と入力します。
ソースファイル(App.java・MainCav.java・Cmn.java) BattleCanvas ver1
| App.java |
| import com.nttdocomo.ui.*; public class App extends IApplication { //アプリの開始 public void start(){ MainCav myObj=new MainCav(); Display.setCurrent(myObj); myObj.stApp(); } } |
| MainCav.java |
| import com.nttdocomo.ui.*; public class MainCav extends Canvas{ private Graphics g; //コンストラクタ public MainCav(){ //共通処理にデータを設定 Cmn.InitCmn(this); } //描画 public void paint(Graphics g) {} //プログラムの開始 public void stApp (){ Cmn.drawMsg(1,1,128,27); } } |
| Cmn.java |
| import com.nttdocomo.ui.*; public class Cmn{ //********************************************************************************** //* 画面表示関係 開始 * //********************************************************************************** //************************画面表示関係 フィールドデータ群 開始******************** public static Graphics g; public static Canvas c; public static final int claWht=16777215;//白の色データ public static final int claBlk=0;//黒の色データ public static final int claNvi=6172416;//ネイビーの色データ public static final int claSil=12632256;//シルバーの色データ //利用するフォント public static Font font = Font.getFont(Font.FACE_MONOSPACE|Font.SIZE_MEDIUM|Font.TYPE_DEFAULT); //文字の高さ(上にスペースを空ける) public static int fnthgt = font.getHeight()+1; //************************画面表示関係 フィールドデータ群 終了******************** //****************************初期処理メソッド************************************** public static void InitCmn(Canvas maincvs){ c=maincvs; g=c.getGraphics(); g.setFont(font); } //********************************************************************************** //************************画面表示関係 メソッドデータ群 開始******************** //画面の初期化 //引数 初期化する色 public static void cleFd(){ //画面を指定された色で塗りつぶす g.setColor(g.getColorOfName(claWht)); g.fillRect(0,0,c.getWidth(),c.getHeight()); } //メッセージ部の描画 //引数 描画座標(z,y座標)、長さ、高さ public static void drawMsg(int xp,int yp,int whd,int hgt){ //メッセージ表示の枠部を描画 int[] x={xp,xp+2,xp+whd-2,xp+whd,xp+whd,xp+whd-2,xp+2,xp,xp}; int[] y={yp+2,yp,yp,yp+2,yp+hgt-2,yp+hgt,yp+hgt,yp+hgt-2,yp+2}; //メッセージ表示部をネイビーで塗りつぶす g.setColor(claNvi); g.fillPolygon(x,y,9); //メッセージ枠を表示 g.setColor(claSil); g.drawPolyline(x,y,9); } //************************画面表示関係 メソッドデータ群 終了******************** //********************************************************************************** //* 画面表示関係 終了 * //********************************************************************************** } |
結果 iappliTool

このアプリでは、Appクラス・MainCav・Cmnクラスから構成されます。Appクラスは、IApplicationを継承したアプリの起動元になるクラスです。実行時に起動されるとプログラムが終了するまで制御が戻りません。MainCavクラスは、Canvasクラスを継承した画面描画の為のクラスです。しかし、実際に描画処理を行うのは他のクラスです。描画エリアを提供する役割がメインのクラスになります。そして、実際の描画処理を行うのがCmnクラスです。Cmnクラスには、静的フィールド・メソッドを定義します。このクラスに具体的な処理を記述する事になります。このクラスの名称Comは、Commonの略称です。「共通の」という意味です。通常、この様な名称を使うと全システムで共通で利用する機能を定義する事になります。当マニュアルのCmnクラスでは、共通処理ではありません。アプリのサイズを節約する為に、処理をあるクラスにまとめる目的として定義してあります。もちろん、他クラスでも利用する共通処理も定義してあります。システム開発者には、違和感のあるクラスでしょう。
処理の流れを確認してみましょう。Appクラスで、MainCavクラスのインスタンスを生成してカレントフレームに設定します。その後、MainCavクラスのstAppメソッドを実行します。あとは、アプリ終了までこのクラスの制御は戻りません。
MainCav myObj=new MainCav();
Display.setCurrent(myObj);
myObj.stApp();
MainCavクラスでは、コンストラクタでCmnクラスのInitCmnメソッドを実行しています。そして、stAppメソッドで、CmnクラスのdrawMsgメソッドを実行してます。つまり、処理はInitCmnメソッド→drawMsgメソッドと流れている訳です。
//コンストラクタ
public MainCav(){
//共通処理にデータを設定
Cmn.InitCmn(this);
}
//プログラムの開始
public void stApp (){
Cmn.drawMsg(1,1,128,27);
}
Cmnクラスに、処理に必要なフィールドとメソッドが定義されています。取りあえず今回、Cmnクラスに定義したフィールドは以下のようなデータです。
public static Graphics g;
public static Canvas c;
public static final int claWht=16777215;//白の色データ
public static final int claBlk=0;//黒の色データ
public static final int claNvi=6172416;//ネイビーの色データ
public static final int claSil=12632256;//シルバーの色データ
//利用するフォント
public static Font font = Font.getFont(Font.FACE_MONOSPACE|Font.SIZE_MEDIUM|Font.TYPE_DEFAULT);
//文字の高さ(上にスペースを空ける)
public static int fnthgt = font.getHeight()+1;
フィールドのデータを大別すると、Canvasクラス・Graphicsクラス・色のデータ・フォント・文字の高さです。Canvasクラス・Graphicsクラスは画面に描画を行う上で様々な情報とメソッドを提供します。描画に関わる全てのメソッドから利用できるようにフィールドとして定義しました。色を表すフィールドは、GraphicsクラスのgetColorOfName・getColorOfRGBメソッドで取得できますが利用頻度の高い色のデータのみ定数にとして定義しています。
public static Font font = Font.getFont(Font.FACE_MONOSPACE|Font.SIZE_MEDIUM|Font.TYPE_DEFAULT);
フォントの指定は、機種によって利用されているフォントが違う事もありますので描画するフォントを指定する為に利用します。FontクラスのgetFontでフォントを取得する事が出来ます。getFontメソッドの引数には、フェイス、スタイル、サイズを表す定数をビット毎の論理和を取って指定します。このサンプルでは、フォントのサイズをミディアムサイズのフォントにしています。文字の大きさは、12×12ピクセルになります。getFontメソッドについては、APIリファレンスを確認してください。
public static int fnthgt = font.getHeight()+1;
最後の文字の高さをgetHeightメソッドで取得します。ミディアムサイズのフォントのFontクラスを利用していますからgetHeightメソッドの戻り値は12になります。文字の高さを表すフィールドは、画面の描画には文字が欠かせない存在です。このアプリで利用する「文字の高さ」を統一する為にフィールドに定義しています。文字の高さに+1しているの文字を描画した再に少しゆとりをもって描画させたいからです。好みによっては、+1をとっても構いません。
Cmnクラスで最初に実行されるInitCmnメソッドでは、初期データを登録しています。描画に利用するCanvasクラスの参照を引数で受け取ってクラスの内部の参照に設定します。ここで、描画に利用するGraphicsクラスのフォントの設定も行っています。
public static void InitCmn(Canvas maincvs){
c=maincvs;
g=c.getGraphics();
g.setFont(font);//フォントの設定
}
ソースコードを見れば分かるように、CmnクラスのCanvasクラス・Graphicsクラスのフィールドへの設定を行っています。つまり、このメソッドは必ず最初に実行しなければなりません。実行しない場合は、Cmnクラス内で利用するGraphicsクラスが無い為に描画処理が行えません。
実際に画面に描画を行っているのは、drawMsgメソッドです。引数は、描画するx,y座標とエリアの幅と高さです。Graphicsクラスのフィールドgを利用して多角形を描画しています。
public static void drawMsg(int xp,int yp,int
whd,int hgt){
//メッセージ表示の枠部を描画
int[] x={xp,xp+2,xp+whd-2,xp+whd,xp+whd,xp+whd-2,xp+2,xp,xp};
int[] y={yp+2,yp,yp,yp+2,yp+hgt-2,yp+hgt,yp+hgt,yp+hgt-2,yp+2};
//メッセージ表示部をネイビーで塗りつぶす
g.setColor(claNvi);
g.fillPolygon(x,y,9);
//メッセージ枠を表示
g.setColor(claSil);
g.drawPolyline(x,y,9);
}
このメソッドの引数のxp,ypは、座標が交わる始点です。処理的には、xp,ypの始点から始まる幅whd高さhgtの四角形から角を取り除いた図形を描画しているのです。

多角形を描画するfillPolygonメソッド・drawPolylineメソッドには、多角形を結ぶ頂点の配列を指定します。
構文
fillPolygon(x座標の配列,y座標の配列,頂点の数(配列の数));
drawPolyline(x座標の配列,y座標の配列,頂点の数(配列の数));
多角形を描画する頂点の配列は、ローカル変数x,yに格納しています。
int[] x={xp,xp+2,xp+whd-2,xp+whd,xp+whd,xp+whd-2,xp+2,xp,xp};
int[] y={yp+2,yp,yp,yp+2,yp+hgt-2,yp+hgt,yp+hgt,yp+hgt-2,yp+2};
配列の表す各頂点を図にすると以下のようになります。各要素がx,yと1組になっているのです。最後に、x[0]y[0]とx[8]y[8]の座標を同じにします。これで、各ラインを結ぶ事が出来ます。

頂点の計算式を説明します。描画する多角形は、四角形から角を2ピクセルづつとっています。最初の頂点(x[0]y[0])は、x座標に変わりはありません。y座標は、始点から2ピクセル下に描画しますからyp-2になります。次の頂点(x[0]y[0])では、x座標を右に2ピクセル移しますからxp+2になります。y座標はypにします。是の二つを結べば斜め上に線が引けます。

横に長い線を描きます。横の長さは、whdという引数でもらっています。最初の斜め線にx座標を2ピクセル利用しているので横棒の長さはwhdから2っを引く事になります。つまり、次の頂点((x[2]y[2]))は、x座標の始点からwhd-2がx座標(xp-whd-2)になります。y座標は、ypのままです。前回の(x[1]y[1])からこの点を結べば横の長い線が出来ます。

斜め下に延びる線を描きます。この頂点(x[3]y[3])は、x座標が描画エリアの長さwhdになります。y座標は、始点のypから-2します。前回のx[2]y[2]と結ぶ事によって斜め下に延びる線が描けます。

縦に長い線を描きます。この頂点(x[4]y[4])は、横に長い線を描画したのと同じ要領です。横が縦に変わっただけです。y座標は始点+高さ-角の大きさ、つまり、yp+hgt-2で求められます。x座標は、xp+whdのままです。これで縦に長い線が描けます。

右隅の斜め線を引きます。この頂点(x[5]y[5])のx座標は、x[2]y[2]の頂点と同じです。y座表は、表示するエリアの高さです。y座標の始点+高さですからyp+hgtとなります。x[4]y[4]の頂点と結ぶ事で斜め左に向かう線が引けます。

下部の横に長い線を引きます。この線の長さは、上部の線と同じです。但し、今回は描画する線が右から左へと描画します。つまり、x座標(xp-whd-2)からxp-2の座標まで描画します。この頂点(x[6]y[6])は、x座標がxp-2、y座標が前回の(x[5]y[5])と同じyp+hgtになります。

左下の斜め線を引きます。この頂点(x[7]y[7])の、x座標は始点(xp)です。高さがy座標の始点+高さ-2となりますからyp+hgt-2となります。これで、斜め下の線が引けます。

最後に左の縦線を引きます。頂点(x[7]y[7])と頂点(x[0]y[0])を結ぶだけです。つまり、この頂点(x[8]y[8])は頂点(x[0]y[0])と同じになります。これで枠が作成できました。

もし仕組みが分からなければfillPolygonメソッドのサンプルを独自に作成してテストしてみましょう。どの様な座標を指定するとどの様な図形が描画できるか試すのです。三角形や五角形を作ってみましょう。2〜3種類作ればfillPolygonメソッドの働きがわかるはずです。
2.3 戦闘画面の作成 各エリアの描画
基本的なウィンドウの描画は出来ました。次に、前回作成したdrawMsgメソッドを利用して各情報のウィンドウを作成します。BattleCanvasプロジェクトのソースファイルに機能を追加しました。MainCavクラスについては、テスト用ですので変更した内容が重要な訳では有りません。一番、重要なのは処理を行うCmnクラスの内容です。追加・修正した部分を太文字で強調してあります。
ソースファイル(App.java・MainCav.java・Cmn.java) BattleCanvas ver2
| App.java |
| import com.nttdocomo.ui.*; public class App extends IApplication { //アプリの開始 public void start(){ MainCav myObj=new MainCav(); Display.setCurrent(myObj); myObj.stApp(); } } |
| MainCav.java |
| import com.nttdocomo.ui.*; public class MainCav extends Canvas{ private Graphics g; //コンストラクタ public MainCav(){ //共通処理にデータを設定 Cmn.InitCmn(this); } //描画 public void paint(Graphics g) {} //プログラムの開始 public void stApp (){ int a=0; while(true){ switch(Cmn.gKey()){ case 1: Cmn.cleBf(); String[] cstr={"戦う","特技","道具","逃走"}; Cmn.drawChs(cstr); Cmn.drawcTriangle(24,63); continue; case 2: Cmn.cleBf(); Cmn.drawSel(); Cmn.drawcTriangle(23,32); continue; case 3: Cmn.cleBf(); Cmn.drawHpmp(100,90); continue; case 4: Cmn.drawDamagi(); continue; default: return; } } } } |
| Cmn.java |
| import com.nttdocomo.ui.*; public class Cmn{ //********************************************************************************** //* 画面表示関係 開始 * //********************************************************************************** //************************画面表示関係 フィールドデータ群 開始******************** public static Graphics g; public static Canvas c; public static final int claWht=16777215;//白の色データ public static final int claBlk=0;//黒の色データ public static final int claNvi=6172416;//ネイビーの色データ public static final int claSil=12632256;//シルバーの色データ //画面の描画座標 public static int cx=0; public static int cy=0; //戦闘画面の描画開始座標 public static int bcx=5; public static int bcy=5; //戦闘画面の行動選択ウィンドウの描画開始座標 public static int dcx; public static int dcy; //戦闘画面の選択ウィンドウの描画開始座標 public static int dsx; public static int dsy; //戦闘画面のメッセージウィンドウの描画開始座標 public static int dmx; public static int dmy; //戦闘画面のHP/MPウィンドウの描画開始座標 public static int hcx; public static int hcy; //利用するフォント public static Font font = Font.getFont(Font.FACE_MONOSPACE|Font.SIZE_MEDIUM|Font.TYPE_DEFAULT); //文字の高さ(上にスペースを空ける) public static int fnthgt = font.getHeight()+1; //選択肢の文字幅 public static int cWdh = font.stringWidth("選択")+12; //HPの文字幅 public static int hwgt = font.stringWidth("HP:000")+4; //************************画面表示関係 フィールドデータ群 終了******************** //****************************初期処理メソッド************************************** //初期処理 public static void InitCmn(Canvas maincvs){ c=maincvs; g=c.getGraphics(); g.setFont(font); //画面の描画座標の設定 int i; if((i=c.getWidth())>130){ cx=(i-130)/2; //戦闘画面の描画位置 bcx=cx+5; //bcx=(i-120)/2; } if((i=c.getHeight())>130){ cy=(i-130)/2; //戦闘画面の描画位置 bcy=cy+5; //bcy=(i-120)/2; } //戦闘画面の行動選択ウィンドウの描画位置 dcx=bcx+1; dcy=bcy+31; //戦闘画面の選択ウィンドウの描画位置 dsx=bcx; dsy=bcy; //戦闘画面のメッセージウィンドウの描画位置 dmx=bcx+1; dmy=bcy+89; //戦闘画面のHP/MPウィンドウの描画位置(上段への描画) hcx=bcx+1; hcy=bcy; //戦闘画面のHP/MPウィンドウの描画位置(下部への描画) /* hcx=bcx+1; hcy=bcy+99; */ } //********************************************************************************** //************************画面表示関係 メソッドデータ群 開始******************** //------------------------ 画面表示関係 開始 -------------------------- //画面の初期化 //引数 なし public static void cleFd(){ //画面を塗りつぶす g.setColor(claWht); g.fillRect(0,0,c.getWidth(),c.getHeight()); } //戦闘画面の初期化 //引数 なし public static void cleBf(){ //画面を塗りつぶす g.setColor(claBlk); g.fillRect(bcx,bcy,120,120); } //メッセージ表示エリアの描画 //引数 描画座標(z,y座標)、長さ、高さ public static void drawMsg(int xp,int yp,int whd,int hgt){ //メッセージ表示の枠部を描画 int[] x={xp,xp+2,xp+whd-2,xp+whd,xp+whd,xp+whd-2,xp+2,xp,xp}; int[] y={yp+2,yp,yp,yp+2,yp+hgt-2,yp+hgt,yp+hgt,yp+hgt-2,yp+2}; //メッセージ表示部をネイビーで塗りつぶす g.setColor(claNvi); g.fillPolygon(x,y,9); //メッセージ枠を表示 g.setColor(claSil); g.drawPolyline(x,y,9); } //行動選択ウィンドウの描画 //引数 デフォルトの選択項目、表示文字列 public static void drawChs(String[] sChois){ //エラー処理 場合によって利用 //if(sChois.length<1 || sChois.length>4) return; int myp=dcy+fnthgt; drawMsg(dcx,dcy,cWdh,(fnthgt*4)+3); for(int i=0;i<=3;i++) g.drawString(sChois[i],dcx+12,myp+(i*fnthgt)); } //行動選択カーソルの描画 //引数 描画座標(x,y座標) public static void drawcTriangle(int xp,int yp){ int[] x={xp,xp+6,xp+6,xp,xp}; int[] y={yp,yp+4,yp+5,yp+9,yp}; //メッセージ枠を表示 g.setColor(claWht); g.fillPolygon(x,y,5); } //HP/MPウィンドウの描画 //引数 HP MP public static void drawHpmp(int hp,int mp){ //区切りバーの描画座標 int yp=hcy; int xp=hcx; //描画エリアの初期化 g.setColor(claBlk); g.fillRect(xp,yp,hwgt+2,30); //表示文字列の加工用クラス StringBuffer shp=new StringBuffer("HP:"); StringBuffer smp=new StringBuffer("MP:"); char sp=(' '); //描画座標の変更 yp+=fnthgt; //区切りバーの描画データ int[] x={xp,xp+1,xp+hwgt-2,xp+hwgt,xp+hwgt,xp+hwgt-2,xp+1,xp,xp}; int[] y={yp+1,yp,yp,yp+1,yp+2,yp+3,yp+3,yp+2,yp+1}; //HPの文字を加工 if(hp<10) shp.append(sp).append(sp); else if(hp<100) shp.append(sp); shp.append(Integer.toString(hp)); //MPの文字を加工 if(mp<10) smp.append(sp).append(sp); else if(mp<100) smp.append(sp); smp.append(Integer.toString(mp)); //HP/MPの描画 xp+=3; yp-=2; g.setColor(claWht); g.drawString(shp.toString(),xp,yp); g.drawString(smp.toString(),xp,yp+17); //HPとMPの境目を描画(中) g.setColor(5549479); g.fillPolygon(x,y,9); //HPとMPの境目を描画(枠) g.setColor(claSil); g.drawPolyline(x,y,9); } //ダメージの描画 //引数 なし public static void drawDamagi(){ int yp=hcy-13; int xp=hcx; g.setColor(255); g.fillRect(xp,yp,hwgt+2,30); } //サブ選択ウィンドウの表示 //引数 グラフィッククラス public static void drawSel(){ //メッセージ表示部を描画 drawMsg(dsx,dsy,120,120); //アイテムリストと説明の区切り線の描画 g.setColor(claSil); g.drawLine(dsx,dsy+92,dsx+120,dsy+92); //テスト用の描画 次回は削除 for(int i=0;i<=6;i++) g.drawString("やめる",dsx+12,(dsy+12)+(i*13)); for(int i=0;i<=1;i++) g.drawString("やめる",dsx+1,(dsy+92+12)+(i*13)); } //************************画面表示関係 メソッドデータ群 終了******************** //********************************************************************************** //* 画面表示関係 終了 * //********************************************************************************** //********************************************************************************** //* 共通処理 開始 * //********************************************************************************** //キー状態の取得 開始-------------------------------------------------------------- public static int gKey(){ int a; while(true){ //スリープ 最初のスリープ処理ボタンを連続して押されたとき //メッセージがすぐ消えてしまう。 try { Thread.sleep(200); } catch (Exception e) {} if((a=c.getKeypadState())==0) continue; //キーが押されたら待機から抜ける for(int i=0;i<=22;i++){ if(( 1<<i & a)!=0){ return i; } } } } //キー状態の取得 終了-------------------------------------------------------------- //********************************************************************************** //* 共通処理 終了 * //********************************************************************************** } |
エミュレーターの1〜4のボタンを押してみましょう。行動選択・サブ選択・HP/MP・ダメージと各処理に利用する描画が行われます。まずは、Cmnクラスの追加フィールドと
InitCmnメソッドを確認しましょう。
フィールドには、座標を表すデータが追加されています。当マニュアルでは、画面サイズは130×130を想定しています。戦闘画面は、120×120のエリアに描画します。座標をフィールドで持つのは、機種によって画面サイズが異なる為です。画面サイズが130×130を超えた場合は、描画位置を修正しなければなりません。例えば、横幅が140の場合にx座標を0から描画しては左に詰まった感じで描画されてしまいます。140-130で余分なサイズが求まります。その値を2で除算する事でx座標の描画位置が求められます。描画座標は、部分的にCmnクラス外から参照されます。通常は、privateと使い分けるべきですが「なぜこの座標はprivateで、これはpublicなど?」と言った疑問が気にならないように全て公開して有ります。逆に、このマニュアルを読み進めていくうちに「このフィールドは。外部から参照されてないから隠蔽した方が良い」などと思われるでしょう。
//画面の描画座標
public static int cx=0;
public static int cy=0;
//戦闘画面の描画開始座標
public static int bcx=5;
public static int bcy=5;
//戦闘画面の行動選択ウィンドウの描画開始座標
public static int dcx;
public static int dcy;
//戦闘画面の選択ウィンドウの描画開始座標
public static int dsx;
public static int dsy;
//戦闘画面のメッセージウィンドウの描画開始座標
public static int dmx;
public static int dmy;
//戦闘画面のHP/MPウィンドウの描画開始座標
public static int hcx;
public static int hcy;
また、フィールドにはよく利用するデータとして行動選択エリアの幅とHP/MPの描画エリアの幅をフィールドとして定義してあります。
//選択肢の文字幅
public static int cWdh = font.stringWidth("選択")+12;
//HPの文字幅
public static int hwgt = font.stringWidth("HP:000")+4;
InitCmnメソッドでは、新たに追加された座標を表すフィールドにデータをセットしています。最初にアプリの描画座標(cx,cy)と戦闘画面座標(bcx,bcy)の描画座標を算出しています。これらの値は、cx,cyが0で初期化されています。そして、戦闘画面の座標は、キャンパスの座標から+5された値、つまり、5で初期化されています。
もし、キャンパスの画面サイズが130×130を超えていた場合、描画座標(cx,cy)と戦闘画面(bcx,bcy)座標を修正します。画面の大きさを取得するには、CanvasクラスのgetWidthメソッドとgetHeightメソッドを利用します。それぞれ、描画するキャンパスの幅と高さを取得します。x座標を求めるには、変数フィールドcxにキャンバスの大きさを代入します。130より大きければ、130を引いた値を2で除算します。そして、その値をcxに代入します。戦闘画面は、cxに5を加算しても構いませんし、「(i-120)/2」でも求める事が出来ます。これで、x座標が求まりました。y座標も同じ要領で処理しています。
int i;
if((i=c.getWidth())>130){
cx=(i-130)/2;
//戦闘画面の描画位置
bcx=cx+5;
//bcx=(i-120)/2;
}
if((i=c.getHeight())>130){
cy=(i-130)/2;
//戦闘画面の描画位置
bcy=cy+5;
//bcy=(i-120)/2;
}
戦闘画面の描画座標が求まれば、その値を元に各エリアの座標を設定します。ここは、特に問題ないでしょう。戦闘画面のメッセージウィンドウのdmx,dmyフィールドは、今回のサンプルでは利用しません。
//戦闘画面の行動選択ウィンドウの描画位置
dcx=bcx+1;
dcy=bcy+31;
//戦闘画面の選択ウィンドウの描画位置
dsx=bcx;
dsy=bcy;
//戦闘画面のメッセージウィンドウの描画位置
dmx=bcx+1;
dmy=bcy+89;
//戦闘画面のHP/MPウィンドウの描画位置(上段への描画)
hcx=bcx+1;
hcy=bcy;
最後のコメント化された座標は、HP/MPの表示エリアを左下に描画する為の座標です。もし、左下の描画を利用した場合はこの座標を利用してください。
/*
//戦闘画面のHP/MPウィンドウの描画位置(下部への描画)
hcx=bcx+1;
hcy=bcy+99;
*/
キャンバスのサイズは、機種によって異なります。その為、座標データをフィールドに持っています。また、エミュレーターでは正常に表示されますが実機では微妙にズレているといった事も起こります。できれば、実機にダウンロードして座標の微修正をすることをお勧めします。微修正は、このフィールド群の値を修正する事で調整する事が出来ます。また、実機へのダウンロードは次の項のサンプルプログラムで行うことをお勧めします。
では、各ウィンドウの描画処理について見ていきましょう。各ウィンドウの描画処理メソッドは、MainCavクラスの分岐処理を確認する事で使用しているメソッドが分かります。次からは、各ウィンドウの描画処理を確認します。
2.4 行動選択ウィンドウ
まずは、行動選択ウィンドウを見ていきましょう。行動選択ウィンドウはMainCavクラスの分岐のcase1で処理しています。命令は、以下の通りです。
case 1:
Cmn.cleBf();
String[] cstr={"戦う","特技","道具","逃走"};
Cmn.drawChs(0,cstr);
Cmn.drawcTriangle(24,63);
continue;
最初の「Cmn.cleBf();」は戦闘画面のエリアの初期化です。次に、選択肢をString配列に格納します。その配列をdrawChsメソッドに渡して行動選択ウィンドウを作成します。drawcTriangleメソッドは、選択肢を選ぶカーソルを表示するメソッドです。drawChsメソッドの内容を確認しましょう。drawChsメソッドは、Cmnクラスの静的メソッドです。
//行動選択部の描画
//引数 デフォルトの選択項目、表示文字列
public static void drawChs(String[] sChois){
//エラー処理 場合によって利用
//if(sChois.length<1 || sChois.length>4)
return;
int myp=dcy+fnthgt;
drawMsg(dcx,dcy,cWdh,(fnthgt*4)+3);
for(int i=0;i<=3;i++) g.drawString(sChois[i],dcx+12,myp+(i*fnthgt));
}
最初にコメント化された命令があります。行動選択は、4つの選択肢を表示します。選択肢が無かった場合や選択肢が4つを超えた場合のエラー処理です。エラー処理を記述した方が良いと思われますが、個人レベルで使用するプログラムですし、命令が多くなるとアプリのサイズが増えてしまいます。ここは、「自分自身で、気をつけて利用する」と覚悟してエラー処理を無くしてあります。次に変数の宣言と初期化を行っています。
int myp=dcy+fnthgt;
変数mypには、行動選択ウィンドウを表示する文字列のy座標を設定しています。キャンパスクラスの概要で説明したように、図形や左隅の座標を起点としているのに対して、文字は文字の底を座標にしています。その為、文字列の描画位置はウィンドウの描画位置(dcy)から文字の高さ(fnthgt)を加算して算出します。
drawMsg(dcx,dcy,cWdh,(fnthgt*4)+3);
drawMsgメソッドでウィンドウのエリアを描画します。このメソッドの機能は、前回のサンプルの通りです。描画するx,y座標には、それぞれdcx,dcyを指定します。dcx,dcyは、コンストラクタで算出した行動選択ウィンドウの描画開始座標です。描画するウィンドウの幅には、フィールドをして指定ます。cWdhは、フィールドとして以下の用に定義されています。全角2文字の文字幅に12を足した値です。
public static int cWdh = font.stringWidth("選択")+12;
+12した理由は、選択肢を指すカーソルの描画幅を考慮に入れたからです。ウィンドウの幅には、「(fnthgt*4)+3」としています。fnthgtフィールドには、「文字の高さ+1」の値が設定されています。そこに+3しています。この加算の理由は、ウィンドウの枠の部分に1ピクセルとられます。上下で、2ピクセル必要になります。そして、文字の高さは+1と文字のサイズに+1をしています。文字の頭に1ピクセルの隙間を作っているのです。その為、ウィンドウの底辺と描画した文字の間に1ピクセル加算しないと描画バランスが悪くなります。この値を「(fnthgt*4)+2」等に変えて試してみましょう。描画バランスの意味が分かるはずです。
図 下辺と文字の間にスペースが無い

最後の命令のfor文で文字列を描画しています。x座標は、カーソルの描画エリア分(12ピクセル)、横にずらして指定します。y座標は、mypを基準として文字毎に文字の高さづつy座標を加算して描画します。座標の移り変わりは、myp+0,myp+13,myp+26,myp+39となっていきます。
for(int i=0;i<=3;i++) g.drawString(sChois[i],dcx+12,myp+(i*fnthgt));
これで、選択ウィンドウの描画は出来ました。drawChsメソッドが終了します。MainCavクラスでは、次にdrawcTriangleメソッドでカーソルを描画します。引数の値は気にしないで下さい。画面描画の為に直接指定しただけです。
Cmn.drawcTriangle(24,63);
drawcTriangleメソッドは、GraphicsクラスのfillPolygonメソッドで多角形を塗りつぶしているだけです。中身を確認してみましょう。
//行動選択カーソルの描画
//引数 描画座標(x,y座標)
public static void drawcTriangle(int xp,int
yp){
int[] x={xp,xp+6,xp+6,xp,xp};
int[] y={yp,yp+4,yp+5,yp+9,yp};
//メッセージ枠を表示
g.setColor(claWht);
g.fillPolygon(x,y,5);
}
描画する座標、引数xp,ypで渡してもらいます。x座標の最大値はxp+6となっています。ということは、横幅が6と言う事になります。y座標の最大値は、yp+9ですから高さは9と言う事です。つまり、横幅が6ピクセル、縦が9ピクセルの三角形を描画します。このサイズは見た目ですので、気に入らなければ好きに変えて構いません。好みの問題です。以上が行動選択ウィンドウの描画です。
2.5 サブ選択ウィンドウ
サブ選択ウィンドウを見ていきましょう。サブ選択ウィンドウはMainCavクラスの分岐のcase2で処理しています。命令は、以下の通りです。
case 2:
Cmn.cleBf();
Cmn.drawSel();
Cmn.drawcTriangle(23,32);
continue;
最初の「Cmn.cleBf();」は戦闘画面のエリアの初期化です。次に、drawSelでサブ選択ウィンドウを作成します。drawSelメソッドの中身を確認しましょう。
/サブ選択ウィンドウの表示
//引数 グラフィッククラス
public static void drawSel(){
//メッセージ表示部を描画
drawMsg(dsx,dsy,120,120);
//アイテムリストと説明の区切り線の描画
g.setColor(claSil);
g.drawLine(dsx,dsy+92,dsx+120,dsy+92);
//テスト用の描画 次回は削除
for(int i=0;i<=6;i++) g.drawString("やめる",dsx+12,(dsy+12)+(i*13));
for(int i=0;i<=1;i++) g.drawString("やめる",dsx+1,(dsy+92+12)+(i*13));
}
基本的な処理は、行動選択ウィンドウと変わりません。サイズなどが変わっただけです。drawMsgメソッドでウィンドウを描画しています。
drawMsg(dsx,dsy,120,120);
サブ選択ウィンドウは、120×120のサイズです。選択画面と変わりません。描画座標のdsx,dsyも戦闘画面の描画座標(bcx,bcy)と同じです。当マニュアルを読んでいる皆さんがプログラムを修正してサブ選択画面のサイズを変えた時は描画座標のdsx,dsyも変更してください。
//アイテムリストと説明の区切り線の描画
g.setColor(claSil);
g.drawLine(dsx,dsy+92,dsx+120,dsy+92);
文字を描画する前に線を引いています。描画位置は、ウィンドウの開始座標から7文字目の位置です。これは、このラインより上部が特技やアイテムなど一覧を表示します。そして、このラインより下部に選択されている特技やアイテムの説明を描画することを想定しているからです。次は、テストの為の命令です。次のサンプルプログラムでは、削除します。
//テスト用の描画 次回は削除
for(int i=0;i<=6;i++) g.drawString("やめる",dsx+12,(dsy+12)+(i*13));
for(int i=0;i<=1;i++) g.drawString("やめる",dsx+1,(dsy+92+12)+(i*13));
テスト用ですが最初のfor文で、アイテム一覧を描画しているつもりです。x座標は、カーソルの描画を考慮して12ピクセル右にずらします。y座標の算出は、行動選択ウィンドウと同じです。次のfor文では、説明欄に文字を表示しています。カーソルを考慮に入れなくても良いのでx座標が変わっています。
少し補足をします。前回は、文字を描画するy座標を変数に格納しました。今回は、変数を利用していません。これは、drawSelメソッドでは文字列を描画しないからです。是には、理由があります。理由は、各ウィンドウの制御のサンプルプログラムまで待ってください。
これで、drawSelメソッドの処理を終了します。後の処理は、drawcTriangleメソッドですので説明は省かせてもらいます。
2.5 HP/MPウィンドウ
HP/MPウィンドウを見ていきましょう。HP/MPウィンドウウィンドウはMainCavクラスの分岐のcase3で処理しています。命令は、以下の通りです。
case 3:
Cmn.cleBf();
Cmn.drawHpmp(100,90);
continue;
最初の「Cmn.cleBf();」は戦闘画面のエリアの初期化です。次に、drawHpmpでサブ選択ウィンドウを作成します。drawSelメソッドの引数は、HPとMPです。サンプルでは、HP100、MP90を描画しています。drawHpmpメソッドの中身を確認しましょう。
//HP/MPウィンドウの描画
//引数 HP MP
public static void drawHpmp(int hp,int mp){
//区切りバーの描画座標
int yp=hcy;
int xp=hcx;
//描画エリアの初期化
g.setColor(claBlk);
g.fillRect(xp,yp,hwgt+2,30);
//表示文字列の加工用クラス
StringBuffer shp=new StringBuffer("HP:");
StringBuffer smp=new StringBuffer("MP:");
char sp=(' ');
//描画座標の変更
yp+=fnthgt;
//区切りバーの描画データ
int[] x={xp,xp+1,xp+hwgt-2,xp+hwgt,xp+hwgt,xp+hwgt-2,xp+1,xp,xp};
int[] y={yp+1,yp,yp,yp+1,yp+2,yp+3,yp+3,yp+2,yp+1};
//HPの文字を加工
if(hp<10) shp.append(sp).append(sp);
else if(hp<100) shp.append(sp);
shp.append(Integer.toString(hp));
//MPの文字を加工
if(mp<10) smp.append(sp).append(sp);
else if(mp<100) smp.append(sp);
smp.append(Integer.toString(mp));
//HP/MPの描画
xp+=3;
yp-=2;
g.setColor(claWht);
g.drawString(shp.toString(),xp,yp);
g.drawString(smp.toString(),xp,yp+17);
//HPとMPの区切りバーを描画(中)
g.setColor(5549479);
g.fillPolygon(x,y,9);
//HPとMPの区切りバーを描画(枠)
g.setColor(claSil);
g.drawPolyline(x,y,9);
}
一見、コードが多くて嫌になりそうですが処理的には難しくありません。大まかな処理の流れとしては、描画エリアの初期化、文字の加工、文字の描画、HPとMPの区切りバーの描画です。HPとMPの区切りバーの描画は、抜いて文字列の操作だけを考えましょう。文字の加工の必要な理由として、例えば、HPが100で描画すると「HP:100」と描画されます。HPが10の時、HPをそのまま文字列に変換して描画すると右詰めで「HP:10」と表示されてしまいます。以下の「加工無し」ようになるわけです。
図 文字が右詰め

不恰好ですね。そこで、HP/MPが一桁ならスペースを2ついれて、2桁なら1つスペースを入れます。HP/MPのヘッダー部は、StringBufferクラスに格納しています。このインスタンスにHP/MPの大きさによってスペースを追加しているに過ぎません。文字列に利用する変数は以下の部分です。変数spにスペースを格納しています。
//表示文字列の囲うようデータ
StringBuffer shp=new StringBuffer("HP:");
StringBuffer smp=new StringBuffer("MP:");
char sp=(' ');
文字列を加工する命令は、以下の部分です。appendを2つ記述するか1つ記述するかの違いです。この処理で文字列が同のように加工されるかは分かりますね。
//HPの文字を加工
if(hp<10) shp.append(sp).append(sp);
else if(hp<100) shp.append(sp);
shp.append(Integer.toString(hp));
//MPの文字を加工
if(mp<10) smp.append(sp).append(sp);
else if(mp<100) smp.append(sp);
smp.append(Integer.toString(mp));
但し、加工部のif文は、少しうまくありません。以下のようにする方が良いでしょう。
if(hp>0 & hp<10) shp.append(sp).append(sp);
else if(hp>=10 & hp<100) shp.append(sp);
サンプルは、コードを節約する為に論理演算子を利用していません。アプリのサイズを考えるとサンプルに従った方が良いでしょう。文字列の加工が終わると文字列を描画します。
//HP/MPの描画
xp+=3;
yp-=2;
g.setColor(claWht);
g.drawString(shp.toString(),xp,yp);
g.drawString(smp.toString(),xp,yp+17);
座標のxp,ypを加工しています。文字はバーの上と下に表示します。HPは、バーの上に表示しますからHPのy座標はバーの描画座標から少し上(y座標をマイナス)の座標に指定します(yp-=2)。文字の描画は、文字の底辺が座標でしたね。x座標は、バーより少し中に文字を描画する為の調整です。x座標の修正は好みによります。製作者には修正した方がかっこよく表示されるような気がするのです。ここは、判断がつきません。この命令をコメント化しても構いません。描画して、自分の好みに合わせてください。また、MPは、区切りバーのサイズも考慮してy座標を決めています。文字列の表示は以上です。次に、区切りバーの処理をまとめると以下のようになります。
//区切りバーの描画データ
int[] x={xp,xp+1,xp+hwgt-2,xp+hwgt,xp+hwgt,xp+hwgt-2,xp+1,xp,xp};
int[] y={yp+1,yp,yp,yp+1,yp+2,yp+3,yp+3,yp+2,yp+1};
//HPとMPの区切りバーを描画(中)
g.setColor(5549479);
g.fillPolygon(x,y,9);
//HPとMPの区切りバーを描画(枠)
g.setColor(claSil);
g.drawPolyline(x,y,9);
drawMsgメソッドの処理と同じです。塗りつぶす色とウィンドウのサイズが変わっただけです。ここで、お詫びがあります。HP/MPの描画については、ある程度、描画位置を計算したら微修正を行ってください。「区切りバーとHPはどれくらい離すと見た目が良い」等といった公式が浮かばないのです。これは、見た目の好みの問題もあるでしょう。サンプルが気にいた無い場合は、バーの描画座標をずらしたりしてベストの位置を考えてください。
2.6 その他のメソッド ダメージウィンドウ・キーイベントの取得
ダメージウィンドウを見ていきましょう。HP/MPウィンドウウィンドウはMainCavクラスの分岐のcase4で処理しています。命令は、以下の通りです。
case 4:
Cmn.drawDamagi();
continue;
ダメージウィンドウは、モンスターからの攻撃を受けるとHP/MPの表示エリアと赤で塗りつぶすだけです。これこそ、好みが大きく左右します。画面全体にモンスターの攻撃を表す描画オ行っても構いませんし、イメージを描画しても良いでしょう。取りあえず、何も無いと寂しいので付けただけです。drawDamagiメソッドの内容は、四角形を塗りつぶすfillRectメソッドを赤い色を指定して実行しているだけです。
//ダメージの描画
//引数 なし
public static void drawDamagi(){
int yp=hcy-13;
int xp=hcx;
g.setColor(255);
g.fillRect(xp,yp,hwgt+2,30);
}
ダメージの描画は、好きに変えてください。次に、キーイベントの取得メソッドを見ていきましょう。このメソッドは、前章のキャンバスクラスの利用で説明したgKeyメソッドと同じです。まったく同様です。特に説明も要らないでしょう。キーイベントの取得には、このメソッドを利用していきます。
public static int gKey(){
int a;
while(true){
//スリープ 最初のスリープ処理ボタンを連続して押されたとき
//メッセージがすぐ消えてしまう。
try {
Thread.sleep(200);
} catch (Exception e) {}
if((a=c.getKeypadState())==0)
continue;
//キーが押されたら待機から抜ける
for(int i=0;i<=22;i++){
if(( 1<<i & a)!=0){
return i;
}
}
}
}
2.7 ウインドウのコントロール
前回までのサンプルプログラムで各ウィンドウの描画は出来ました。面倒なのは「描画する位置やサイズをどうするか」と言う事だけです。利用しているクラスやメソッドも基本的なものばかりです。ここでは、行動選択ウィンドウ、サブ選択ウィンドウ、メッセージの描画制御を行います。
BattleCanvasプロジェクトのソースファイルに機能を追加します。MainCavクラスの内容も変更しますが、テスト用ですので変更した内容が重要な訳では有りません。追加・修正した部分を太文字で強調してあります。また、ビルドする前に、drawSelメソッドを確認してください。以下のように、前回、使用した「テスト用の表示」の命令部分をコメント化するのを忘れないで下さい。
//テスト用の描画 次回は削除
//for(int i=0;i<=6;i++) g.drawString("やめる",dsx+12,(dsy+12)+(i*13));
//for(int i=0;i<=1;i++) g.drawString("やめる",dsx+1,(dsy+92+12)+(i*13));
ソースファイル(App.java・MainCav.java・Cmn.java) BattleCanvas ver3
| App.java |
| import com.nttdocomo.ui.*; public class App extends IApplication { //アプリの開始 public void start(){ MainCav myObj=new MainCav(); Display.setCurrent(myObj); myObj.stApp(); } } |
| MainCav.java |
| import com.nttdocomo.ui.*; public class MainCav extends Canvas{ private Graphics g; //コンストラクタ //コンストラクタ public MainCav(){ //共通処理にデータを設定 Cmn.InitCmn(this); } //描画 public void paint(Graphics g) {} //プログラムの開始 public void stApp (){ int i=0; Cmn.cleBf(); while(true){ switch(Cmn.gKey()){ case 1: String[] cstr={"戦う","特技","道具","逃走"}; Cmn.battleMsg("どうする?"); //選択ウィンドウの描画 Cmn.drawChs(cstr); //行動選択ウィンドウのカーソル座標 int cxp=Cmn.dcx+3; int cyp=Cmn.dcy+13; //選択肢の制御 i=Cmn.chsCnt(cxp,cyp,mc,4); //選択肢のインデックスを出力 System.out.println(cstr[i]); Cmn.cleBf(); continue; case 2: String[] subNam1={"回復","火魔法","水魔法","稲妻魔法","氷魔法","究極魔法"}; //サブ選択肢一覧の描画と制御 i=Cmn.subCnt(subNam1,6); Cmn.cleBf(); if(i==6){ System.out.println("やめるを選択"); continue; } System.out.println(subNam1[i]); continue; case 3: String[] subNam2={"回復","火魔法","水魔法","稲妻魔法","氷魔法","究極魔法"}; //サブ選択肢一覧の描画と制御 i=Cmn.subCnt(subNam2,4); Cmn.cleBf(); if(i==6){ System.out.println("やめるを選択"); continue; } System.out.println(subNam2[i]); continue; case 4: String[] subNam3={"薬草","丸薬","","花火","火薬",""}; //サブ選択肢一覧の描画と制御 i=Cmn.subCnt(subNam3,6); Cmn.cleBf(); if(i==6){ System.out.println("やめるを選択"); continue; } System.out.println(subNam3[i]); continue; case 5: Cmn.cleFd(); String strTest="みなさん、こんにちわ。|ちゃんと表示されていますか?。"; Cmn.msgWrite(strTest,0,-2,118,9,0,0); Cmn.tSlp(1000); Cmn.cleFd(); Cmn.msgWrite(strTest,10,20,90,3,0,0); Cmn.tSlp(1000); Cmn.cleFd(); Cmn.msgWrite(strTest,20,10,80,2,0,0); Cmn.tSlp(1000); Cmn.cleFd(); Cmn.msgWrite(strTest,10,20,70,1,0,0); Cmn.tSlp(1000); Cmn.cleFd(); Cmn.msgWrite("左寄せ|出来てる?",0,0,118,2,0,0); Cmn.tSlp(1000); Cmn.cleFd(); Cmn.msgWrite("第一章|冒険の始まり",-1,-1,118,2,1,0); Cmn.tSlp(1000); Cmn.cleFd(); Cmn.msgWrite("右寄せ|出来てる?",0,-2,118,2,2,0); Cmn.tSlp(1000); Cmn.cleFd(); Cmn.msgWrite(strTest,0,-2,118,9,0,0); Cmn.tSlp(1000); Cmn.cleFd(); strTest="みなさん、こんにちわ。|ちゃんと表示されていますか?。|みなさん、こんにちわ。|ちゃんと表示されていますか?。表示されていなかったらエラーです。|作り直しましょう|どうよ|うまくいかない?|おねがい|うまくいって|う〜ん|わからん"; Cmn.msgWrite(strTest,0,-2,118,9,0,1); Cmn.tSlp(1000); Cmn.cleFd(); continue; default: return; } } } } |
| Cmn.java |
| import com.nttdocomo.ui.*; public class Cmn{ //********************************************************************************** //* 画面表示関係 開始 * //********************************************************************************** //************************画面表示関係 フィールドデータ群 開始******************** public static Graphics g; public static Canvas c; public static final int claWht=16777215;//白の色データ public static final int claBlk=0;//黒の色データ public static final int claNvi=6172416;//ネイビーの色データ public static final int claSil=12632256;//シルバーの色データ //画面の描画座標 private static int cx=0; private static int cy=0; //戦闘画面の描画開始座標 private static int bcx=5; private static int bcy=5; //戦闘画面の行動選択ウィンドウの描画開始座標 public static int dcx; public static int dcy; //戦闘画面の選択ウィンドウの描画開始座標 public static int dsx; public static int dsy; //戦闘画面のメッセージウィンドウの描画開始座標 public static int dmx; public static int dmy; //戦闘画面のHP/MPウィンドウの描画開始座標 public static int hcx; public static int hcy; //利用するフォント public static Font font = Font.getFont(Font.FACE_MONOSPACE|Font.SIZE_MEDIUM|Font.TYPE_DEFAULT); //文字の高さ(上にスペースを空ける) public static int fnthgt = font.getHeight()+1; public static int cWdh = font.stringWidth("選択")+12;//選択肢の文字幅 public static int hwgt = font.stringWidth("HP:000")+4;//HPの文字幅 //************************画面表示関係 フィールドデータ群 終了******************** //****************************初期処理メソッド************************************** //初期処理 public static void InitCmn(Canvas maincvs){ c=maincvs; g=c.getGraphics(); g.setFont(font); //画面の描画座標の設定 int i; if((i=c.getWidth())>130){ cx=(i-130)/2; //戦闘画面の描画位置 bcx=cx+5; //bcx=(i-120)/2; } if((i=c.getHeight())>130){ cy=(i-130)/2; //戦闘画面の描画位置 bcy=cy+5; //bcy=(i-120)/2; } //戦闘画面の行動選択ウィンドウの描画位置 dcx=bcx+1; dcy=bcy+31; //戦闘画面の選択ウィンドウの描画位置 dsx=bcx; dsy=bcy; //戦闘画面のメッセージウィンドウの描画位置 dmx=bcx-cx+1; dmy=bcy-cy+89; //戦闘画面のHP/MPウィンドウの描画位置(上段への描画) hcx=bcx+1; hcy=bcy; /* //戦闘画面のHP/MPウィンドウの描画位置(下部への描画) hcx=bcx+1; hcy=bcy+99; */ } //********************************************************************************** //************************画面表示関係 メソッドデータ群 開始******************** //------------------------ 画面表示関係 開始 -------------------------- //画面の初期化 //引数 なし public static void cleFd(){ //画面を指定された色で塗りつぶす //g.setColor(g.getColorOfName(a)); g.setColor(claWht); g.fillRect(0,0,c.getWidth(),c.getHeight()); } //戦闘画面の初期化 //引数 なし public static void cleBf(){ //画面を指定された色で塗りつぶす //g.setColor(g.getColorOfName(a)); g.setColor(claBlk); g.fillRect(bcx,bcy,120,120); } //メッセージ表示エリアの描画 //引数 描画座標(z,y座標)、長さ、高さ public static void drawMsg(int xp,int yp,int whd,int hgt){ //メッセージ表示の枠部を描画 //サイズデフォルトは118 int[] x={xp,xp+2,xp+whd-2,xp+whd,xp+whd,xp+whd-2,xp+2,xp,xp}; int[] y={yp+2,yp,yp,yp+2,yp+hgt-2,yp+hgt,yp+hgt,yp+hgt-2,yp+2}; //メッセージ表示部をネイビーで塗りつぶす g.setColor(claNvi); g.fillPolygon(x,y,9); //メッセージ枠を表示 g.setColor(claSil); g.drawPolyline(x,y,9); } //行動選択ウインドウの描画 //引数 表示文字列 public static void drawChs(String[] sChois){ //エラー処理 場合によって利用 //if(sChois.length<1 || sChois.length>4) return; int myp=dcy+fnthgt; drawMsg(dcx,dcy,cWdh,(4*fnthgt)+3); for(int i=0;i<=3;i++) g.drawString(sChois[i],dcx+12,myp+(i*fnthgt)); } //行動選択カーソルの描画 //引数 描画座標(x,y座標) public static void drawcTriangle(int xp,int yp){ int[] x={xp,xp+6,xp+6,xp,xp}; int[] y={yp,yp+4,yp+5,yp+9,yp}; //メッセージ枠を表示 g.setColor(claWht); g.fillPolygon(x,y,5); } //HP/MPウインドウの描画 //引数 HP,MP public static void drawHpmp(int hp,int mp){ //区切りバーの描画座標 int yp=hcy; int xp=hcx; //描画エリアの初期化 g.setColor(claBlk); g.fillRect(xp,yp,hwgt+2,30); //表示文字列の加工用クラス StringBuffer shp=new StringBuffer("HP:"); StringBuffer smp=new StringBuffer("MP:"); char sp=(' '); //描画座標の変更 yp+=fnthgt; //区切りバーの描画データ int[] x={xp,xp+1,xp+hwgt-2,xp+hwgt,xp+hwgt,xp+hwgt-2,xp+1,xp,xp}; int[] y={yp+1,yp,yp,yp+1,yp+2,yp+3,yp+3,yp+2,yp+1}; //HPの文字を加工 if(hp<10) shp.append(sp).append(sp); else if(hp<100) shp.append(sp); shp.append(Integer.toString(hp)); //MPの文字を加工 if(mp<10) smp.append(sp).append(sp); else if(mp<100) smp.append(sp); smp.append(Integer.toString(mp)); //HP/MPの描画 xp+=3; yp-=2; g.setColor(claWht); g.drawString(shp.toString(),xp,yp); g.drawString(smp.toString(),xp,yp+17); //HPとMPの区切りバーを描画(中) g.setColor(5549479); g.fillPolygon(x,y,9); //HPとMPの区切りバーを描画(枠) g.setColor(claSil); g.drawPolyline(x,y,9); } //ダメージの描画 //引数 なし public static void drawDamagi(){ //int yp=dmy+40; int yp=hcy-13; int xp=hcx; g.setColor(255); g.fillRect(xp,yp,hwgt+2,30); /* tSlp(500); g.setColor(claBlk); g.fillRect(xp,yp,hwgt+2,30); */ } //サブ選択ウインドウの表示 //引数 グラフィッククラス public static void drawSel(){ //メッセージ表示部を描画 drawMsg(dsx,dsy,120,120); //アイテムリストと説明の区切り線の描画 g.setColor(claSil); g.drawLine(dsx,dsy+92,dsx+120,dsy+92); //テスト用の描画 次回は削除 //for(int i=0;i<=6;i++) g.drawString("やめる",dsx+12,(dsy+12)+(i*13)); //for(int i=0;i<=1;i++) g.drawString("やめる",dsx+1,(dsy+92+12)+(i*13)); } //選択カーソル(cursor)の描画管理 //引数 描画座標(x,y座標),選択位置(0起算),選択数 public static void csrCnt(int xp,int yp,int sel,int cnt){ //カーソルのy座標を修正 yp-=10; int typ=yp+(sel*fnthgt); //カーソル移動エリアを塗りつぶす g.setColor(claNvi); g.fillRect(xp,yp,6,(cnt*fnthgt)-4); //カーソルの描画 drawcTriangle(xp,typ); } //説明部の文字列表示 //引数 文字列 public static void drawSub(String tit,String cntnt){ int xp=dsx+1; int yp=dsy+93; //説明部の塗りつぶし g.setColor(claNvi); g.fillRect(xp,yp,120,(fnthgt*2)+1); //文字座標の修正 yp+=fnthgt; //色の設定 g.setColor(claWht); //題目の描画 g.drawString(tit,xp,yp); //内容の描画 g.drawString(cntnt,xp,yp+fnthgt); } //行動選択の制御 //引数 x,y座標、デフォルトの選択肢、選択肢の数 public static int chsCnt(int xp,int yp,int dChois,int ichoices){ //選択位置を設定 int ch=dChois; //選択肢の最大インデックスを格納 int maxchs=ichoices-1; //選択カーソルの描画 csrCnt(xp,yp,dChois,ichoices); //イベント処理のループ while(true){ switch(gKey()){ case Display.KEY_UP: ch--; if(ch<0) ch=maxchs; break; case Display.KEY_DOWN: ch++; if(ch>maxchs) ch=0; break; case Display.KEY_SELECT: return ch; default: continue; } //選択カーソルの描画 csrCnt(xp,yp,ch,4); } } //サブ選択の制御 //引数 文字列(配列) 表示する項目のインデックス public static int subCnt(String[] sub,int ce){ if(sub.length<6 | ce>5) return -1; int ch=0; int xp=dsx; int yp=dsy+font.getHeight(); //道具で使用 インデックスを加算しながら次の使える道具のインデックスを探す //最初の要素も空白なら「やめる」にインデックスを移動する if(sub[ch].trim().equals("")){ while(sub[ch].trim().equals("")){ ch++; if(ch==6) break; } } //表示する選択肢の数を0起算に変換 ce--; //グラフィックのロック解除 g.lock(); //サブ選択ウィンドウの描画 drawSel(); //選択肢の描画 g.setColor(claWht); for(int i=0;i<=ce;i++) g.drawString(sub[i],xp+12,yp+(i*fnthgt)); g.drawString("やめる",xp+12,yp+(6*fnthgt)); //テスト用の描画 drawSub("題名を表示","内容を表示"); //選択カーソルの描画位置に更新 xp+=3; //選択カーソルの描画 csrCnt(xp,yp,ch,7); //グラフィックのロック解除 g.unlock(true); while(true){ switch(gKey()){ case Display.KEY_UP: ch--; if(ch<0){ ch=6; break; } else if(ch>ce){ ch=ce; break; } //道具で使用 インデックスを減算しながら次の使える道具のインデックスを探す //最初の要素も空白なら「やめる」に移動して処理を終える if(sub[ch].trim().equals("")){ while(sub[ch].trim().equals("")){ ch--; if(ch<0){ ch=6; break; } } } break; case Display.KEY_DOWN: ch++; if(ch==6) break; if(ch>6){ ch=0; } else if(ch>ce){ ch=6; break; } //道具で使用 インデックスを加算しながら次の使える道具のインデックスを探す。 //「やめる」インデックスになったら加算処理を止める if(sub[ch].trim().equals("")){ while(sub[ch].trim().equals("")){ ch++; if(ch==6) break; } } break; case Display.KEY_SELECT: return ch; default: continue; } //グラフィックのロック g.lock(); //選択カーソルの描画 csrCnt(xp,yp,ch,7); //テスト用の描画 drawSub("題名を表示","内容を表示"); //グラフィックのロック解除 g.unlock(true); } } //戦闘画面でのメッセージ //引数 文字列(配列) public static void battleMsg(String smg){ //メッセージの描画 Cmn.msgWrite(smg,dmx,dmy,118,2,0,0); } //メッセージの表示 //引数 表示文字列・表示する行・描画部の表示位置(x・y座標)・表示の幅・文字の表示位置・処理判別フラグ public static void msgWrite(String smg,int xp,int yp,int len,int row,int alg,int bp){ //文字列が存在しない場合は終了 if(smg.length()==0) return; int i=0,j=0,k,l=0,phgt=0; String wmsg;//分割した表示文字を格納 int slp=1000;//スリープ処理の時間 //不正な値の場合は0で初期化 if(bp!=1) bp=0; //y座標にマイナスが指定されている場合は、自動調整 if(yp==-1) yp=(130-(5+(fnthgt*row)))/2;//中段表示 else if(yp==-2) yp=(130-(5+(fnthgt*row)));//下段表示 //y座標にマイナスが指定されている場合は、自動調整 if(xp==-1) xp=(130-(len))/2;///中心表示 else if(xp==-2) xp=130-len;//右表示 //画面サイズ130を超えた部分の座標修正 xp+=cx; yp+=cy; //高さを設定 phgt=(row*fnthgt)+3; drawMsg(xp,yp,len,phgt); //表示文字がなくなるまで続ける while(i<smg.length()){ //メッセージウインドウを描画 drawMsg(xp,yp,len,phgt); //メッセージフィールドの上段と下段の二つを描画処理する for(k=1;k<=row;k++){ j=i; i=font.getLineBreak(smg,i,smg.length()-i,len); wmsg=smg.substring(j,i); //'|'があった場合は、改行する if(wmsg.indexOf('|')>=0){ i=i-(wmsg.length()-wmsg.indexOf('|')); wmsg=smg.substring(j,i); i+=1; } //文字列の描画 g.setColor(claSil); if(alg==0) g.drawString(wmsg,xp+4,yp+(k*fnthgt)); else if(alg==1) g.drawString(wmsg,xp+2+(len-4-font.stringWidth(wmsg))/2,yp+(k*fnthgt)); else if(alg==2) g.drawString(wmsg,xp+2+(len-4-font.stringWidth(wmsg)),yp+(k*fnthgt)); //文字列が無くなればループを抜ける if(i>=smg.length()) break; } if(bp==1){//手動で次の文字列の描画へ進ませる場合 if(i<smg.length()){ g.setColor(g.getColorOfName(g.AQUA)); g.fillRect(xp+len-4,yp+phgt-4,4,4); g.setColor(g.getColorOfName(g.SILVER)); g.drawRect(xp+len-4,yp+phgt-4,4,4); } while(true){ if(gKey()==Display.KEY_SELECT) break; } } else{//自動的に次の文字列の描画に進む場合 //スリープ処理 try { Thread.sleep(slp); } catch (Exception e) {} } } } //************************画面表示関係 メソッドデータ群 終了******************** //********************************************************************************** //* 画面表示関係 終了 * //********************************************************************************** //********************************************************************************** //* 共通処理 開始 * //********************************************************************************** //キー状態の取得 開始-------------------------------------------------------------- public static int gKey(){ int a; while(true){ //スリープ 最初のスリープ処理ボタンを連続して押されたとき //メッセージがすぐ消えてしまう。 try { Thread.sleep(200); } catch (Exception e) {} if((a=c.getKeypadState())==0) continue; //キーが押されたら待機から抜ける for(int i=0;i<=22;i++){ if(( 1<<i & a)!=0){ return i; } } } } //キー状態の取得 終了-------------------------------------------------------------- //スリープ処理 開始-------------------------------------------------------------- public static void tSlp(int slpt){ try { Thread.sleep(slpt); } catch (Exception e) {} } //スリープ処理 終了-------------------------------------------------------------- //********************************************************************************** //* 共通処理 終了 * //********************************************************************************** } |
エミュレーターの1〜5のボタンを押してみましょう。1が行動選択ウィンドウの制御、2・3・4がサブ選択ウィンドウの制御、5がメッセージウィンドウの制御です。今回、追加されたメソッドはchsCnt・csrCnt・subCnt・drawSub・battleMsg・msgWriteの5つです。各メソッドの働きを利用するウィンドウ別に振り分けると以下のようになります。
行動選択ウィンドウの処理
chsCnt:行動選択ウィンドウの制御
カーソルの描画と制御の処理
csrCnt:カーソルの描画と制御
サブ選択ウィンドウの処理
subCnt:サブ選択ウィンドウの制御
drawSub:サブ選択ウィンドウの説明欄に文字列を描画
メッセージウィンドウの処理
battleMsg:戦闘画面用のメッセージウィンドウを描画
msgWrite:メッセージウィンドウを描画
エミュレーターで一通り動作を確認したら以降で処理毎の説明を行います。
2.8 行動選択ウィンドウの制御
行動選択ウィンドウの制御はchsCntメソッドが行います。MainCavクラスの分岐では、case1の処理がこの制御に当たります。
case 1:
String[] cstr={"戦う","特技","道具","逃走"};
//選択ウィンドウの描画
Cmn.drawChs(cstr);
//選択ウィンドウの描画
Cmn.drawChs(cstr);
//行動選択ウィンドウのカーソル座標
int cxp=Cmn.dcx+3;
int cyp=Cmn.dcy+Cmn.fnthgt;
//選択肢の制御
i=Cmn.chsCnt(cxp,cyp,mc,4);
//選択肢のインデックスを出力
System.out.println(cstr[i]);
Cmn.cleBf();
continue;
「Cmn.battleMsg」の命令は、メッセージウィンドウを描画している処理です。見た目をそろえるために描画しています。battleMsgは、「メッセージウィンドウの制御」で説明します。
次に、drawChsメソッドで選択肢の描画を行っています。これは、前回のサンプルのdrawChsメソッドと同じです。drawChsメソッドで選択ウィンドウの描画が出来たら選択肢に当てるカーソルの座標を算出します。「どうしてこの値になるのか」は最後の補足で説明します。
次にchsCntメソッドを呼び出します。chsCntメソッドは、行動選択の選択肢を選んでセレクトボタンを押すとその選択肢に対応するインデックスが戻るようになっています。つまり、戻り値は選択された選択肢のインデックスです。インデックスは、0起算になります。例えば、サンプルでは「"戦う","特技","道具","逃走"」の表示を行います。選択肢から「特技」にカーソルを合わせて、セレクトボタンを押すと「1」が返ります。
引数は、intデータが4つです。最初の二つは、x,y座標です。次の引き数は、デフォルトの選択肢のインデックスを指定します。サンプルでは、1を指定した場合、「特技」にカーソルが当たった状態で描画されます。最後の引き数には、選択肢の数を指定します。サンプルのように、選択肢が4つある場合は「4」を指定します。デフォルトのインデックスと選択肢の数では、起算する値が0と1で違いますので注意して下さい。なぜ、選択肢のインデックスを0起算にしているかというとそのまま配列の要素に指定できるように考慮した為です。
chsCntメソッドの内容を確認しましょう。処理は、以下のようになっています。
//行動選択の制御
//引数 x,y座標、デフォルトの選択肢、選択肢の数
public static int chsCnt(int xp,int yp,int
dChois,int ichoices){
//選択位置を設定
int ch=dChois;
//選択肢の最大インデックスを格納
int maxchs=ichoices-1;
//選択カーソルの描画
csrCnt(xp,yp,dChois,ichoices);
//イベント処理のループ
while(true){
switch(gKey()){
case Display.KEY_UP:
ch--;
if(ch<0) ch=maxchs;
break;
case Display.KEY_DOWN:
ch++;
if(ch>maxchs) ch=0;
break;
case Display.KEY_SELECT:
return ch;
default:
continue;
}
//選択カーソルの描画
csrCnt(xp,yp,ch,4);
}
}
最初にカーソルを当てる選択肢のインデックスを変数に格納します。サンプルでは、最初の選択肢のインデックスには0が格納されています。
//選択位置を設定
int ch=dChois;
//選択肢の最大インデックスを格納
int maxchs=ichoices-1;
次に、選択肢の最大インデックスを求めます。インデックスは、0起算です。選択肢(ichoices)を1減算すれば求められます。サンプルでは、4が設定されています。0起算にすると「0,1,2,3」になります。算出方法に問題は無いですね。
//選択カーソルの描画
csrCnt(xp,yp,dChois,maxchs);
カーソルの描画は、csrCntメソッドを利用します。x,y座標と選択肢のインデックス・選択肢の数を引き数に渡すとカーソルを描画してくれます。サンプルの行動選択画面では、最初の選択位置はdChois(0)で選択肢はmaxchs(4)です。カーソルは「戦う」に当たった状態になります。csrCntメソッドは、後述するので呼び出し方と機能を知っておいてください。
カールの描画が済んだら行動選択の制御をしているループ処理を確認します。セレクトボタンが押されるまでループを抜ける事はありません。
switch(gKey()){
case Display.KEY_UP:
ch--;
if(ch<0) ch=maxchs;
break;
case Display.KEY_DOWN:
ch++;
if(ch>maxchs) ch=0;
break;
case Display.KEY_SELECT:
return ch;
default:
continue;
}
switch文の値に、gkeyメソッドを指定しています。押されたキーがあると制御を行います。上ボタンが押されると「ch--」でchの値を-1します。下ボタンが押されると「ch++」でchの値を+1します。chは、選択肢のインデックスを表します。この様に処理することで選択肢の選択を上下に移動させます。
図 選択肢の移動

もし、上ボタンを押した時、chが0を下回った場合はchにmaxchsの値(3)を代入します。サンプルでは、「"戦う","特技","道具","逃走"」が描画されています。それぞれのインデックスは、「0,1,2,3」となります。chは、このインデックスに当たります。chが0で、「戦う」が選択されています。一番上の選択肢です。ここで、上を押すと「ch--」でchは-1になり選択肢がありません。この場合は、インデックスをmaxchsの値(3)にして選択肢の最後の「逃走」が選択されるようにします。
図 最上部の場合に「上」を押すと最下部に移動

選択肢が最下部の選択肢「逃走」を指している時も同じ様な処理です。この時のインデックスはmaxchsの値(3)です。ここで、下を押すとch++でchには4が代入されます。インデックス4の選択はありませんからchに0を代入してインデックスが先頭の選択肢になるようにします。
図 最下部の場合に「下」を押すと最上部に移動

セレクトボタンを押した場合は、returnでchを返しています。つまり、選択のインデックスを返します。上・下・セレクト以外だとcontinueでループの最初に戻ります。結果的には、switch文でgkeyメソッドの戻り値を待つ事になります。
上下ボタンで選択肢を移動させると、選択肢のカーソルを変えなければなりません。「戦う」で下を押した場合は、インデックスが加算されて「1」になりますから「特技」が選択されたことになります。カーソルの描画位置の更新は、switchブロックを抜けた以下の命令で行っています。ループの前の処理と同じです。
//選択カーソルの描画
csrCnt(xp,yp,ch,4);
カーソルを制御する csrCntメソッドを確認してみましょう。引き数は、x座標・y座標・選択されている選択肢・選択肢の数です。
public static void csrCnt(int xp,int yp,int
sel,int cnt){
//カーソルのy座標を修正
yp-=10;
int typ=yp+(sel*fnthgt);
//カーソル移動エリアを塗りつぶす
g.setColor(claNvi);
g.fillRect(xp,yp,6,(cnt*fnthgt)-4);
//カーソルの描画
drawcTriangle(xp,typ);
}
最初に、引き数ypを10減算します。これは、z座標の引数には文字の座標が渡される事を想定しています。カーソルは、選択肢とパックで利用しますから文字と一緒に利用されると思います。その為、描画位置の座標を文字に合わせて修正しているのです。文字の高さは12です。当マニュアルでは、更に文字に隙間を与えますから+1しますから13となります。文字の描画位置(底辺)から-10で文字の上程から少し下の位置にカーソルが描画できます。
次に、typ変数を「sel*fnthgt」で初期化します。typ変数がカーソルを描画する座標です。選択肢のインデックスを文字の高さで乗算します(sel*fnthgt)。この値と描画開始位置(yp)の値を加算する事でカーソルの移動エリアが求まります。そして、カーソルの移動エリアを塗りつぶしてカーソルを描画します。カーソルの描画は、枠の無いウィンドウの様に考えて良いと思います。つまり、選択肢の横にカーソルの移動エリアのサイズを持ったウィンドウを描画するのです。そして、引き数に合わせてカーソルを描画します。
図 塗りつぶしエリア

ここで、csrCntメソッドを作成した理由を説明します。選択肢を選ぶウィンドウで、選択される選択肢が変わるとカーソルの再描画を行います。しかし、カーソル以外に選択肢の文字列も再描画していては処理が無駄になります。カーソルの移動するエリアをネイビーで塗りつぶしてカーソルだけ再描画すれば良いのです。塗りつぶしには、fillRectメソッドを利用します。引数のxp,ypにはカーソルの描画開始座標が入っています。塗りつぶす幅は、カーソルの幅である6を指定します。塗りつぶす高さは、「上部の文字とカーソルの隙間(3ピクセル)」と「下部の文字とカーソルの隙間(1ピクセル)」でエリアの違い(4ピクセル)を求めて、「文字の高さ×選択肢の数」から減算します。
以上が、行動選択ウィンドウの制御です。このメソッドの戻り値を調べる事で、戦闘画面でユーザーが選択した行動が分かります。
補足
chsCntメソッドを呼び出す時の座標の算出命令を覚えていますか?。カーソルと移動エリアを描画するcsrCntメソッドの説明を読んだ今なら分かるのではないでしょうか?。
//矢印の座標の設定
int xp=dcx+3;
int yp=dcy+13;
選択肢を描画する行動選択ウィンドウのx座標と文字列の描画位置は12ピクセル空いています。カーソルの横幅は、6ピクセルです。枠の描画に使う1ピクセルを考えても5ピクセルのゆとりがあります。行動選択ウィンドウの描画開始x座標dcxに+3をして行動選択ウィンドウの枠と文字列の真ん中あたりにカーソルが描画されるようにします。枠よりにカーソルを描画したければ加算する値を少なくしてください。次に、y座標です。文字は、底辺が描画座標になります。選択肢の文字を描画するには、行動選択ウィンドウの描画開始y座標dcyに+13した座標を描画座標に設定します。なぜならカーソルと移動エリアの描画を行うcsrCntメソッドは文字の描画座標を指定すればメソッドの中で座標を調整してくれます。
2.9 サブ選択ウィンドウの制御
サブ選択ウィンドウの制御はsubCntメソッドが行います。MainCavクラスの分岐では、case2・case3・case4の処理がこの制御に当たります。基本的な制御は、case2です。
case 2:
String[] subNam1={"回復","火魔法","水魔法","稲妻魔法","氷魔法","究極魔法"};
i=Cmn.subCnt(subNam1,6);
Cmn.cleBf();
if(i==6){
System.out.println("やめるを選択");
continue;
}
System.out.println(subNam1[i]);
continue;
subCntメソッドは、サブ選択ウィンドウに描画された選択肢を選んでセレクトボタンを押すと選択肢に対応するインデックス番号を返します。戻り値や大まかな機能はchsCntメソッドを同じです。しかし、アイテムや特技の一覧に利用しますから行動選択とは違った処理が行われています。
subCntメソッドには、String配列とintのデータを渡します。String配列は、一覧に表示する選択肢です。chsCntメソッドと同じです。次の引数のintデータには、一覧に描画する選択肢の数を渡します。サンプルでは、「"回復","火魔法","水魔法","稲妻魔法","氷魔法","究極魔法"」の選択肢があります。この場合に、intの引数に「3」を渡すと3番目の選択肢までしか表示されません。つまり、「稲妻魔法」まで表示されるわけです。インデックスを変えた処理はcase3です。なぜ、この様な引数を持つかというと、道具などは持っている全てを描画すればよいでしょう。しかし、特技はどうでしょうか?。特技は、レベルによって使えたり、使えなかったりします。レベルが低いのに究極の魔法など使えては困りますね。そこで、最後の引数で表示できる選択肢のインデックスを指定している訳です。サブ選択ウィンドウで表示できる選択肢は6つまでです。6を渡せばサブ選択画面に表示できるすべて表示できます。道具の表示場合は、6を渡しましょう。補足ですが、当マニュアルでは道具や特技は5つまでと決めています。もっと持たせたい場合などはプログラムを修正してください。
ここで、注意して下さい。subCntメソッドの戻り値は、0起算です。つまり、選択肢の配列のインデックスです。表示する選択肢の数は、日常の計算と同じ1から起算しています。戻り値の1で起算する方が良いかもしれません。subCntメソッドでは、表示した選択肢からどの選択肢が選ばれたかを制御するメソッドです。戻り値は、選択肢に対応した形で使われるでしょう。その為、引き数を選択肢の配列にそのまま利用できるようにしたのです。これは、戦闘画面の制御まで進むと意図がわかるでしょう。
では、chsCntメソッドの処理を確認しましょう。ここで、言っておきますがchsCntメソッドは後で書き換えます。描画にStringクラスを利用していますが、次の章では独自に定義したクラスを利用します。だからといって、飛ばさないで下さいね。描画のクラスが違うだけで制御内容は同じですからね。
//サブ選択の制御
//引数 文字列(配列) 表示する項目のインデックス
public static int subCnt(String[] sub,int
ce){
if(sub.length<6 | ce>6) return
-1;
int ch=0;
int xp=dsx;
int yp=dsy+font.getHeight();
//道具で使用 インデックスを加算しながら次の使える道具のインデックスを探す
//最初の要素も空白なら「やめる」にインデックスを移動する
//道具で使用 インデックスを加算しながら次の使える道具のインデックスを探す
//最初の要素も空白なら「やめる」にインデックスを移動する
if(sub[ch].trim().equals("")){
while(sub[ch].trim().equals("")){
ch++;
if(ch==6) break;
}
}
//表示する選択肢の数を0起算に変換
ce--;
//グラフィックのロック
g.lock();
//サブ選択ウィンドウの描画
drawSel();
//選択肢の描画
g.setColor(claWht);
for(int i=0;i<=ce;i++) g.drawString(sub[i],xp+12,yp+(i*fnthgt));
g.drawString("やめる",xp+12,yp+(6*fnthgt));
//テスト用の描画
drawSub("題名を表示","内容を表示");
//選択カーソルの描画位置に更新
xp+=3;
//選択カーソルの描画
csrCnt(xp,yp,ch,7);
//グラフィックのロック解除
g.unlock(true);
while(true){
switch(gKey()){
case Display.KEY_UP:
ch--;
if(ch<0){
ch=6;
break;
}
else if(ch>ce){
ch=ce;
break;
}
//道具で使用 インデックスを減算しながら次の使える道具のインデックスを探す
//最初の要素も空白なら「やめる」に移動して処理を終える
if(sub[ch].trim().equals("")){
while(sub[ch].trim().equals("")){
ch--;
if(ch<0){
ch=6;
break;
}
}
}
break;
case Display.KEY_DOWN:
ch++;
if(ch==6) break;
if(ch>6){
ch=0;
}
else if(ch>ce){
ch=6;
break;
}
//道具で使用 インデックスを加算しながら次の使える道具のインデックスを探す。
//「やめる」インデックスになったら加算処理を止める
if(sub[ch].trim().equals("")){
while(sub[ch].trim().equals("")){
ch++;
if(ch==6)
break;
}
}
break;
case Display.KEY_SELECT:
return ch;
default:
continue;
}
//グラフィックのロック
g.lock();
//選択カーソルの描画
csrCnt(xp,yp,ch,7);
//テスト用の描画
drawSub("題名を表示","内容を表示");
//グラフィックのロック解除
g.unlock(true);
}
}
最初に、エラー処理と各変数の設定を行っています。エラー処理は、「選択肢の配列が6以下」か「ceの値が6を超えた」場合に-1を返してメソッドを終了します。
if(sub.length<6 | ce>5) return -1;
int ch=0;
chは、chsCntメソッドと同じで選択肢のインデックスを格納します。subCntメソッドは道具を扱います。道具は、1度使えばなくなります。インデックスを指定させるとデータの無い選択肢を指してしまう事もあります。また、特技でも利用できない特技のインデックスを指定されると困ります。この様な処理をエラー処理で対処するより、最初から選択肢のデフォルト選択インデックスを指定できないようにして対処しています。subCntメソッドにデフォルト選択インデックスを指定できるようにする場合、これらのエラー処理を記述する必要があります。
int xp=dsx;
int yp=dsy+font.getHeight();
次に座標の設定を行っています。処理的に問題ありませんね。xp,ypには、サブ選択ウィンドウの描画座標dsx,dsyを代入します。y座標は、図形と文字の描画位置の違いを修正して指定します。描画位置から文字の高さ分y座標を加算して選択肢を描画します。その為、座標を+12しています。「fnthgtを加算しないの?」と思われるかもしれません。サブ選択ウィンドは、かなりの選択肢を描画するのでゆとりがありません。文字列は、ウィンドウのぎりぎりに表示します。その為、「文字の高さ+1」のfnthgtは使用せず文字の「高さ(font.getHeight())」を直接指定しています。
ところで、座標の設定で、今までも、ウィンドウの描画座標をローカル変数に代入しています。ウィンドウの描画座標は、非常に大切なデータです。万が一のプログラムミスでも書き換えられては困ります。その為、必ずローカル変数に代入して利用します。機種の違いによる画面サイズを吸収する為に変数として定義していますが本来は定数として定義するフィールドなのです。
//道具で使用 インデックスを加算しながら次の使える道具のインデックスを探す
//最初の要素も空白なら「やめる」にインデックスを移動する
if(sub[ch].trim().equals("")){
while(sub[ch].trim().equals("")){
ch++;
if(ch==6) break;
}
}
変数の宣言の後に、カーソルを表示する選択肢を検索します。アイテムの表示の場合、利用したアイテムは削除されます。インデックスが0のアイテムが利用された場合は、インデックス0にアイテム情報が存在しません。存在しないアイテムにカーソルが表示されるのはおかしな感じがします。これを、回避する為の処理です。データが「""」(0ストリング)の要素は、データが有りません。インデックスを加算しながらデータが存在する要素を調べます。データが存在するインデックスを見つけると、そのインデックスをカーソルのデフォルトインデックスにします。見つからない場合は、「やめる」に対応するインデックスに設定します。
//表示する選択肢の数を0起算に変換
ce--;
引き数ceを減算しています。文字列を描画する配列は0起算です。例えば、引き数ceに「5」が渡された場合は5番目までの選択肢を描画すると言う事です。文字列の配列だと、0・1・2・3・4となります。減算して0起算に合わせているのです。
基本データの加工が終わったらサブ選択ウィンドウの描画を行います。処理の流れは、描画のロック・サブ選択ウィンドウの描画・選択肢の描画・説明部分の内容の描画・カーソルの描画・描画ロックの解除です。
//グラフィックのロック解除
g.lock();
//サブ選択ウィンドウの描画
drawSel();
//選択肢の描画
g.setColor(claWht);
for(int i=0;i<=ce;i++) g.drawString(sub[i],xp+12,yp+(i*fnthgt));
g.drawString("やめる",xp+12,yp+(6*fnthgt));
//テスト用の描画
drawSub("題名を表示","内容を表示");
//選択カーソルの描画位置に更新
xp+=3;
//選択カーソルの描画
csrCnt(xp,yp,ch,7);
//グラフィックのロック解除
g.unlock(true);
最初にGraphicsクラスのlockメソッドを実行します。lockメソッドは、描画をロックします。以降、描画に関するメソッドを実行しても画面に描画されません。描画情報だけが格納されます。そして、unlockメソッドで一気に描画を行います。この描画のロックを利用する事で画面のちらつきを抑える事が出来ます。
for文の選択肢の描画の仕組みは、chsCntメソッドと同じです。カーソルの描画部分を考慮してx座標をずらします。最後に、「やめる」を表示します。サブ選択に表示された選択肢を利用しないと言う意味の「やめる」です。
選択肢の描画が終わると、説明部分のエリアに文字を描画します。drawSubメソッドを利用しています。drawSubメソッドは、最初の引数を説明部の題名に描画して、次の引数を内容部に描画します。処理内容は以下のようになっています。
//説明部の文字列表示
//引数 文字列
public static void drawSub(String tit,String
cntnt){
int xp=dsx+1;
int yp=dsy+93;
//説明部の塗りつぶし
g.setColor(claNvi);
g.fillRect(xp,yp,120,(fnthgt*2)+1);
//文字座標の修正
yp+=fnthgt;
//色の設定
g.setColor(claWht);
//題目の描画
g.drawString(tit,xp,yp);
//内容の描画
g.drawString(cntnt,xp,yp+fnthgt);
}
メソッドの処理は、難しくありませんね。説明部をネイビーで塗りつぶした後に、説明部に文字が描画されるように座標を設定しているだけです。
subCntメソッドに戻ります。説明部に内容を表示した後、カーソルの描画を行います。カーソルの描画の仕組みもchsCntメソッドと同じです。座標が違うだけです。
//選択カーソルの描画位置に更新
xp+=3;
//選択カーソルの描画
csrCnt(xp,yp,ch,7);
カーソルの描画が出来たらサブ選択ウィンドウの描画は終了です。これで、先頭の選択肢にカーソルが当たっている初期ウィンドウが描画されています。次のループ処理でサブ選択肢の制御を行います。この選択肢の制御も基本的な仕組みはchsCntメソッドと同じです。選択肢の数が多いだけです。つまり、chの最大値が「やめる」を含めると「6」となります。ただし、選択肢の数が変動する事と道具などは使うと無くなる事がると言う事からchsCntメソッドより複雑な処理が必要となります。
繰り返しますが、特技は歯抜けになることなく表示されますが道具の場合は必ずしも順番に要素が表示されているとは限りません。戦闘中に使用すればなくなりますから、例えば、2番目のアイテムを使用した場合は2番目の要素は空白となります。次に道具を使う場合には、1番目で下ボタンが押されたら3番目に移動しなければなりません。これらの制御が必要になります。下に特技と道具の制御の違いをまとめます。
特技の使用
下キー
使用できる特技を超えてた場合、「やめる」に移動する。
「やめる」の時に下キーを押されたら最初の要素に移動する。
上キー
最初の選択肢の場合、やめるに移動する。
「やめる」の時に上キーを押されたら使える要素(ce)のインデックスに移動する。
道具の使用
下キー
移動するアイテムが空白の場合、次のアイテムの要素を見る。空白ならまた次の要素を見る。「やめる」になった場合は、「やめる」にインデックスを移して終了。
上キー
最初の選択肢の場合、やめるに移動する。
「やめる」で下キーを押された場合は、次のアイテムの要素を見る空白なら、さらに次の要素を見る。最終要素も空白なら「やめる」に移動する。
これらの相違点を埋めるため冗長な制御処理を行っています。このメソッドは、特技版と道具版のようにメソッドを分けた方が良いでしょうアプリのサイズに余裕があればメソッドを分けられるのですがアプリのサイズも考慮に入れます。苦肉の策で、冗長な制御で問題を解決することにします。
最初に、上キーが押された処理「case Display.KEY_UP:」の処理を見ていきましょう。まずは、最初に行われる以下の分岐処理を確認します。
ch--;
if(ch<0){
ch=6;
break;
}
else if(ch>ce){
ch=ce;
break;
}
インデックスchを減算して「ch<0」で分岐するよりは問題ないですね。最上部で上キーを押すと最下部の選択肢に移動します。次の「else
if」が特技に対応した処理です。インデックスchが利用可能なインデックスceを超えたらchにceを代入します。これは、インデックスが「やめる」を指している時に対処する分岐です。「やめる」で上キーが押された場合は、ceを元に今つかえる特技の最大インデックスまで移動します。chにceを代入すれば済みます。
選択内容が道具を制御していた事を想定しましょう。「やめる」のインデックスは「6」です。ここで、上キーを押すと「ch--」でchは5となります。道具を制御する場合は、ceには5を指定します(暗黙のルール)。つまり、道具の場合は「やめる」を選択した状態で上キーを押したとしても「ch>ce」が成立しないのです。つまり、この「else if」に入る事はありません。道具で、「やめる」を選択した状態で上キーを押した場合は制御には引っかからずに次の処理に移動します。
//道具で使用 インデックスを減算しながら次の使える道具のインデックスを探す
//最初の要素も空白なら「やめる」に移動して処理を終える
if(sub[ch].trim().equals("")){
while(sub[ch].trim().equals("")){
ch--;
if(ch<0){
ch=6;
break;
}
}
}
道具の場合は、次の選択肢が存在するか分かりません。使われてなくなっているかもしれません。「やめる」(インデックスは「6」)で、上キーを押すとchは5となります。選択肢を格納した引数subのインデックス5の要素を確認します。スペースだけの存在ではないかtrimメソッドを実行した戻り値のStringクラスのequalsメソッドで格納字列が「""」(何も無い状態)でないか確認します。文字列の比較には、「==」ではありません。equalsメソッドを利用します。また、trimの処理は当マニュアルでは必要ありません。何も無い場合は。「""」ではなく「スペース」格納するといった使用を好む人もいるかもしれません。因みに製作者の初期のアプリでは、何も無い場合はスペースを入れていました。
話を戻します。次の選択肢の文字列が「""」だった場合はループで選択肢の文字列が格納されている要素を探します。インデックスchをch--で減算して次の要素が「""」出ないか確認します。「""」だったら、またインデックスchをch--で減算して次の要素が「""」出ないか確認します。この様にしてデータが格納されている要素を調べます。しかし、要素の数は決まっています。0に向かってインデックスを減算して判定しますからchが0以下になったらchに6を代入してループを終了します。つまり、「やめる」のインデックスにしてループを終了する訳です。これで、上キーの制御は終わりです。
では、下キーが押された処理「case DDisplay.KEY_DOWN:」の処理を見ていきましょう。この処理も最初に行われる分岐を確認しましょう。
ch++;
if(ch>6){
ch=0;
}
else if(ch>ce){
ch=6;
break;
}
分岐は、上キーと同じです。「やめる」(インデックス6)で下キーを押したら選択肢の最初の要素にインデックス(0)を移します。else
ifは、特技と道具のための制御です。使える特技のインデックスを超えた場合は、chに6を代入して「やめる」に移動します。選択内容が道具を制御していた事を想定しましょう。道具を制御する場合は、ceには5を指定します(暗黙のルール)。「やめる」のインデックスは「6」です。選択肢が最大値の「5」だった時に、下キーを押すと「ch++」で加算されてchは「6」になります。chがceを超えるのでchに6を代入して分岐を終了します。今回は、選択肢のインデックスが最大値の場合は、道具も「else
if」の制御を行います。
分岐以降の処理は、道具の為の処理です。道具の場合は、次の選択肢が存在するか分かりません。使われてなくなっているかもしれません。基本的な構造は上キーの時の処理と変わりません。今回は、下キーが押された場合ですのでchを加算しながらデータが格納されている選択肢を探していきます。
//道具で使用 インデックスを加算しながら次の使える道具のインデックスを探す。
//「やめる」インデックスになったら加算処理を止める
if(sub[ch].trim().equals("")){
while(sub[ch].trim().equals("")){
ch++;
if(ch==6) break;
}
}
以上が選択肢の制御です。インデックスの制御が面倒でしたね。インデックスの変化を考えていけば理解できると思いますので色々と試してみると良いでしょう。インデックスの制御のswitch文を抜けると描画処理に移ります。
//グラフィックのロック
g.lock();
//選択カーソルの描画
csrCnt(xp,yp,ch,7);
//テスト用の描画
drawSub("題名を表示","内容を表示");
//グラフィックのロック解除
g.unlock(true);
この描画処理は、最初の処理と同じです。今回のサンプルでは、説明部分の描画はテスト用です。本番では、選択された選択肢の内容を表示します。
如何でしたか?。多少めんどくさいところはありますが、何となくは仕組みを理解できるのではないでしょうか?。どんな値の時にどんな処理をするか、そして、処理によって値がどの様に変化するか想像の作業です。ある程度の値の変化を考えながらプログラムは作られていきます。
2.10 メッセージウィンドウの制御
メッセージウィンドウの制御はmsgWriteメソッドが行います。戦闘画面用のメッセージはbattleMsgメッセージが行います。battleMsgメソッドは、受け取った引数を座標を指定してmsgWriteメソッドを実行しているに過ぎません。msgWriteメソッドがこの章の最大の複雑さを持ちます。MainCavクラスの分岐では、case5の処理がこの制御に当たります。コードが長いので全部の処理は省略させていただきます。msgWriteメソッドは、与えられた文字列を分解しながら文字を表示します。また、文字列に全角の「|」が有った場合は改行と見なします。msgWriteメソッドの内容は「如何に文字列を分解するか」なのです。
case 5:
Cmn.cleFd();
String strTest="みなさん、こんにちわ。|ちゃんと表示されていますか?。";
Cmn.msgWrite(strTest,0,-2,118,9,0,0);
Cmn.tSlp(1000);
メッセージの描画は、戦闘画面・フィールドでの会話・表題など様々な場面で利用されます。そのウィンドウも文字を2行で表示したり、3行で表示したりと表示する文字数・行数・表示エリアのサイズも様々に変化します。また、ウィンドウに文字が表示しきれない場合は、内容を切り替えなければなりません。これも、自動的に文字を描画していくのか、次の文字に移動するのをユーザーに指示させるのかといった事も考慮に入れなければなりません。つまり、様々な場面での利用に対応していかなければならないのです。メッセージ描画メソッドに求められる機能を考えると、以下のような機能を満たす必要があります。
1メッセージの描画エリアの大きさを指定できる
2メッセージの描画エリアを任意の座標から描画できる
3描画の文字を、右詰め・中央・左詰めと選択できる。
また、機種によっての違いも考慮に入れなければなりません
1機種によって文字サイズが違う
2機種のよって画面サイズが違う
この様な機能を実現する為に、メソッドの引数の数も増えています。
msgWriteメソッド
機能:
引数で渡された文字列をメッセージウィンドウに描画
引数:
描画する文字
x座標
y座標
表示の幅
表示する行
文字の表示位置
処理判別フラグ
引数の内、描画文字・x,y座標・表示の幅(ウィンドウの横幅)・表示する行の意味は分かるでしょう。残りの引数について補足しましょう。「文字の表示位置」とは、メッセージウィンドウに文字を表示する位置です。ワープロソフトなどで、左揃え・中揃え・右揃えと文字表示の位置を指定できます。この機能を指定する為の引数が「文字の表示位置」です。0が左揃え、1が中揃え、2が右揃えです。
「処理判別フラグ」は、ウィンドウに文字が収まりきらない場合は次の文字列を再描画します(文字送り)。これを、自動で行うか、ユーザーがボタンを押したら文字送りを行うようにするかの指定です。引数が、「0」の時は自動で文字送りを行います。「1」の場合は、ユーザーの指示で文字送りを行います。
補足的に、座標の値を説明します。x,y座標の引数には-1と-2が指定できます。y座標が-1の場合は、ウィンドウを画面の真ん中(縦位置の)に表示します。-2の場合は、最下部(縦位置の)に表示します。x座標が-1の場合は、ウィンドウを画面の真ん中(横位置の)に表示します。-2の場合は、右に寄せて(横位置の)に表示します。x座標の使い道は無いかもしれませんが、y座標はタイトルの表示などで利用できるでしょう。ちなみに、画面最上部に表示したい場合は「0」を指定するだけなので自動修正は必要ありません。
では、msgWriteメソッドの処理内容を確認しましょう。msgWriteメソッドの内容は以下の通りです。
//メッセージの表示
//引数 表示文字列・表示する行・描画部の表示位置(x・y座標)・表示の幅・文字の表示位置・処理判別フラグ
public static void msgWrite(String smg,int
xp,int yp,int len,int row,int alg,int bp){
//文字列が存在しない場合は終了
if(smg.length()==0) return;
int i=0,j=0,k,l=0,phgt=0;
String wmsg;//分割した表示文字を格納
int slp=1000;//スリープ処理の時間
//1以外の値の場合は0で初期化
if(bp!=1) bp=0;
//y座標にマイナスが指定されている場合は、自動調整
if(yp==-1) yp=(130-(5+(fnthgt*row)))/2;//中段表示
else if(yp==-2) yp=(130-(5+(fnthgt*row)));//下段表示
//x座標にマイナスが指定されている場合は、自動調整
if(xp==-1) xp=(130-(len))/2;///中心表示
else if(xp==-2) xp=130-len;//右表示
//画面サイズ130を超えた部分の座標修正
xp+=cx;
yp+=cy;
//高さを設定
phgt=(row*fnthgt)+3;
drawMsg(xp,yp,len,phgt);
//表示文字がなくなるまで続ける
while(i<smg.length()){
//メッセージウインドウを描画
drawMsg(xp,yp,len,phgt);
//メッセージフィールドの上段と下段の二つを描画処理する
for(k=1;k<=row;k++){
j=i;
i=font.getLineBreak(smg,i,smg.length()-i,len);
wmsg=smg.substring(j,i);
//'|'があった場合は、改行する
if(wmsg.indexOf('|')>=0){
i=i-(wmsg.length()-wmsg.indexOf('|'));
wmsg=smg.substring(j,i);
i+=1;
}
//文字列の描画
g.setColor(claSil);
if(alg==0) g.drawString(wmsg,xp+4,yp+(k*fnthgt));
else if(alg==1) g.drawString(wmsg,xp+2+(len-4-font.stringWidth(wmsg))/2,yp+(k*fnthgt));
else if(alg==2) g.drawString(wmsg,xp+2+(len-4-font.stringWidth(wmsg)),yp+(k*fnthgt));
//文字列が無くなればループを抜ける
if(i>=smg.length())
break;
}
if(bp==1){//手動で次の文字列の描画へ進ませる場合
if(i<smg.length()){
g.setColor(g.getColorOfName(g.AQUA));
g.fillRect(xp+len-4,yp+phgt-4,4,4);
g.setColor(g.getColorOfName(g.SILVER));
g.drawRect(xp+len-4,yp+phgt-4,4,4);
}
while(true){
if(gKey()==Display.KEY_SELECT)
break;
}
}
else{//自動的に次の文字列の描画に進む場合
//スリープ処理
try {
Thread.sleep(slp);
} catch (Exception e) {}
}
}
}
ループに入る前までは、初期処理です。変数の宣言や引数のデータ型によって座標を調整します。最初にエラー処理と変数の宣言部があります。
//文字列が存在しない場合は終了
if(smg.length()==0) return;
int i=0,j=0,k,l=0,phgt=0;
String wmsg;//分割した表示文字を格納
int slp=1000;//スリープ処理の時間
エラー処理と変数の宣言部に珍しいデータ型は使用していません。スリープ時間は、文字送りの間の待機時間です。
//1以外の値の場合は0で初期化
if(bp!=1) bp=0;
引数bpは、文字送りの種類を格納しています。1の場合は手動による文字送りです。0と1しか利用しません。1以外の場合は、0に変更して自動で文字送りをするようにします。
//y座標にマイナスが指定されている場合は、自動調整
if(yp==-1) yp=(130-(5+(fnthgt*row)))/2;//中段表示
else if(yp==-2) yp=(130-(5+(fnthgt*row)));//下段表示
//x座標にマイナスが指定されている場合は、自動調整
if(xp==-1) xp=(130-(len))/2;///中心表示
else if(xp==-2) xp=130-len;//右表示
この部分は、ウィンドウの描画位置を自動修正しています。引数ypの値を判定して処理を分岐します。-1の場合、画面の中央に描画エリアを表示します。「130-描画エリアの高さ/2」でy座標が求められます。下部に表示する場合、「120-描画エリアの高さ」でy座標が求められます。画面上部に、表示したい場合は「0」を指定すればそのまま表示できますので特に「0」による分岐は行いません。他の値は指定された座標から描画を行います。x座標を自動修正する式の仕組みも同じです。高さを求めるのに「(5+(fnthgt*row)」という計算式を使用しています。+5を行っているのはメッセージ表示は少し余裕を持って文字の間隔を取っているからです。
今回のプログラムでは、ここまでの分岐しかありません。例えば、座標を130を超えて指定した場合、画面に描画エリアが表示されないことになります。描画エリアの高さと描画座標を比較して、130の座標の中に描画エリアが表示できない場合は、修正・エラー処理するなどの処理も付け加えた方が良いでしょう。今回、この様な処理が記述されていないのは、アプリケーションのザイズを小さくするためです。座標の指定は、なんとかプログラマの注意で対処できますし、例えエラーでもプログラムが強制終了するなどのエラーには成り難い処理です。その為、「エラー処理のプログラムようサイズ増加」と「エラーの重要性」を比較してプログラムの節約を優先しました。
例えばypが-1なら画面サイズ(130)からウィンドウの高さを引いた値を2で除算します。これで、y座標が求められます。2で除算しなければ画面下部のy座標が求められます。
//画面サイズ130を超えた部分の座標修正
xp+=cx;
yp+=cy;
引数による座標の修正が終わったら画面のサイズに合わせて座標を修正します。これは、画面サイズが130×130を超えたら描画するx,y座標の位置をコンストラクタで算出しています。値は、それぞれcx,cyフィールドに格納しました。cx,cyフィールドの値をxp,ypに加算します。
//高さを設定
phgt=(row*fnthgt)+3;
初期処理の最後として、ウィンドウの高さをローカル変数に格納します。メッセージウィンドウの高さは行数でわたされます。行数×文字の高さで計算する事が出来ます。ここまでで、初期処理が終了します。
文字列を分解して表示するのは、ループの処理の中です。殆ど文字列分解の要領です。
while(i<smg.length()){
ループの条件には、変数iが引数の文字列の長さより下の場合です。変数iが引数の文字列の長さを超えたら表示する文字がないということですから処理を終了します。
//メッセージウィンドウを描画
drawMsg(xp,yp,len,phgt);
ループの最初には、メッセージウィンドウの描画を行っています。ここは、特に問題は無いでしょう。次のfor文が文字を切り出して表示している部分です。
for(k=1;k<=row;k++){
描画は、文字の行数だけ行います。ループの条件は描画する回数です。3行表示する場合は.文字の表示座標を文字の高さずらしながら3回行います。ここは、行動選択・サブ選択ウィンドウの描画と同じ仕組みです。ただ、描画する文字の数がウィンドウの大きさによって変動するのでループカウンタが少し違うだけです。
j=i;
i=font.getLineBreak(smg,i,smg.length()-i,len);
wmsg=smg.substring(j,i);
この部分で、文字列の分解を行っています。変数jが文字を切り出す開始位置、変数iが切り出す終了位置です。どの文字まで切り出すかは、fontクラスのgetLineBreakメソッドで求めます。引数には、文字列・計算する文字列のオフセット
・計算する文字列の長さ・行の幅(ピクセル単位)を指定します。文字のサイズは、全角半角、フォントのサイズで大きさが異なります。単純に「何文字分切り出せばよい」と言う訳ではありません。そこで、getLineBreakメソッドを利用します。getLineBreakメソッドは、フォントサイズの大きさと長さを比較して長さに収まる文字の切り出し位置を返してくれます。こgetLineBreakメソッドについては、APIリファレンスに詳しく書いてあります。APIリファレンスを必ず確認してください。できれば、リファレンスのサンプルプログラムを作成して機能を確かめて見ましょう。
getLineBreakメソッドで得た切り出し位置を変数iに代入します。これで、jからiまでの文字を切り出せば一行分の文字が切り出せます。次の命令で文字の切り出しを行っています。substringで文字を切り出します。この処理は問題ないでしょう。
wmsg=smg.substring(j,i);
次の条件式では「|」(改行文字)が入っているか判定しています。
//'|'があった場合は、改行する
if(wmsg.indexOf('|')>=0){
i=i-(wmsg.length()-wmsg.indexOf('|'));
wmsg=smg.substring(j,i);
i+=1;
}
もし、「|」(改行文字)が入っている場合は改行処理をしなければなりません。まずは、切り出し位置の変数iの値を変更します。切り出した文字の「|」の位置を数得ます。この値を変数iから減算します。
例えば、「テスト表示です。あいうえお|かきくけこ」という文字があるとします。そして、最初に「テスト表示です。」が切り出されたとします。この処理の後で変数iは、次の文字「あ」のインデックスである8を格納します。次の文字列を切り出す為、getLineBreakメソッドで求めたインデックスは16でした。この結果、8から16を引数にしてsubstringメソッドを実行すると「あいうえお|かき」が切り出されます。切り出した文字は、wmsgに格納します。しかし、インデックスが13の位置に「|」が有ります。つまり、ここでは「あいうえお」を切り出さなければなりません。変数iの値を13にしたいのです。そこで、「(wmsg.length()-wmsg.indexOf('|')」を計算します。wmsgに格納した文字は「あいうえお|かき」ですからlength()メソッドで文字列を求めると8が求められます。「wmsg.indexOf('|')」で「|」の出現位置を求めると5です。長さから出現位置を減算すると「8-5=3」で「3」が求められます。変数iの値(16)から3を引くと、「|」の出現位置13が求められます。変数iの位置を求めたら新しい変数iを利用してsubstringメソッドを実行します。これで、「あいうえお」が切り出せます。最後に、変数iを+1すれば「14」となり、「か」を切り出し開始位置にすることが出来ます。少し、複雑ですが数の変化を考えてください。
//文字列の描画
g.setColor(claSil);
if(alg==0) g.drawString(wmsg,xp+4,yp+(k*fnthgt));
else if(alg==1) g.drawString(wmsg,xp+2+(len-4-font.stringWidth(wmsg))/2,yp+(k*fnthgt));
else if(alg==2) g.drawString(wmsg,xp+2+(len-4-font.stringWidth(wmsg)),yp+(k*fnthgt));
改行処理が終わると、文字列を表示します。algは、左揃え・中揃え・右揃えを表す値を格納します。algの様に、何かの処理の目印にする値をフラグと呼びます。変化するのは、x座標です。計算式は、左揃えの場合はx座標(xp)に+4した位置から描画します。中揃えは、ウインドウの幅から表示文字の幅を引いて2で除算します。これで、真ん中に表示が出来ます。ウインドウの幅から表示文字の幅を引いた値を除算しなければ右寄せになります。文字描画のy座標は、for文のインデックスに文字の高さを乗算します。
//文字列が無くなればループを抜ける
if(i>=smg.length()) break;
文字表示が終了すれば、ループの判断です。これは、for文の判断です。表示する文字がなくなってもfor文のインデックスがrow以下ならば処理が実行されてしまいます。インデックスがrow未満でも表示する文字列が無くなればfor文の処理を抜けるようにしている訳です。
最後に、文字送りの分岐をしています。文字送りのフラグbpが1ならば手動の文字送りです。それ以外は、アプリ側で文字送りをします。アプリ側の文字送りは、elseの処理です。一定時間、スリープをして処理を待機しています。今表示されている文字を読む時間を設定している訳です。
if(bp==1){//手動で次の文字列の描画へ進ませる場合
if(i<smg.length()){
g.setColor(g.getColorOfName(g.AQUA));
g.fillRect(xp+len-4,yp+phgt-4,4,4);
g.setColor(g.getColorOfName(g.SILVER));
g.drawRect(xp+len-4,yp+phgt-4,4,4);
}
while(true){
if(gKey()==Display.KEY_SELECT)
break;
}
}
else{//自動的に次の文字列の描画に進む場合
//スリープ処理
try {
Thread.sleep(slp);
} catch (Exception e) {}
}
ユーザー側の文字送りは、少し複雑です。次の表示メッセージがあるかお知らせしなければなりません。その為、ウィンドウの右下に信号を表示します。これは、自作のイメージでも構わないでしょう。「まだ、読むメッセージがあるか」ユーザーに通知できれば良いのです。お知らせの信号を秒が知ったらセレクトボタンが押されるまでループで待機します。ループを抜けると最初の「while(i<smg.length())」のループに戻って処理を継続します。
如何でしたでしょうか。多少うんざりしているのではないでしょうか。値などを変えて試してみると機能がわかります。是非試してみてください。このメソッドの大まかな処理の流れを示すと以下のようになります。
初期処理
文字列分解のループ
ウィンドウの描画
行毎の分解(ループ)
文字の描画
文字送りの処理
今回は、文字列分解と座標の修正で複雑に感じた事でしょう。補足として、静的フィールドで定義する事に違和感があるでしょう。ただ、これもクラスを使わないプログラムをしなければならない宿命だと思ってください。ただ、ここにあるのはサンプルです。「仕組みを利用して通常のクラス化するのも由」と言ったところです。
2.11 補足1
最後に使いませんでしたが、Cmnクラスにスリープ処理のメソッドを追加しています。
//スリープ処理 開始--------------------------------------------------------------
public static void tSlp(int slpt){
try {
Thread.sleep(slpt);
} catch (Exception e) {}
}
//スリープ処理 終了--------------------------------------------------------------
これは、try〜catchを利用するとアプリのサイズが大きくなってしまうのです。ですから、try〜catchは利用を控えなければなりません。sleepメソッドを利用する時はtry〜catchを利用しなければなりませんのでメソッドにまとめています。但し、頻繁に利用する処理では、try〜catchを利用してsleepを記述して良いでしょう。
2.12 補足2
戦闘画面の描画と表示データの制御を作成しました。描画するウィンドウの位置は、実機にダウンロードすると誤差がある場合があります。また、好みによっては、ウィンドウ間の間隔などが気に入らない事もあるでしょう。各ウィンドウの描画の開始座標は、フィールドに格納していますからフィールドの値を調整して自分の好みのレイアウトに調整してください。