RPG01

MAGI JAVA -Make a game in Java-
JAVA Game作成講座 017回
アイテムを装備する


kouza017 jarファイル



[今回追加したもの]

・アイテムを追加で2つ配置した 防具、装飾品
・装備できるようにした
・他、微妙に修正している

アイテム入手オブジェの追加は簡単すぎるので説明を省く。
こうやってデバッグモードが出来上がっていくのだな、と実感できる。
都合のいいアイテム入手やフラグ操作をずらっと並べて、
テストしたい状況を再現するためのモードのようなものだ。
正規マップが出来たら最終的にここには入れないようにする。

まずは追加したイベントの作成手順をおさらいしていく。

//カテゴリー、ナンバーを受けとり、それに対応したアイテムを戻す。基本のアイテム作成方法。
[ItemDataSetクラス]
public ItemData dataSet_easy(int dt1,int dt2) {

init();
ItemData idata=new ItemData();
//System.out.println("dt1, dt2="+dt1+","+dt2);
switch (dt1) {
case 1: //手装備
idata=weaponSet(dt2);
break;
case 2:
idata=armorSet(dt2);
break;
case 3:
idata=accesorySet(dt2);
break;
case 4:
idata=itemSet(dt2);
break;
}
idata.category=dt1;
idata.itemNumber=dt2;

return idata;


}

weaponSetとItemSetは前回作ったもの。
armorSetとaccesorySetを見ていく。
weaponSetを簡略化しただけなのでほとんど同じだが。

//category 2 防具データのセット
public ItemData armorSet(int num) {

ItemData idata=new ItemData();
String[] itemName= {"ダミー防具","布の服","皮鎧","銅鎧","鉄鎧","獣皮鎧"};
idata.name=itemName[num];
int[] price= {0,50,150,400,1000,2000};
int[][] ist= {
{0,0,0,0,0,0,0,0,0,0,0},//0
{0,0,0,0,3,1,0,0,1,0,0},//1
{0,0,0,0,6,2,0,0,2,0,0},//2
{0,0,0,0,10,0,0,0,5,0,0},//3
{0,0,0,0,20,2,0,0,10,0,0},//4
{0,0,0,0,35,2,0,0,15,0,0}//5
};
//上記の変数を各ステータスに割り振っていく。vst0-2 fst3-6 mst7-10の順に入っている。
for (int i=0;i<=2;i++) {
idata.vst[i]=ist[num][i];
}
for (int i=0;i<=3;i++) {
idata.fst[i]=ist[num][i+3];
idata.mst[i]=ist[num][i+7];
}
idata.price=price[num];
return idata;


}
//category 3 防具データのセット
public ItemData accesorySet(int num) {

ItemData idata=new ItemData();
String[] itemName= {"ダミー装飾","守備石1","腕力紐1","敏捷輪1","生命印1","魔力珠1"};
idata.name=itemName[num];
int[] price= {0,200,200,300,500,500};
int[][] ist= {
{0,0,0,0,0,0,0,0,0,0,0},//0
{0,0,0,1,5,1,1,0,3,0,1},//1
{0,0,0,5,1,0,0,0,2,0,0},//2
{0,0,0,0,0,5,3,0,5,0,0},//3
{10,0,2,0,0,2,0,0,0,2,0,0},//4
{0,10,4,0,0,0,0,3,2,2,2}//5
};
//上記の変数を各ステータスに割り振っていく。vst0-2 fst3-6 mst7-10の順に入っている。
for (int i=0;i<=2;i++) {
idata.vst[i]=ist[num][i];
}
for (int i=0;i<=3;i++) {
idata.fst[i]=ist[num][i+3];
idata.mst[i]=ist[num][i+7];
}
idata.price=price[num];
return idata;


}

ステータスの割り振りが違うだけで、武器とほとんどやってることは同じ。
こうやって装備を作り分けているんだなぁ、と思って貰えれば。

次はStatusWindowクラスの追加部分を見ていく。


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

//一部抜粋する。
case 7: //装備変更するキャラ選択中 7-9は連動している
if (pushKey==1) {
if (unitLiveCheck(cursorPos[mode]) ) {
selectUnitNumber=cursorPos[mode];
modeChange=8;
}
}
if (pushKey==2) modeChange=1;
break;
case 8: //装備変更する部位を選択中
if (pushKey==1) {
//選んだユニットの、替えたい装備部位のナンバーを記憶する。武器とか防具とかそういうのな。
selectPartNumber=cursorPos[mode];
//この処理だけはカーソルを初期化する。
//cursorSet(17,40,320,0,10);
cursorSet(9,40,320,199);
//ここからがポイント。アイテムリストから、選んだ部位のナンバーと同じアイテムを抜き出し、番号だけキープする。
int count=0;
int pickCategory=1;//片手装備 左手 右手
if (selectPartNumber==2) pickCategory=2; //体防具
if (selectPartNumber>=3) pickCategory=3; //その他装備
for (int i=0;i<=199;i++) {
pickItemNum[i]=1000; //ありえないほど大きな数値で初期化する。この数値の場合は空欄扱いする。
if (gData.siData[i].category<=3 && gData.siData[i].category==pickCategory) {
pickItemNum[count]=i;
count++;
}
}
pickItemCount=count;//装備部位と同じアイテムの個数
modeChange=9;
}
if (pushKey==2) modeChange=7;
break;
case 9: //リストの中から装備したいアイテムを選ぶ
if (pushKey==1) equipmentItem();
if (pushKey==2) modeChange=8;
break;
}


以下略…
case7 装備変更するキャラを選ぶ。
カーソル位置=変更するキャラのナンバーをキープしてmode8へ移る。

case8 装備変更する部位を選ぶ。
キャラ選択とほとんど同じ処理になる。
0-武器1, 1-2,2-鎧、3-防具1、4-防具2
部位が決まったらカーソル位置=ナンバーをキープしてmode9へ移る。

…の前に、そのカテゴリーのアイテムを集めたリストを作成する。
武器なら武器だけ表示し、防具なら防具だけ。
装備可能アイテムリスト、のような感じで番号を抜き出す。
pickItemNum という配列にそのアイテムのあるアドレスだけ代入していく。
199個目までチェックし終わったら作業終了。
大半は1000が入るので、無効アイテムが後半ずらりと並ぶ。

case9
処理が長くなるので視認性をよくするため関数に分けた。

[ equipmentItem 関数]
public void equipmentItem() {

UnitData cUnit=gData.uData[selectUnitNumber]; //装備を変えるユニット
ItemData cItem=cUnit.eiData[selectPartNumber]; //装備を変える部位のアイテムデータ
//カーソルのあっている所のアイテムが有効なアイテムかどうかチェック
int num=cursorPos[mode];
int pickNum=pickItemNum[num];
if (pickNum<=199) {
ItemData evItem=cItem; //Evacuation 退避の略
ItemData sItem=gData.siData[pickNum]; //新しく装備する予定のアイテムデータ
//その部位に装備があれば退避させる
Boolean evacuationFlag=false;
if (cItem.category>=1) evacuationFlag=true;
idSet.dataCopy(cItem,sItem);
if (evacuationFlag) {//sItemをcItemにコピーする。退避したアイテムデータをアイテムリストにコピーして戻す
gData.siData[pickNum]=evItem;
} else {//装備部位が元から空欄なら抜いたアイテム欄を初期化する
gData.siData[pickNum].init();
}
//装備変更後、ステータスを再計算する。
cUnit.totalDataSet(0);
//装備部位変更モードに戻して終了。
modeChange=8;
} else if (cItem.category>=1) {
int bnum=idSet.blankCheck(gData.siData);
if (bnum<=199) {
idSet.dataCopy(gData.siData[bnum],cItem);
gData.uData[selectUnitNumber].eiData[selectPartNumber].init();
cUnit.totalDataSet(0);
modeChange=8;
}
}


}

それほど難しいことはやっていないが、これを書くのに一番時間を食った。

Boolean evacuationFlag=false;
if (cItem.category>=1) evacuationFlag=true;
idSet.dataCopy(cItem,sItem);
if (evacuationFlag) {//sItemをcItemにコピーする。退避したアイテムデータをアイテムリストにコピーして戻す
gData.siData[pickNum]=evItem;
} else {//装備部位が元から空欄なら抜いたアイテム欄を初期化する
gData.siData[pickNum].init();
}

この処理が少しわかりにくいかもしれない。
装備しようとしている箇所に既に装備がされているか/いないか を調べているだけなんだが。
もしされている evaculationFlag==true なら、今の装備データを別枠evItemにコピーしておく。
で、空いた場所に新しく装備するアイテムのデータを丸ごと写す。

idSet.dataCopy(cItem,sItem); データ渡しに関数を使っているが、これはcItemにsItemの中身をコピーする関数。
cItem=sItemじゃだめなの? という疑問がわくだろう。
それをやるとあとでエラーが起きる。
元データを消したときに、コピー先まで消えてしまうのだ。
参照渡しみたいな扱いになっているのかもしれない。

めんどくさいけど、逐一データを変数ごとに読込んでコピーしまくる。
そういう処理がこのdataCopy関数になる。

} else if (cItem.category>=1) {
int bnum=idSet.blankCheck(gData.siData);
if (bnum<=199) {
idSet.dataCopy(gData.siData[bnum],cItem);
gData.uData[selectUnitNumber].eiData[selectPartNumber].init();
cUnit.totalDataSet(0);
modeChange=8;
}
}



選択した装備予定のアイテムが空欄だった場合の処理。
何かを装備していた場合に限り、装備解除を行う。
blankCheckでアイテムリストの空欄をサーチする。
空欄が見つかったらその場所に装備中のアイテムデータをコピーする。

idSet.dataCopy(gData.siData[bnum],cItem);
gData.uData[selectUnitNumber].eiData[selectPartNumber].init();
ポイントはここ。initする際に直接操作する変数を指定している事。
cItem.init();
でも同じじゃない? と思うかもしれないが、
cItemはあくまで参照のようなものなので、
これを操作するとコピー先のgData.siData[bnum]も干渉を受ける。
普通に上手く行く時もあるが、
何度も装備→装備解除を繰り返しているとアイテムが消える。
そういう時は面倒くさいけど元データを直接操作すると安全。

では最後にpaintを見ていく。


if (mode>=7 && mode<=9) {
cSelWinDraw(mode);
}

キャラセレクト用のウィンドウを描いている。
一連の処理中表示し続けるので、7-9の間有効にしている。

if (mode==8) {

gSet.rectSet2(g,32,220,280,260,0,220);

UnitData cUnit=gData.uData[selectUnitNumber];
gSet.wordDisp_short2(g,"NAME",40,240,2);
gSet.wordDisp_short2(g,cUnit.name,110,240,0);

String[] word= {"片手1","片手2","体防具","防具1","防具2"};
for (int i=0;i<=4;i++) {
gSet.wordDisp_short2(g,word[i],62,280+i*22,2);
String word2="――――――――" ;
if (cUnit.eiData!=null) {
if (cUnit.eiData[i].category>=1) {
word2=cUnit.eiData[i].name;
}
}
gSet.wordDisp_short2(g,word2,140,280+i*22,0);
}

gSet.wordDisp_short2(g,"→",cursorX[8],cursorY[8]+cursorPos[8]*22,2);


}

装備部位を表示し、そこに装備しているアイテム名も表示している。
無装備の場合は無条件で棒線一本だけ描画するようにしている。

if (mode==9) {

gSet.rectSet2(g,32,220,280,380,0,220);
UnitData cUnit=gData.uData[selectUnitNumber];
gSet.wordDisp_short2(g,"NAME",40,240,2);
gSet.wordDisp_short2(g,cUnit.name,110,240,0);
String barstr="――――――――";
String[] word= {"片手1","片手2","体防具","防具1","防具2"};
gSet.wordDisp_short2(g,word[selectPartNumber],62,280,2);
String str2="";
if (cUnit.eiData[selectPartNumber].category>=1) {
str2=cUnit.eiData[selectPartNumber].name;
} else {
str2=barstr;
}
int page=cursorPos[9]/10;
int kz=cursorPos[9]-page*10;
gSet.wordDisp_short2(g,str2,140,280,0);
gSet.wordDisp_short2(g,"→",cursorX[9],cursorY[9]+kz*22,2);

for (int i=0;i<=9;i++) {
String str=barstr;
int num=page*10+i;
if (pickItemNum[num]<=200) { //1000で入ってる奴は存在しないアイテム
ItemData cItem=gData.siData[pickItemNum[num]];
str=cItem.name;
}
gSet.wordDisp_short2(g,str,62,320+i*22,0);
}
gSet.wordDisp_short2(g,"No."+cursorPos[9],62,558,0);
gSet.wordDisp_short2(g,"Page "+page+"/ 19",62,580,0);

//現装備と、変更後の装備の対比を出来るウィンドウを表示する。
gSet.rectSet2(g,312,220,380,380,0,220);

String[] word2= {"HP","MP","KP","―――――――――――――――","AP","DP","HIT","AVD","MAP","MDP","MHIT","MAVD"};
int sx=396;
int sy=320;
for (int i=0;i<=word2.length-1;i++) {
gSet.wordDisp_short2(g,word2[i],322,320+i*22,2);
}

for (int i=0;i<=2;i++) {
gSet.wordDisp_short2(g,""+cUnit.vst_max_total[i],sx,sy+i*22,0);
}
sy=410;
for (int i=0;i<=3;i++) {
gSet.wordDisp_short2(g,""+cUnit.fst_total[i],sx,sy+i*22,0);
gSet.wordDisp_short2(g,""+cUnit.mst_total[i],sx,sy+(i+4)*22,0);
}
sy=320;
if (pickItemNum[cursorPos[9]]<=200) {
ItemData nItem=cUnit.eiData[selectPartNumber];
ItemData pItem=gData.siData[pickItemNum[cursorPos[9]]];
//現在の装備品と、新しく装備する予定の物のデータを比較して差を割り出す
for (int i=0;i<=2;i++) {
int vst_sa=pItem.vst[i]-nItem.vst[i];
String st="↑";
if (vst_sa<-1) st="▼";
if (vst_sa!=0) gSet.wordDisp_short2(g,st+vst_sa,sx+80,sy+i*22,2);
}
for (int i=0;i<=3;i++) {
int fst_sa=pItem.fst[i]-nItem.fst[i];
int mst_sa=pItem.mst[i]-nItem.mst[i];
String st="↑";
if (fst_sa<-1) st="▼";
if (fst_sa!=0) gSet.wordDisp_short2(g,st+fst_sa,sx+80,sy+(i+4)*22,2);
st="↑";
if (mst_sa<-1) st="▼";
if (mst_sa!=0) gSet.wordDisp_short2(g,st+mst_sa,sx+80,sy+(i+8)*22,2);
}
}


}

やたら長く分かりにくいかもしれないが、何をやっているかだけ書いておく。
大きく2つに処理が分かれている。
一つ目。
装備部位と装備しているアイテムを抜き出し、最上段に表示。
次の処理は10アイテム1ページの単位で装備できるアイテム名を描画している。
pickItemNumを元にデータを抜き出す為、大半が空欄で埋まるはず。

二つ目の処理は今の装備でのステータスを表示。
カーソルのあっている場所のアイテムを装備した場合、
どのステータスが幾つ変動するかを描画している。

変動値の出し方がポイント。
今の装備と、装備予定のアイテムの全ステータスで引き算をおこない結果を記憶。
それを即描画して、次のステータスに移る。
これをvst-fst-mstの全ステータス終わるまで繰り返している。
のちに耐性値の増減も加えたらさらに描く部分が増えるが、基本はこれと同じ処理になる。

長く分かりにくい処理でもいくらかの単位で分けて考えるとそれほど難しい事はやっていない。
ただ積み重ねて行ったら長くなっただけで、一項目ずつ作っていけば完成する。
描画はエラーが出る率が低いが、参照する変数のサイズを越えると即エラーになる。
マックス値を意識して処理を見ていくこと。
エラーが出たらその部分が判明するまでコメントアウトして何度もエラー元を調べる。

追加でもう一つ。
[オブジェデータ打ち込みのアレンジ]
今回のやつには組み込んでいないが、次回からこれに変わっている。

[GameDataクラス]
public void objKariSet() {

objCount=0;
objKariSet2(6,6,0,2,1,2);
objKariSet2(10,6,0,3,1,1);
objKariSet2(14,6,0,3,4,1);
objKariSet2(18,6,0,3,4,2);
objKariSet2(22,6,0,3,2,1);
objKariSet2(26,6,0,3,3,1);


}

public void objKariSet2(int x,int y,int shift,int evnum,int dt1,int dt2) {

ObjectBase obj=new ObjectBase();
obj=odtSet.dataSetEasy(x*32,y*32,shift,objCount,evnum,dt1,dt2);
obj.setMapSize(mapSize[0]*32, mapSize[1]*32);
objs.add(obj);
objCount++;


}

オブジェクトの数が増えてきたので、入力で手抜きをする。
・objectCountというグローバル変数を使い、これの打ち込みを簡略化した。
・x,yの座標入力も32ドット単位でしか使わないので、架空マップ座標を打つタイプに変えた。
objにデータを移すときに実座標に切り替えている。
・objKariSet2が呼ばれるたびに自動的に一個ずつ増やすので、
「これ何番目のイベントだったっけ?」と覚えていなくても連番が打たれていく。

あとは繰り返し同じ文章打たなくてもいいので見やすくなる+エラーの率がへる。
気がついたらこういうショートカットを作っていくとデータ入力が楽になる。
デバッグ中はすぐに結果が見たいので、打ち込みの手間が少ないほど有難い。

[さらにもう一つ地味な修正]
[ARPanelクラス objDraw]
public void objDraw(Graphics g,BufferedImage gra, int[] oxy,int pmc) {

//ここの数値が間違っていた。pShift==8のときにsy=148にしていたので、
//上を向くと座標がずれる…。正しくは144です。これで上下左右違和感なく動く。
if (pShift==8) sy=144;
if (pShift==2) sy=0;
if (pShift==4) sy=48;
if (pShift==6) sy=96;



今回は以上で終わり。
次は戦闘シーン…ではなく、場所移動イベントを作る。
戦闘シーン作り出すとそれにかかりっきりで時間を食うので、
ゲームとして最低限必要なイベントを作っておきたい。
それが出来たら簡単な特技を追加し、いよいよ戦闘にする予定。

順番としては、
18回 場所移動イベント作成 + HPなどを全回復の結界作成
19回 特技・魔法の追加
20回 戦闘シーン1 作成開始
となる予定。
回復イベントを先行して作る事で、
戦闘イベントを連続でテストできる利点がある。

特技、魔法無しで通常攻撃のみの戦闘シーンを作ってもいいが、
すぐに追加しなきゃならなくなるので、それなら最初からアリで作成したほうがいい。
魔法や特技の無い戦闘はつまらないし、「レベルを上げて物理で殴る」単調なゲームになるだけだ。

面白そうな部分を早く作りたい気持ちはよくわかる。
戦闘シーンは今までの処理よりも複雑でリアルタイム処理がいくらか必要になる。
エラーとテストランに時間を食っているうちに通常イベントの作り方を忘れてしまうのだ。
ゲームとしての基本部分を忘れる前に作っておいた方がいい。

まずは出来そうな部分から手を付けていくのが完成のコツ。
あまりにもエラー続きでうんざりするとやる気も無くなる。
手ぶらでいきなり壁に挑むより、出来た部分を積み重ねていくと壁を越えやすい。



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