Java

オブジェクト指向でRPGを作ってみる – GUI編

更新日:

RPGを作るには、やはりGUIの画面がないと寂しいですね。

まずは画面を表示してみましょう。

これまでは App.java でいろいろとやっていましたが、新しくGuiApp.javaを作ります。

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

public class GuiApp {
  public static void main(String[] args) {
    JFrame f = new JFrame("RPG");
    f.setSize(500, 400);
    SwingUtilities.invokeLater(new WindowInvoker(f));
  }
  private static class WindowInvoker implements Runnable {
    private JFrame frame;
    public WindowInvoker(JFrame jframe) {
      this.frame = jframe;
    }
    @Override
    public void run() {
      frame.pack();
      frame.setVisible(true);
    }
  }
}

画面上に勇者のアイコンを表示してみましょう。

import java.awt.Container;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class GuiApp {
  public static void main(String[] args) {
    JFrame f = new JFrame("RPG");
    ImageIcon icon = new ImageIcon("brave.png");
    JLabel lavel = new JLabel(icon);
    Container pane = f.getContentPane();
    pane.add(lavel);
    f.setSize(500, 400);
    SwingUtilities.invokeLater(new WindowInvoker(f));
  }
  private static class WindowInvoker implements Runnable {
    private JFrame frame;
    public WindowInvoker(JFrame jframe) {
      this.frame = jframe;
    }
    @Override
    public void run() {
      frame.pack();
      frame.setVisible(true);
    }
  }
}

勇者を表示するためのコンポーネントを用意します。

import java.awt.Color;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

public class BraveLabel extends JLabel {
  public BraveLabel() {
    setIcon(new ImageIcon("brave.png"));
    setBackground(Color.WHITE);
  }
}

草原を表示するためのコンポーネントを用意します。

import java.awt.Color;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

public class FieldLabel extends JLabel {
  public FieldLabel() {
    setIcon(new ImageIcon("grass.jpg"));
  }
}

これらを画面上に配置してみます。

import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

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

public class GuiApp {
  public static void main(String[] args) {
    JFrame f = new JFrame("RPG");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(500, 400);
    Container pane = f.getContentPane();
    pane.setLayout(new GridBagLayout());
    for (int i = 0; i < 5; i++) {
      for (int j = 0; j < 4; j++) {
        if (i == 2 && j == 2) {
          add(pane, new BraveLabel(), i, j, 1, 1);
        } else {
          add(pane, new FieldLabel(), i, j, 1, 1);
        }
      }
    }
    SwingUtilities.invokeLater(new WindowInvoker(f));
  }

  private static class WindowInvoker implements Runnable {
    private JFrame frame;
    public WindowInvoker(JFrame jframe) {
      this.frame = jframe;
    }
    @Override
    public void run() {
      frame.setVisible(true);
    }
  }

  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);
  }
}

RPG用の画面もクラスとして用意しましょう。

import javax.swing.JFrame;

public class RpgFrame extends JFrame {
  public RpgFrame() {
    super("RPG");
    super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  }
}

画面上にコンポーネントを配置するのも、RpgFrameの責任で行うようにして、GuiAppクラスの仕事を減らします。

import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.JFrame;

public class RpgFrame extends JFrame {
  public RpgFrame() {
    super("RPG");
    super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    Container pane = getContentPane();
    pane.setLayout(new GridBagLayout());
    for (int i = 0; i < 5; i++) {
      for (int j = 0; j < 4; j++) {
        if (i == 2 && j == 2) {
          add(pane, new BraveLabel(), i, j, 1, 1);
        } else {
          add(pane, new FieldLabel(), i, j, 1, 1);
        }
      }
    }
  }
  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);
  }
}

GuiApp.java

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

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

  private static class WindowInvoker implements Runnable {
    private JFrame frame;
    public WindowInvoker(JFrame jframe) {
      this.frame = jframe;
    }
    @Override
    public void run() {
      frame.pack();
      frame.setVisible(true);
    }
  }
}

元の画像のサイズが大きいけれど、画像サイズを小さくしてフィールドを表示するように変更します。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

public class BraveLabel extends JLabel {
  public BraveLabel() {
    ImageIcon icon = new ImageIcon("brave.png");
    Image i = icon.getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH);
    setIcon(new ImageIcon(i));
    setBackground(Color.WHITE);
    setOpaque(true);
    setPreferredSize(new Dimension(20, 20));
  }
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

public class FieldLabel extends JLabel {
  public FieldLabel() {
    ImageIcon icon = new ImageIcon("grass.jpg");
    Image i = icon.getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH);
    setIcon(new ImageIcon(i));
    setBackground(Color.WHITE);
    setOpaque(true);
    setPreferredSize(new Dimension(30, 30));
  }
}

アイコンのサイズはRpgFrameに定数を定義して、他のクラスはその値を参照するようにする。

import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.JFrame;

public class RpgFrame extends JFrame {
  public static final int IconWidth = 64;
  public static final int IconHeight = 64;
  public RpgFrame() {
    super("RPG");
    super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    Container pane = getContentPane();
    pane.setLayout(new GridBagLayout());
    for (int i = 0; i < 5; i++) {
      for (int j = 0; j < 4; j++) {
        if (i == 2 && j == 2) {
          add(pane, new BraveLabel(), i, j, 1, 1);
        } else {
          add(pane, new FieldLabel(), i, j, 1, 1);
        }
      }
    }
  }
  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);
  }
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

public class BraveLabel extends JLabel {
  public BraveLabel() {
    ImageIcon icon = new ImageIcon("brave.png");
    Image i = icon.getImage().getScaledInstance(RpgFrame.IconWidth, RpgFrame.IconHeight, Image.SCALE_SMOOTH);
    setIcon(new ImageIcon(i));
    setBackground(Color.WHITE);
    setOpaque(true);
    setPreferredSize(new Dimension(RpgFrame.IconWidth, RpgFrame.IconHeight));
  }
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

public class FieldLabel extends JLabel {
  public FieldLabel() {
    ImageIcon icon = new ImageIcon("grass.jpg");
    Image i = icon.getImage().getScaledInstance(RpgFrame.IconWidth, RpgFrame.IconHeight, Image.SCALE_SMOOTH);
    setIcon(new ImageIcon(i));
    setBackground(Color.WHITE);
    setOpaque(true);
    setPreferredSize(new Dimension(RpgFrame.IconWidth, RpgFrame.IconHeight));
  }
}

フィールドの幅と高さ、そして勇者の座標をRpgFrame内に定義します。

import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.JFrame;

public class RpgFrame extends JFrame {
  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;
  public RpgFrame() {
    super("RPG");
    super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    Container pane = getContentPane();
    pane.setLayout(new GridBagLayout());
    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);
        }
      }
    }
  }
  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);
  }
}

RpgFrameで画面上に描画する部分を別メソッドに分離します。

import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.JFrame;

public class RpgFrame extends JFrame {
  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;
  public RpgFrame() {
    super("RPG");
    super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    drawField();
  }
  private void drawField() {
    Container pane = getContentPane();
    pane.removeAll();
    pane.setLayout(new GridBagLayout());
    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);
        }
      }
    }
  }
  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);
  }
}

キー入力を受け取って、勇者のアイコンを動かすようにしてみましょう。

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;

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;
  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);
  }
  @Override
  public void keyReleased(KeyEvent e) {
    //
  }
}

敵と遭遇するようにしてみます。

GuiApp内のWindowInvokerを単独クラスにする。

import javax.swing.JFrame;

public class WindowInvoker implements Runnable {
  private JFrame frame;
  public WindowInvoker(JFrame jframe) {
    this.frame = jframe;
  }
  @Override
  public void run() {
    frame.pack();
    frame.setVisible(true);
  }  
}

戦闘画面を追加します。

import javax.swing.JFrame;

public class BattleFrame extends JFrame {
  public BattleFrame() {
    super("戦闘");
    setSize(300, 300);
  }
}

敵と遭遇したら、戦闘画面を表示します。

  @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();
      SwingUtilities.invokeLater(new WindowInvoker(bf));
    }
  }
  @Override
  public void keyReleased(KeyEvent e) {
    //
  }
  private boolean isEncount() {
    double v = Math.random();
    return v < 0.05;
  }
}

動きが遅いのを改善する

勇者のアイコンが画面上を動くようになりましたが、毎回の再描画が遅い状況です。

これを改善してみましょう。

BraveLabelとFieldLabelではインスタンスを生成するたびに画像ファイルを読み込み、画像サイズを調整するという処理を行っています。

この処理を一度だけやるようにすれば、かなり改善できそうです。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

public class FieldLabel extends JLabel {
  private static Image image = null;
  public FieldLabel() {
    if (image == null) {
      ImageIcon icon = new ImageIcon("grass.jpg");
      image = icon.getImage().getScaledInstance(RpgFrame.IconWidth, RpgFrame.IconHeight, Image.SCALE_SMOOTH);
    }
    setIcon(new ImageIcon(image));
    setBackground(Color.WHITE);
    setOpaque(true);
    setPreferredSize(new Dimension(RpgFrame.IconWidth, RpgFrame.IconHeight));
  }
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

public class BraveLabel extends JLabel {
  private static Image image = null;
  public BraveLabel() {
    if (image == null) {
      ImageIcon icon = new ImageIcon("brave.png");
      image = icon.getImage().getScaledInstance(RpgFrame.IconWidth, RpgFrame.IconHeight, Image.SCALE_SMOOTH);
    }
    setIcon(new ImageIcon(image));
    setBackground(Color.WHITE);
    setOpaque(true);
    setPreferredSize(new Dimension(RpgFrame.IconWidth, RpgFrame.IconHeight));
  }
}

-Java
-, ,

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