データベースとは?|初心者でもわかるプログラミング学習入門
GEEK JOB編集部
目次
前章までで、キーボードと時間計測を利用して、ごく簡単な自機と弾を作りました。
しかし、このまま要素が増えるたびにFieldに変数を追加していくと、変数の数が増えて大変なことになります。
コードの可読性も地に落ちます。
それを避けるために、この章では、自機や弾、敵をクラスに分割して管理する方法を学びます。
以下が完成イメージです。
まず、自機のクラスPlayerを作り、Fieldから分離します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
class Field extends JPanel implements KeyListener, ActionListener { private int bullet_x = 300; private int bullet_y = 300; private Player player; public Field() { setSize(600, 400); setBackground(Color.white); setFocusable(true); addKeyListener(this); Timer timer = new Timer(20, this); timer.start(); player = new Player(); } @Override public void paintComponent(Graphics g) { g.clearRect(0, 0, getWidth(), getHeight()); g.setColor(Color.red); player.draw(g); g.fillOval(bullet_x, bullet_y, 10, 10); } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { player.move(e.getKeyCode()); } @Override public void keyReleased(KeyEvent e) { } @Override public void actionPerformed(ActionEvent e) { bullet_y -= 5; repaint(); } } class Player { private int x; private int y; public Player() { this.x = 300; this.y = 300; } public void move(int keycode) { switch (keycode) { case KeyEvent.VK_LEFT : x -= 10; break; case KeyEvent.VK_RIGHT : x += 10; break; case KeyEvent.VK_UP : y -= 10; break; case KeyEvent.VK_DOWN : y += 10; break; } } public void draw(Graphics g) { g.fillOval(x, y, 50, 50); } } |
Fieldの中のプレイヤーに関する情報が、すべてplayerという変数に隠蔽されていることが分かります。
これなら、もしプレイヤーに関する変数を増やしたくなったとしても、Fieldではなく、Playerクラスの中に変数を追加すればよいことになります。
変数の数が同じでも、それらがすべてFieldの中で宣言されているのと、Playerなどのクラスにまとめられているのでは、読みやすさが大きく違います。
同時に、Fieldの各メソッドで、何をやっているかが分かりやすくなっていることに気づきましたか?
たとえば、keyPressed()メソッドの中を先ほどと比較してみましょう。
プレイヤー分離前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public void keyPressed(KeyEvent e) { int keycode = e.getKeyCode(); switch(keycode) { case KeyEvent.VK_LEFT : x -= 10; break; case KeyEvent.VK_RIGHT : x += 10; break; case KeyEvent.VK_UP : y -= 10; break; case KeyEvent.VK_DOWN : y += 10; break; } } |
プレイヤー分離後
1 2 3 |
public void keyPressed(KeyEvent e) { player.move(e.getKeyCode()); } |
プレイヤーを分離する前のソースコードでは、keycodeをswitch文によって分岐させ、「x」や「y」の値を変化させていることしか分かりません。
「x」や「y」が具体的に何を意味するかは、Fieldクラスの変数の宣言まで戻らないと判断することはできません。
「player_x」のような名前を付けることも可能ですが、どんどんコードが冗長になってしまいます。
ここで、プレイヤーを独立したクラスに抽出した後のコードを見てみましょう。
「player.move()」メソッドにキーボード入力を渡すことで、プレイヤーを動かしているということが一目瞭然です。
同様に、paintComponent()の中のプレイヤーの描画も分かりやすくなっています。
このようにオブジェクト指向には、ソースコードを読んだときに何をしているかが分かりやすくなる、というメリットがあります。
これはある程度規模の大きな開発や、複数人での開発における大きな助けになります。
また、今回書き換えたように、プログラムの機能を変えずにソースコードを整理することをリファクタリングと呼びます。
同様に、弾もBulletクラスとして分離していきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
class Field extends JPanel implements KeyListener, ActionListener { private Player player; private Bullet bullet; public Field() { setSize(600, 400); setBackground(Color.white); setFocusable(true); addKeyListener(this); Timer timer = new Timer(20, this); timer.start(); player = new Player(); bullet = new Bullet(300, 300); } @Override public void paintComponent(Graphics g) { g.clearRect(0, 0, getWidth(), getHeight()); g.setColor(Color.red); player.draw(g); bullet.draw(g); } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { player.move(e.getKeyCode()); repaint(); } @Override public void keyReleased(KeyEvent e) { } @Override public void actionPerformed(ActionEvent e) { bullet.update(); repaint(); } } class Bullet { private int x; private int y; public Bullet(int x, int y) { this.x = x; this.y = y; } public void update() { this.y -= 5; } public void draw(Graphics g) { g.fillOval(x, y, 10, 10); } } |
Playerのときと同様に、記述がシンプル、かつ直感的になりました。
このまま、「z」キーが押されたときにプレイヤーの座標から弾が発射されるように、プログラムを改造してしまいましょう。
まず、プレイヤーに以下のshoot()メソッドを追加します。
1 2 3 |
public Bullet shoot() { return new Bullet(this.x, this.y); } |
次に、FieldのkeyPressed()メソッドに以下の記述を追加すれば改造は終了です。
1 2 3 4 5 6 7 |
public void keyPressed(KeyEvent e) { player.move(e.getKeyCode()); if(e.getKeyChar() == 'z') { bullet = player.shoot(); } repaint(); } |
zキーが押されたときに、player.shoot()メソッドで生成されたBulletクラスのインスタンスを、Field内のbullet変数に代入しています。
ただし、この際bulletを書き換えるので、古いほうの弾は消えてしまっています。
以下がこれまでのソースコードの全体になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
package simpleshooting; import java.awt.Color; import java.awt.Graphics; import java.awt.event.*; import javax.swing.*; public class SimpleShooting { public static void main(String[] args) { JFrame frame = new JFrame("SimpleShooting"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(600, 400); frame.setLocationRelativeTo(null); Field field = new Field(); frame.add(field); frame.setVisible(true); } } class Field extends JPanel implements KeyListener, ActionListener { private Player player; private Bullet bullet; public Field() { setSize(600, 400); setBackground(Color.white); setFocusable(true); addKeyListener(this); Timer timer = new Timer(20, this); timer.start(); player = new Player(); bullet = new Bullet(300, 300); } @Override public void paintComponent(Graphics g) { g.clearRect(0, 0, getWidth(), getHeight()); g.setColor(Color.red); player.draw(g); bullet.draw(g); } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { player.move(e.getKeyCode()); if(e.getKeyChar() == 'z') { bullet = player.shoot(); } } @Override public void keyReleased(KeyEvent e) { } @Override public void actionPerformed(ActionEvent e) { bullet.update(); repaint(); } } class Player { private int x; private int y; public Player() { this.x = 300; this.y = 300; } public Bullet shoot() { return new Bullet(this.x, this.y); } public void move(int keycode) { switch(keycode) { case KeyEvent.VK_LEFT : x -= 10; break; case KeyEvent.VK_RIGHT : x += 10; break; case KeyEvent.VK_UP : y -= 10; break; case KeyEvent.VK_DOWN : y += 10; break; } } public void draw(Graphics g) { g.fillOval(x, y, 50, 50); } } class Bullet { private int x; private int y; public Bullet(int x, int y) { this.x = x; this.y = y; } public void update() { this.y -= 5; } public void draw(Graphics g) { g.fillOval(x, y, 10, 10); } } |
ところで、このソースコード、長すぎませんか。
せっかくリファクタリングできれいにしたのに、結局読みにくくなってしまっています。
これを機に、クラスごとにファイルを分割しましょう。
別ファイルにクラスを作成するには、そのままソースコードを移せば大丈夫です。
ただし、ソースコードの最初で、packageとimportを設定しなおすのを忘れないようにしてください。
参考までに、netbeansでは以下の手順で新しいクラスを作成できます。
最後に、必要に応じてpackageの下に必要なimportを追加すれば完了です。
ところで、上の画像ではクラスの宣言時にpublicという接頭辞が付いていますが、この単語はファイル名とクラス名が同じときにだけ付けることができます。
付けなくても実行は出来ますが、一応付けておいてください。