Java

オブジェクト指向でRPGを作ってみる – 敵とバトル

更新日:

以前は、勇者がフィールドを移動しているときに、一定確率で敵と遭遇するようにして、敵と遭遇したら戦闘画面を表示するところまでは作りました。

これからは、戦闘画面の中身を作ってみたいと思います。

まずは戦闘画面に勇者とスライムを表示してみましょう。

スライムを表示するために、IconLabelを継承してSlimeLabelを作ります。

import java.awt.Image;

public class SlimeLabel extends IconLabel {
  private static Image image = null;
  public SlimeLabel() {
    super();
  }

  @Override
  String getImageFilename() {
    return "slime.png";
  }

  @Override
  Image getImage() {
    return image;
  }

  @Override
  void setImage(Image image) {
    SlimeLabel.image = image;
  }

}

そして、戦闘画面に勇者とスライムを表示します。

import java.awt.Container;
import java.awt.FlowLayout;

import javax.swing.JFrame;

public class BattleFrame extends JFrame {
  public BattleFrame() {
    super("戦闘");
    Container pane = getContentPane();
    pane.setLayout(new FlowLayout());
    pane.add(new BraveLabel());
    pane.add(new SlimeLabel());
  }
}

GUIの画面を作るのはちょっと手間がかかります。

画面上にどのように配置するのかをプログラムで指定してやる必要があります。

縦方向と横方向にレイアウトするためにBoxLayoutを使います。

import java.awt.Container;
import java.awt.FlowLayout;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class BattleFrame extends JFrame {
  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);
    iconsPanel.add(new BraveLabel());
    iconsPanel.add(new SlimeLabel());
    buttonsPanel.add(new JButton("たたかう"));
  }
}

画面上に「たたかう」ボタンが表示されましたが、何も反応しません。

反応するように変更してみましょう。

「たたかう」ボタンが押されたときに呼び出される BattleAction クラスを作成します。

とりあえず、コンソールに「たたかう」と出力するだけの実装にしておきます。

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class BattleAction implements ActionListener {

  @Override
  public void actionPerformed(ActionEvent e) {
    System.out.println("たたかう");
  }
}

このアクションと「たたかう」ボタンを関連付けます。

import java.awt.Container;
import java.awt.FlowLayout;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class BattleFrame extends JFrame {
  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);
    iconsPanel.add(new BraveLabel());
    iconsPanel.add(new SlimeLabel());
    JButton battleButton = new JButton("たたかう");
    battleButton.addActionListener(new BattleAction());
    buttonsPanel.add(battleButton);
    buttonsPanel.add(new JButton("にげる"));
  }
}

「にげる」ボタンにも、同様に EscapeAction を関連付けてみましょう。

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class EscapeAction implements ActionListener {

  @Override
  public void actionPerformed(ActionEvent e) {
    System.out.println("にげる");
  }
  
}
import java.awt.Container;
import java.awt.FlowLayout;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class BattleFrame extends JFrame {
  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);
    iconsPanel.add(new BraveLabel());
    iconsPanel.add(new SlimeLabel());
    JButton battleButton = new JButton("たたかう");
    battleButton.addActionListener(new BattleAction());
    buttonsPanel.add(battleButton);
    JButton escapeButton = new JButton("にげる");
    escapeButton.addActionListener(new EscapeAction());
    buttonsPanel.add(escapeButton);
  }
}

戦闘を実行するために、スライムのモデルを作成します。
勇者とスライムのそれぞれのモデルに、インスタンス変数のhpを追加して、モデルでhpの値を管理します。
攻撃を受けたときは、damage()メソッドでhpを削ります。

public class Slime {
  private int hp = 3;
  private int mp = 3;
  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;
  }
}

勇者のクラスにもhpを削るメソッドを追加しておきます。

import java.util.ArrayList;
import java.util.List;

public class Brave {
  private int hp = 10;
  private int mp = 10;
  private String name;
  private Arms arms;
  private List<Item> items = new ArrayList<>();
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public void setArms(Arms arms) {
    this.arms = arms;
  }
  public void addItem(Item item) {
    items.add(item);
  }

  public String getHp() {
    return null;
  }

  public void damage(int v) {
    hp -= v;
  }

  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("名前:" + name);
    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();
  }
}

RpgFrameはRpgのビューとして動かすようにします。

Rpg.java

public class Rpg {
  private Brave brave;
  public Rpg() {
    brave = new Brave();
    brave.setName("ヨシヒコ");
  }
  public Brave getBrave() {
    return brave;
  }
  public Slime getSlime() {
    Slime slime = new Slime();
    slime.setName("スライム");
    return slime;
  }
}

PrgFrameにはRpgのインスタンスを渡すようにしてビューからモデルを参照します。

import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class RpgFrame extends JFrame implements KeyListener {
  public static final int IconWidth = 64;
  public static final int IconHeight = 64;
  public static final int FieldWidth = 20;
  public static final int FieldHeight = 12;
  private int braveX = 0;
  private int braveY = 0;
  private Rpg rpg;
  public RpgFrame() {
    super("RPG");
    super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    addKeyListener(this);
    Container pane = getContentPane();
    pane.setLayout(new GridBagLayout());
    drawField();
  }
  private void drawField() {
    Container pane = getContentPane();
    pane.removeAll();
    for (int i = 0; i < FieldWidth; i++) {
      for (int j = 0; j < FieldHeight; j++) {
        if (i == braveX && j == braveY) {
          add(pane, new BraveLabel(), i, j, 1, 1);
        } else {
          add(pane, new FieldLabel(), i, j, 1, 1);
        }
      }
    }
    pane.revalidate();
  }
  static void add(Container pane,
    Component comp,
    int x,
    int y,
    int width,
    int height) {
    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.BOTH;
    c.gridx = x;
    c.gridy = y;
    c.gridwidth = width;
    c.gridheight = height;
    pane.add(comp, c);
  }
  @Override
  public void keyTyped(KeyEvent e) {
    //
  }
  @Override
  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());
      bf.setSlime(rpg.getSlime());
      SwingUtilities.invokeLater(new WindowInvoker(bf));
    }
  }
  @Override
  public void keyReleased(KeyEvent e) {
    //
  }
  private boolean isEncount() {
    double v = Math.random();
    return v < 0.05;
  }
  public void setModel(Rpg rpg) {
    this.rpg = rpg;
  }
}

BattleFrameにも勇者とスライムのモデルを渡すようにして、hpを削りあうように作ります。

import java.awt.Container;
import java.awt.FlowLayout;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class BattleFrame extends JFrame {
  private Brave brave;
  private Slime slime;
  private BraveLabel braveLabel;
  private SlimeLabel slimeLabel;
  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();
    braveLabel.setText(brave.getName() + " HP:" + brave.getHp());
    slimeLabel = new SlimeLabel();
    iconsPanel.add(braveLabel);
    iconsPanel.add(slimeLabel);
    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 Slime getSlime() {
    return slime;
  }
  
  public void setSlime(Slime slime) {
    this.slime = slime;
  }

  public BraveLabel getBraveLabel() {
    return braveLabel;
  }

  public SlimeLabel getSlimeLabel() {
    return slimeLabel;
  }
}

GuiAppは、Rpgのゲームを開始するだけのシンプルな役割を持つようにしておきます。

import javax.swing.SwingUtilities;

public class GuiApp {
  public static void main(String[] args) {
    RpgFrame f = new RpgFrame();
    f.setModel(new Rpg());
    SwingUtilities.invokeLater(new WindowInvoker(f));
  }
}

では、戦うところを作ってみましょう。
BattleActionは、勇者とスライムを知らないので、今は戦わせることができません。
さきほど、BattleFrameに勇者とスライムにインスタンスを持たせるようにしたので、BattleActionのコンストラクタでBattleFrameのインスタンスを渡すようにします。

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class BattleAction implements ActionListener {
  private BattleFrame battleFrame;
  public BattleAction(BattleFrame bf) {
    battleFrame = bf;
  }
  @Override
  public void actionPerformed(ActionEvent e) {
    System.out.println("こうげき");
  }
}

BattleFrameの中のBattleActionを生成するところでコンパイルエラーが出ますので、これを修正します。

    battleButton.addActionListener(new BattleAction(this));

BattleActionは、BattleFrameから勇者とスライムを取得して戦わせます。

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class BattleAction implements ActionListener {
  private BattleFrame battleFrame;
  public BattleAction(BattleFrame bf) {
    battleFrame = bf;
  }
  @Override
  public void actionPerformed(ActionEvent e) {
    System.out.println("こうげき");
    Brave brave = battleFrame.getBrave();
    Slime slime = battleFrame.getSlime();
    slime.damage(1);
    BraveLabel bl = battleFrame.getBraveLabel();
    SlimeLabel sl = battleFrame.getSlimeLabel();
    bl.setText(brave.getName() + " HP:" + brave.getHp());
    sl.setText(slime.getName() + " HP:" + slime.getHp()); 
  }
}

スライムからもこうげきを受けるようにしてみましょう。

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class BattleAction implements ActionListener {
  private BattleFrame battleFrame;
  public BattleAction(BattleFrame bf) {
    battleFrame = bf;
  }
  @Override
  public void actionPerformed(ActionEvent e) {
    Brave brave = battleFrame.getBrave();
    Slime slime = battleFrame.getSlime();
    System.out.println(brave.getName() + "のこうげき");
    System.out.println(slime.getName() + "のこうげき");
    slime.damage(1);
    brave.damage(1);
    BraveLabel bl = battleFrame.getBraveLabel();
    SlimeLabel sl = battleFrame.getSlimeLabel();
    bl.setText(brave.getName() + " HP:" + brave.getHp());
    sl.setText(slime.getName() + " HP:" + slime.getHp()); 
  }
}

スライムのHPが0になったらダイアログを表示し、ダイアログを閉じると戦闘画面を閉じるようにします。

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();
    Slime slime = battleFrame.getSlime();
    System.out.println(brave.getName() + "のこうげき");
    System.out.println(slime.getName() + "のこうげき");
    slime.damage(1);
    brave.damage(1);
    BraveLabel bl = battleFrame.getBraveLabel();
    SlimeLabel sl = battleFrame.getSlimeLabel();
    bl.setText(brave.getName() + " HP:" + brave.getHp());
    sl.setText(slime.getName() + " HP:" + slime.getHp()); 
    if (slime.getHp() <= 0) {
      JOptionPane.showMessageDialog(battleFrame, slime.getName() + "をやっつけた!");
      battleFrame.setVisible(false);
    }
  }
}

勇者のHPが0になったときもダイアログを表示して、ダイアログを閉じたら戦闘画面も閉じるようにします。

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();
    Slime slime = battleFrame.getSlime();
    System.out.println(brave.getName() + "のこうげき");
    System.out.println(slime.getName() + "のこうげき");
    slime.damage(1);
    brave.damage(1);
    BraveLabel bl = battleFrame.getBraveLabel();
    SlimeLabel sl = battleFrame.getSlimeLabel();
    bl.setText(brave.getName() + " HP:" + brave.getHp());
    sl.setText(slime.getName() + " HP:" + slime.getHp()); 
    if (slime.getHp() <= 0) {
      JOptionPane.showMessageDialog(battleFrame, slime.getName() + "をやっつけた!");
      battleFrame.setVisible(false);
    }
    if (brave.getHp() <= 0) {
      JOptionPane.showMessageDialog(battleFrame, brave.getName() + "はしんでしまった。");
      battleFrame.setVisible(false);
    }
  }
}

スライムを倒したらゴールドを獲得できるようにしてみましょう。
まずスライムがドロップするゴールドの数を取得するメソッドを追加します。

public class Slime {
  private int hp = 3;
  private int mp = 3;
  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 int getGold() {
    return 1;
  }
}

勇者は獲得したゴールドを受け取れるようにします。

import java.util.ArrayList;
import java.util.List;

public class Brave {
  private int hp = 5;
  private int mp = 10;
  private int gold = 0;
  private String name;
  private Arms arms;
  private List<Item> items = new ArrayList<>();
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public void setArms(Arms arms) {
    this.arms = arms;
  }
  public void addItem(Item item) {
    items.add(item);
  }

  public int getHp() {
    return hp;
  }

  public void damage(int v) {
    hp -= v;
  }

  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("名前:" + name);
    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() + "は、" + gold + "ゴールドを持っています。");
  }
}

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();
    Slime slime = battleFrame.getSlime();
    System.out.println(brave.getName() + "のこうげき");
    System.out.println(slime.getName() + "のこうげき");
    slime.damage(1);
    brave.damage(1);
    BraveLabel bl = battleFrame.getBraveLabel();
    SlimeLabel sl = battleFrame.getSlimeLabel();
    bl.setText(brave.getName() + " HP:" + brave.getHp());
    sl.setText(slime.getName() + " HP:" + slime.getHp()); 
    if (slime.getHp() <= 0) {
      int gold = slime.getGold();
      String msg = slime.getName() + "をやっつけた!\n";
      msg += gold + "ゴールドを獲得した!";
      brave.addGold(gold);
      JOptionPane.showMessageDialog(battleFrame, msg);
      battleFrame.setVisible(false);
    }
    if (brave.getHp() <= 0) {
      JOptionPane.showMessageDialog(battleFrame, brave.getName() + "はしんでしまった。");
      battleFrame.setVisible(false);
    }
  }
}

ダイアログにタイトルとアイコンを指定するように修正します。

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();
    Slime slime = battleFrame.getSlime();
    System.out.println(brave.getName() + "のこうげき");
    System.out.println(slime.getName() + "のこうげき");
    slime.damage(1);
    brave.damage(1);
    BraveLabel bl = battleFrame.getBraveLabel();
    SlimeLabel sl = battleFrame.getSlimeLabel();
    bl.setText(brave.getName() + " HP:" + brave.getHp());
    sl.setText(slime.getName() + " HP:" + slime.getHp()); 
    if (slime.getHp() <= 0) {
      int gold = slime.getGold();
      String msg = slime.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);
    }
  }
}

-Java
-, ,

Copyright© UMLとJavaで学ぶオブジェクト指向プログラミング入門 , 2024 All Rights Reserved Powered by STINGER.