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