情報学部 菅沼ホーム Java 目次 索引

シューティングゲーム

  1. ステップ1: ゲームの枠組み

      基本的に,「ゲーム枠の作成」で説明した方法とほぼ同じ方法で作成します.ただし,パネルのサイズは変更しています.以下,各クラスに対して,「ゲーム枠の作成」の場合との違いについて説明していきます.

    1. Game クラス

        「ゲーム枠の作成」における Game クラスと全く同じプログラムです( Game.java ).

      /*************************/
      /* 迷路                  */
      /*   coded by Y.Suganuma */
      /*************************/
      import java.awt.*;
      import java.awt.event.*;
      import javax.swing.*;
      import main.*;
      
      public class Game {
      	public static void main (String[] args)
      	{
      		Win win = new Win("迷路");
      	}
      }
      
      class Win extends JFrame
      {
      	/******************/
      	/* コンストラクタ */
      	/******************/
      	Win(String name)
      	{
      					// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
      		super(name);
      					// Windowの大きさ
      		setSize(560, 590);   // 40+20, 70+20
      					// MainPanel の大きさを決定
      		Dimension size = getSize();
      		size.width  -=60;
      		size.height -=90;
      					// ContentPain を取得し,設定
      		Container CP = getContentPane();   // ContentPane を取得
      		CP.setLayout(null);   // レイアウトマネージャを停止
      		CP.setBackground(new Color(220, 255, 220));   // 背景色
      					// MainPanel を追加し,設定
      		MainPanel pn = new MainPanel(size);   // MainPanel オブジェクトの生成
      		CP.add(pn);   // MainPanel オブジェクトを ContentPane に追加
      		pn.setSize(size.width, size.height);
      		pn.setLocation(10, 10);
      					// ウィンドウを表示
      		setVisible(true);
      					// イベントリスナ
      		addWindowListener(new WinEnd());
      	}
      
      	/******************************/
      	/* 上,左,下,右の余白の設定 */
      	/******************************/
      	public Insets getInsets()
      	{
      		return new Insets(50, 20, 20, 20);
      	}
      
      	/************/
      	/* 終了処理 */
      	/************/
      	class WinEnd extends WindowAdapter
      	{
      		public void windowClosing(WindowEvent e) {
      			System.exit(0);
      		}
      	}
      }
      				

    2. MainPanel クラス

        このクラスに関しても,「ゲーム枠の作成」における MainPanel クラスと全く同じプログラムです( MainPanel.java ).

      package main;
      
      import java.awt.*;
      import java.awt.event.*;
      import javax.swing.*;
      import start.*;
      import game.*;
      import clear.*;
      import over.*;
      
      public class MainPanel extends JPanel implements Runnable
      {
      	Dimension size;   // パネルの大きさ
      	boolean in_game = true;   // ゲーム実行中はtrue
      	public int state = 0;   // ゲーム状態(0:表紙,1:ゲーム,2:クリア,3:オーバー,4:終了)
      	public int level = 1;   // ゲームレベル
      	int old_state = 0;   // 直前のゲーム状態
      	StartPanel sp;
      	GamePanel gp;
      	GameClearPanel gcp;
      	GameOverPanel gop;
      	Thread td;
      			// コンストラクタ
      	public MainPanel(Dimension size1)
      	{
      		size = size1;
      					// グリッドレイアウト
      		setLayout(new GridLayout(1, 1, 0, 0));
      					// ゲームパネルの生成
      		sp = new StartPanel(size, this);   // スタート(タイトル)
      		add(sp);
      					// スレッドの生成
      		td = new Thread(this);
      		td.start();
      	}
      			// ゲームの状態を変更
      	public void run()
      	{
      		while (in_game) {
      			try {
      				td.sleep(100);   // 100 ms 毎の実施
      			}
      			catch (InterruptedException e) {}
      			if (state != old_state) {
      							// 前のパネルの削除
      				if (old_state == 0)
      					remove(sp);
      				else if (old_state == 1)
      					remove(gp);
      				else if (old_state == 2)
      					remove(gcp);
      				else
      					remove(gop);
      							// 新しいパネルの追加
      				if (state == 4)   // ゲーム終了
      					in_game = false;
      				else {
      					if (state == 0) {   // StartPanel
      						sp = new StartPanel(size, this);
      						add(sp);
      					}
      					else if (state == 1) {   // GamePanel
      						gp = new GamePanel(size, this);
      						add(gp);
      					}
      					else if (state == 2) {   // GameClearPanel
      						gcp = new GameClearPanel(size, this);
      						add(gcp);
      					}
      					else {   // GameOverPanel
      						gop = new GameOverPanel(size, this);
      						add(gop);
      					}
      					validate();
      					old_state = state;
      				}
      			}
      		}
      	}
      }
      				

    3. StartPanel クラス

        「ゲーム枠の作成」における StartPanel クラスでは,「 s 」キーを押すとゲームを開始するように設定していましたが,ここではマウスのダブルクリックによって開始するように変更してあります( 36 行目,73 行目~ 78 行目).当然のことながら,ゲームタイトル及び「遊び方」の内容を変更しています( StartPanel.java ).

      001	package start;
      002	
      003	import java.awt.*;
      004	import java.awt.event.*;
      005	import javax.swing.*;
      006	import main.*;
      007	
      008	public class StartPanel extends JPanel implements ActionListener
      009	{
      010		boolean in_game = true;
      011		Dimension size;   // パネルの大きさ
      012		MainPanel mp;
      013		JButton bt;
      014				// コンストラクタ
      015		public StartPanel(Dimension size1, MainPanel mp1)
      016		{
      017			size = size1;
      018			mp   = mp1;
      019						// レイアウトマネージャの停止
      020			setLayout(null);
      021						// 背景色の設定
      022			setBackground(Color.white);
      023						// ボタンの配置
      024			Font f = new Font("SansSerif", Font.BOLD, 20);
      025			FontMetrics fm = getFontMetrics(f);
      026			String str = "遊び方";
      027			int w = fm.stringWidth(str) + 40;
      028			int h = fm.getHeight() + 10;
      029			bt = new JButton(str);
      030			bt.setFont(f);
      031			bt.addActionListener(this);
      032			bt.setSize(w, h);
      033			bt.setLocation(size.width/2-w/2, 5);
      034			add(bt);
      035						// マウスリスナの追加
      036			addMouseListener(new Mouse());
      037		}
      038				// 描画
      039		public void paintComponent(Graphics g)
      040		{
      041			super.paintComponent(g);   // 親クラスの描画
      042			FontMetrics fm;
      043			Font f;
      044			String str;
      045			int w, h;
      046	
      047			f   = new Font("SansSerif", Font.BOLD, 40);
      048			fm  = g.getFontMetrics(f);
      049			str = "シューティングゲーム";
      050			w   = fm.stringWidth(str);
      051			h   = fm.getHeight();
      052			g.setFont(f);
      053			g.drawString(str, size.width/2-w/2, size.height/2);
      054	
      055			f   = new Font("Serif", Font.PLAIN, 20);
      056			fm  = g.getFontMetrics(f);
      057			str = "ゲーム開始:ダブルクリック";
      058			w   = fm.stringWidth(str);
      059			h   = size.height - fm.getHeight() - 10;
      060			g.setFont(f);
      061			g.drawString(str, size.width/2-w/2, h);
      062		}
      063				// ボタンがクリックされたときの処理
      064		public void actionPerformed(ActionEvent e)
      065		{
      066			if (e.getSource() == bt) {
      067				Method db = new Method();
      068				db.setVisible(true);
      069				requestFocusInWindow();
      070			}
      071		}
      072				// ダブルクリックされたときの処理
      073		class Mouse extends MouseAdapter {
      074			public void mouseClicked(MouseEvent e) {
      075				if (e.getClickCount() == 2)
      076					mp.state = 1;
      077			}
      078		}
      079	}
      080	
      081	/******************/
      082	/* ゲームの遊び方 */
      083	/******************/
      084	class Method extends JDialog
      085	{
      086				// コンストラクタ
      087		Method()
      088		{
      089			setTitle("ゲームの遊び方");
      090					// ContetPain
      091			Container cp = getContentPane();
      092			cp.setLayout(new FlowLayout(FlowLayout.CENTER));
      093			cp.setBackground(new Color(220, 255, 220));   // 背景色
      094			Font f = new Font("MS 明朝", Font.PLAIN, 20);
      095			setSize(550, 160);
      096					// TextArea の追加
      097			JTextArea ta = new JTextArea(5, 50);
      098			ta.setFont(f);
      099			ta.setEditable(false);
      100			ta.setLineWrap(true);
      101			ta.setText("・ゲーム開始: 画面上でダブルクリック\n");
      102			ta.append("・自機の移動: 「↑」,「↓」,「←」,「→」キーで上下左右に移動\n");
      103			ta.append("・弾の発射: 「スペース」キー\n");
      104			JScrollPane scroll = new JScrollPane(ta);
      105			cp.add(scroll);
      106					// Window を閉じるため
      107			addWindowListener(new WinEnd());
      108		}
      109					// 終了処理
      110		class WinEnd extends WindowAdapter
      111		{
      112			public void windowClosing(WindowEvent e) {
      113				setVisible(false);
      114			}
      115		}
      116	}
      				

    4. GamePanel クラス

        GamePanel クラスは,実際のゲームを実現するクラスです.従って,「ゲーム枠の作成」における GamePanel クラスとは,ゲームの種類によってその内容は大きく異なります.今後,このクラス及びその関連クラスを完成させていくことになりますが,ここでは,敵機と自機を表示しています.なお,敵機には 2 種類あり,今後,大きな方をボス,小さな方を敵機と呼びます.そのソースファイルは以下に示す通りです( GamePanel.java : 001 行目~ 094 行目, My.java : 096 行目~ 116 行目,Boss.java : 118 行目~ 141 行目,Enemy.java : 143 行目~ 174 行目).

        最終的な段階では,「ゲームクリア」及び「ゲームオーバー」のボタンは必要ありませんが,今後,レベルアップの状況を確認すべき場合が多いため,ゲームの完成前(ステップ5)までは残しておきます( 009 行目の ActionListener,013 行目,029 行目~ 053 行目,086 行目~ 093 行目).

      001	package game;
      002	
      003	import java.awt.*;
      004	import java.awt.event.*;
      005	import javax.swing.*;
      006	import java.util.Random;
      007	import main.*;
      008	
      009	public class GamePanel extends JPanel implements ActionListener
      010	{
      011		Dimension size;   // パネルの大きさ
      012		MainPanel mp;
      013		JButton left, right;
      014		My my;   // 自機
      015		Boss bs;   // ボス
      016		int no = 2;   // 敵機の数
      017		Enemy em[] = new Enemy [no];   // 敵機
      018		boolean ex[] = new boolean [no];   // 敵機の存在
      019		Random rn;
      020				// コンストラクタ
      021		public GamePanel(Dimension size1, MainPanel mp1)
      022		{
      023			size = size1;
      024			mp   = mp1;
      025						// レイアウトマネージャの停止
      026			setLayout(null);
      027						// 背景色の設定
      028			setBackground(Color.white);
      029						// ボタンの配置
      030			Font f = new Font("SansSerif", Font.BOLD, 15);
      031			FontMetrics fm = getFontMetrics(f);
      032			String str1 = "ゲームクリア";
      033			int w1 = fm.stringWidth(str1) + 40;
      034			int h1 = fm.getHeight() + 10;
      035			left = new JButton(str1);
      036			left.setFont(f);
      037			left.addActionListener(this);   // アクションリスナ
      038			left.setSize(w1, h1);
      039	
      040			String str2 = "ゲームオーバー";
      041			int w2 = fm.stringWidth(str2) + 40;
      042			int h2 = fm.getHeight() + 10;
      043			right = new JButton(str2);
      044			right.setFont(f);
      045			right.addActionListener(this);   // アクションリスナ
      046			right.setSize(w2, h2);
      047	
      048			int w = size.width / 2 - (w1 + w2 + 5) / 2;
      049			int h = size.height - h1 - 10;
      050			left.setLocation(w, h);
      051			add(left);
      052			right.setLocation(w+w1+5, h);
      053			add(right);
      054						// ランダム変数の初期化
      055			rn = new Random();
      056						// 自機,ボス,敵機の配置
      057			my = new My(size, mp);
      058			if (mp.level == 1) {
      059				ex[0] = false;
      060				ex[1] = false;
      061			}
      062			else {
      063				ex[0] = true;
      064				ex[1] = true;
      065			}
      066			bs = new Boss(size, rn, this, mp);
      067			if (ex[0])
      068				em[0] = new Enemy(bs, 0, size, rn, mp);
      069			if (ex[1])
      070				em[1] = new Enemy(bs, 1, size, rn, mp);
      071		}
      072				// 描画
      073		public void paintComponent(Graphics g)
      074		{
      075			super.paintComponent(g);   // 親クラスの描画
      076						// 自機
      077			g.drawImage(my.image, my.x, my.y, this);
      078						// ボス
      079			g.drawImage(bs.image, bs.x, bs.y, this);
      080						// 敵機
      081			for (int i1 = 0; i1 < no; i1++) {
      082				if (ex[i1])
      083					g.drawImage(em[i1].image, em[i1].x, em[i1].y, this);
      084			}
      085		}
      086				// ボタンがクリックされたときの処理
      087		public void actionPerformed(ActionEvent e)
      088		{
      089			if (e.getSource() == left)   // ゲームクリア
      090				mp.state = 2;
      091			else   // ゲームオーバー
      092				mp.state = 3;
      093		}
      094	}
      095	
      096	package game;
      097	
      098	import java.awt.*;
      099	import main.*;
      100	
      101	class My
      102	{
      103		Image image;   // 自機の画像
      104		int width = 50;   // 自機の幅
      105		int height = 51;   // 自機の高さ
      106		int x, y;   // 自機の位置
      107				// コンストラクタ
      108		public My(Dimension size, MainPanel mp)
      109		{
      110						// 自機画像の読み込み
      111			image = mp.getToolkit().getImage("game/image/my.gif");
      112						// 自機の初期位置
      113			x  = size.width / 2 - width / 2;
      114			y  = size.height - height -10;
      115		}
      116	}
      117	
      118	package game;
      119	
      120	import java.awt.*;
      121	import java.util.Random;
      122	import main.*;
      123	
      124	class Boss
      125	{
      126		Image image;   // ボス画像
      127		int width = 66;   // ボスの幅
      128		int height = 95;   // ボスの高さ
      129		int x, y;   // ボスの位置
      130		GamePanel gp;
      131				// コンストラクタ
      132		public Boss(Dimension size, Random rn, GamePanel gp1, MainPanel mp)
      133		{
      134			gp = gp1;
      135						// ボス画像の読み込み
      136			image = mp.getToolkit().getImage("game/image/boss.gif");
      137						// ボスの初期位置
      138			x = 100 + rn.nextInt(size.width - 200 - width);
      139			y = 10 + rn.nextInt(20);
      140		}
      141	}
      142	
      143	package game;
      144	
      145	import java.awt.*;
      146	import java.util.Random;
      147	import main.*;
      148	
      149	class Enemy
      150	{
      151		Image image;   // 敵機の画像
      152		int width = 27;   // 敵機の幅
      153		int height = 41;   // 敵機の高さ
      154		int x, y;   // 敵機の位置
      155		Boss bs;   // ボス
      156		int n;   // 敵機番号
      157				// コンストラクタ
      158		public Enemy(Boss bs1, int n1, Dimension size, Random rn, MainPanel mp)
      159		{
      160			bs = bs1;
      161			n  = n1;
      162						// 敵機画像の読み込み
      163			image = mp.getToolkit().getImage("game/image/enemy.gif");
      164						// 敵機の初期位置
      165			if (n == 0) {
      166				x = bs.x - 150 + rn.nextInt(100);
      167				y = bs.y + bs.height - 80 + rn.nextInt(100);
      168			}
      169			else {
      170				x = bs.x + bs.width + 50 + rn.nextInt(100);
      171				y = bs.y + bs.height - 80 + rn.nextInt(100);
      172			}
      173		}
      174	}
      				
      014 行目~ 018 行目

        今後,その機能を拡張していく可能性がありますので,自機,ボス,及び,敵機は,クラスとして別ファイル( 096 行目~ 116 行目,118 行目~ 141 行目,及び,143 行目~ 174 行目)に定義してあります.ここでは,それらのオブジェクトを代入するフィールド,及び,敵機の数及び各敵機が存在するか否か(レベル,または,自機による攻撃の結果変化する)を示すフィールドを定義しています.なお,敵機は複数存在しますので,配列を利用しています.

      057 行目

        自機に対するオブジェクトを生成しています.

      058 行目~ 065 行目

        レベル1では敵機は存在せず(ボスだけ),レベル2の初期状態では 2 機存在しますので,その状態を設定しています.

      066 行目

        ボスに対するオブジェクトを生成しています.

      067 行目~ 070 行目

        敵機が存在する場合は,対応する敵機のオブジェクトを生成しています.

      073 行目~ 085 行目

        自機,ボス,及び,存在する敵機を描画しています.

      096 行目~ 116 行目

        自機に対応するクラスの定義です.画像を読み込み,それを描画する初期位置(画面の下部で,水平位置は中央)を設定しています( 113,114 行目).

      118 行目~ 141 行目

        ボスに対応するクラスの定義です.画像を読み込み,それを描画する初期位置(垂直位置に関しては [ 10, 30 ]区間のランダム,水平位置に関しては [ 100, 画面サイズ-200-ボスの幅] 区間のランダム値)を設定しています( 138,139 行目).

      143 行目~ 174 行目

        敵機に対応するクラスの定義です.画像を読み込み,それを描画する初期位置を設定しています.敵機1に対しては,x 座標は [ ボスの左の座標-150, ボスの左の座標-50 ],y 座標は [ ボスの下の座標-80, ボスの下の座標+20 ] 区間からランダムに決定されます( 166,167 行目).また,敵機2に対しては,x 座標は [ ボスの右の座標+50, ボスの右の座標+150 ],y 座標は [ ボスの下の座標-80, ボスの下の座標+20 ] 区間からランダムに決定されます( 170,171 行目).

    5. GameClearPanel クラス

        「ゲーム枠の作成」における GameClearPanel クラスと,ほぼ同じです.違いは,レベルが 2 までしか無い点だけです( GameClearPanel.java ).

      package clear;
      
      import java.awt.*;
      import java.awt.event.*;
      import javax.swing.*;
      import main.*;
      
      public class GameClearPanel extends JPanel implements ActionListener
      {
      	Dimension size;   // パネルの大きさ
      	MainPanel mp;
      	JButton bt1, bt2;
      			// コンストラクタ
      	public GameClearPanel(Dimension size1, MainPanel mp1)
      	{
      		size = size1;
      		mp   = mp1;
      					// レイアウトマネージャの停止
      		setLayout(null);
      					// 背景色の設定
      		setBackground(Color.white);
      					// ボタンの配置
      		Font f = new Font("SansSerif", Font.BOLD, 20);
      		FontMetrics fm = getFontMetrics(f);
      		String str1 = "ゲーム終了";
      		int w1 = fm.stringWidth(str1) + 40;
      		int h1 = fm.getHeight() + 10;
      		bt1 = new JButton(str1);
      		bt1.setFont(f);
      		bt1.addActionListener(this);   // アクションリスナ
      		bt1.setSize(w1, h1);
      		bt1.setLocation(size.width/2-w1-5, size.height-h1-20);
      		add(bt1);
      
      		String str2;
      		if (mp.level == 2)
      			str2 = "最初から再開";
      		else
      			str2 = "次のレベル";
      		int w2 = fm.stringWidth(str2) + 40;
      		int h2 = fm.getHeight() + 10;
      		bt2 = new JButton(str2);
      		bt2.setFont(f);
      		bt2.addActionListener(this);   // アクションリスナ
      		bt2.setSize(w2, h2);
      		bt2.setLocation(size.width/2+5, size.height-h2-20);
      		add(bt2);
      	}
      			// 描画
      	public void paintComponent(Graphics g)
      	{
      		super.paintComponent(g);   // 親クラスの描画
      		Font f = new Font("SansSerif", Font.BOLD, 40);
      		FontMetrics fm = g.getFontMetrics(f);
      		String str = "Game Clear!";
      		int w = fm.stringWidth(str);
      		g.setFont(f);
      		g.drawString(str, size.width/2-w/2, size.height/2);
      	}
      			// ボタンがクリックされたときの処理
      	public void actionPerformed(ActionEvent e)
      	{
      		if (e.getSource() == bt1) {
      			mp.state = 4;
      			bt1.setEnabled(false);
      			bt2.setEnabled(false);
      		}
      		else {
      			mp.level++;
      			if (mp.level > 2) {   // 最初からゲーム再開
      				mp.state = 0;
      				mp.level = 1;
      			}
      			else   // レベルアップ
      				mp.state = 1;
      		}
      	}
      }
      				

    6. GameOverPanel クラス

        「ゲーム枠の作成」における GameOverPanel クラスと同じです( GameOverPanel.java ).

      package over;
      
      import java.awt.*;
      import java.awt.event.*;
      import javax.swing.*;
      import main.*;
      
      public class GameOverPanel extends JPanel implements ActionListener
      {
      	Dimension size;   // パネルの大きさ
      	MainPanel mp;
      	JButton bt1, bt2, bt3;
      			// コンストラクタ
      	public GameOverPanel(Dimension size1, MainPanel mp1)
      	{
      		size = size1;
      		mp   = mp1;
      					// レイアウトマネージャの停止
      		setLayout(null);
      					// 背景色の設定
      		setBackground(Color.white);
      					// ボタンの配置
      		Font f = new Font("SansSerif", Font.BOLD, 15);
      		FontMetrics fm = getFontMetrics(f);
      		String str1 = "終了";
      		int w1 = fm.stringWidth(str1) + 40;
      		int h1 = fm.getHeight() + 10;
      		bt1 = new JButton(str1);
      		bt1.setFont(f);
      		bt1.addActionListener(this);   // アクションリスナ
      		bt1.setSize(w1, h1);
      
      		String str2 = "現在のレベルで再開";
      		int w2 = fm.stringWidth(str2) + 40;
      		int h2 = fm.getHeight() + 10;
      		bt2 = new JButton(str2);
      		bt2.setFont(f);
      		bt2.addActionListener(this);   // アクションリスナ
      		bt2.setSize(w2, h2);
      
      		String str3 = "最初から再開";
      		int w3 = fm.stringWidth(str3) + 40;
      		int h3 = fm.getHeight() + 10;
      		bt3 = new JButton(str3);
      		bt3.setFont(f);
      		bt3.addActionListener(this);   // アクションリスナ
      		bt3.setSize(w3, h3);
      
      		int w = size.width / 2 - (w1 + w2 + w3 + 10) / 2;
      		bt1.setLocation(w, size.height-h1-20);
      		add(bt1);
      		w += (w1 + 5);
      		bt2.setLocation(w, size.height-h1-20);
      		add(bt2);
      		w += (w2 + 5);
      		bt3.setLocation(w, size.height-h1-20);
      		add(bt3);
      
      	}
      			// 描画
      	public void paintComponent(Graphics g)
      	{
      		super.paintComponent(g);   // 親クラスの描画
      		Font f = new Font("SansSerif", Font.BOLD, 40);
      		FontMetrics fm = g.getFontMetrics(f);
      		String str = "Game Over!";
      		int w = fm.stringWidth(str);
      		g.setFont(f);
      		g.drawString(str, size.width/2-w/2, size.height/2);
      	}
      			// ボタンがクリックされたときの処理
      	public void actionPerformed(ActionEvent e)
      	{
      		if (e.getSource() == bt1) {   // ゲーム終了
      			mp.state = 4;
      			bt1.setEnabled(false);
      			bt2.setEnabled(false);
      			bt3.setEnabled(false);
      		}
      		else if (e.getSource() == bt2)   // 現在のレベルでゲーム再開
      			mp.state = 1;
      		else {   // 最初からゲーム再開
      			mp.state = 0;
      			mp.level = 1;
      		}
      	}
      }
      				

  2. ステップ2: 移動(自機)

      ここでは,「↑」,「↓」,「←」,「→」キーによって,自機を上下左右に移動させる機能を付加します.修正するプログラムは,game パッケージ内の My クラス( My.java )と GamePanel クラス( GamePanel.java )です.まず,My クラスに,キーを押したときの移動量を定義しておきます( 12 行目).

    01	package game;
    02	
    03	import java.awt.*;
    04	import main.*;
    05	
    06	class My
    07	{
    08		Image image;   // 自機の画像
    09		int width = 50;   // 自機の幅
    10		int height = 51;   // 自機の高さ
    11		int x, y;   // 自機の位置
    12		int v = 20;   // 移動キーが押されたときの移動量
    13				// コンストラクタ
    14		public My(Dimension size, MainPanel mp)
    15		{
    16						// 自機画像の読み込み
    17			image = mp.getToolkit().getImage("game/image/my.gif");
    18						// 自機の初期位置
    19			x  = size.width / 2 - width / 2;
    20			y  = size.height - height -10;
    21		}
    22	}
    			

      GamePanel クラスでは,キーイベントに対する処理及び将来必要となるスレッドの処理を追加します.

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import java.util.Random;
    007	import main.*;
    008	
    009	public class GamePanel extends JPanel implements ActionListener, Runnable
    010	{
    011		Dimension size;   // パネルの大きさ
    012		MainPanel mp;
    013		JButton left, right;
    014		My my;   // 自機
    015		Boss bs;   // ボス
    016		int no = 2;   // 敵機の数
    017		Enemy em[] = new Enemy [no];   // 敵機
    018		boolean ex[] = new boolean [no];   // 敵機の存在
    019		Random rn;
    020		Thread td;
    021		boolean in_game = true;
    022				// コンストラクタ
    023		public GamePanel(Dimension size1, MainPanel mp1)
    024		{
    025			size = size1;
    026			mp   = mp1;
    027						// レイアウトマネージャの停止
    028			setLayout(null);
    029						// 背景色の設定
    030			setBackground(Color.white);
    031						// ボタンの配置
    032			Font f = new Font("SansSerif", Font.BOLD, 15);
    033			FontMetrics fm = getFontMetrics(f);
    034			String str1 = "ゲームクリア";
    035			int w1 = fm.stringWidth(str1) + 40;
    036			int h1 = fm.getHeight() + 10;
    037			left = new JButton(str1);
    038			left.setFont(f);
    039			left.addActionListener(this);   // アクションリスナ
    040			left.setSize(w1, h1);
    041	
    042			String str2 = "ゲームオーバー";
    043			int w2 = fm.stringWidth(str2) + 40;
    044			int h2 = fm.getHeight() + 10;
    045			right = new JButton(str2);
    046			right.setFont(f);
    047			right.addActionListener(this);   // アクションリスナ
    048			right.setSize(w2, h2);
    049	
    050			int w = size.width / 2 - (w1 + w2 + 5) / 2;
    051			int h = size.height - h1 - 10;
    052			left.setLocation(w, h);
    053			add(left);
    054			right.setLocation(w+w1+5, h);
    055			add(right);
    056						// ランダム変数の初期化
    057			rn = new Random();
    058						// 自機,ボス,敵機の配置
    059			my = new My(size, mp);
    060			if (mp.level == 1) {
    061				ex[0] = false;
    062				ex[1] = false;
    063			}
    064			else {
    065				ex[0] = true;
    066				ex[1] = true;
    067			}
    068			bs = new Boss(size, rn, this, mp);
    069			if (ex[0])
    070				em[0] = new Enemy(bs, 0, size, rn, mp);
    071			if (ex[1])
    072				em[1] = new Enemy(bs, 1, size, rn, mp);
    073						// スレッドの生成
    074			td = new Thread(this);
    075			td.start();
    076						// キーリスナの追加
    077			addKeyListener(new Key());
    078		}
    079				// スレッドの実行
    080		public void run()
    081		{
    082			while (in_game) {
    083				try {
    084					td.sleep(10);
    085				}
    086				catch (InterruptedException e) {}
    087				repaint();
    088			}
    089		}
    090				// 描画
    091		public void paintComponent(Graphics g)
    092		{
    093			super.paintComponent(g);   // 親クラスの描画
    094						// 自機
    095			g.drawImage(my.image, my.x, my.y, this);
    096						// ボス
    097			g.drawImage(bs.image, bs.x, bs.y, this);
    098						// 敵機
    099			for (int i1 = 0; i1 < no; i1++) {
    100				if (ex[i1])
    101					g.drawImage(em[i1].image, em[i1].x, em[i1].y, this);
    102			}
    103						// この Component が入力フォーカスを取得することを要求
    104			requestFocusInWindow();
    105		}
    106				// ボタンがクリックされたときの処理
    107		public void actionPerformed(ActionEvent e)
    108		{
    109			in_game = false;
    110			if (e.getSource() == left)   // ゲームクリア
    111				mp.state = 2;
    112			else   // ゲームオーバー
    113				mp.state = 3;
    114		}
    115				// キーが押されたときの処理(イベントアダプタ)
    116		class Key extends KeyAdapter {
    117			public void keyPressed(KeyEvent ke)
    118			{
    119				if (ke.getKeyCode() == KeyEvent.VK_UP)   // 「↑」キー( 38 )
    120					my.y -= my.v;
    121				else if (ke.getKeyCode() == KeyEvent.VK_DOWN)   // 「↓」キー( 40 )
    122					my.y += my.v;
    123				else if (ke.getKeyCode() == KeyEvent.VK_LEFT)   // 「←」キー( 37 )
    124					my.x -= my.v;
    125				else if (ke.getKeyCode() == KeyEvent.VK_RIGHT)   // 「→」キー( 39 )
    126					my.x += my.v;
    127			}
    128		}
    129	}
    			
    009 行目

      スレッドを使用するために,Runnable インタフェースを追加しています.

    020 行目~ 021 行目,074 行目~ 075 行目

      スレッドを生成しています.

    077 行目

      キーリスナを追加しています.

    080 行目~ 089 行目

      Thread クラスの run メソッドをオーバーライドしています.現時点では,10 ms 毎に再描画しているだけです.

    104 行目

      キーイベントのために,入力フォーカスを取得しています.

    116 行目~ 128 行目

      キーが押されたときの処理です.KeyEvent クラスのスタティックフィールド VK_UP などを利用して,キーコードを判定しています.具体的な数値を使用して,例えば,119 行目を,
    	if (ke.getKeyCode() == 38)   // 「↑」キー( 38 )				
    のように記述することも可能です.

  3. ステップ3: 移動(ボス,敵機)

      ここでは,ボス及び敵機をあらかじめ決めたパターンに沿って動かします.修正するプログラムは,game パッケージ内の Boss クラス( Boss.java )と GamePanel クラス( GamePanel.java )です.まず,Boss クラスのソースコードは,以下に示すとおりです.

    01	package game;
    02	
    03	import java.awt.*;
    04	import java.util.Random;
    05	import main.*;
    06	
    07	class Boss implements Runnable
    08	{
    09		Image image;   // ボス画像
    10		int width = 66;   // ボスの幅
    11		int height = 95;   // ボスの高さ
    12		int x, y;   // ボスの位置
    13		Thread td;
    14		boolean in_game = true;
    15		int ptn1[][] = {{-5, 0, 50}, {0, 20, 55}, {5, 0, 105}, {0, -20, 110}};
    16		int ptn2[][] = {{5, 0, 50}, {0, 20, 55}, {-5, 0, 105}, {0, -20, 110}};
    17		int ptn[][];
    18		int ct = 1;
    19		GamePanel gp;
    20				// コンストラクタ
    21		public Boss(Dimension size, Random rn, GamePanel gp1, MainPanel mp)
    22		{
    23			gp = gp1;
    24						// ボス画像の読み込み
    25			image = mp.getToolkit().getImage("game/image/boss.gif");
    26						// ボスの初期位置
    27			x = 100 + rn.nextInt(size.width - 200 - width);
    28			y = 10 + rn.nextInt(5);
    29			if (x > size.width/2-width/2)
    30				ptn = ptn1;
    31			else
    32				ptn = ptn2;
    33						// スレッドの生成
    34			td = new Thread(this);
    35			td.start();
    36		}
    37				// スレッドの実行
    38		public void run()
    39		{
    40			while (in_game) {
    41				try {
    42					td.sleep(50);
    43				}
    44				catch (InterruptedException e) {}
    45				ct++;
    46				if (ct > 110)
    47					ct = 1;
    48						// ボスの位置
    49				int k = -1;
    50				for (int i1 = 0; i1 < ptn.length-1 && k < 0; i1++) {
    51					if (ct <= ptn[i1][2])
    52						k = i1;
    53				}
    54				if (k < 0)
    55					k = ptn.length - 1;
    56				x += ptn[k][0];
    57				y += ptn[k][1];
    58						// 敵機の位置
    59				if (gp.ex[0]) {
    60					gp.em[0].x += ptn[k][0];
    61					gp.em[0].y += ptn[k][1];
    62				}
    63				if (gp.ex[1]) {
    64					gp.em[1].x += ptn[k][0];
    65					gp.em[1].y += ptn[k][1];
    66				}
    67			}
    68		}
    69	}
    			
    07 行目

      スレッドを使用するために,Runnable インタフェースを継承すると共に,13,14 行目を追加し,run メソッド( 38 行目~ 68 行目)をオーバーライドしています.

    15 行目~ 18 行目

      ボスの行動パターンを 2 次元配列で表し,初期設定しています( 015,016 行目).フィールド ptn1 は,初期状態でボスが画面の右側にいるときのパターン,また,フィールド ptn2 は,そうでない場合のパターンであり,29 行目~ 32 行目においてフィールド ptn に設定されます.初期設定における内側の括弧は,配列の各行の値に相当し,この例では,いずれも 4 行 3 列の配列になります.配列の項で説明していますように,配列変数 ptn1,ptn2,ptn などは,データを記憶している領域の先頭を指すポインタとみなすことができます.例えば,30 行目において ptn1 を ptn に代入していますが,ptn1 の全てのデータをコピーして ptn に代入しているわけではなく,ptn1 と ptn が同じデータ領域を指すことになるだけです.従って,ptn を介して各要素の値を変更すれば,ptn1 の対応する要素の値も変化します(逆も同様).

      18 行目に定義されているフィールド ct は,カウンタであり,50 ms 毎( 42 行目)に 1 ずつ増加し,110 を超えると再び 1 に戻されます( 45 行目~ 47 行目).この ct の値に従って,例えば,15 行目は,
    	{-5, 0, 50} : ct の値が 1 ~ 50 の間は x 座標は 5 ピクセルずつ減らし,y 座標は変化させない
    	{0, 20, 55} : ct の値が 51 ~ 55 の間は x 座標は変化させず,y 座標は 20 ピクセルずつ増やす
    		・・・・・				
    のような意味を持っています.この結果,ボスは矩形に沿った動きをします.

      一般に,多次元配列は,配列の各要素を配列として定義することによって可能です.例えば,2 行 3 列の配列 x は,
    	int x[][] = new int [2][3];				
    または,
    	int x[][] = new int [2][];
    	x[0] = new int [3];   // 以下,繰り返し文を使用可能
    	x[1] = new int [3];				
    のような形で定義可能です.初期設定を行いたい場合は,
    	int x[][] = {{1, 2, 3}, {4, 5, 6}};   // 内側の括弧が各行に相当				
    または,
    	int x1[] = {1, 2, 3};
    	int x2[] = {4, 5, 6};
    	int x[][] = {x1, x2};				
    のような方法で行います.

    49 行目~ 55 行目

      ct の値がパターンのどの位置に対応しているかを調べています.50 行目の length は,配列のサイズ(大きさ)を表しています.

    56,57 行目

      パターンに従って,ボスの x 及び y 座標を変化させています.

    59 行目~ 66 行目

      敵機に対しても,ボスと同じ動きをさせています.

      GamePanel クラスにおける変更は 1 行だけです.ゲームの状態を変更する前に,110 行目において,Boss クラスのスレッドを停止しています.

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import java.util.Random;
    007	import main.*;
    008	
    009	public class GamePanel extends JPanel implements ActionListener, Runnable
    010	{
    011		Dimension size;   // パネルの大きさ
    012		MainPanel mp;
    013		JButton left, right;
    014		My my;   // 自機
    015		Boss bs;   // ボス
    016		int no = 2;   // 敵機の数
    017		Enemy em[] = new Enemy [no];   // 敵機
    018		boolean ex[] = new boolean [no];   // 敵機の存在
    019		Random rn;
    020		Thread td;
    021		boolean in_game = true;
    022				// コンストラクタ
    023		public GamePanel(Dimension size1, MainPanel mp1)
    024		{
    025			size = size1;
    026			mp   = mp1;
    027						// レイアウトマネージャの停止
    028			setLayout(null);
    029						// 背景色の設定
    030			setBackground(Color.white);
    031						// ボタンの配置
    032			Font f = new Font("SansSerif", Font.BOLD, 15);
    033			FontMetrics fm = getFontMetrics(f);
    034			String str1 = "ゲームクリア";
    035			int w1 = fm.stringWidth(str1) + 40;
    036			int h1 = fm.getHeight() + 10;
    037			left = new JButton(str1);
    038			left.setFont(f);
    039			left.addActionListener(this);   // アクションリスナ
    040			left.setSize(w1, h1);
    041	
    042			String str2 = "ゲームオーバー";
    043			int w2 = fm.stringWidth(str2) + 40;
    044			int h2 = fm.getHeight() + 10;
    045			right = new JButton(str2);
    046			right.setFont(f);
    047			right.addActionListener(this);   // アクションリスナ
    048			right.setSize(w2, h2);
    049	
    050			int w = size.width / 2 - (w1 + w2 + 5) / 2;
    051			int h = size.height - h1 - 10;
    052			left.setLocation(w, h);
    053			add(left);
    054			right.setLocation(w+w1+5, h);
    055			add(right);
    056						// ランダム変数の初期化
    057			rn = new Random();
    058						// 自機,ボス,敵機の配置
    059			my = new My(size, mp);
    060			if (mp.level == 1) {
    061				ex[0] = false;
    062				ex[1] = false;
    063			}
    064			else {
    065				ex[0] = true;
    066				ex[1] = true;
    067			}
    068			bs = new Boss(size, rn, this, mp);
    069			if (ex[0])
    070				em[0] = new Enemy(bs, 0, size, rn, mp);
    071			if (ex[1])
    072				em[1] = new Enemy(bs, 1, size, rn, mp);
    073						// スレッドの生成
    074			td = new Thread(this);
    075			td.start();
    076						// キーリスナの追加
    077			addKeyListener(new Key());
    078		}
    079				// スレッドの実行
    080		public void run()
    081		{
    082			while (in_game) {
    083				try {
    084					td.sleep(10);
    085				}
    086				catch (InterruptedException e) {}
    087				repaint();
    088			}
    089		}
    090				// 描画
    091		public void paintComponent(Graphics g)
    092		{
    093			super.paintComponent(g);   // 親クラスの描画
    094						// 自機
    095			g.drawImage(my.image, my.x, my.y, this);
    096						// ボス
    097			g.drawImage(bs.image, bs.x, bs.y, this);
    098						// 敵機
    099			for (int i1 = 0; i1 < no; i1++) {
    100				if (ex[i1])
    101					g.drawImage(em[i1].image, em[i1].x, em[i1].y, this);
    102			}
    103						// この Component が入力フォーカスを取得することを要求
    104			requestFocusInWindow();
    105		}
    106				// ボタンがクリックされたときの処理
    107		public void actionPerformed(ActionEvent e)
    108		{
    109			in_game = false;
    110			bs.in_game = false;
    111			if (e.getSource() == left)   // ゲームクリア
    112				mp.state = 2;
    113			else   // ゲームオーバー
    114				mp.state = 3;
    115		}
    116				// キーが押されたときの処理(イベントアダプタ)
    117		class Key extends KeyAdapter {
    118			public void keyPressed(KeyEvent ke)
    119			{
    120				if (ke.getKeyCode() == KeyEvent.VK_UP)   // 「↑」キー( 38 )
    121					my.y -= my.v;
    122				else if (ke.getKeyCode() == KeyEvent.VK_DOWN)   // 「↓」キー( 40 )
    123					my.y += my.v;
    124				else if (ke.getKeyCode() == KeyEvent.VK_LEFT)   // 「←」キー( 37 )
    125					my.x -= my.v;
    126				else if (ke.getKeyCode() == KeyEvent.VK_RIGHT)   // 「→」キー( 39 )
    127					my.x += my.v;
    128			}
    129		}
    130	}
    			

  4. ステップ4: 弾の発射(自機)

      ここでは,「スペース」キーを押すことによって,自機の弾を発射させる処理を行っています.修正するプログラムは,game パッケージ内の My クラス( My.java )と GamePanel クラス( GamePanel.java )です.また,自機の弾に対応するクラス Bullet クラス( Bullet.java )を新規に作成します.まず,My クラスのソースコードは以下に示すとおりであり,Bullet クラスのオブジェクトを保存するためのフィールドを定義し( 13 行目),Bullet クラスのオブジェクトを生成しているだけです( 23 行目).

    01	package game;
    02	
    03	import java.awt.*;
    04	import main.*;
    05	
    06	class My
    07	{
    08		Image image;   // 自機の画像
    09		int width = 50;   // 自機の幅
    10		int height = 51;   // 自機の高さ
    11		int x, y;   // 自機の位置
    12		int v = 20;   // 移動キーが押されたときの移動量
    13		Bullet bl;   // 弾
    14				// コンストラクタ
    15		public My(Dimension size, MainPanel mp)
    16		{
    17						// 自機画像の読み込み
    18			image = mp.getToolkit().getImage("game/image/my.gif");
    19						// 自機の初期位置
    20			x  = size.width / 2 - width / 2;
    21			y  = size.height - height -10;
    22						// Bullet クラスのオブジェクトを生成
    23			bl = new Bullet(size, this);
    24		}
    25	}
    			

      Bullet クラスは自機の弾を表すクラスです.スペースキーを押すと 5 個の弾が連続的に発射されます.5 個目の弾を発射し終わらない限り,スペースキーを押しても新たな弾の発射を行うことができません.

    01	package game;
    02	
    03	import java.awt.*;
    04	
    05	class Bullet implements Runnable
    06	{
    07		int width = 24;   // 弾の幅
    08		int no = 15;   // 弾の全数
    09		int no_1 = 5;   // 一度に撃てる弾数
    10		int ct = 0;   // 現在の弾の数
    11		int x[], y[];   // 弾の位置
    12		int v = 30;   // 弾の速さ
    13		boolean fire = false;
    14		boolean ex[];   // 弾の存在
    15		boolean in_game = true;
    16		Thread td;
    17		Dimension size;
    18		My my;
    19				// コンストラクタ
    20		public Bullet(Dimension size1, My my1)
    21		{
    22			size = size1;
    23			my   = my1;
    24						// 初期設定
    25			x  = new int [no];
    26			y  = new int [no];
    27			ex = new boolean [no];
    28			for (int i1 = 0; i1 < no; i1++)
    29				ex[i1] = false;
    30						// スレッドの生成
    31			td = new Thread(this);
    32			td.start();
    33		}
    34				// スレッドの実行
    35		public void run()
    36		{
    37			while (in_game) {
    38				try {
    39					td.sleep(30);
    40				}
    41				catch (InterruptedException e) {}
    42						// 弾の移動
    43				for (int i1 = 0; i1 < no; i1++) {
    44					if (ex[i1]) {
    45						y[i1] -= v;
    46						if (y[i1] < -width)
    47							ex[i1] = false;
    48					}
    49				}
    50						// 次の弾の発射
    51				if (fire) {
    52					if (ct < no_1) {
    53						boolean sw = true;
    54						for (int i1 = 0; i1 < no && sw; i1++) {
    55							if (!ex[i1]) {
    56								x[i1]  = my.x + my.width / 2 - width / 2;
    57								y[i1]  = my.y - width;
    58								ex[i1] = true;
    59								sw     = false;
    60							}
    61						}
    62						ct++;
    63						if (ct >= no_1) {
    64							fire = false;
    65							ct   = 0;
    66						}
    67					}
    68				}
    69			}
    70		}
    71				// 最初の弾の発射
    72		void shoot()
    73		{
    74			if (!fire) {
    75				fire = true;
    76				ct   = 1;
    77				boolean sw = true;
    78				for (int i1 = 0; i1 < no && sw; i1++) {
    79					if (!ex[i1]) {
    80						x[i1]  = my.x + my.width / 2 - width / 2;
    81						y[i1]  = my.y - width;
    82						ex[i1] = true;
    83						sw     = false;
    84					}
    85				}
    86			}
    87		}
    88	}
    			
    13 行目

      弾を発射しているか否かを表すフィールドです.「スペース」キーを押すと true に設定され,弾の発射が始まります.すべての弾( 5 個,09 行目のフィールド no_1 で指定)を発射し終わると,false に再設定されます.

    25 行目~ 29 行目

      各弾の位置を示すフィールド x,y,及び,その弾が存在するか否かを示すフィールド ex を準備しています.当然のことながら,初期状態では,弾は全く存在しません( 28,29 行目).なお,no は,08 行目で定義されており,画面に表示可能な弾の総数です.一度に撃てる弾の数,弾の速度,画面の大きさ等から見て,これ以上の数が一度に表示される必要はないと思います.

    43 行目~ 49 行目

      存在する弾の位置を変更し( 45 行目),その弾が画面外に出た場合は消去しています( 46,47 行目).

    51,52 行目

      弾を発射中であり( 51 行目),かつ,すべての弾を発射し終わっていない場合( 52 行目)は,53 行目~ 66 行目の処理を行います.

    53 行目~ 61 行目

      新しい弾を発射します.その弾の情報は,フィールド ex を調べ,弾が存在しない位置に保存します.なお,弾の初期位置は,自機の先端です( 56,57 行目).

      54 行目の「 i1 < no && sw; 」の記述から,sw の値が false になると,for 文によるループを抜け出します.ここでは,新しい弾を追加した時点で,59 行目において false に設定されますので,ループから抜け出します.この例の場合は,sw を使用せず,59 行目を break 文に変更した場合と同じ結果になりますが,多重のループから一気に一番外へ抜け出したいような場合には便利な方法だと思います.なお,break 文が実行されると,break 文が入っている最も内側のループの外に出て(ループの処理を中止して),そのループの次の文が実行されます.

    62 行目~ 66 行目

      最後の弾を発射し終わった場合( 63 行目)は,スペースキーによる次の弾の発射を可能にします( 64,65 行目).

    72 行目~ 87 行目

      「スペース」キーを押したとき呼ばれるメソッドであり(弾の発射を開始する),後に説明する GamePanel クラスにおけるキーイベントの処理の中で利用されます.

      GamePanel クラスでは,自機の弾の描画,及び,「スペース」キーを押したときの処理を追加しています.

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import java.util.Random;
    007	import main.*;
    008	
    009	public class GamePanel extends JPanel implements ActionListener, Runnable
    010	{
    011		Dimension size;   // パネルの大きさ
    012		MainPanel mp;
    013		JButton left, right;
    014		My my;   // 自機
    015		Boss bs;   // ボス
    016		int no = 2;   // 敵機の数
    017		Enemy em[] = new Enemy [no];   // 敵機
    018		boolean ex[] = new boolean [no];   // 敵機の存在
    019		Random rn;
    020		Thread td;
    021		boolean in_game = true;
    022				// コンストラクタ
    023		public GamePanel(Dimension size1, MainPanel mp1)
    024		{
    025			size = size1;
    026			mp   = mp1;
    027						// レイアウトマネージャの停止
    028			setLayout(null);
    029						// 背景色の設定
    030			setBackground(Color.white);
    031						// ボタンの配置
    032			Font f = new Font("SansSerif", Font.BOLD, 15);
    033			FontMetrics fm = getFontMetrics(f);
    034			String str1 = "ゲームクリア";
    035			int w1 = fm.stringWidth(str1) + 40;
    036			int h1 = fm.getHeight() + 10;
    037			left = new JButton(str1);
    038			left.setFont(f);
    039			left.addActionListener(this);   // アクションリスナ
    040			left.setSize(w1, h1);
    041	
    042			String str2 = "ゲームオーバー";
    043			int w2 = fm.stringWidth(str2) + 40;
    044			int h2 = fm.getHeight() + 10;
    045			right = new JButton(str2);
    046			right.setFont(f);
    047			right.addActionListener(this);   // アクションリスナ
    048			right.setSize(w2, h2);
    049	
    050			int w = size.width / 2 - (w1 + w2 + 5) / 2;
    051			int h = size.height - h1 - 10;
    052			left.setLocation(w, h);
    053			add(left);
    054			right.setLocation(w+w1+5, h);
    055			add(right);
    056						// ランダム変数の初期化
    057			rn = new Random();
    058						// 自機,ボス,敵機の配置
    059			my = new My(size, mp);
    060			if (mp.level == 1) {
    061				ex[0] = false;
    062				ex[1] = false;
    063			}
    064			else {
    065				ex[0] = true;
    066				ex[1] = true;
    067			}
    068			bs = new Boss(size, rn, this, mp);
    069			if (ex[0])
    070				em[0] = new Enemy(bs, 0, size, rn, mp);
    071			if (ex[1])
    072				em[1] = new Enemy(bs, 1, size, rn, mp);
    073						// スレッドの生成
    074			td = new Thread(this);
    075			td.start();
    076						// キーリスナの追加
    077			addKeyListener(new Key());
    078		}
    079				// スレッドの実行
    080		public void run()
    081		{
    082			while (in_game) {
    083				try {
    084					td.sleep(10);
    085				}
    086				catch (InterruptedException e) {}
    087				repaint();
    088			}
    089		}
    090				// 描画
    091		public void paintComponent(Graphics g)
    092		{
    093			super.paintComponent(g);   // 親クラスの描画
    094						// 自機と弾
    095			g.drawImage(my.image, my.x, my.y, this);
    096			g.setColor(Color.green);
    097			for (int i1 = 0; i1 < my.bl.no; i1++) {
    098				if (my.bl.ex[i1])
    099					g.fillOval(my.bl.x[i1], my.bl.y[i1], my.bl.width, my.bl.width);
    100			}
    101						// ボス
    102			g.drawImage(bs.image, bs.x, bs.y, this);
    103						// 敵機
    104			for (int i1 = 0; i1 < no; i1++) {
    105				if (ex[i1])
    106					g.drawImage(em[i1].image, em[i1].x, em[i1].y, this);
    107			}
    108						// この Component が入力フォーカスを取得することを要求
    109			requestFocusInWindow();
    110		}
    111				// ボタンがクリックされたときの処理
    112		public void actionPerformed(ActionEvent e)
    113		{
    114			in_game = false;
    115			bs.in_game = false;
    116			my.bl.in_game = false;
    117			if (e.getSource() == left)   // ゲームクリア
    118				mp.state = 2;
    119			else   // ゲームオーバー
    120				mp.state = 3;
    121		}
    122				// キーが押されたときの処理(イベントアダプタ)
    123		class Key extends KeyAdapter {
    124			public void keyPressed(KeyEvent ke)
    125			{
    126				if (ke.getKeyCode() == KeyEvent.VK_UP)   // 「↑」キー( 38 )
    127					my.y -= my.v;
    128				else if (ke.getKeyCode() == KeyEvent.VK_DOWN)   // 「↓」キー( 40 )
    129					my.y += my.v;
    130				else if (ke.getKeyCode() == KeyEvent.VK_LEFT)   // 「←」キー( 37 )
    131					my.x -= my.v;
    132				else if (ke.getKeyCode() == KeyEvent.VK_RIGHT)   // 「→」キー( 39 )
    133					my.x += my.v;
    134				else if (ke.getKeyCode() == KeyEvent.VK_SPACE)   // 「スペース」キー( 32 )
    135					my.bl.shoot();
    136			}
    137		}
    138	}
    			
    096 行目~ 100 行目

      自機の弾を描画しています.

    116 行目

      ゲームの状態を変える前に,自機の弾のスレッドを停止しています.

    134,135 行目

      「スペース」キーを押したとき,Bullet クラスの shoot メソッドによって,最初の弾の発射を行っています.

  5. ステップ5: 弾の発射(ボス,敵機)

      ここでは,ボス及び敵機から弾を発射させる処理を行っています.修正するプログラムは,game パッケージ内の Boss クラス( Boss.java ),Enemy クラス( Enemy.java ),及び,GamePanel クラス( GamePanel.java )です.また,ボスの弾及び敵機の弾に対応するクラスとして,Bullet_b クラス( Bullet_b.java )及び Bullet_e クラス( Bullet_e.java )を新規に作成します.まず,Boss クラスのソースコードは以下に示すとおりであり,Bullet_b クラスのオブジェクトを保存するためのフィールドを定義し( 20 行目),Bullet_b クラスのオブジェクトを生成しているだけです( 38 行目).

    01	package game;
    02	
    03	import java.awt.*;
    04	import java.util.Random;
    05	import main.*;
    06	
    07	class Boss implements Runnable
    08	{
    09		Image image;   // ボス画像
    10		int width = 66;   // ボスの幅
    11		int height = 95;   // ボスの高さ
    12		int x, y;   // ボスの位置
    13		Thread td;
    14		boolean in_game = true;
    15		int ptn1[][] = {{-5, 0, 50}, {0, 20, 55}, {5, 0, 105}, {0, -20, 110}};
    16		int ptn2[][] = {{5, 0, 50}, {0, 20, 55}, {-5, 0, 105}, {0, -20, 110}};
    17		int ptn[][];
    18		int ct = 1;
    19		GamePanel gp;
    20		Bullet_b bl;   // 弾
    21				// コンストラクタ
    22		public Boss(Dimension size, Random rn, GamePanel gp1, MainPanel mp)
    23		{
    24			gp = gp1;
    25						// ボス画像の読み込み
    26			image = mp.getToolkit().getImage("game/image/boss.gif");
    27						// ボスの初期位置
    28			x = 100 + rn.nextInt(size.width - 200 - width);
    29			y = 10 + rn.nextInt(5);
    30			if (x > size.width/2-width/2)
    31				ptn = ptn1;
    32			else
    33				ptn = ptn2;
    34						// スレッドの生成
    35			td = new Thread(this);
    36			td.start();
    37						// Bullet_b クラスのオブジェクトを生成
    38			bl = new Bullet_b(size, this, gp.my);
    39		}
    40				// スレッドの実行
    41		public void run()
    42		{
    43			while (in_game) {
    44				try {
    45					td.sleep(50);
    46				}
    47				catch (InterruptedException e) {}
    48				ct++;
    49				if (ct > 110)
    50					ct = 1;
    51						// ボスの位置
    52				int k = -1;
    53				for (int i1 = 0; i1 < ptn.length-1 && k < 0; i1++) {
    54					if (ct <= ptn[i1][2])
    55						k = i1;
    56				}
    57				if (k < 0)
    58					k = ptn.length - 1;
    59				x += ptn[k][0];
    60				y += ptn[k][1];
    61						// 敵機の位置
    62				if (gp.ex[0]) {
    63					gp.em[0].x += ptn[k][0];
    64					gp.em[0].y += ptn[k][1];
    65				}
    66				if (gp.ex[1]) {
    67					gp.em[1].x += ptn[k][0];
    68					gp.em[1].y += ptn[k][1];
    69				}
    70			}
    71		}
    72	}
    			

      Enemy クラスにおいても,Bullet_e クラスのオブジェクトを保存するためのフィールドを定義し( 15 行目),Bullet_e クラスのオブジェクトを生成しているだけです( 33 行目).

    01	package game;
    02	
    03	import java.awt.*;
    04	import java.util.Random;
    05	import main.*;
    06	
    07	class Enemy
    08	{
    09		Image image;   // 敵機の画像
    10		int width = 27;   // 敵機の幅
    11		int height = 41;   // 敵機の高さ
    12		int x, y;   // 敵機の位置
    13		Boss bs;   // ボス
    14		int n;   // 敵機番号
    15		Bullet_e bl;   // 弾
    16				// コンストラクタ
    17		public Enemy(Boss bs1, int n1, Dimension size, Random rn, MainPanel mp)
    18		{
    19			bs = bs1;
    20			n  = n1;
    21						// 敵機画像の読み込み
    22			image = mp.getToolkit().getImage("game/image/enemy.gif");
    23						// 敵機の初期位置
    24			if (n == 0) {
    25				x = bs.x - 150 + rn.nextInt(100);
    26				y = bs.y + bs.height - 80 + rn.nextInt(100);
    27			}
    28			else {
    29				x = bs.x + bs.width + 50 + rn.nextInt(100);
    30				y = bs.y + bs.height - 80 + rn.nextInt(100);
    31			}
    32						// Bullet_e クラスのオブジェクトを生成
    33			bl = new Bullet_e(size, rn, this);
    34		}
    35	}
    			

      Bullet_b クラスはボスの弾を表すクラスです.100 ms 毎に弾の位置の変更は行われますが,弾の発射は 12 行目のフィールド pr に指定した間隔で行われます(つまり,この値の場合は 500 ms 毎).

    01	package game;
    02	
    03	import java.awt.*;
    04	
    05	class Bullet_b implements Runnable
    06	{
    07		int width = 24;   // 弾の幅
    08		int no = 15;   // 弾の全数
    09		int x[], y[];   // 弾の位置
    10		int v = 30;   // 弾の速さ
    11		int vx[], vy[];   // 横及び縦方向の弾の速さ
    12		int pr = 5;   // 弾の発射間隔
    13		int ct = 0;
    14		boolean ex[];   // 弾の存在
    15		boolean in_game = true;
    16		Thread td;
    17		Dimension size;
    18		Boss bs;
    19		My my;
    20				// コンストラクタ
    21		public Bullet_b(Dimension size1, Boss bs1, My my1)
    22		{
    23			size = size1;
    24			bs   = bs1;
    25			my   = my1;
    26						// 初期設定と最初の弾の発射
    27			x  = new int [no];
    28			y  = new int [no];
    29			vx = new int [no];
    30			vy = new int [no];
    31			ex = new boolean [no];
    32			for (int i1 = 0; i1 < no; i1++)
    33				ex[i1] = false;
    34			shoot();
    35						// スレッドの生成
    36			td = new Thread(this);
    37			td.start();
    38		}
    39				// スレッドの実行
    40		public void run()
    41		{
    42			while (in_game) {
    43				try {
    44					td.sleep(100);
    45				}
    46				catch (InterruptedException e) {}
    47						// 弾の移動
    48				for (int i1 = 0; i1 < no; i1++) {
    49					if (ex[i1]) {
    50						x[i1] += vx[i1];
    51						y[i1] += vy[i1];
    52						if (x[i1] < -width || x[i1] > size.width || y[i1] > size.height)
    53							ex[i1] = false;
    54					}
    55				}
    56						// 次の弾の発射
    57				ct = (ct + 1) % pr;
    58				if (ct == 0)
    59					shoot();
    60			}
    61		}
    62				// 弾の発射
    63		void shoot()
    64		{
    65			boolean sw = true;
    66			int xt, yt;
    67			for (int i1 = 1; i1 < no && sw; i1++) {
    68				if (!ex[i1]) {
    69					sw     = false;
    70					ex[i1] = true;
    71					x[i1]  = bs.x + bs.width / 2 - width / 2;
    72					y[i1]  = bs.y + bs.height;
    73					yt     = my.y + my.height / 2 - (y[i1] + width / 2);
    74					xt     = my.x + my.width / 2 - (x[i1] + width / 2);
    75					double ang = Math.atan2(yt, xt);
    76					vx[i1] = (int)(v * Math.cos(ang) + 0.5);
    77					vy[i1] = (int)(v * Math.sin(ang) + 0.5);
    78				}
    79			}
    80		}
    81	}
    			
    27 行目~ 33 行目

      各弾の位置を示すフィールド x,y,各弾の水平方向と垂直方向の速さを示すフィールド vx,vy,及び,その弾が存在するか否かを示すフィールド ex を準備しています.当然のことながら,初期状態では,弾は全く存在しません( 32,33 行目).

    34 行目

      メソッド shoot を使用して,最初の弾を発射しています.

    48 行目~ 55 行目

      発射された弾の位置を変更しています.画面の外にでた場合は消滅させます( 52,53 行目).

    57 行目~ 59 行目

      発射条件が整ったとき,次の弾を発射しています.

    63 行目~ 80 行目

      弾の発射処理を行うメソッドです.弾の初期位置はボスの先端です( 71,72 行目).73 行目~ 75 行目において,ボスから見た自機の方向( ang )を計算しています.atan2 は,Math クラススタティックstatic )なメソッドであり,逆正接の計算を行います.つまり,atan2(y, x) は,tan(ang) = y/x となるような角度 ang をラジアン単位で返します.このように,弾は常に自機に向かって発射されますので,自機が停止していれば必ず命中します.避けるのがかなり難しいので,もう少し命中しにくい発射方向にすべきかもしれません.

      この例に示すように,スタティックメソッド( atan2,cos,sin など)やスタティック変数( Bullet_e クラス 78 行目の PI など)は,「オブジェクト.メソッド名」「オブジェクト.変数名」ではなく,「クラス名.メソッド名」「クラス名.変数名」という形で参照することに注意してください.

      76,77 行目では,上で計算した方向に基づいて,弾の水平方向及び垂直方向の速さを計算しています.なお,角度等は double 型で宣言してあるため,int 型に変換しています(型変換( cast 演算子)).

      Bullet_e クラスは敵機の弾を表すクラスです.1 回の発射で no( 09 行目)個の弾を連続して発射しますが,発射したすべての弾が画面の外に出ない限り次の発射は行いません.また,発射方向は,発射毎に,敵機の前方 ±45°の範囲内でランダムに決定されます.

    01	package game;
    02	
    03	import java.awt.*;
    04	import java.util.Random;
    05	
    06	class Bullet_e implements Runnable
    07	{
    08		int width = 14;   // 弾の幅
    09		int no = 5;   // 弾の全数
    10		int ct;   // 現在の弾の数
    11		int x[], y[];   // 弾の位置
    12		int v = 30;   // 弾の速さ
    13		int vx, vy;   // 横及び縦方向の弾の速さ
    14		boolean ex[];   // 弾の存在
    15		boolean in_game = true;
    16		Thread td;
    17		Dimension size;
    18		Random rn;
    19		Enemy em;
    20				// コンストラクタ
    21		public Bullet_e(Dimension size1, Random rn1, Enemy em1)
    22		{
    23			size = size1;
    24			rn   = rn1;
    25			em   = em1;
    26						// 初期設定と最初の弾の発射
    27			x  = new int [no];
    28			y  = new int [no];
    29			ex = new boolean [no];
    30			shoot();
    31						// スレッドの生成
    32			td = new Thread(this);
    33			td.start();
    34		}
    35				// スレッドの実行
    36		public void run()
    37		{
    38			while (in_game) {
    39				try {
    40					td.sleep(50);
    41				}
    42				catch (InterruptedException e) {}
    43						// 弾の数
    44				if (ct < no)
    45					ct++;
    46						// 弾の移動
    47				boolean sw = false;
    48				for (int i1 = 0; i1 < no; i1++) {
    49					if (ex[i1]) {
    50						x[i1] += vx;
    51						y[i1] += vy;
    52						if (x[i1] < -width || x[i1] > size.width || y[i1] > size.height)
    53							ex[i1] = false;
    54						else
    55							sw = true;
    56					}
    57				}
    58						// 最初の弾の発射
    59				if (!sw)
    60					shoot();
    61						// 次の弾の発射
    62				else {
    63					if (ct < no) {
    64						x[ct]  = em.x + em.width / 2 - width / 2;
    65						y[ct]  = em.y + em.height;
    66						ex[ct] = true;
    67					}
    68				}
    69			}
    70		}
    71				// 最初の弾の発射
    72		void shoot()
    73		{
    74			ct    = 0;
    75			ex[0] = true;
    76			for (int i1 = 1; i1 < no; i1++)
    77				ex[i1] = false;
    78			double ang = 0.25 * Math.PI + 0.5 * Math.PI * rn.nextDouble();
    79			vx   = (int)(v * Math.cos(ang) + 0.5);
    80			vy   = (int)(v * Math.sin(ang) + 0.5);
    81			x[0] = em.x + em.width / 2 - width / 2;
    82			y[0] = em.y + em.height;
    83		}
    84	}
    			
    27 行目~ 29 行目

      各弾の位置を示すフィールド x,y,及び,その弾が存在するか否かを示すフィールド ex を準備しています.

    30 行目

      メソッド shoot( 72 行目~ 83 行目)を使用して,最初の弾を発射しています.

    44,45 行目

      発射された弾の数をカウントしています.

    47 行目~ 57 行目

      弾の位置を修正した( 50,51 行目)後,画面の外に出たか否かの判定( 52 行目)を行っています.

    59,60 行目

      47 行目~ 57 行目を実行した結果,すべての弾が画面外に出た場合は,変数 sw の値が false になっています.その場合は,新たに no 個の弾を発射することになります.ここでは,その 1 発目の弾を発射しています.

    63 行目~ 67 行目

      一部の弾が画面内にあり,かつ,その数が no 個に満たない場合は,次の弾を発射します.発射方向は最初の弾と同じです.つまり,no 個の弾は同じ方向に発射されます.

    72 行目~ 83 行目

      最初の弾の発射処理を行うメソッドです.弾の初期位置は敵機の先端です( 81,82 行目).78 行目において,敵機の前方 ±45°の範囲内でランダムな方向を決定しています.79,80 行目では,この方向に基づいて,弾の水平方向及び垂直方向の速さを計算しています.

      GamePanel クラスでは,ボス及び敵機の弾の描画を追加しています.

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import java.util.Random;
    007	import main.*;
    008	
    009	public class GamePanel extends JPanel implements ActionListener, Runnable
    010	{
    011		Dimension size;   // パネルの大きさ
    012		MainPanel mp;
    013		JButton left, right;
    014		My my;   // 自機
    015		Boss bs;   // ボス
    016		int no = 2;   // 敵機の数
    017		Enemy em[] = new Enemy [no];   // 敵機
    018		boolean ex[] = new boolean [no];   // 敵機の存在
    019		Random rn;
    020		Thread td;
    021		boolean in_game = true;
    022				// コンストラクタ
    023		public GamePanel(Dimension size1, MainPanel mp1)
    024		{
    025			size = size1;
    026			mp   = mp1;
    027						// レイアウトマネージャの停止
    028			setLayout(null);
    029						// 背景色の設定
    030			setBackground(Color.white);
    031						// ボタンの配置
    032			Font f = new Font("SansSerif", Font.BOLD, 15);
    033			FontMetrics fm = getFontMetrics(f);
    034			String str1 = "ゲームクリア";
    035			int w1 = fm.stringWidth(str1) + 40;
    036			int h1 = fm.getHeight() + 10;
    037			left = new JButton(str1);
    038			left.setFont(f);
    039			left.addActionListener(this);   // アクションリスナ
    040			left.setSize(w1, h1);
    041	
    042			String str2 = "ゲームオーバー";
    043			int w2 = fm.stringWidth(str2) + 40;
    044			int h2 = fm.getHeight() + 10;
    045			right = new JButton(str2);
    046			right.setFont(f);
    047			right.addActionListener(this);   // アクションリスナ
    048			right.setSize(w2, h2);
    049	
    050			int w = size.width / 2 - (w1 + w2 + 5) / 2;
    051			int h = size.height - h1 - 10;
    052			left.setLocation(w, h);
    053			add(left);
    054			right.setLocation(w+w1+5, h);
    055			add(right);
    056						// ランダム変数の初期化
    057			rn = new Random();
    058						// 自機,ボス,敵機の配置
    059			my = new My(size, mp);
    060			if (mp.level == 1) {
    061				ex[0] = false;
    062				ex[1] = false;
    063			}
    064			else {
    065				ex[0] = true;
    066				ex[1] = true;
    067			}
    068			bs = new Boss(size, rn, this, mp);
    069			if (ex[0])
    070				em[0] = new Enemy(bs, 0, size, rn, mp);
    071			if (ex[1])
    072				em[1] = new Enemy(bs, 1, size, rn, mp);
    073						// スレッドの生成
    074			td = new Thread(this);
    075			td.start();
    076						// キーリスナの追加
    077			addKeyListener(new Key());
    078		}
    079				// スレッドの実行
    080		public void run()
    081		{
    082			while (in_game) {
    083				try {
    084					td.sleep(10);
    085				}
    086				catch (InterruptedException e) {}
    087				repaint();
    088			}
    089		}
    090				// 描画
    091		public void paintComponent(Graphics g)
    092		{
    093			super.paintComponent(g);   // 親クラスの描画
    094						// 自機と弾
    095			g.drawImage(my.image, my.x, my.y, this);
    096			g.setColor(Color.green);
    097			for (int i1 = 0; i1 < my.bl.no; i1++) {
    098				if (my.bl.ex[i1])
    099					g.fillOval(my.bl.x[i1], my.bl.y[i1], my.bl.width, my.bl.width);
    100			}
    101						// ボスと弾
    102			g.drawImage(bs.image, bs.x, bs.y, this);
    103			g.setColor(Color.orange);
    104			for (int i1 = 0; i1 < bs.bl.no; i1++) {
    105				if (bs.bl.ex[i1])
    106					g.fillOval(bs.bl.x[i1], bs.bl.y[i1], bs.bl.width, bs.bl.width);
    107			}
    108						// 敵機と弾
    109			g.setColor(Color.red);
    110			for (int i1 = 0; i1 < no; i1++) {
    111				if (ex[i1]) {
    112					g.drawImage(em[i1].image, em[i1].x, em[i1].y, this);
    113					for (int i2 = 0; i2 < em[i1].bl.no; i2++) {
    114						if (em[i1].bl.ex[i2])
    115							g.fillOval(em[i1].bl.x[i2], em[i1].bl.y[i2],
    116										em[i1].bl.width, em[i1].bl.width);
    117					}
    118				}
    119			}
    120						// この Component が入力フォーカスを取得することを要求
    121			requestFocusInWindow();
    122		}
    123				// ボタンがクリックされたときの処理
    124		public void actionPerformed(ActionEvent e)
    125		{
    126			in_game = false;
    127			bs.in_game = false;
    128			bs.bl.in_game = false;
    129			my.bl.in_game = false;
    130			for (int i1 = 0; i1 < no; i1++) {
    131				if (ex[i1])
    132					em[i1].bl.in_game = false;
    133			}
    134			if (e.getSource() == left)   // ゲームクリア
    135				mp.state = 2;
    136			else   // ゲームオーバー
    137				mp.state = 3;
    138		}
    139				// キーが押されたときの処理(イベントアダプタ)
    140		class Key extends KeyAdapter {
    141			public void keyPressed(KeyEvent ke)
    142			{
    143				if (ke.getKeyCode() == KeyEvent.VK_UP)   // 「↑」キー( 38 )
    144					my.y -= my.v;
    145				else if (ke.getKeyCode() == KeyEvent.VK_DOWN)   // 「↓」キー( 40 )
    146					my.y += my.v;
    147				else if (ke.getKeyCode() == KeyEvent.VK_LEFT)   // 「←」キー( 37 )
    148					my.x -= my.v;
    149				else if (ke.getKeyCode() == KeyEvent.VK_RIGHT)   // 「→」キー( 39 )
    150					my.x += my.v;
    151				else if (ke.getKeyCode() == KeyEvent.VK_SPACE)   // 「スペース」キー( 32 )
    152					my.bl.shoot();
    153			}
    154		}
    155	}
    			
    103 行目~ 107 行目

      ボス及びボスから発射された弾を描画しています.

    109 行目~ 119 行目

      敵機及び敵機から発射された弾を描画しています.

    126 行目~ 133 行目

      ゲームの状態を変える前に,ゲーム内で使用したすべてのスレッドを停止しています.

  6. ステップ6: 完成

      ここでは,弾の命中判定を付加し,ゲームを完成させます.以下の説明においては,すべての処理を同時に行うように記述してありますが,このままでは,自機の弾がボスや敵機に命中したときの判定が正しく行われているか否かが見極めにくいと思います.そこで,ボスや敵機の弾に対する判定を行わず( 114 行目~ 167 行目をコメントにする)に,自機の弾に対する判定だけを行い,それが正しく動作したときに,ボスや敵機の弾に対する判定を行うようにした方がデバッグしやすいと思います.

      修正するプログラムは,game パッケージ内の Boss クラス( Boss.java )と GamePanel クラス( GamePanel.java )です.まず,Boss クラスの変更は簡単です.ボスは,1 発の弾だけでは撃墜されないようにするため,命中した弾に対するカウンタ( 13 行目),及び,何発以上の弾が当たると撃墜されるのかを示すフィールド( 14 行目)を設定しておくだけです.

    01	package game;
    02	
    03	import java.awt.*;
    04	import java.util.Random;
    05	import main.*;
    06	
    07	class Boss implements Runnable
    08	{
    09		Image image;   // ボス画像
    10		int width = 66;   // ボスの幅
    11		int height = 95;   // ボスの高さ
    12		int x, y;   // ボスの位置
    13		int h_ct = 0;   // 命中した弾の数
    14		int h_max = 5;   // 耐えうる命中した弾の数
    15		Thread td;
    16		boolean in_game = true;
    17		int ptn1[][] = {{-5, 0, 50}, {0, 20, 55}, {5, 0, 105}, {0, -20, 110}};
    18		int ptn2[][] = {{5, 0, 50}, {0, 20, 55}, {-5, 0, 105}, {0, -20, 110}};
    19		int ptn[][];
    20		int ct = 1;
    21		GamePanel gp;
    22		Bullet_b bl;   // 弾
    23				// コンストラクタ
    24		public Boss(Dimension size, Random rn, GamePanel gp1, MainPanel mp)
    25		{
    26			gp = gp1;
    27						// ボス画像の読み込み
    28			image = mp.getToolkit().getImage("game/image/boss.gif");
    29						// ボスの初期位置
    30			x = 100 + rn.nextInt(size.width - 200 - width);
    31			y = 10 + rn.nextInt(5);
    32			if (x > size.width/2-width/2)
    33				ptn = ptn1;
    34			else
    35				ptn = ptn2;
    36						// スレッドの生成
    37			td = new Thread(this);
    38			td.start();
    39						// Bullet_b クラスのオブジェクトを生成
    40			bl = new Bullet_b(size, this, gp.my);
    41		}
    42				// スレッドの実行
    43		public void run()
    44		{
    45			while (in_game) {
    46				try {
    47					td.sleep(50);
    48				}
    49				catch (InterruptedException e) {}
    50				ct++;
    51				if (ct > 110)
    52					ct = 1;
    53						// ボスの位置
    54				int k = -1;
    55				for (int i1 = 0; i1 < ptn.length-1 && k < 0; i1++) {
    56					if (ct <= ptn[i1][2])
    57						k = i1;
    58				}
    59				if (k < 0)
    60					k = ptn.length - 1;
    61				x += ptn[k][0];
    62				y += ptn[k][1];
    63						// 敵機の位置
    64				if (gp.ex[0]) {
    65					gp.em[0].x += ptn[k][0];
    66					gp.em[0].y += ptn[k][1];
    67				}
    68				if (gp.ex[1]) {
    69					gp.em[1].x += ptn[k][0];
    70					gp.em[1].y += ptn[k][1];
    71				}
    72			}
    73		}
    74	}
    			

      命中判定は,基本的に,GamePanel クラスで行います.また,最後に,レベル変更の確認を行うために使用していた「ゲームクリア」,及び,「ゲームオーバー」ボタンを削除しておく必要があります.

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import java.util.Random;
    007	import main.*;
    008	
    009	public class GamePanel extends JPanel implements Runnable
    010	{
    011		Dimension size;   // パネルの大きさ
    012		MainPanel mp;
    013		My my;   // 自機
    014		Boss bs;   // ボス
    015		int no = 2;   // 敵機の数
    016		Enemy em[] = new Enemy [no];   // 敵機
    017		boolean ex[] = new boolean [no];   // 敵機の存在
    018		Random rn;
    019		Thread td;
    020		boolean in_game = true;
    021				// コンストラクタ
    022		public GamePanel(Dimension size1, MainPanel mp1)
    023		{
    024			size = size1;
    025			mp   = mp1;
    026						// レイアウトマネージャの停止
    027			setLayout(null);
    028						// 背景色の設定
    029			setBackground(Color.white);
    030						// ランダム変数の初期化
    031			rn = new Random();
    032						// 自機,ボス,敵機の配置
    033			my = new My(size, mp);
    034			if (mp.level == 1) {
    035				ex[0] = false;
    036				ex[1] = false;
    037			}
    038			else {
    039				ex[0] = true;
    040				ex[1] = true;
    041			}
    042			bs = new Boss(size, rn, this, mp);
    043			if (ex[0])
    044				em[0] = new Enemy(bs, 0, size, rn, mp);
    045			if (ex[1])
    046				em[1] = new Enemy(bs, 1, size, rn, mp);
    047						// スレッドの生成
    048			td = new Thread(this);
    049			td.start();
    050						// キーリスナの追加
    051			addKeyListener(new Key());
    052		}
    053				// スレッドの実行
    054		public void run()
    055		{
    056			while (in_game) {
    057				try {
    058					td.sleep(10);
    059				}
    060				catch (InterruptedException e) {}
    061						// 自機の弾による命中判定
    062				boolean hit = false;
    063				int xb, yb, xt, yt, h, w;
    064								// ボスに対して
    065				for (int i1 = 0; i1 < my.bl.no && !hit; i1++) {
    066					if (my.bl.ex[i1]) {
    067						xb = my.bl.x[i1] + my.bl.width / 2;
    068						yb = my.bl.y[i1] + my.bl.width / 2;
    069						w  = bs.width / 2 + my.bl.width / 2;
    070						h  = bs.height / 2 + my.bl.width / 2;
    071						xt = bs.x + bs.width / 2;
    072						yt = bs.y + bs.height / 2;
    073						if (xb > xt-w && xb < xt+w && yb > yt-h && yb < yt+h) {
    074							my.bl.ex[i1] = false;
    075							bs.h_ct++;
    076							if (bs.h_ct > bs.h_max) {
    077								hit = true;
    078								in_game = false;
    079								bs.in_game = false;
    080								bs.bl.in_game = false;
    081								my.bl.in_game = false;
    082								for (int i2 = 0; i2 < no; i2++) {
    083									if (ex[i2])
    084										em[i2].bl.in_game = false;
    085								}
    086								mp.state = 2;   // ゲームクリア
    087							}
    088						}
    089					}
    090				}
    091								// 敵機に対して
    092				if (!hit) {
    093					for (int i1 = 0; i1 < no && !hit; i1++) {
    094						if (ex[i1]) {
    095							for (int i2 = 0; i2 < my.bl.no && !hit; i2++) {
    096								if (my.bl.ex[i2]) {
    097									xb = my.bl.x[i2] + my.bl.width / 2;
    098									yb = my.bl.y[i2] + my.bl.width / 2;
    099									w  = em[i1].width / 2 + my.bl.width / 2;
    100									h  = em[i1].height / 2 + my.bl.width / 2;
    101									xt = em[i1].x + em[i1].width / 2;
    102									yt = em[i1].y + em[i1].height / 2;
    103									if (xb > xt-w && xb < xt+w && yb > yt-h && yb < yt+h) {
    104										hit = true;
    105										ex[i1] = false;
    106										my.bl.ex[i2] = false;
    107										em[i1].bl.in_game = false;
    108									}
    109								}
    110							}
    111						}
    112					}
    113				}
    114						// ボスの弾による命中判定
    115				if (!hit) {
    116					for (int i1 = 0; i1 < bs.bl.no && !hit; i1++) {
    117						if (bs.bl.ex[i1]) {
    118							xb = bs.bl.x[i1] + bs.bl.width / 2;
    119							yb = bs.bl.y[i1] + bs.bl.width / 2;
    120							w  = my.width / 2;
    121							h  = my.width / 2;
    122							xt = my.x + w;
    123							yt = my.y + h;
    124							if (xb > xt-w && xb < xt+w && yb > yt-h && yb < yt+h) {
    125								hit = true;
    126								in_game = false;
    127								bs.in_game = false;
    128								bs.bl.in_game = false;
    129								my.bl.in_game = false;
    130								for (int i3 = 0; i3 < no; i3++) {
    131									if (ex[i3])
    132										em[i3].bl.in_game = false;
    133								}
    134								mp.state = 3;   // ゲームオーバー
    135							}
    136						}
    137					}
    138				}
    139						// 敵機の弾による命中判定
    140				if (!hit) {
    141					for (int i1 = 0; i1 < no && !hit; i1++) {
    142						if (ex[i1]) {
    143							for (int i2 = 0; i2 < em[i1].bl.no && !hit; i2++) {
    144								if (em[i1].bl.ex[i2]) {
    145									xb = em[i1].bl.x[i2] + em[i1].bl.width / 2;
    146									yb = em[i1].bl.y[i2] + em[i1].bl.width / 2;
    147									w  = my.width / 2;
    148									h  = my.width / 2;
    149									xt = my.x + w;
    150									yt = my.y + h;
    151									if (xb > xt-w && xb < xt+w && yb > yt-h && yb < yt+h) {
    152										hit = true;
    153										in_game = false;
    154										bs.in_game = false;
    155										bs.bl.in_game = false;
    156										my.bl.in_game = false;
    157										for (int i3 = 0; i3 < no; i3++) {
    158											if (ex[i3])
    159												em[i3].bl.in_game = false;
    160										}
    161										mp.state = 3;   // ゲームオーバー
    162									}
    163								}
    164							}
    165						}
    166					}
    167				}
    168						// 再描画
    169				repaint();
    170			}
    171		}
    172				// 描画
    173		public void paintComponent(Graphics g)
    174		{
    175			super.paintComponent(g);   // 親クラスの描画
    176						// 自機と弾
    177			g.drawImage(my.image, my.x, my.y, this);
    178			g.setColor(Color.green);
    179			for (int i1 = 0; i1 < my.bl.no; i1++) {
    180				if (my.bl.ex[i1])
    181					g.fillOval(my.bl.x[i1], my.bl.y[i1], my.bl.width, my.bl.width);
    182			}
    183						// ボスと弾
    184			g.drawImage(bs.image, bs.x, bs.y, this);
    185			g.setColor(Color.orange);
    186			for (int i1 = 0; i1 < bs.bl.no; i1++) {
    187				if (bs.bl.ex[i1])
    188					g.fillOval(bs.bl.x[i1], bs.bl.y[i1], bs.bl.width, bs.bl.width);
    189			}
    190						// 敵機と弾
    191			g.setColor(Color.red);
    192			for (int i1 = 0; i1 < no; i1++) {
    193				if (ex[i1]) {
    194					g.drawImage(em[i1].image, em[i1].x, em[i1].y, this);
    195					for (int i2 = 0; i2 < em[i1].bl.no; i2++) {
    196						if (em[i1].bl.ex[i2])
    197							g.fillOval(em[i1].bl.x[i2], em[i1].bl.y[i2],
    198										em[i1].bl.width, em[i1].bl.width);
    199					}
    200				}
    201			}
    202						// この Component が入力フォーカスを取得することを要求
    203			requestFocusInWindow();
    204		}
    205				// キーが押されたときの処理(イベントアダプタ)
    206		class Key extends KeyAdapter {
    207			public void keyPressed(KeyEvent ke)
    208			{
    209				if (ke.getKeyCode() == KeyEvent.VK_UP)   // 「↑」キー( 38 )
    210					my.y -= my.v;
    211				else if (ke.getKeyCode() == KeyEvent.VK_DOWN)   // 「↓」キー( 40 )
    212					my.y += my.v;
    213				else if (ke.getKeyCode() == KeyEvent.VK_LEFT)   // 「←」キー( 37 )
    214					my.x -= my.v;
    215				else if (ke.getKeyCode() == KeyEvent.VK_RIGHT)   // 「→」キー( 39 )
    216					my.x += my.v;
    217				else if (ke.getKeyCode() == KeyEvent.VK_SPACE)   // 「スペース」キー( 32 )
    218					my.bl.shoot();
    219			}
    220		}
    221	}
    			
    009 行目

      ボタンをクリックしたときの処理が必要でなくなったため,ActionListener インタフェースを削除しています.この結果,2 つのボタン,及び,それらのボタンがクリックされたときの処理を行うメソッド actionPerformed (ステップ5における 031 行目~ 055 行目,及び,124 行目~ 138 行目)を削除しています.

    065 行目~ 090 行目

      自機の弾のボスに対する命中判定を行っています.ボスは,画像の幅と高さのそれぞれに弾の直径を加えた大きさを持つ矩形として扱い,この中に弾の中心が入った場合は命中と判定しています.命中した場合は,命中した弾の数をカウントアップし( 075 行目),その数が上限を超えていたら( 076 行目),ゲームクリアの状態に移行します.

    092 行目~ 113 行目

      自機の弾の敵機に対する命中判定を行っています.ボスの場合と同様,敵機は,画像の幅と高さのそれぞれに弾の直径を加えた大きさを持つ矩形として扱い,この中に弾の中心が入った場合は命中と判定しています.命中した場合は,その敵機を削除します.

    116 行目~ 137 行目

      ボスの弾の自機に対する命中判定を行っています.自機は,画像の幅から決まる正方形として扱い,この中に弾の中心が入った場合は命中と判定しています.命中判定を,自機の弾に対する判定より多少甘くしています.命中した場合は,ゲームオーバーになります.

    141 行目~ 166 行目

      敵機の弾の自機に対する命中判定を行っています.自機は,画像の幅から決まる正方形として扱い,この中に弾の中心が入った場合は命中と判定しています.ボスの弾と同様,命中判定を,自機の弾に対する判定より多少甘くしています.命中した場合は,ゲームオーバーになります.

情報学部 菅沼ホーム Java 目次 索引