勇者とスライムには共通しているところがいくつかあります。
オブジェクト指向プログラミングでは、共通部分をまとめたクラスを作り、その共通部分を「継承」したクラスを作ることができます。
勇者とスライムの共通部分をCharaクラスにまとめます。
変数hpは、Braveクラスの数か所でも参照しているので、継承先のサブクラスでもアクセスできるように、protectedで宣言しておきます。
public class Chara {
protected int hp;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHp() {
return hp;
}
public void damage(int v) {
hp -= v;
}
}
スライムのクラスは大半が共通部分に吸収されているので、以下のように変わります。
public class Slime extends Chara {
public Slime() {
hp = 3;
setName("スライム");
}
public int getGold() {
return 1;
}
}
勇者クラスは独自の実装がたくさんありますので、あまり小さくはなりません。
import java.util.ArrayList;
import java.util.List;
public class Brave extends Chara {
private int mp = 10;
private int gold = 0;
private Arms arms;
private List<Item> items = new ArrayList<>();
public Brave() {
hp = 10;
setName("ヨシヒコ");
}
public void setArms(Arms arms) {
this.arms = arms;
}
public void addItem(Item item) {
items.add(item);
}
private int maxHp() {
return 20;
}
public boolean isMaxHp() {
return maxHp() == hp;
}
private int maxMp() {
return 20;
}
public boolean isMaxMp() {
return maxMp() == mp;
}
public void introduce() {
System.out.println();
System.out.println("名前:" + getName());
System.out.println("HP:" + hp + " MP:" + mp);
if (arms == null) {
System.out.println("武器: なし");
} else {
System.out.println("武器:" + arms.getName());
}
System.out.println("アイテム:");
for (Item item: items) {
System.out.println(item.getName());
}
}
public Item getItem(String name) {
for (Item item : items) {
if (item.getName().equals(name)) return item;
}
return null;
}
public boolean isUsable(Item item) {
return item.isUsable(this);
}
public void useItem(Item item) {
if (!isUsable(item)) {
System.out.println(item.getName() + "は、使えません。");
return;
}
if (items.contains(item)) {
System.out.println(item.getName() + "を使った。");
item.effect(this);
items.remove(item);
} else {
System.out.println(item.getName() + "を持ってなかった!");
}
}
public void recoverHp(int hp) {
this.hp += hp;
if(this.hp > maxHp()) this.hp = maxHp();
}
public void recoverMp(int mp) {
this.mp += mp;
if(this.mp > maxMp()) this.mp = maxMp();
}
public void addGold(int gold) {
this.gold += gold;
System.out.println(getName() + "は、" + this.gold + "ゴールドを持っています。");
}
}
新しいモンスターを追加する
継承を使って新しいモンスター「ドラキー」を追加してみましょう。
まずモデルクラスを作成します。
Slimeクラスをマネをして作ればいいので簡単です。
public class Dracky extends Chara {
public Dracky() {
hp = 11;
setName("ドラキー");
}
public int getGold() {
return 2;
}
}
ビューにあたる DrackyLabel も作ります。
こちらも SlimeLabel と同様に作ればいいので簡単です。
import java.awt.Image;
public class DrackyLabel extends IconLabel {
private static Image image = null;
public DrackyLabel() {
super();
}
@Override
String getImageFilename() {
return "dracky.png";
}
@Override
Image getImage() {
return image;
}
@Override
void setImage(Image image) {
DrackyLabel.image = image;
}
}
スライムとドラキーを作ると、勇者とモンスターは分けて考えたほうが直感的にわかりやすいような気がするので、Monsterクラスを作成して、SlimeとDrackyはMonsterを継承するように変更します。
モンスターは勇者と区別するために形式的に作っておくだけなので、最初は何もなくていいと思います。
後でモンスター共通の実装があったときはこちらに追加します。
public class Monster extends Chara {
}
public class Slime extends Monster {
public Slime() {
hp = 3;
setName("スライム");
}
public int getGold() {
return 1;
}
}
public class Dracky extends Monster {
public Dracky() {
hp = 11;
setName("ドラキー");
}
public int getGold() {
return 2;
}
}
RpgFrameで敵と遭遇する部分を変更します。
現状は敵はスライムだけですが、ここを確率1/2でスライムとドラキーが出現するようにします。
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
if (braveX > 0) braveX--;
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
if (braveX < FieldWidth - 1) braveX++;
}
if (e.getKeyCode() == KeyEvent.VK_UP) {
if (braveY > 0) braveY--;
}
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
if (braveY < FieldHeight - 1) braveY++;
}
drawField();
System.out.println("x=" + braveX + ", y=" + braveY);
if (isEncount()) {
System.out.println("敵と遭遇しました。");
BattleFrame bf = new BattleFrame();
bf.setBrave(rpg.getBrave());
Monster m = rpg.getMonster();
bf.setMonster(m);
bf.setMonsterLabel(getMonsterLabel(m));
SwingUtilities.invokeLater(new WindowInvoker(bf));
}
}
private IconLabel getMonsterLabel(Monster m) {
IconLabel l = null;
if (m instanceof Slime) {
l = new SlimeLabel();
}
if (m instanceof Dracky) {
l = new DrackyLabel();
}
return l;
}
モンスターはRpgクラスから取得します。
public class Rpg {
private Brave brave;
public Rpg() {
brave = new Brave();
brave.setName("ヨシヒコ");
}
public Brave getBrave() {
return brave;
}
public Monster getMonster() {
double v = Math.random();
Monster m = null;
if (v < 0.5) {
m = new Slime();
} else {
m = new Dracky();
}
return m;
}
}
BattleFrameでもスライム固定だったところをモンスターに変更します。
import java.awt.Container;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BattleFrame extends JFrame {
private Brave brave;
private Monster monster;
private BraveLabel braveLabel;
private IconLabel monsterLabel;
public BattleFrame() {
super("戦闘");
Container pane = getContentPane();
pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
JPanel iconsPanel = new JPanel();
iconsPanel.setLayout(new BoxLayout(iconsPanel, BoxLayout.X_AXIS));
JPanel buttonsPanel = new JPanel();
buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.Y_AXIS));
pane.add(iconsPanel);
pane.add(buttonsPanel);
braveLabel = new BraveLabel();
monsterLabel = getMonsterLabel();
iconsPanel.add(braveLabel);
iconsPanel.add(monsterLabel);
JButton battleButton = new JButton("こうげき");
battleButton.addActionListener(new BattleAction(this));
buttonsPanel.add(battleButton);
JButton escapeButton = new JButton("にげる");
escapeButton.addActionListener(new EscapeAction());
buttonsPanel.add(escapeButton);
}
public Brave getBrave() {
return brave;
}
public void setBrave(Brave brave) {
this.brave = brave;
}
public Monster getMonster() {
return monster;
}
public void setMonster(Monster monster) {
this.monster = monster;
}
public BraveLabel getBraveLabel() {
return braveLabel;
}
public IconLabel getMonsterLabel() {
return monsterLabel;
}
public void setMonsterLabel(IconLabel label) {
this.monsterLabel = label;
}
}
実行すると、敵と遭遇した時にNullPointerExceptionが発生してしまいました。
これを回避するために、以下のように周世します。
import java.awt.Container;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BattleFrame extends JFrame {
private Brave brave;
private Monster monster;
private BraveLabel braveLabel;
private IconLabel monsterLabel;
private JPanel iconsPanel;
public BattleFrame() {
super("戦闘");
Container pane = getContentPane();
pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
iconsPanel = new JPanel();
iconsPanel.setLayout(new BoxLayout(iconsPanel, BoxLayout.X_AXIS));
JPanel buttonsPanel = new JPanel();
buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.Y_AXIS));
pane.add(iconsPanel);
pane.add(buttonsPanel);
braveLabel = new BraveLabel();
iconsPanel.add(braveLabel);
JButton battleButton = new JButton("こうげき");
battleButton.addActionListener(new BattleAction(this));
buttonsPanel.add(battleButton);
JButton escapeButton = new JButton("にげる");
escapeButton.addActionListener(new EscapeAction());
buttonsPanel.add(escapeButton);
}
public Brave getBrave() {
return brave;
}
public void setBrave(Brave brave) {
this.brave = brave;
}
public Monster getMonster() {
return monster;
}
public void setMonster(Monster monster) {
this.monster = monster;
}
public BraveLabel getBraveLabel() {
return braveLabel;
}
public IconLabel getMonsterLabel() {
return monsterLabel;
}
public void setMonsterLabel(IconLabel label) {
this.monsterLabel = label;
iconsPanel.add(monsterLabel);
}
}
BattleActionでコンパイルエラーが出ていますので、それを修正します。
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JOptionPane;
public class BattleAction implements ActionListener {
private BattleFrame battleFrame;
public BattleAction(BattleFrame bf) {
battleFrame = bf;
}
@Override
public void actionPerformed(ActionEvent e) {
Brave brave = battleFrame.getBrave();
Monster monster = battleFrame.getMonster();
System.out.println(brave.getName() + "のこうげき");
System.out.println(monster.getName() + "のこうげき");
monster.damage(1);
brave.damage(1);
BraveLabel bl = battleFrame.getBraveLabel();
IconLabel sl = battleFrame.getMonsterLabel();
bl.setText(brave.getName() + " HP:" + brave.getHp());
sl.setText(monster.getName() + " HP:" + monster.getHp());
if (monster.getHp() <= 0) {
int gold = monster.getGold();
String msg = monster.getName() + "をやっつけた!\n";
msg += gold + "ゴールドを獲得した!";
brave.addGold(gold);
JOptionPane.showMessageDialog(
battleFrame, msg,
"勝利", JOptionPane.INFORMATION_MESSAGE);
battleFrame.setVisible(false);
}
if (brave.getHp() <= 0) {
JOptionPane.showMessageDialog(
battleFrame, brave.getName() + "はしんでしまった。",
"敗北", JOptionPane.INFORMATION_MESSAGE);
battleFrame.setVisible(false);
}
}
}
24行目でコンパイルエラーが出ていますので、これを解決するためにMonsterクラスにgetGold()メソッドを追加します。
public class Monster extends Chara {
public int getGold() {
return 0;
}
}
さらにゴーストを追加する
モンスターを増やせるようになったので、さらにゴーストを追加してみましょう。
ゴーストは、英語で Ghost で、HPは13、ドロップするゴールドは3です。
public class Ghost extends Monster {
public Ghost() {
hp = 13;
setName("ゴースト");
}
public int getGold() {
return 3;
}
}
ビューにあたる GhostLabel を作ります。
import java.awt.Image;
public class GhostLabel extends IconLabel {
private static Image image = null;
public GhostLabel() {
super();
}
@Override
String getImageFilename() {
return "ghost.png";
}
@Override
Image getImage() {
return image;
}
@Override
void setImage(Image image) {
GhostLabel.image = image;
}
}
Rpgクラスの getMonster() で一定確率でゴーストを出現させるようにします。
public class Rpg {
private Brave brave;
public Rpg() {
brave = new Brave();
brave.setName("ヨシヒコ");
}
public Brave getBrave() {
return brave;
}
public Monster getMonster() {
double v = Math.random();
Monster m = null;
if (v < 0.4) {
m = new Slime();
} else if (v < 0.7) {
m = new Dracky();
} else {
m = new Ghost();
}
return m;
}
}
RpgFrame では、ゴーストのビューを生成するようにします。
private IconLabel getMonsterLabel(Monster m) {
IconLabel l = null;
if (m instanceof Slime) {
l = new SlimeLabel();
}
if (m instanceof Dracky) {
l = new DrackyLabel();
}
if (m instanceof Ghost) {
l = new GhostLabel();
}
return l;
}
「にげる」を実装する
戦闘画面に「にげる」ボタンがありますが、まだ実装がありません。
「にげる」ボタンを押したら、ダイアログを表示して戦闘面を閉じるようにしましょう。
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JOptionPane;
public class EscapeAction implements ActionListener {
private BattleFrame battleFrame;
public EscapeAction(BattleFrame bf) {
battleFrame = bf;
}
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("にげる");
Brave b = battleFrame.getBrave();
String msg = b.getName() + "は、逃げ出した。";
JOptionPane.showMessageDialog(
battleFrame, msg,
"逃げた", JOptionPane.INFORMATION_MESSAGE);
battleFrame.setVisible(false);
}
}