情報学部 菅沼ホーム 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(360, 490);   // 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 」キーを押すとゲームを開始するように設定していましたが,ここではマウスのダブルクリックによって開始するように変更してあります( 036 行目,073 行目~ 078 行目).当然のことながら,ゲームタイトル及び「遊び方」の内容を変更しています( 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	
      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 クラスとは,ゲームの種類によってその内容は大きく異なります.今後,このクラス及びその関連クラスを完成させていくことになりますが,ここでは,ブロック,ラケット,及び,ボールを表示しています.そのソースファイルは以下に示す通りです( GamePanel.java : 001 行目~ 105 行目, Block.java : 107 行目~ 133 行目,Racket.java : 135 行目~ 157 行目,Ball.java : 159 行目~ 180 行目).

      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		Block blk;   // ブロック
      015		Racket rk;   // ラケット
      016		Ball bl;   // ボール
      017		Random rand;
      018		Thread td;
      019		boolean in_game = true;
      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			blk = new Block(mp);
      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			rand = new Random();
      058						// ボール及びラケットの配置
      059			rk = new Racket(h-10, size, mp);
      060			bl = new Ball(blk, size, rand);
      061						// スレッドの生成
      062			td = new Thread(this);
      063			td.start();
      064		}
      065				// スレッドの実行
      066		public void run()
      067		{
      068			while (in_game) {
      069				try {
      070					td.sleep(50);
      071				}
      072				catch (InterruptedException e) {}
      073				bl.x += bl.vx;
      074				bl.y += bl.vy;
      075				repaint();
      076			}
      077		}
      078				// 描画
      079		public void paintComponent(Graphics g)
      080		{
      081			super.paintComponent(g);   // 親クラスの描画
      082						// ブロック
      083			for (int i1 = 0; i1 < blk.row; i1++) {
      084				for (int i2 = 0; i2 < blk.col; i2++) {
      085					if (blk.ex[i1][i2])
      086						g.drawImage(blk.block, blk.width*i2, blk.height*i1, this);
      087				}
      088			}
      089						// ラケット
      090			g.setColor(rk.color);
      091			g.fillRect(rk.x, rk.y, rk.width, rk.height);
      092						// ボール
      093			g.setColor(bl.color);
      094			g.fillOval(bl.x, bl.y, bl.r*2, bl.r*2);
      095		}
      096				// ボタンがクリックされたときの処理
      097		public void actionPerformed(ActionEvent e)
      098		{
      099			in_game = false;
      100			if (e.getSource() == left)   // ゲームクリア
      101				mp.state = 2;
      102			else   // ゲームオーバー
      103				mp.state = 3;
      104		}
      105	}
      106	
      107	package game;
      108	
      109	import java.awt.*;
      110	import main.*;
      111	
      112	class Block
      113	{
      114		Image block;   // ブロックの画像
      115		int width = 75;   // ブロックの幅
      116		int height = 38;   // ブロックの高さ
      117		int row = 2;   // ブロックの行数
      118		int col = 4;   // ブロックの列数
      119		int number = row * col;   // ブロックの数
      120		boolean ex[][];   // ブロックの状態(存在するか否か)
      121				// コンストラクタ
      122		public Block(MainPanel mp)
      123		{
      124						// ブロックの読み込み
      125			block = mp.getToolkit().getImage("game/image/block.jpg");
      126						// ブロックの状態(存在するか否か)の設定
      127			ex = new boolean [row][col];
      128			for (int i1 = 0; i1 < row; i1++) {
      129				for (int i2 = 0; i2 < col; i2++)
      130					ex[i1][i2] = true;
      131			}
      132		}
      133	}
      134	
      135	package game;
      136	
      137	import java.awt.*;
      138	import main.*;
      139	
      140	class Racket
      141	{
      142		Color color = new Color(0, 255, 0);   // ラケットの色
      143		int width;   // ラケットの幅
      144		int height = 20;   // ラケットの高さ
      145		int x;   // ラケットの横位置
      146		int y;   // ラケットの縦位置
      147				// コンストラクタ
      148		public Racket(int h, Dimension size, MainPanel mp)
      149		{
      150			if (mp.level == 1)
      151				width = 80;
      152			else
      153				width = 40;
      154			x = size.width / 2 - width / 2;
      155			y = h - height;
      156		}
      157	}
      158	
      159	package game;
      160	
      161	import java.awt.*;
      162	import java.util.Random;
      163	
      164	class Ball
      165	{
      166		Color color = new Color(255, 0, 0);   // ボールの色
      167		int r = 7;   // ボールの半径
      168		int x;   // ボールの横位置
      169		int y;   // ボールの縦位置
      170		int vx;   // ボールの横方向速度成分
      171		int vy;   // ボールの縦方向速度成分
      172				// コンストラクタ
      173		public Ball(Block blk, Dimension size, Random rn)
      174		{
      175			x  = rn.nextInt(size.width - 2 * r);
      176			y  = blk.height * blk.row + 10;
      177			vx = 0;
      178			vy = 0;
      179		}
      180	}
      				
      030,059,060 行目

        今後,その機能を拡張していく可能性がありますので,ブロック,ラケット,及び,ボールは,クラスとして別ファイル( 107 行目~ 133 行目,135 行目~ 157 行目,及び,159 行目~ 180 行目)に定義してあります.ここでは,それらに対するオブジェクトを生成しています.

      034,042 行目

        ラケットを左右に移動させるための「左」ボタンと「右」ボタンに対応します.これらのボタンがクリックされた時の処理は,メソッド actionPerformed ( 097 行目~ 104 行目)で実行されますが,現時点では,それぞれ,ゲームクリア及びゲームオーバーの状態に移行するようにプログラムしてあります.

      062,063 行目

        Thread クラスのオブジェクトを生成し,それをスタートしています.この結果,run メソッド( 066 行目~ 077 行目)の while 文内の処理が 50 ms 毎( 070 行目)に実行されることになります.

      073,074 行目

        ボールを横軸方向に速さ bl.vx,縦軸方向に速さ bl.vy で動かしていますが,現時点では,速度が 0 に設定されていますので動きません.

      083 行目~ 088 行目

        Block クラスでは,ブロックが存在するか否かを表現するために 2 次元配列を使用しています.ここでは,ブロックが存在する場所だけにブロックを描画しています( Block クラス参照).

      107 行目~ 133 行目

        ブロックに対するクラス( Block クラス)の定義です.2 次元配列 ex を使用して( 120 行目,127 行目),ブロックの存在を表現しています.初期状態では,すべてのブロックが存在するため,すべてに true を設定しています.

        一般に,多次元配列は,配列の各要素を配列として定義することによって可能です.例えば,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};					
      のような方法で行います.

      135 行目~ 157 行目

        ラケットに対するクラス( Racket クラス)の定義です.レベル 2 になると,ラケットの幅を半分にしています.

      159 行目~ 180 行目

        ボールに対するクラス( Ball クラス)の定義です.初期状態において,ボールの横位置はランダム,また,縦位置はブロックの 10 ピクセル下に設定してあります.

    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, 10);
      		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 パッケージ内の GamePanel クラス( GamePanel.java : 029 行目~ 169 行目)と Ball クラス( Ball.java : 001 行目~ 027 行目)です.

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.util.Random;
    005	
    006	class Ball
    007	{
    008		Color color = new Color(255, 0, 0);   // ボールの色
    009		int r = 7;   // ボールの半径
    010		int x;   // ボールの横位置
    011		int y;   // ボールの縦位置
    012		int v = 8;   // ボールの速度
    013		int vx;   // ボールの横方向速度成分
    014		int vy;   // ボールの縦方向速度成分
    015				// コンストラクタ
    016		public Ball(Block blk, Dimension size, Random rn)
    017		{
    018			x = rn.nextInt(size.width - 2 * r);
    019			y = blk.height * blk.row + 10;
    020			double a = v * Math.cos(45.0 * Math.PI / 180.0);
    021			vy = (int)a;
    022			if (x < size.width / 2 - r)
    023				vx = (int)a;
    024			else
    025				vx = -(int)a;
    026		}
    027	}
    028	
    029	package game;
    030	
    031	import java.awt.*;
    032	import java.awt.event.*;
    033	import javax.swing.*;
    034	import java.util.Random;
    035	import main.*;
    036	
    037	public class GamePanel extends JPanel implements ActionListener, Runnable
    038	{
    039		Dimension size;   // パネルの大きさ
    040		MainPanel mp;
    041		JButton left, right;
    042		Block blk;   // ブロック
    043		Racket rk;   // ラケット
    044		Ball bl;   // ボール
    045		Random rand;
    046		Thread td;
    047		boolean in_game = true;
    048				// コンストラクタ
    049		public GamePanel(Dimension size1, MainPanel mp1)
    050		{
    051			size = size1;
    052			mp   = mp1;
    053						// レイアウトマネージャの停止
    054			setLayout(null);
    055						// 背景色の設定
    056			setBackground(Color.white);
    057						// ブロックの配置
    058			blk = new Block(mp);
    059						// ボタンの配置
    060			Font f = new Font("SansSerif", Font.BOLD, 15);
    061			FontMetrics fm = getFontMetrics(f);
    062			String str1 = "左";
    063			int w1 = fm.stringWidth(str1) + 40;
    064			int h1 = fm.getHeight() + 10;
    065			left = new JButton(str1);
    066			left.setFont(f);
    067			left.addActionListener(this);   // アクションリスナ
    068			left.setSize(w1, h1);
    069	
    070			String str2 = "右";
    071			int w2 = fm.stringWidth(str2) + 40;
    072			int h2 = fm.getHeight() + 10;
    073			right = new JButton(str2);
    074			right.setFont(f);
    075			right.addActionListener(this);   // アクションリスナ
    076			right.setSize(w2, h2);
    077	
    078			int w = size.width / 2 - (w1 + w2 + 5) / 2;
    079			int h = size.height - h1 - 10;
    080			left.setLocation(w, h);
    081			add(left);
    082			right.setLocation(w+w1+5, h);
    083			add(right);
    084						// ランダム変数の初期化
    085			rand = new Random();
    086						// ボール及びラケットの配置
    087			rk = new Racket(h-10, size, mp);
    088			bl = new Ball(blk, size, rand);
    089						// スレッドの生成
    090			td = new Thread(this);
    091			td.start();
    092		}
    093				// スレッドの実行
    094		public void run()
    095		{
    096			while (in_game) {
    097				try {
    098					td.sleep(50);
    099				}
    100				catch (InterruptedException e) {}
    101						// 移動
    102				bl.x += bl.vx;
    103				bl.y += bl.vy;
    104						// 壁に衝突したときの処理
    105				int sw = 0;
    106								// 下へ移動中
    107				if (bl.vy > 0) {
    108					if (bl.y >= size.height-bl.r*2) {   // 下の壁に衝突
    109						bl.y  = size.height - bl.r * 2;
    110						bl.vy = -bl.vy;
    111						sw    = 1;
    112					}
    113				}
    114								// 上へ移動中
    115				else {
    116					if (bl.y <= 0) {   // 上の壁に衝突
    117						bl.y  = 0;
    118						bl.vy = -bl.vy;
    119						sw    = 1;
    120					}
    121				}
    122								// 上下の壁に衝突していない場合
    123				if (sw == 0) {
    124										// 右方向へ移動中
    125					if (bl.vx > 0) {
    126						if (bl.x >= size.width-bl.r*2) {   // 右の壁に衝突
    127							bl.x  = size.width - bl.r * 2;
    128							bl.vx = -bl.vx;
    129						}
    130					}
    131										// 左方向へ移動中
    132					else {
    133						if (bl.x <= 0) {   // 左の壁に衝突
    134							bl.x  = 0;
    135							bl.vx = -bl.vx;
    136						}
    137					}
    138				}
    139						// 再描画
    140				repaint();
    141			}
    142		}
    143				// 描画
    144		public void paintComponent(Graphics g)
    145		{
    146			super.paintComponent(g);   // 親クラスの描画
    147						// ブロック
    148			for (int i1 = 0; i1 < blk.row; i1++) {
    149				for (int i2 = 0; i2 < blk.col; i2++) {
    150					if (blk.ex[i1][i2])
    151						g.drawImage(blk.block, blk.width*i2, blk.height*i1, this);
    152				}
    153			}
    154						// ラケット
    155			g.setColor(rk.color);
    156			g.fillRect(rk.x, rk.y, rk.width, rk.height);
    157						// ボール
    158			g.setColor(bl.color);
    159			g.fillOval(bl.x, bl.y, bl.r*2, bl.r*2);
    160		}
    161				// ボタンがクリックされたときの処理
    162		public void actionPerformed(ActionEvent e)
    163		{
    164			if (e.getSource() == left)   // ラケットを左へ移動
    165				rk.x -= (rk.width - 5);
    166			else   // ラケットを右へ移動
    167				rk.x += (rk.width - 5);
    168		}
    169	}
    			
    012 行目

      ボールの速度を設定しています.

    020,021 行目

      Math クラスstatic メソッドスタティックメソッドcos を使用して,45 度の余弦を求め,速度の y 軸方向の成分を計算しています.Math クラスは static なクラスであり,そのオブジェクトを生成することはできません.このような場合は,「クラス名.メソッド名」という形式でそのクラスのメソッドやフィールド(変数)( Math.PI など)を利用します.Math クラスには,数学で使用するメソッドが多く定義されています.なお,変数 a は double 型で宣言してあるため,int 型に変換しています(型変換( cast 演算子)).

    022 行目~ 025 行目

      初期状態においてボールが中央より左にあった場合は右方向へ,そうでない場合は,左方向に移動します.

    107 行目~ 121 行目

      上下の壁に衝突したときの処理を行っています.時間的なタイミング上,正確に上下の壁の位置と一致した場合に処理を行うことは不可能です(勿論,場合によっては可能ですが).そこで,少し壁にめり込んだ位置で処理を行います.そのため,垂直方向の座標を,壁の位置に強制的に修正しています( 109 行目,117 行目).また,110 行目,118 行目は,縦方向の速度を逆方向に設定しています.これらの点は,左右の壁についても同様です.

    125 行目~ 137 行目

      上下の壁に衝突しなかった場合に対して,左右の壁に衝突したときの処理を行っています.

    062 行目,070 行目,162 行目~ 168 行目

      「左」または「右」ボタンがクリックされると,このメソッドが実行されます( 067,075 行目参照).ラケットを左右に移動させる処理です.

  3. ステップ3: ブロック崩し

      ボールがブロックに当たったとき,そのブロックを消去する処理を行っています.修正するプログラムは,game パッケージ内の GamePanel クラスです( GamePanel.java ).

    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		Block blk;   // ブロック
    015		Racket rk;   // ラケット
    016		Ball bl;   // ボール
    017		Random rand;
    018		Thread td;
    019		boolean in_game = true;
    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			blk = new Block(mp);
    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			rand = new Random();
    058						// ボール及びラケットの配置
    059			rk = new Racket(h-10, size, mp);
    060			bl = new Ball(blk, size, rand);
    061						// スレッドの生成
    062			td = new Thread(this);
    063			td.start();
    064		}
    065				// スレッドの実行
    066		public void run()
    067		{
    068			while (in_game) {
    069				try {
    070					td.sleep(50);
    071				}
    072				catch (InterruptedException e) {}
    073						// 移動
    074				bl.x += bl.vx;
    075				bl.y += bl.vy;
    076						// 壁に衝突したときの処理
    077				int sw = 0;
    078								// 下へ移動中
    079				if (bl.vy > 0) {
    080					if (bl.y >= size.height-bl.r*2) {   // 下の壁に衝突
    081						bl.y  = size.height - bl.r * 2;
    082						bl.vy = -bl.vy;
    083						sw    = 1;
    084					}
    085				}
    086								// 上へ移動中
    087				else {
    088					if (bl.y <= blk.row*blk.height) {
    089						int k = -1;
    090										// 横方向のブロック位置
    091						for (int i1 = 1; i1 < blk.col && k < 0; i1++) {
    092							if (bl.x+bl.r <= i1*blk.width)
    093								k = i1 - 1;
    094						}
    095						if (k < 0)
    096							k = blk.col - 1;
    097										// ブロックとの衝突
    098						for (int i1 = blk.row; i1 >= 0 && sw == 0; i1--) {
    099							if (bl.y <= i1*blk.height) {
    100								if (i1 == 0 || blk.ex[i1-1][k]) {
    101									bl.y  = i1 * blk.height;
    102									bl.vy = -bl.vy;
    103									sw    = 1;
    104									if (i1 > 0) {
    105										blk.ex[i1-1][k] = false;
    106										blk.number--;
    107										if (blk.number == 0) {   // ゲームクリア
    108											in_game  = false;
    109											mp.state = 2;
    110										}
    111									}
    112								}
    113							}
    114						}
    115					}
    116				}
    117								// 上下で衝突していない場合
    118				if (sw == 0) {
    119										// 右方向へ移動中
    120					if (bl.vx > 0) {
    121						if (bl.x >= size.width-bl.r*2) {   // 右の壁に衝突
    122							bl.x  = size.width - bl.r * 2;
    123							bl.vx = -bl.vx;
    124						}
    125					}
    126										// 左方向へ移動中
    127					else {
    128						if (bl.x <= 0) {   // 左の壁に衝突
    129							bl.x  = 0;
    130							bl.vx = -bl.vx;
    131						}
    132					}
    133				}
    134						// 再描画
    135				repaint();
    136			}
    137		}
    138				// 描画
    139		public void paintComponent(Graphics g)
    140		{
    141			super.paintComponent(g);   // 親クラスの描画
    142						// ブロック
    143			for (int i1 = 0; i1 < blk.row; i1++) {
    144				for (int i2 = 0; i2 < blk.col; i2++) {
    145					if (blk.ex[i1][i2])
    146						g.drawImage(blk.block, blk.width*i2, blk.height*i1, this);
    147				}
    148			}
    149						// ラケット
    150			g.setColor(rk.color);
    151			g.fillRect(rk.x, rk.y, rk.width, rk.height);
    152						// ボール
    153			g.setColor(bl.color);
    154			g.fillOval(bl.x, bl.y, bl.r*2, bl.r*2);
    155		}
    156				// ボタンがクリックされたときの処理
    157		public void actionPerformed(ActionEvent e)
    158		{
    159			if (e.getSource() == left)   // ラケットを左へ移動
    160				rk.x -= (rk.width - 5);
    161			else   // ラケットを右へ移動
    162				rk.x += (rk.width - 5);
    163		}
    164	}
    			
    088 行目

      この if 文により,ボールがブロックの存在する位置より上にあるときだけ,089 行目~ 114 行目の処理を行います.

    089 行目~ 096 行目

      ボールの中心に基づき,水平位置として,どのブロックの位置にいるかを調べています(配列 blk.ex の列の位置を決める).このプログラムでは,横方向はボールの中心を基準にして衝突判定を行っているため,ボールの一部がブロックの角をかすったようなときは,衝突にならない場合があります.また,ボールが上部へ移動中だけに衝突判定を行っているため,跳ね返ったボールが他のブロックに衝突しても衝突とはみなしません.

    098 行目

      この for 文によって,ブロックの行の数だけ,099 行目~ 113 行目が繰り返されます.ブロックまたは上の壁に衝突すると,変数 sw の値が 1 に設定され,繰り返し文の外に出ます.一番下のブロックから調べているため,i1 の値がブロックの行数から始まり,順に減らされている点に注意してください.

    099 行目

      この if 文によって,ボールが壁または i1 行目のブロックに衝突している場合に,100 行目~ 112 行目の処理が行われます.

    100 行目~ 112 行目

      100 行目の if 文により,i1 が 0 の場合(上の壁に衝突),または,(i1-1) 行 k 列にブロックが存在した場合(ブロックに衝突)に以下の処理が行われます.ボールの位置と速度を修正し,sw の値を 1 に設定した後( 101 行目~ 103 行目),衝突したのがブロックであった場合( 104 行目~ 111 行目)は,そのブロックを消去し,ブロックの数を減らしています( 105,106 行目).すべてのブロックが消去された場合は,ゲームクリアとなります( 107 行目~ 110 行目).

  4. ステップ4: 完成

      ボールがラケットに当たったときの処理を行っています.修正するプログラムは,game パッケージ内の GamePanel クラスです( GamePanel.java ).080 行目~ 090 行目において,ラケットに当たったか否かの判定を行っています.当たった場合はボールを跳ね返し,当たらなかった場合はゲームオーバーとなります.

    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		Block blk;   // ブロック
    015		Racket rk;   // ラケット
    016		Ball bl;   // ボール
    017		Random rand;
    018		Thread td;
    019		boolean in_game = true;
    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			blk = new Block(mp);
    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			rand = new Random();
    058						// ボール及びラケットの配置
    059			rk = new Racket(h-10, size, mp);
    060			bl = new Ball(blk, size, rand);
    061						// スレッドの生成
    062			td = new Thread(this);
    063			td.start();
    064		}
    065				// スレッドの実行
    066		public void run()
    067		{
    068			while (in_game) {
    069				try {
    070					td.sleep(50);
    071				}
    072				catch (InterruptedException e) {}
    073						// 移動
    074				bl.x += bl.vx;
    075				bl.y += bl.vy;
    076						// 壁に衝突したときの処理
    077				int sw = 0;
    078								// 下へ移動中
    079				if (bl.vy > 0) {
    080					if (bl.y >= rk.y-bl.r*2) {   // ラケットの位置より下か?
    081						if (bl.x+bl.r >= rk.x && bl.x+bl.r <= rk.x+rk.width) {   // ラケット?
    082							bl.y  = rk.y - bl.r * 2;
    083							bl.vy = -bl.vy;
    084						}
    085						else {   // ゲームオーバー
    086							in_game  = false;
    087							mp.state = 3;
    088						}
    089						sw = 1;
    090					}
    091				}
    092								// 上へ移動中
    093				else {
    094					if (bl.y <= blk.row*blk.height) {
    095						int k = -1;
    096										// 横方向のブロック位置
    097						for (int i1 = 1; i1 < blk.col && k < 0; i1++) {
    098							if (bl.x+bl.r <= i1*blk.width)
    099								k = i1 - 1;
    100						}
    101						if (k < 0)
    102							k = blk.col - 1;
    103										// ブロックとの衝突
    104						for (int i1 = blk.row; i1 >= 0 && sw == 0; i1--) {
    105							if (bl.y <= i1*blk.height) {
    106								if (i1 == 0 || blk.ex[i1-1][k]) {
    107									bl.y  = i1 * blk.height;
    108									bl.vy = -bl.vy;
    109									sw    = 1;
    110									if (i1 > 0) {
    111										blk.ex[i1-1][k] = false;
    112										blk.number--;
    113										if (blk.number == 0) {   // ゲームクリア
    114											in_game  = false;
    115											mp.state = 2;
    116										}
    117									}
    118								}
    119							}
    120						}
    121					}
    122				}
    123								// 上下で衝突していない場合
    124				if (sw == 0) {
    125										// 右方向へ移動中
    126					if (bl.vx > 0) {
    127						if (bl.x >= size.width-bl.r*2) {   // 右の壁に衝突
    128							bl.x  = size.width - bl.r * 2;
    129							bl.vx = -bl.vx;
    130						}
    131					}
    132										// 左方向へ移動中
    133					else {
    134						if (bl.x <= 0) {   // 左の壁に衝突
    135							bl.x  = 0;
    136							bl.vx = -bl.vx;
    137						}
    138					}
    139				}
    140						// 再描画
    141				repaint();
    142			}
    143		}
    144				// 描画
    145		public void paintComponent(Graphics g)
    146		{
    147			super.paintComponent(g);   // 親クラスの描画
    148						// ブロック
    149			for (int i1 = 0; i1 < blk.row; i1++) {
    150				for (int i2 = 0; i2 < blk.col; i2++) {
    151					if (blk.ex[i1][i2])
    152						g.drawImage(blk.block, blk.width*i2, blk.height*i1, this);
    153				}
    154			}
    155						// ラケット
    156			g.setColor(rk.color);
    157			g.fillRect(rk.x, rk.y, rk.width, rk.height);
    158						// ボール
    159			g.setColor(bl.color);
    160			g.fillOval(bl.x, bl.y, bl.r*2, bl.r*2);
    161		}
    162				// ボタンがクリックされたときの処理
    163		public void actionPerformed(ActionEvent e)
    164		{
    165			if (e.getSource() == left)   // ラケットを左へ移動
    166				rk.x -= (rk.width - 5);
    167			else   // ラケットを右へ移動
    168				rk.x += (rk.width - 5);
    169		}
    170	}
    			

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