勇者とスライムには共通しているところがいくつかあります。
オブジェクト指向プログラミングでは、共通部分をまとめたクラスを作り、その共通部分を「継承」したクラスを作ることができます。
勇者とスライムの共通部分を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); } }