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