RPG01

MAGI JAVA -Make a game in Java-
JAVA Game作成講座 015回
ステータスウィンドウ追加


kouza015 jarファイル


[今回追加したもの]

・ユニットのステータス設定
・ステータスウィンドウを開けるようにした

追加要素が多く、思った以上に時間を食った。
連動している部分をかいつまんで説明していく。

まずトリガーとなるイベントの呼び出し。
ARPanel上で読み込む。

[ARPanel クラス]
if (gameMode==0) {

gData.pPlatform.keyCheck(keyPressTbl);
gData.pPlatform.move(gData);
gData.cameraSet(1);
objsMove();//オブジェ移動
//ブロック衝突チェック
blockHitCheck();
//オブジェ接触チェック
objHitCheck();
if (gData.pPlatform.keyPush[6]==true) statusWindowSet();


}

pPlatformでkey状態を読み込んでいる。
そして keyPush[6]==true
keyPush[6]は"f"キーを押すとtrueになる。
そして、satusWindow呼び出し関数に入る。
"f"キーは"z"でも"x"でも好きに変えて良いと思う。
ただ単に左手の人差し指の位置だから、という理由でそうしただけ。

[ARPanel クラス - statusWindowSet関数]
private void statusWindowSet() {

gData.pPlatform.keyPush[6]=false;
System.out.println("STWinCallしてます");

gameMode=1;
evExe.dataSet2(gData);
evExe.staticEventDataSet(1);


}

イベント呼び出しとやることはほぼ同じ。
違いは、固定イベントを呼び出すことと、
触ったオブジェがないので、イベント番号などのキープが無い事。

evExe.dataSet2(gData);
この処理でgDataだけ先渡しする。
これをやらずに一度目のイベントとして呼び出すとエラーが起きる。
何か別のイベントを起動しているとエラーはおきない。

evExe.staticEventDataSet(1);
これがポイント。datablockの設定を完全固定で入力する。
今回のように外部の数値がいらないイベント起動に使う。
タイトル画面の呼び出しもこの方法でやる。

gameModeを1に切り替えているので、
これ以降イベント駆動モードに切り替わる。

では、evExeクラスのデータセットを見て行こう。

[EventExecution クラス - staticEventDataSet関数]
//固定イベント呼び出し 細かい設定を抜きにして強制的にイベントを起動する。
public void staticEventDataSet(int num) {

firstDataInit();
if (num==1) {
datablock[0][0]=4;
datablock[1][0]=999;
}
//タイトル画面のイベント。ロードしたときは0で処理が終わる。
if (num==2) {
datablock[0][0]=21;
datablock[1][0]=5;
datablock[1][1]=3;
datablock[2][0]=999;
}


}

入力されたナンバーに応じて、固定のイベントを読み込む。
datablockがこのクラスの心臓なので、これさえセットされていれば
大抵のイベントは動かせる。

今回の新イベントは4番のイベント。
statusWindowEventSet();
初期データをこの関数でセットする。

public void statusWindowEventSet() {

swinMode=1;
eventMode=4;
//ステータスウィンドウクラスを初期化、データ渡して使えるようにする。
//以下このクラスをいじってイベント処理を進める。
sWin.dataSet(gData);


}

sWinクラスにgDataを渡す。
eventModeを4に切り替え、sWin関連のループに入る。
swinModeってのが良く分からない…なぜ入れたのか不明。なくてもいい。
sWinクラスに行く前に、イベントループの基点となる関数を見ていく。

public void sWinEventStart() {

sWin.eventStart(gData);
//終了フラグが送られてきたらイベントフェーズを進める。
if (sWin.stWinEnd) {
phasePlus++;
}


}

大分コメントアウトしてあるが、
今は使わないので必要部分を抜き出すとこれだけ。
ひたすら
sWin.eventStart(gData);
を回し続ける。
内部で終了フラグが立ったらフェーズ終了する。
999が次のイベントなのでそれ回して終了。
ステータスウィンドウクラスを読み込むだけの最小限の処理だ。
では、その新クラスを見ていこう。

[StatusWindow クラス - eventStart関数]
public void eventStart(GameData gdt) {

int cursorPlus=0;
int pushKey=0;
if (gData.pPlatform.keyPush[4]) {
gData.pPlatform.keyPush[4]=false;
pushKey=1;
}
if (gData.pPlatform.keyPush[5]) {
gData.pPlatform.keyPush[5]=false;
pushKey=2;
}
if (gData.pPlatform.keyPush[0]) {
gData.pPlatform.keyPush[0]=false;
cursorPos[mode]--;
}
if (gData.pPlatform.keyPush[1]) {
gData.pPlatform.keyPush[1]=false;
cursorPos[mode]++;
}
if (cursorPos[mode] > cursorMax[mode]) {
cursorPos[mode]=0;
//if (mode==20) pagePlus++;
}
if (cursorPos[mode] < 0) {
cursorPos[mode]=cursorMax[mode];
//if (mode==20) pagePlus--;
}

switch (mode) {
case 1://総合メニュー
if (pushKey==1) mainMenuMode0();
if (pushKey==2) stWinEnd=true;
break;
case 2: //ステータスを見るキャラを選択するモード
if (pushKey==1) {
if (unitLiveCheck(cursorPos[mode]) ) {
selectUnitNumber=cursorPos[mode];
modeChange=3;
}
}
if (pushKey==2) modeChange=1;
break;
case 3:
if (pushKey==2) modeChange=2;
break;
}

if (modeChange>=1) {
mode=modeChange;
modeChange=0;
count=0;
timerReset(0);
}


}

public void timerReset(int endtime) {

timer[0]=0;
timer[1]=endtime;


}

キー入力関連はそのまんまの処理。
アイテム使用モードで左右入力を追加するが、今は上下、space, d キーしか使わない。
現在のmodeに応じて起動するイベントを変える。
最初のメニューは見かけ上6項目作ってあるが、一個目しか動作しない。

spaceキーを押すとpushKey=1が入り、
mainMenuMode0 関数内でmode=2に切り替える。

mode2の状態でこのループに入ると、
ステータスを見たいキャラを選ぶ処理に入る。
4枠キャラを登録できるようにしてあるが、増やすことも減らすことも可能。
キャラ登録するクラスの紹介を忘れていた…あとで書く。

mode3の状態はステータスを表示しつづけるのみ。
キャンセルキーで戻るまでそのまま。
ここの描画をするためにunitData クラスでユニットのデータ入力が必要なのだ。
どこでそれをやっているのか、それを今から説明する。
paint関連はそのあとで。
ユニットデータの入力は呼び出しがARPanel、実際はgData内で行う。

[GameData クラス]
//プレイヤーユニットを一人分登録する。主人公ね
public void unitKariSet() {
uData[0]=uDataSet.dataSet(1, 1, 1, 0);
}

これだけ。しかし、この一行に膨大な変数が詰まっている。
uDataというのはUnitDataクラス。
playerPlatformがマップ移動用のデータをまとめたクラスだとすれば、
uDataはユニットの能力値を記憶するクラス。
その中身はHP,MP,STR など多くの変数のカタマリだ。
細かく解説すると果てしないので説明は省略する…。
・vst=hp,mp,kpをまとめたもの
・fst=str,prt,dex,agl 物理関連のステータス
・mst=mstr,mprt,mdex,magl 魔法関連のステータス
・eiData=装備のデータが入っている。これもItemDataクラスでまとまっている。
ItemDataクラスは何も作っていない。空データだが、もうじき手を付ける。
・lv=体レベルと魔レベルで分けている。
なぜ分けたかというと、将来的に「転生」を作るため。
長い脱線になるので読み飛ばしてもいい。


[転生]
元々ディスガイアをパクってシステムを作る予定だった名残。
転生するとなぜINTまで下がるんだろう、という疑問があった。
転生しても1レベルになるのは体レベルのみで、魔レベルは下がらない。

じゃあ魔レベルを重視した装備とスキルで固めた方が良いじゃん、
と思うだろうけど、その代わりに魔レベルは必要経験値が多く上がりにくい。
体レベルを上げまくった方が速く成長できる。しかし、転生しないと差がつく。
こういうジレンマを再現しようとぼんやり思っていた。
…というシステムを組み込もうと思っているので二つのレベルに分けてある。
めんどくさかったら一本化してもいい。
ステータスを計算する箇所でレベルの反映を一つにまとめれば一本化できる。

ステータスに関しては詳しく書くとキリがないので端折る。
しかし、何もかも説明しないのもいかんので、一か所だけ抜き出して説明する。
マイルールみたいなもんを詳しく説明するのもなんか変だけど…。

[UnitData クラス]
public void statusLvBonusSet2() {

int flv=lv[0];
int mlv=lv[1];
int clv=flv;
for (int i=0;i<=2;i++) {
if (i>=1) clv=mlv; //hpは体LV、mp,kpは精神Lv
vst_up[i]=(vst_base[i]+vst_base_bp[i])*10/4; //up値も再計算する。bpが増えるとレベルが高い程一気に上がる。
vst_max[i]=vst_base[i]+vst_base_bp[i] +vst_up[i]*clv/4;
vst[i]=vst_max[i];
}
clv=flv;
for (int i=0;i<=3;i++) {
fst_up[i]=(fst_base[i]+fst_base_bp[i])*10/4
fst_max[i]=fst_base[i]+fst_base_bp[i] +fst_up[i]*clv/4;
fst[i]=fst_max[i];
}
clv=mlv; //str-dexは体LV,mstr以降は精神LV。
for (int i=0;i<=3;i++) {
mst_up[i]=(mst_base[i]+mst_base_bp[i])*10/4
mst_max[i]=mst_base[i]+mst_base_bp[i] +mst_up[i]*clv/4;
mst[i]=mst_max[i];
}


}

この処理はステータスとレベルを説明するのに適している。
vst_max[i]=vst_base[i]+vst_base_bp[i] +vst_up[i]*clv/4;

vst_maxというのが、hp,mp,kpの装備を含めない素の状態のステータス最大値。
装備を含めるとvst_totalという値になる。これが実際に使うhp,mp,kpになる。
kpってなによ、と言われると、「気 ki」point。ドラゴンボールのアレです。
ロマサガをやっているならWPのようなもの、というのが分かりやすいかも。
魔法を使うとmpが減り、技を使うとkpが減る、と考えるのが基本。
両方使う「術技」も設定できる予定だが、それはあとで決めていけばいい。

vst_base=ベース値。これの1/4がレベルアップ時の上昇値、vst_upになる。
vst_up=アップ値。レベル×コレ+ベース値がステータス計算の基本。
ベース値が高い程up値も高くなるが、先天的な物なのでup値自体は種族で固定。
後天的な修行などで変動するのが、vst_base_bp。
これが上がるほどベース値が増える=up値も増える。

人間よりもドラゴンの方がベース値が高く、
base_bpの上がり具合もドラゴンの方が遥かに高い。
しかし、人間も極限まで鍛えぬけば常人の数倍の能力まで上げられる。
ドラゴンなどの種族は必要経験値が多く、レベルアップに時間がかかる。

paintに行く前にUnitDataSetクラスの説明がいるか…。

[UnitDataSet クラス]
public UnitData dataSet(int unum, int flv, int mlv, int stbp) {

unumというのはユニット固有のナンバー。1-10は味方キャラ、
11-敵キャラのデータを入力する予定。
番号ごとに固有のデータがあり、外見なども番号とリンクする。
flv,mlvはユニットのレベル。
stbpはいわゆるボス補正のようなもの。
base値が高くなるので、とうぜんup値も増える。レベルが高いほどより強くなる。
さらに耐性値も上がるので、即死魔法が聞かなかったり眠らなかったりと色々設定できる。
まだなにもそこらへん入力してないけど。

他は…細かく説明すると果てしないので省略。
気がついたら書き足す。

ItemData, ItemDataSetクラスを形だけ作ったが、中身は何も設定してない。
次の講座で作る予定。

[最後。再び StatusWindow クラス - paint]
public void paint(Graphics gr) {

g=gr;
Font font2 = new Font("Meiryo UI", Font.PLAIN, 24);
int[] stxy= {56,88};
//0 メインメニューは常時出続ける。
if (mode>=1) {
String[] word= {"能力確認","特技","装備","アイテム","スキル習得","セーブ","ロード"};
g.setFont(font2);
gSet.rectSet2(g,32,64,144,190,0,0);
stxy[0]=56;
stxy[1]=88;
for (int i=0;i<=word.length-1;i++) {
gSet.wordDisp_short(gr,word[i],stxy[0],stxy[1]+i*22);
}
gSet.wordDisp_short(g,"→",cursorX[1],cursorY[1]+cursorPos[1]*22);
//所持金も表示しておく。
int sx=32;
int sy=254;
gSet.rectSet2(g,sx,sy,144,55,0,200);
gSet.wordDisp_short(g,"[所持金]",sx+24,sy+24);
gSet.wordDisp_short(g,String.valueOf(gData.money),sx+34,sy+48);
}

ここまでが最初のメインメニュー描画。
四角を描き、その上に文字を打ち込んでいるだけ。
それほど複雑な処理をしていないが、
ポイントはGraSetクラスで四角と文字を描画している事。

一個前のメッセージ表示プログラムではGameDataクラスに同じものを作って、
そこで描画するようにしていた。
その機能を丸ごとGraSetクラスに移行した。
画像を扱う処理全般を一つのクラスにまとめた方が感覚的に分かりやすい。
コロコロ仕様を変えて申し訳ないが、今後このやり方で進める。
良いと思ったら多少遠回りになっても何度でも作り直す。

if (mode==2 || mode==3) {
int sx=32;
int sy=208;
cSelWinDraw(mode);
}

cSelWinDrawというのはどのキャラクターに働きかけるか、
という名前と矢印を表示する描画をまとめた関数。詳しくはあとで書く。
ステータスの確認以外に、アイテム使用、スキル習得、魔法をかける相手、
装備を変更するキャラの選択…などこの先も使いまわすので、
どのmodeからでも参照できるように関数にまとめてある。
二か所以上で同じ処理をする場合はどんどん下請けの関数に振ったほうがいい。
修正が一度で済むし、エラーが出ても見る箇所1つに絞れるからだ。

//能力表示モード。
if (mode==3) {
//charaSelectWinDraw(10);
gSet.rectSet2(g,32,200,450,340,0,220);
//選んだキャラのデータを頻繁に使うので一時的に使いやすい形にする。
UnitData udt=gData.uData[selectUnitNumber];

int kx=40;
int ky=220;
String[] wd1= {"NAME","FLV","MLV"};
String[] nd1= {udt.name,String.valueOf(udt.lv[0]),String.valueOf(udt.lv[1])};
for (int i=0;i<=2;i++) {
gSet.wordDisp_short2(g,wd1[i],kx,ky+i*20,2);
gSet.wordDisp_short2(g,nd1[i],kx+70,ky+i*20,0);
}
for (int i=0;i<=1;i++) {
gSet.wordDisp_short2(g,"EXP",kx+120,ky+20+i*20,2);
gSet.wordDisp_short2(g,Integer.toString(udt.exp[i]),kx+170,ky+20+i*20,0);
gSet.wordDisp_short2(g,"/",kx+270,ky+20+i*20,2);
gSet.wordDisp_short2(g,Integer.toString(udt.exp_next[i]),kx+290,ky+20+i*20,0);
}
//HPなどを描画 アイテムなどの補正値を加算した最終ステータス
String[] wd2= {"HP","MP","KP"};
kx=40;
ky=300;
for (int i=0;i<=2;i++) {
gSet.wordDisp_short2(g,wd2[i],kx,ky+i*20,2);
gSet.wordDisp_short2(g,Integer.toString(udt.vst_total[i]),kx+70,ky+i*20,0);
gSet.wordDisp_short2(g,"/",kx+150,ky+i*20,2);
gSet.wordDisp_short2(g,Integer.toString(udt.vst_max_total[i]),kx+170,ky+i*20,0);
}
//基礎ステータス描画
kx=40;
ky=380;
String[] wd3= {"STR","PRT","DEX","AGL","MSTR","MPRT","MDEX","MAGL"};
for (int i=0;i<=wd3.length-1;i++) {
gSet.wordDisp_short2(g,wd3[i],kx,ky+i*20,2);
}
for (int i=0;i<=3;i++) {
gSet.wordDisp_short2(g,Integer.toString(udt.fst[i]),kx+70,ky+i*20,0);
gSet.wordDisp_short2(g,Integer.toString(udt.mst[i]),kx+70,ky+80+i*20,0);
}
kx=240;
String[] wd4= {"AP","DP","HIT","AVD","MAP","MDP","MHIT","MAVD"};
for (int i=0;i<=wd4.length-1;i++) {
gSet.wordDisp_short2(g,wd4[i],kx,ky+i*20,2);
}
for (int i=0;i<=3;i++) {
gSet.wordDisp_short2(g,Integer.toString(udt.fst_total[i]),kx+100,ky+i*20,0);
gSet.wordDisp_short2(g,Integer.toString(udt.mst_total[i]),kx+100,ky+80+i*20,0);
}
}


}

とくに難しい事はやっていない。HP,MPとかの項目を表示し、
実数値を各ステータスから読み込んで描画しているだけ。
g,Integer.toString(udt.fst_total[i])
というのは、数値を文字列に変換する処理。
数値を直接渡すとエラーになる。

String(udt.fst_total[i])
ではなぜかダメなので、長いけど上記のやり方で書かないといけない。

[cSelWinDraw関数]
private void cSelWinDraw(int setMode) {

String[] moji= {charaName[0],charaName[1],charaName[2],charaName[3]};
gSet.rectSet2(g,177,64,144,100,0,0);
int stx=204;
int sty=88;
for (int i=0;i<=moji.length-1;i++) {
if (unitLiveCheck(i)==true) gSet.wordDisp_short(g,moji[i],stx,sty+i*22);
}
gSet.wordDisp_short(g,"→",cursorX[setMode],cursorY[setMode]+cursorPos[setMode]*22);


}

charaNameというのはこのクラスにデータを渡すときに設定してある。
プレイヤーのパーティー全員の名前を格納してある変数。
live==0 存在しない名前は空欄に置き換えてある。
sursorX、Yは、モードごとに管理している。
このクラスから抜けない限り前回の値を保持し続ける為、
戻るボタンを押してもカーソルの位置がリセットされない。
毎回リセットした方が安全なのだが、不具合が起きないならこのまま使う。


説明不足の気がするが、次に移る。
今度はアイテムの取得イベントを作る。
そしてそれを使う。
さらにもう一つ、装備品を拾うイベントも作る。

まだ装備する段階まではいかない。
それをやりだすと今回以上に複雑な処理になるので、
前段階としてアイテムの取得と管理、消耗アイテムの使用まで作る。

こんなペースでRPGができるのかよ?
と疑問に思うかもしれないが、もう少しの辛抱だ。
装備まで作ったらいよいよ戦闘シーンに取り掛かるので、

・まずはアイテム取得、使用
・次が装備
・その次が戦闘
そういう順番で作る予定。

全ては戦闘シーンを作るための下準備なので、
データのやり取りが適当だとゲームにならない。


・トップページへ戻る
inserted by FC2 system