今からはじめるプログラミング14
すいません。前回が12ではなく13なので、こちらは14になります。
そしてさらにすいません。簡単な戦闘を行うくらいまで実装したかったのですが、前回、なんとなく違和感があった部分。FieldRendererでモンスター遭遇イベントを記述していたのですが、イベント発生→小画面表示、親画面(フィールドの画面)を使用できないようにする、という感じで実装しようとしたところ、イベントが何度も発生している、というバグに出会いました。
実際はバグではなく、イベントがなども発生するプログラムを書いていた、ということでした。
まぁバグというのはそういうものですね。
そんなわけで、イベントの発生をフィールドの方に異動。
バグの原因はレンダラーはカラム(れつ)ごとに設定されているので、マップが5かける5なので、5回発生する。。。みたいな。
また「東」のみ移動するプログラムでしたが、これを「東西南北」に移動できるように改造(EastListenerとしていましたが、要は方向が違うだけなので、同じリスナーで4方向移動できるように修正。
さらに、いままで移動するメソッドの中身はテーブルの大きさを5かける5と固定でプログラムしていたのですが、これだと別の大きさのマップに対応できないため、マップの配列の長さでループするように変更しました。(こういうプログラムが僕は苦手でめんどうだった。。。すいません。プログラムらしいプログラムといえばそんな感じなのですが、、、、別途機会を儲けたいですね。ソートとか。アルゴリズムというもの。)
とはいえテーブル作成時などその他の場所も修正しないといけないと思いますが、将来のあたりをつける感じです。
今回の修正で、かなりいろんな場所をいじったので、なかなか解説が難しいです。
そして今回メインで解説したかった、画面と画面の制御(モーダルダイアログ)について。
夜勤明けで眠気がとれず上手い言葉が見当たりませんが、フィールドを歩いているとイベントが発生、モンスターとの遭遇であれば、戦闘画面が表示される、みたいな感じでゲームが進むのをイメージしています。
すると、メインの画面、ここではフィールドの画面に対して、画面自体を書き換えて、戦闘画面に変更してしまえばいいのですが、画面の構成がガラリと変わる場合、一つの画面に書ききるにはかなり見づらいプログラムになってしまうことが想定されますので、別の画面にした方が、手っ取り早そうだ・・・という作り手の都合ですね。画面を別に作成する。すると画面の数が増えても画面ごとのプログラムは少なくて済むみたいな。
また、複数の画面を構成する場合、一つの画面を表示している間、別の画面は操作されてしまうのは都合が悪い、そういう時に、画面間での制御を行います。モーダルダイアログという感じです。例えば、入力画面で入力項目にエラーがあった場合など、メッセージのウィンドウは、それが閉じられるまで、もと画面は操作できない、というものならわかりますよね。
今回も戦闘などをしている間にフィールドを歩き回られては困るわけです。
呼び出し元を使えないようにして、別の画面を呼び出す、そういう制御を行う画面をモーダルダイアログと呼んでいます。
さらに重要なことに注目します。今までも使用してはいたのですが、「参照値」を渡す、というプログラミングです。正確にはjavaの参照渡しは他の言語とは違うという方もいらっしゃいますが、僕には難しいので、単純に引数で渡すものが別のものになるか、同じものの値を変えているのか、ぐらいの違いと思っています。
でその画面と画面において、遷移元の画面の制御を遷移先で行いたいので、遷移先の画面に遷移元の画面の参照を渡します。最初のころのサンプルプログラムでファイルのパスを次の表示画面に渡していたり、フラグを管理するクラスのインスタンスをいろんなところで利用したい、という感じでプログラムしていたものです。
便利に使ってますが、なかなか難しい理屈が潜んでいたりします。僕は難しいことはわかりませんが。
そんなわけで、動かして思うようにうごかないな、という時に初めて、参照が渡っていない、異なるオブジェクトが渡ってしまっていることに気が付く、というようなトラブルに遭遇したりします。
そういうわけで、モーダル表示するまでのプログラムになってしまいました。
アルファベット順に。
FieldMap
---------------------------------------
package sample7;package sample7;
import javax.swing.JFrame;import javax.swing.JPanel;import javax.swing.JTable;
public class FieldMap extends JPanel {
private JTable tblField = null; private JFrame parent = null;
private String fieldCode = "";
public String getFieldCode() { return this.fieldCode; }
public JFrame getParent() { return this.parent; }
private String map = new String { { "2", "2", "2", "2", "2" }, { "2", "1", "1", "1", "2" }, { "2", "1", "1", "1", "2" }, { "2", "1", "1", "1", "2" }, { "2", "2", "2", "2", "2" }, };
public String getMap() { return this.map; }
public static final int V_NORTH = 1; public static final int V_SOUTH = 2; public static final int V_EAST = 3; public static final int V_WEST = 4;
public void shift(int vector) { int mapLen = map.length; System.out.println("mapLen=" + mapLen); // save String sv = new String[mapLen];
if (vector == V_SOUTH) { for (int i = 0; i < mapLen; i++) { int mapLimt = mapLen - 1; for (int j = 0; j < mapLen; j++) { if (j == 0) { // 最初 sv[i] = map[j][i]; } if (j == (mapLimt - 1)) { // 最後 map[j][i] = map[j + 1][i]; map[j + 1][i] = sv[i]; break; } map[j][i] = map[j + 1][i]; } } } if (vector == V_NORTH) { // save for (int i = 0; i < mapLen; i++) { int mapLimt = mapLen - 1; for (int j = mapLimt; j > 0; j--) { if (j == mapLimt) { // 最初 sv[i] = map[j][i]; map[j][i] = map[j - 1][i]; } if (j == 1) { // 最後 map[j][i] = map[j - 1][i]; map[j - 1][i] = sv[i]; break; } map[j][i] = map[j - 1][i]; } } } if (vector == V_EAST) { // save for (int i = 0; i < mapLen; i++) { int mapLimt = mapLen - 1; for (int j = 0; j < mapLen; j++) { if (j == 0) { // 最初 sv[i] = map[i][j]; } if (j == (mapLimt - 1)) { // 最後 map[i][j] = map[i][j + 1]; map[i][j + 1] = sv[i]; break; } map[i][j] = map[i][j + 1]; }
} } if (vector == V_WEST) { // save for (int i = 0; i < mapLen; i++) {
int mapLimt = mapLen - 1; for (int j = mapLimt; j > 0; j--) { if (j == mapLimt) { // 最初 sv[i] = map[i][j]; map[i][j] = map[i][j - 1]; } if (j == 1) { // 最後 map[i][j] = map[i][j - 1]; map[i][j - 1] = sv[i]; break; } map[i][j] = map[i][j - 1]; } } }
}
public void init() { String map = this.getMap(); tblField = new JTable(5, 5);//TODO マップの広さで // テーブルに値を設定 for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { tblField.setValueAt(map[i][j], i, j);
} } // テーブルにレンダラーを設定 for (int j = 0; j < 5; j++) { tblField.getColumnModel().getColumn(j).setCellRenderer(new FieldRenderer()); tblField.getColumnModel().getColumn(j).setPreferredWidth(32); } // 最後にキャラクターを表示する(キャラクターのコードを0として)
tblField.setValueAt("0", 2, 2);// TODO まんなか
// テーブルをパネル(自分)に追加 this.add(tblField); }
public void resetValue(String[] map) { // テーブルに値を設定 for (int i = 0; i < 5; i++) {//TODO マップの広さで for (int j = 0; j < 5; j++) {//TODO マップの広さで tblField.setValueAt(map[i][j], i, j);
} } // キャラクターで上がくフィールドの情報 this.fieldCode = map[2][2]; System.out.println("fieldCode=" + this.fieldCode); // 最後にキャラクターを表示する(キャラクターのコードを0として)
tblField.setValueAt("0", 2, 2);// まんなか
}
public FieldMap(JFrame parent) { this.parent = parent; this.init(); }}
--------------------------------------
コピペしたらぐちょってなった。。
次にFieldPropertiesとFieldProperty
--------------------------------------
package sample7;
import java.util.HashMap;
public class FieldProperties {
public static final String KEY_SEA = "2";
public static final String KEY_GLASS = "1";
private HashMap<String,FieldProperty> data = new HashMap<String,FieldProperty>();
public FieldProperty getFieldProperty(String key) {
return data.get(key);
}
public void setFieldProperty(String key,FieldProperty fieldProperty) {
this.data.put(key, fieldProperty);
}
//TODO プロパティファイルからのロード
public FieldProperties() {
FieldProperty prop1= new FieldProperty();
prop1.setName("海");
prop1.setEnabled(1);
prop1.setEncount(20);
prop1.setEvent(0);
prop1.setImg_path("./sea.png");
this.data.put(KEY_SEA, prop1);
FieldProperty prop2 = new FieldProperty();
prop2.setName("草");
prop2.setEnabled(1);
prop2.setEncount(20);
prop2.setEvent(0);
prop2.setImg_path("./glass.png");
this.data.put(KEY_GLASS, prop2);
}
public class FieldProperty{
private int enabled = 0;
private int encount = 0;
private int event = 0;
private String img_path = "";
private String name= "";
public int getEnabled() {
return enabled;
}
public void setEnabled(int enabled) {
this.enabled = enabled;
}
public int getEncount() {
return encount;
}
public void setEncount(int encount) {
this.encount = encount;
}
public int getEvent() {
return event;
}
public void setEvent(int event) {
this.event = event;
}
public String getImg_path() {
return img_path;
}
public void setImg_path(String img_path) {
this.img_path = img_path;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
--------------------------------------
今度は改行認識してる。。。いみふ。
FieldRenderer
--------------------------------------
package sample7;
import java.awt.Component;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import sample7.FieldProperties.FieldProperty;
public class FieldRenderer implements TableCellRenderer {
private FieldProperties fieldProperties = null;
public FieldRenderer() {
fieldProperties = new FieldProperties();
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
String tableValue = (String) table.getValueAt(row, column);
int mapCode = Integer.parseInt(tableValue);
JLabel label = new JLabel();
Icon icon = null;
FieldProperty fieldProp = null;
// TODO 移動が可能かの判定(移動できないフィールドの処理、ここではないかも)
switch (mapCode) {
case 1:
// 式を評価した値が定数1と一致したときに実行される処理
// icon= new ImageIcon("/Users/matsuiyoshikazu/eclipse-workspace/sample/src/resource/11/glass.png");
// icon= new ImageIcon("./glass.png");
fieldProp = fieldProperties.getFieldProperty(FieldProperties.KEY_GLASS);
icon = new ImageIcon(fieldProp.getImg_path());
label.setIcon(icon);
break;
case 2:
// 式を評価した値が定数2と一致したときに実行される処理
fieldProp = fieldProperties.getFieldProperty(FieldProperties.KEY_SEA);
icon = new ImageIcon(fieldProp.getImg_path());
label.setIcon(icon);
break;
case 0:
// 0をキャラクターとする
icon = new ImageIcon("/Users/matsuiyoshikazu/eclipse-workspace/sample/src/resource/11/ぺんぎん.png");
label.setIcon(icon);
break;
}
return label;
}
}
--------------------------------------
MainClass7は変わっていません。
ので、
MainFrame
--------------------------------------
package sample7;package sample7;
import java.awt.BorderLayout;import java.awt.Dimension;
import javax.swing.JButton;import javax.swing.JFrame;
public class MainFrame extends JFrame { private FieldMap filedMap = null; public MainFrame() { this.filedMap = new FieldMap(this); this.setTitle("field"); this.setLayout(new BorderLayout()); this.getContentPane().add(filedMap,BorderLayout.CENTER); JButton btnEast = new JButton("東"); JButton btnWast = new JButton("西"); JButton btnSouth = new JButton("南"); JButton btnNorth = new JButton("北"); btnEast.addActionListener(new TNSPListener(this.filedMap,FieldMap.V_EAST)); btnWast.addActionListener(new TNSPListener(this.filedMap,FieldMap.V_WEST)); btnSouth.addActionListener(new TNSPListener(this.filedMap,FieldMap.V_SOUTH)); btnNorth.addActionListener(new TNSPListener(this.filedMap,FieldMap.V_NORTH)); this.getContentPane().add(btnEast,BorderLayout.EAST); this.getContentPane().add(btnWast,BorderLayout.WEST); this.getContentPane().add(btnSouth,BorderLayout.SOUTH); this.getContentPane().add(btnNorth,BorderLayout.NORTH); this.setSize(new Dimension(400,450)); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); }
}
--------------------------------------
また、ごちゃ。
テキストエディタで開発したい、という人は、置換(チカン)で";"を";+改行"でチカンしてください。こういう機能すごくないですか?
SubFrame
新規です。戦闘画面とか作って行きます。
とりあえずモーダル表示のみ実装しています。
--------------------------------------
package sample7;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class SubFrame extends JFrame implements ActionListener {
private JFrame parent = null;
public SubFrame(JFrame parent) {
init(parent);
}
private void init(JFrame parent) {
this.parent = parent;
this.parent.setEnabled(false);
JButton btnClose = new JButton("閉じる");
btnClose.setActionCommand("close");
btnClose.addActionListener(this);
this.getContentPane().add(btnClose);
this.setSize(new Dimension(300,400));
this.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if(e.getActionCommand().equals("close")) {
this.parent.setEnabled(true);
this.setVisible(false);
this.dispose();
}
}
}
--------------------------------------
最後に、
TNSPListener(とんなんしゃーぺいリスナー)です。
--------------------------------------
package sample7;package sample7;
import java.awt.event.ActionEvent;import java.awt.event.ActionListener;
import sample7.FieldProperties.FieldProperty;
public class TNSPListener implements ActionListener {
private FieldMap filedMap = null; private int v_command = 0; private FieldProperties fieldProperties = null;
public TNSPListener(FieldMap filedMap, int command) { this.fieldProperties = new FieldProperties(); this.filedMap = filedMap; this.v_command = command; }
@Override public void actionPerformed(ActionEvent e) { if (v_command == FieldMap.V_EAST) { // 2次元テーブルの値を東に移動 this.filedMap.shift(FieldMap.V_EAST); } if (v_command == FieldMap.V_WEST) { // 2次元テーブルの値を西に移動 this.filedMap.shift(FieldMap.V_WEST); } if (v_command == FieldMap.V_NORTH) { // 2次元テーブルの値を北に移動 this.filedMap.shift(FieldMap.V_NORTH); } if (v_command == FieldMap.V_SOUTH) { // 2次元テーブルの値を南に移動 this.filedMap.shift(FieldMap.V_SOUTH); } this.filedMap.resetValue(filedMap.getMap()); // フィールドの移動後イベント判定 String fieldCode = filedMap.getFieldCode(); FieldProperty fieldProp = fieldProperties.getFieldProperty(fieldCode);
if (fieldProp != null) { // TODO フィールドイベント System.out.println("場所=" + fieldProp.getName());
Sai saicoro = Sai.getInstance();// サイコロクラス // int encount_per = fieldProp.getEnabled();// 遭遇率 boolean encount = saicoro.isHitProbability(encount_per);// 遭遇率いか if (encount) { System.out.println("モンスターと遭遇した"); // TODO 先頭画面へ new SubFrame(filedMap.getParent());
} // TODO 「調べる」コマンド入力された場合
}
}
}
--------------------------------------
以上です。
戦闘画面といってますが、ポケモンみたいにモンスターを仲間にする、というものでもいいですよね。