RPG01

MAGI JAVA -Make a game in Java-
JAVA Game作成講座2 12回
アイテムエディター作成1


RPGkouza2-12 zipファイル


[アイテムエディター作成1 一通りの設定とセーブ・ロードまで一気に作成]
objエディターと被る部分が多いのでほとんど全部作ってしまった。
ただ、細かい調整をしていないので無駄が多く、足りないものも多い。
さらに各エディターで重複する処理が多かったのでgDataクラスにまとめた部分がある。
(UnitEditorでも同じような処理が続くので、極力リサイクルしていく)

同じ事ばかりやっても面白くないので、今回のエディターではマウスを積極的に使えるようにした。
タイトル画面でもマウスで各エディターを起動できるようにしている。
右クリックで戻るとか、今までのエディターで使っていない機能を実験している。
では、gDataにまとめた部分を少し見ていく。

[GameData クラス]
public int ppKeyCheck() {

mouseX=pPlatform.mouseEvDragged[1];
mouseY=pPlatform.mouseEvDragged[2];
click=pPlatform.mouseEvDragged[0];

mouseCX=pPlatform.mouseEv[1];
mouseCY=pPlatform.mouseEv[2];
releaseC=pPlatform.mouseEv[3];
clickC=pPlatform.mouseEv[0];
if (releaseC==1 && mouseClicked==1) {
System.out.println("mouseRelease sitayo");
mouseClicked=0;
}

pushKey=0;
for (int i=0;i<=19;i++) {
if (pPlatform.keyPush[i]) {
if (i==4) sePlay(1);
if (i==5) sePlay(2);
pPlatform.keyPush[i]=false;
pushKey=i;
}
}
return pushKey;


}

今まで各クラスでキーの読み取り、マウスの読み取りをやっていたのをgDataにまとめた。
押したキーNo.をpushKeyとしてReturnすることでキー入力を読み取る。
マウスの状態もgDataで管理するようにしたので、必要な時にここから抜き取る。
かなりシンプルにまとまっているが、ここまで集約するのに数日かかった。
だが、今後キー入力の場面が来たときにこれを応用することでかなり行数を減らせる。

if (releaseC==1 && mouseClicked==1) {
System.out.println("mouseRelease sitayo");
mouseClicked=0;
}

この処理が地味に大事。マウスをリリースするまでmouseClickedを入れっぱなしにしておく。
mouseClicked=1にするタイミングはどこで? という疑問がわくだろう。
それは、各クラスのキー入力などを全て読み取った最後の行に入れておく。
なぜそのタイミングなのかと言うと、
「大抵のボタンがmouseClicked==1の状態で反応しないようにしている」から。

上記の関数でmouseClicked=1にしてしまうと、
一回目のクリックなのにほとんどのボタンが無反応になる。
反応するのは押しっぱなしでも動くボタンだけ。
そのため、全てのキー入力を読み取った後のラストでクリックしたフラグを立てるわけ。

データ入力のエディターではボタンの反応は1回だけにしないとまずい処理が多い。
戻るボタンにしても押しっぱなしで反応してしまうと細かい前後移動ができなくなる。

public Boolean buttonCheck(int bx,int by,int width,int height,int type) {

Boolean hit=false;
Boolean buttonHit=false;
if (type==0 && clickC==1 && mouseClicked==0) buttonHit=true;
if (type==1 && clickC==1) buttonHit=true; //clicked==1でも反応するパターン

if (buttonHit) {
if (mouseCX>=bx && mouseCX<=bx+width) {
if (mouseCY>=by && mouseCY<=by+height) {
hit=true;
}
}
}
return hit;


}

ボタンをクリックしたら入る関数。
これも地味ながらまとめるのに苦労した…。
ポイントはtypeの追加。0の場合は1回クリックしたら押しっぱなしだと反応しないボタン。
1にするとドラッグ状態でもボタンをクリックし続けるボタン。
マップエディターの塗りつぶし、スクロールバーだけに今は使っている。
基本はtype0で指定し、特殊なケースだけ1にする事で使い分ける。

あとはマウスの座標があっていることでhitしているか否かを返す。
この関数も多くのエディターで共有するのでgDataクラスに集約した。

public void mouseClickCheck() {
if (clickC>=1 && mouseClicked==0) mouseClicked=1;
}

public Boolean mouseCanselCheck() {
Boolean hit=false;
if (clickC==3 && mouseClicked==0) hit=true;
return hit;
}

public String messageScan(String[] args) {
System.out.println("キーボードから入力してください");
Scanner scan = new Scanner(System.in);
String str = scan.next();
return str;
}

mouseClickCheck
クリックしっぱなしを判定する処理。
各クラスの一通りのマウスイベントを通した最後の場所に配置する。

mouseCanselCheck
これも上の物と似ているが、座標と関係なく作動させるのが違う。
右クリックしつつ、押しっぱなしでない場合のみ作動させる。
押しっぱなしでも作動するようにしてしまうと、一度押すだけで連打してしまう。

messageScan
今回の新処理。新しく作ったItemEditorクラスで使い始めた。
コンソール上から日本語入力できるので、アイテム名の設定に使っている。
本当はゲーム画面から入力できるのがベストだが、やり方がわからない。
今できるのはこれしかないので、妥協したやり方でひとまず良しとした。
上達したら直すと思う。
ひらがな、カタカナのみならできるんだけどねぇ…。

追加分はこんな感じ。かなり長くなるが、ItemEditor本体のコードを丸ごとコピペしておく。

import java.awt.Graphics;

public class ItemEditor {

public Graphics g;
private GameData gData=null;
public GraphicSet gSet=null;
public boolean editEnd=false;
public int[] cursorX=new int[60];
public int[] cursorY=new int[60];
public int[] cursorPos=new int[60];
public int[] cursorMax=new int[60];
public int mode=0;
public int[][][] iData=null;
public String[][] iName=null;

public int keepItemNumber=0;
public int keepType=0;
public int keepNum=0;

private final int ITEMMAX=999;
public int[] setNumber=null;
public int setNumber_max=0;

public DataSaveLoad dataSL=null;
public int infoMesCount=0;
public String infoMes="";
public int setType=0;
public int keepMode=0;
public int setCategory=0;
public int setItemNumber=0;
public int setItemStatus=0;

public ItemEditor() {
gData=new GameData();
gSet=new GraphicSet();
dataSL=new DataSaveLoad();
init();
}

public void init() {
editEnd=false;
mode=1;
for (int i=0;i<=59;i++) {
cursorX[i]=0;
cursorY[i]=0;
cursorPos[i]=0;
cursorMax[i]=0;
}
cursorMax[1]=3;
cursorMax[2]=99;
cursorMax[3]=11;
iData=new int[5][101][21]; //カテゴリーごとに管理。
iName=new String[5][101];
for (int i=0;i<=4;i++) { //カテゴリー
for (int j=0;j<=99;j++) { //材質
iName[i][j]="――――――";
for (int k=0;k<=20;k++) { //材質ごとの実データ 20あれば足りると思う
iData[i][j][k]=0;
}
}
}

setNumberInit(4,0,0);

keepItemNumber=0;
keepType=0;
keepNum=0;
infoMesCount=0;
infoMes="";
setCategory=0;
setItemNumber=0;
setItemStatus=0;
}

public void setNumberInit(int max,int type,int kmode) {
setNumber=new int[11];
for (int i=0;i<=10;i++) {
setNumber[i]=0;
}
setNumber_max=max;
setType=type;
keepMode=kmode;
}

public void dataSet(GameData gdt) {
init();
gData=gdt;
//このエディターはセーブデータが固定なのでこの段階でデータを読み込んでおく。
//セーブは任意のタイミングでやるので、オートロードだけ実装する。
iData=dataSL.itemDataLoad();
iName=dataSL.itemNameLoad();
}

public void eventStart(GameData gdt) {
gData=gdt;
int pushKey=0;
int cPlus=0;
int modeChange=0;
//gDataにごっそり移動してみた。キーの受け取り方が変わるが、相当行を圧縮できる。
//ほかのエディターもこっちのやり方に差し替えていく。
pushKey=gData.ppKeyCheck();
if (pushKey==8) cPlus=-1;
if (pushKey==1) cPlus=1;
if (pushKey==2 && mode==2) cPlus=-20;
if (pushKey==3 && mode==2) cPlus=20;

cursorPos[mode]+=cPlus;
if (cursorPos[mode] > cursorMax[mode]) cursorPos[mode]=0;
if (cursorPos[mode] < 0) cursorPos[mode]=cursorMax[mode];

int cpos=cursorPos[mode];

if (gData.clickC==1) {
//modeChange=1;
if (gData.buttonCheck(20,0,60,30,0) ) editEnd=true; //終了ボタン
if (gData.buttonCheck(80,0,60,30,0) ) { //SAVEボタン
dataSL.itemDataSave(iData);
dataSL.itemNameSave(iName);
infoMesSet(1);
}
if (gData.buttonCheck(140,0,60,30,0) ) { //LOADボタン
//oData=dataSL.objDataLoad(mapNumber,floorNumber);
iData=dataSL.itemDataLoad();
iName=dataSL.itemNameLoad();
infoMesSet(2);
}

}
switch (mode) {
case 1://総合メニュー
if (pushKey==4) {
setCategory=cursorPos[mode]+1;
modeChange=2;
}
if (pushKey==5) {
editEnd=true;
}
for (int i=0;i<=3;i++) {
if (gData.buttonCheck(20,60+i*22,80,22,0) ) {
setCategory=i+1;
cursorPos[mode]=i;
modeChange=2;
}
}
break;
case 2://データ設定するアイテム名を選ぶ
if (pushKey==4) {
setItemNumber=cursorPos[mode];
modeChange=3;
}
if (pushKey==5) modeChange=1;
int page=cursorPos[2]/20;
for (int i=0;i<=19;i++) {
if (gData.buttonCheck(280,60+i*22,60,22,0) ) {
setItemNumber=i+page*20;
String mes[]=null;
String str=gData.messageScan(mes);
iName[setCategory][setItemNumber]=str;
}
if (gData.buttonCheck(140,60+i*22,120,22,0) ) {
setItemNumber=page+i;
cursorPos[2]=setItemNumber;
modeChange=3;
}

}
if (gData.mouseCanselCheck() ) modeChange=1;
break;
case 3: //変更するデータを選ぶ。
if (pushKey==4) {
setItemStatus=cursorPos[mode];
setNumberInit(6,1,3);
modeChange=4;
}
if (pushKey==5) modeChange=2;
if (gData.mouseCanselCheck() ) modeChange=2;
break;
case 4: //データを入力する。objeditorで作ったキー入力ウィンドウを使う。
if (pushKey==4 || pushKey==6) {
String word="";
for (int i=0;i<=setNumber_max-1;i++) {
word+=setNumber[i];
}
int result=Integer.parseInt(word);
if (setType==1) iData[setCategory][setItemNumber][setItemStatus]=result;
modeChange=keepMode; //入力後は一個前に戻す。

}
if (pushKey==7) {
int[] setNumber2=new int[11];
for (int i=0;i<=10;i++) {
setNumber2[i]=0;
}
for (int i=1;i<=setNumber_max-1;i++) {
setNumber2[i]=setNumber[i-1];
}
setNumber=setNumber2;
}

if (pushKey>=10 && pushKey<=19) {
if (setNumber[0]==0) {
for (int i=0;i<=setNumber_max-2;i++) {
setNumber[i]=setNumber[i+1];
}
setNumber[setNumber_max-1]=pushKey-10;
}
}
if (pushKey==5) modeChange=3;
if (gData.mouseCanselCheck() ) modeChange=3;
break;

}
gData.mouseClickCheck();
if (modeChange>=1) mode=modeChange;
if (infoMesCount>=1) infoMesCount--;
}


public void infoMesSet(int num) {
String word="";
if (num==1) word="SAVEしました";
if (num==2) word="Loadしました";
infoMes=word;
infoMesCount=30;
}

public void paint(Graphics gr) {
g=gr;
//カーソル位置と無関係に独立した処理をするボタンの描画
int stx=20;
int sty=0;
String[] str= {"END","SAVE","LOAD"};
for (int i=0;i<=2;i++) {
gSet.rectSet2(gr,stx+i*60,sty,60,30,0,0);
gSet.wordDisp_short2(gr,str[i],stx+i*60,sty+20,0);
}
stx=20;
sty=60;
int page=cursorPos[2]/20;
int cy=sty+(cursorPos[2]-page*20)*22;
gSet.rectSet2_2(gr,stx,sty+cursorPos[1]*22,80,20,0,0);
String[] cateList= {"武器","防具","装飾","アイテム"};
for (int i=0;i<=cateList.length-1;i++) {
gSet.wordDisp_short2_2(g,cateList[i],stx,sty+i*22,0);
}
if (mode>=2 && mode<=4) {
int stx2=stx+120;
int cy2=sty+(cursorPos[2]-page*20)*22;
gSet.rectSet2_2(gr,stx2,cy2,120,20,0,0);
for (int i=0;i<=19;i++) {
int inum=i+page*20;
gSet.wordDisp_short2_2(g,""+inum,stx2-30,sty+i*22,0);
gSet.wordDisp_short2_2(g,iName[setCategory][inum],stx2,sty+i*22,0);

if (mode==2) {
String str2= "名前";
gSet.rectSet2(gr,stx2+140,sty+i*22,60,22,0,0);
gSet.wordDisp_short2(gr,str2,stx2+140,sty+20+i*22,0);
}
}
}

if (mode>=3 && mode<=4) {
int stx2=stx+260;
int cy2=sty+cursorPos[3]*22;
gSet.rectSet2_2(gr,stx2,cy2,60,20,0,0);
String[] stnameList= {"HP","MP","KP","STR","PRT","DEX","AGL","MSTR","MPRT","MDEX","MAGL","Price"};
for (int i=0;i<=stnameList.length-1;i++) {
gSet.wordDisp_short2_2(g,stnameList[i],stx2,sty+i*22,0);
gSet.wordDisp_short2_2(g,""+iData[setCategory][setItemNumber][i],stx2+70,sty+i*22,0);
}
}
if (mode==4) {
if (setType<=2) gSet.setNumberDisp(gr,setNumber,setNumber_max,420,60+cursorPos[3]*22);
}

if (infoMesCount>=1) gSet.wordDisp_short2(g,infoMes,20,50,0);
}


}

あまり解説する部分も無いな。名前入力はボタンから呼び出している。
キーボードだけだと名前入力できないが、既にほかのエディターでもsave, loadがマウス必須なので併用前提で作った。
scanに入ったらコンソールで入力するまで一切他の作業ができなくなるのが怖い。
ここらへんがネックだわな…もっといい打ち込み方ないかな?
他の処理は大抵使いまわしなのでobjEditorと共通している。

if (gData.buttonCheck(20,0,60,30,0) ) editEnd=true; //終了ボタン
if (gData.buttonCheck(80,0,60,30,0) ) { //SAVEボタン
dataSL.itemDataSave(iData);
dataSL.itemNameSave(iName);
infoMesSet(1);
}
if (gData.buttonCheck(140,0,60,30,0) ) { //LOADボタン
//oData=dataSL.objDataLoad(mapNumber,floorNumber);
iData=dataSL.itemDataLoad();
iName=dataSL.itemNameLoad();
infoMesSet(2);
}

save loadだけ少し今までと違うのでそこだけ説明を入れる。
戻り値として変数を受け取るので、アイテムデータとアイテム名を別々にセーブ/ロードしている。
グローバル変数として一時保持させるなら一個にまとめてもいいのだが、
クラス間の依存性が高まるのは良くない。なるべく独立させて使いたいので、少々手間だが分けた。

[DataSaveLoadクラス] save部分

public void itemDataSave(int[][][] iData) {
try {
Properties properties = new Properties();
String fileName="ITEM_DATA.properties";
for (int a=0;a<=4;a++) {
for (int b=0;b<=100;b++) {
String line="";
//アイテムステータスのデータを1行にまとめる。データ量を減らせる…。
for (int c=0;c<=20;c++) {
line+=iData[a][b][c]+",";
}
properties.setProperty("iData"+a+"_"+b,String.valueOf(line) );
}
}
properties.store(new FileOutputStream(fileName), "Comments");
} catch (IOException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}

}
public void itemNameSave(String[][] iName) {
try {
String fileName="ITEM_NAME.properties";
Properties properties = new Properties();
for (int a=0;a<=4;a++) {
for (int b=0;b<=100;b++) {
properties.setProperty("iName"+a+"_"+b,String.valueOf(iName[a][b]) );
}
}
properties.store(new FileOutputStream(fileName), "Comments");

} catch (IOException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}

}

設定要素が多いので複雑に見える。
a アイテム属性-武器、防具、アクセサリ、アイテム-
b アイテム種類 武器なら木刀、鉄剣、などの各アイテムの分類
c 各アイテムの詳細データ
という3要素を保存するのでデータが多くなる。
名前は別枠で保存している。同じファイルにまとめるとごちゃごちゃしそうだったので。

Load部分----------------

public int[][][] itemDataLoad() {
int[][][] iData=new int[5][101][21]; //カテゴリーごとに管理。
for (int i=0;i<=4;i++) { //カテゴリー
for (int j=0;j<=99;j++) { //材質
for (int k=0;k<=20;k++) { //材質ごとの実データ 20あれば足りると思う
iData[i][j][k]=0;
}
}
}

try {
Properties properties = new Properties();
String fileName="ITEM_DATA.properties";
properties.load(new FileInputStream(fileName) );
for (int a=0;a<=4;a++) {
for (int b=0;b<=100;b++) {
String line=properties.getProperty("iData"+a+"_"+b);
String[] spstr=new String[100];
spstr=line.split(",",0);
for (int c=0;c<=20;c++) {
iData[a][b][c]=Integer.parseInt(spstr[c]);
}
}
}

return iData;
} catch (IOException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
return iData;
}

public String[][] itemNameLoad() {
String[][] iName=new String[5][101];
for (int i=0;i<=4;i++) { //カテゴリー
for (int j=0;j<=100;j++) { //材質
iName[i][j]="――――――";
}
}

try {
Properties properties = new Properties();
String fileName="ITEM_NAME.properties";
properties.load(new FileInputStream(fileName) );
for (int a=0;a<=4;a++) {
for (int b=0;b<=100;b++) {
iName[a][b]=properties.getProperty("iName"+a+"_"+b);;
}
}

return iName;
} catch (IOException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
return iName;
}

LoadもSave同様、アイテム名とアイテムデータを分けている。
あとはSaveの逆処理で、変数に格納しなおしてそのまま戻すだけ。
本当はアイテム名も一行にまとめるとデータ容量が節約できるのだが、
今の所は一行一アイテム名で保存している。
テキストファイルで読み込んでも意味不明な数字が羅列されているので訳が分からない。
loadすると普通の文字に戻るので不思議なものだ。

次のステップではここで作ったアイテムデータを実際のゲームに反映させてみる。
売り物がガラッと変わるのですぐチェックは終わるだろう。
その次の処理としてはユニットエディターを作り始める。
その入り口まで作ったら次のステップとしてまとめる。

日進月歩だが少しずつ形を変え、時に大胆に破壊して再構築する。
最終的に完成した物は多分最初の原型をとどめていないと思う。
だけど、元となる基本形はどのプログラムでも同じ。
それをどうしたら短くかけるか、重複を無くせるかと切り詰めていくから変化するのだ。
最初からシンプルに書こうとすると何も書けないまま終わるだろう。
ムダや失敗を繰り返して改良の余地が初めて見えてくる。


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