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

アクションゲーム(その2)

  ここでは,画面を格子状に区切り,各格子内に小さな画像を配置し,それらの組合せによって背景や障害物を描いています.主人公の状況把握は簡単になりますが,格子の大きさによっては,画像が粗くなってしまいます.背景や障害物を 1 枚の画像で表現する方法に関しては,「アクションゲーム(その1)」を参照して下さい.

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

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

    1. Game クラス

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

      /*****************************/
      /* アクションゲーム(その1)*/
      /*   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("アクションゲーム(その2)");
      	}
      }
      
      class Win extends JFrame
      {
      	/******************/
      	/* コンストラクタ */
      	/******************/
      	Win(String name)
      	{
      					// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
      		super(name);
      					// Windowの大きさ
      		setSize(460, 390);   // 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 行目~ 074 行目, Hero.java : 076 行目~ 097 行目,Road.java : 099 行目~ 158 行目 ).

      001	package game;
      002	
      003	import java.awt.*;
      004	import java.awt.event.*;
      005	import javax.swing.*;
      006	import main.*;
      007	
      008	public class GamePanel extends JPanel implements Runnable
      009	{
      010		Dimension size;   // パネルの大きさ
      011		MainPanel mp;
      012		Thread td;
      013		boolean in_game = true;
      014		Hero hr;   // 主人公
      015		Road rd;   // 背景(道)
      016				// コンストラクタ
      017		public GamePanel(Dimension size1, MainPanel mp1)
      018		{
      019			size = size1;
      020			mp   = mp1;
      021						// レイアウトマネージャの停止
      022			setLayout(null);
      023						// 背景色の設定
      024			setBackground(Color.white);
      025						// 主人公と道の生成
      026			rd = new Road(size, mp);
      027			hr = new Hero(size, rd, mp);
      028						// スレッドの生成
      029			td = new Thread(this);
      030			td.start();
      031						// マウスリスナの追加
      032			addMouseListener(new Mouse());
      033		}
      034				// スレッドの実行
      035		public void run()
      036		{
      037			while (in_game) {
      038				try {
      039					td.sleep(30);
      040				}
      041				catch (InterruptedException e) {}
      042				hr.x += (int)hr.v_x;
      043				rd.x += (int)rd.v_x;
      044				repaint();
      045			}
      046		}
      047				// 描画
      048		public void paintComponent(Graphics g)
      049		{
      050			super.paintComponent(g);   // 親クラスの描画
      051						// 主人公の描画
      052			g.drawImage(hr.image, hr.x, hr.y, this);
      053						// 背景の描画
      054			for (int i1 = 0; i1 < rd.col; i1++) {
      055				int x = rd.x + rd.width * i1;
      056				if (x + rd.width >= 0 && x <= size.width) {
      057					for (int i2 = 0; i2 < rd.row; i2++) {
      058						if (rd.blk[i2][i1] > 0) {
      059							int y = i2 * rd.height;
      060							g.drawImage(rd.image[rd.blk[i2][i1]-1], x, y, this);
      061						}
      062					}
      063				}
      064			}
      065		}
      066				// マウスイベントの処理
      067		class Mouse extends MouseAdapter {
      068						// ボタンが押されたとき
      069			public void mousePressed(MouseEvent e) {
      070				in_game  = false;
      071				mp.state = 2;
      072			}
      073		}
      074	}
      075	
      076	package game;
      077	
      078	import java.awt.*;
      079	import main.*;
      080	
      081	class Hero
      082	{
      083		Image image;   // 主人公画像
      084		int x, y;   // 主人公の位置
      085		double v_x = 1;   // 主人公の水平方向移動速度
      086		int width = 32;   // 主人公画像の幅
      087		int height = 52;   // 主人公画像の高さ
      088				// コンストラクタ
      089		Hero(Dimension size, Road rd, MainPanel mp)
      090		{
      091						// 主人公画像の読み込み
      092			image = mp.getToolkit().getImage("game/image/char.jpg");
      093						// 主人公の初期位置
      094			x = 0;
      095			y = size.height - rd.height * rd.r_no - height;
      096		}
      097	}
      098	
      099	package game;
      100	
      101	import java.awt.*;
      102	import main.*;
      103	
      104	class Road
      105	{
      106		Image image[] = new Image [2];   // 画像ファイル
      107		int blk1[][] = {
      108			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      109			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      110			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      111			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      112			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      113			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      114			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      115			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      116			{1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2},
      117			{1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2},
      118			{1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2},
      119			{1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2}};
      120		int blk2[][] = {
      121			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      122			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      123			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      124			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      125			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      126			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      127			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      128			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      129			{1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 2, 2, 2},
      130			{1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 2, 2, 2},
      131			{1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 2, 2, 2},
      132			{1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 2, 2, 2}};
      133		int blk[][];   // 背景を構成するブロック
      134		int row = 12;   // ブロックの行数
      135		int col = 26;   // ブロックの列数
      136		int r_no = 4;   // 道を構成する縦のブロック数
      137		int x;   // 背景の位置
      138		double v_x = -2;   // 背景の水平方向移動速度
      139		int width = 25;   // ブロックの幅
      140		int height = 25;   // ブロックの高さ
      141				// コンストラクタ
      142		public Road(Dimension size, MainPanel mp)
      143		{
      144						// 画像の読み込み
      145			image[0]  = mp.getToolkit().getImage("game/image/road1.jpg");
      146			image[1]  = mp.getToolkit().getImage("game/image/road2.jpg");
      147						// 背景の設定
      148			x = 0;
      149			if (mp.level == 1) {
      150				col = 26;
      151				blk = blk1;
      152			}
      153			else {
      154				col = 23;
      155				blk = blk2;
      156			}
      157		}
      158	}
      				
      026,027 行目

        今後,その機能を拡張していく可能性がありますので,背景(道)及び主人公は,クラスとして別ファイル( 099 行目~ 158 行目,及び,076 行目~ 097 行目)に定義してあります.ここでは,それらに対するオブジェクトを生成しています.

      032 行目

        この画面にマウスリスナを追加しています.067 行目~ 073 行目において,マウスのボタンが押されたときの処理を記述しています.「ゲーム枠の作成」における GamePanel クラスでは,ボタンをクリックすることによって「ゲームクリア」や「ゲームオーバー」の状態に移動するように設定しましたが,ここでは,マウスボタンを押すことによって「ゲームクリア」画面に移動します.ただし,実際のゲームにおいては,このような処理を行いません.画面の移動を確認するためにこのような処理を行っています.

      038 行目~ 044 行目

        30 ms 毎に( 039 行目),主人公及び背景を指定した速度で移動させ( 042,043 行目),再描画しています( 044 行目).なお,速度( Hero クラス及び Road クラスのフィールド v_x )は double 型で宣言してあるため,int 型に変換しています(型変換( cast 演算子)).

      054 行目~ 064 行目

        後に説明しますように,画面全体を格子状に区切り,各格子内(ブロック)に指定した画像を描くことによって背景を構成しています.ここでは,その指定された画像を描いています.

      076 行目~ 097 行目

        主人公に対するクラス( Hero クラス)の定義です.image は主人公の画像,x, y は主人公の位置,また,v_x は主人公の水平方向移動速度を表しています.

      107 行目~ 132 行目

        画面全体を row 行 col 列の格子状に区切り,各格子内(ブロック)に画像を配置することによって背景画像を構成します.各ブロックの大きさは,幅 width = 25 ピクセル,高さ height = 25 ピクセルです.変数 blk1 及び blk2は,レベル 1 及び 2 における各ブロックの状態を表すための 2 次元配列です.内側の括弧が,行を表しています.値が 0 の場合は何も描かれず,そうでない場合は指定された画像が描かれます.

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

      149 行目~ 156 行目

        レベルによる違いを設定しています.「 Java To C/C++ 」における配列の項で説明していますように,配列変数 blk1,blk2,blk などは,データを記憶している領域の先頭を指すポインタとみなすことができます.例えば,151 行目において blk1 を blk に代入していますが,blk1 の全てのデータをコピーして blk に代入しているわけではなく,blk1 と blk が同じデータ領域を指すことになるだけです.従って,blk を介して各要素の値を変更すれば,blk1 の対応する要素の値も変化します(逆も同様).

    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 )です.

    01	package game;
    02	
    03	import java.awt.*;
    04	import java.awt.event.*;
    05	import javax.swing.*;
    06	import main.*;
    07	
    08	public class GamePanel extends JPanel implements Runnable
    09	{
    10		Dimension size;   // パネルの大きさ
    11		MainPanel mp;
    12		Thread td;
    13		boolean in_game = true;
    14		Hero hr;   // 主人公
    15		Road rd;   // 背景(道)
    16				// コンストラクタ
    17		public GamePanel(Dimension size1, MainPanel mp1)
    18		{
    19			size = size1;
    20			mp   = mp1;
    21						// レイアウトマネージャの停止
    22			setLayout(null);
    23						// 背景色の設定
    24			setBackground(Color.white);
    25						// 主人公と道の生成
    26			rd = new Road(size, mp);
    27			hr = new Hero(size, rd, mp);
    28						// スレッドの生成
    29			td = new Thread(this);
    30			td.start();
    31						// マウスリスナの追加
    32			addMouseListener(new Mouse());
    33		}
    34				// スレッドの実行
    35		public void run()
    36		{
    37			while (in_game) {
    38				try {
    39					td.sleep(30);
    40				}
    41				catch (InterruptedException e) {}
    42						// 移動と再描画
    43				hr.x += (int)hr.v_x;
    44				rd.x += (int)rd.v_x;
    45				repaint();
    46						// 道の状態のチェック
    47				int x = (hr.x - rd.x + hr.width / 2) / rd.width;
    48				int y = (hr.y + hr.height + rd.height / 2) / rd.height;
    49				if (x >= rd.col || rd.blk[y][x] == 0) {
    50					in_game = false;
    51					mp.state = 3;   // ゲームオーバー
    52				}
    53				else if (rd.blk[y][x] == 2) {
    54					in_game = false;
    55					mp.state = 2;   // ゲームクリア
    56				}
    57			}
    58		}
    59				// 描画
    60		public void paintComponent(Graphics g)
    61		{
    62			super.paintComponent(g);   // 親クラスの描画
    63						// 主人公の描画
    64			g.drawImage(hr.image, hr.x, hr.y, this);
    65						// 背景の描画
    66			for (int i1 = 0; i1 < rd.col; i1++) {
    67				int x = rd.x + rd.width * i1;
    68				if (x + rd.width >= 0 && x <= size.width) {
    69					for (int i2 = 0; i2 < rd.row; i2++) {
    70						if (rd.blk[i2][i1] > 0) {
    71							int y = i2 * rd.height;
    72							g.drawImage(rd.image[rd.blk[i2][i1]-1], x, y, this);
    73						}
    74					}
    75				}
    76			}
    77		}
    78				// マウスイベントの処理
    79		class Mouse extends MouseAdapter {
    80						// マウスがクリックされたとき
    81			public void mousePressed(MouseEvent e) {
    82				in_game  = false;
    83				mp.state = 2;
    84			}
    85		}
    86	}
    			
    47,48 行目

      主人公の足下が配列 blk のどの位置にあるかを計算しています.

    49 行目~ 52 行目

      格子の外側,または,谷(何も描いてないブロック)である場合は,タイマーを止め,ゲームオーバーにしています,

    53 行目~ 56 行目

      ゴールにいる場合(現時点ではあり得ませんが)は,ゲームクリアにしています.

  3. ステップ3: ジャンプ

      主人公がジャンプできるようにプログラムを修正します.修正するプログラムは,game パッケージ内の GamePanel クラスと Hero クラスです( GamePanel.java : 025 行目~ 127 行目, Hero.java : 001 行目~ 023 行目).

    001	package game;
    002	
    003	import java.awt.*;
    004	import main.*;
    005	
    006	class Hero
    007	{
    008		Image image;   // 主人公画像
    009		int x, y;   // 主人公の位置
    010		double v_x = 1;   // 主人公の水平方向移動速度
    011		double v_y = 0;   // 主人公の垂直方向移動速度
    012		int width = 32;   // 主人公画像の幅
    013		int height = 52;   // 主人公画像の高さ
    014				// コンストラクタ
    015		Hero(Dimension size, Road rd, MainPanel mp)
    016		{
    017						// 主人公画像の読み込み
    018			image = mp.getToolkit().getImage("game/image/char.jpg");
    019						// 主人公の初期位置
    020			x = 0;
    021			y = size.height - rd.height * rd.r_no - height;
    022		}
    023	}
    024	
    025	package game;
    026	
    027	import java.awt.*;
    028	import java.awt.event.*;
    029	import javax.swing.*;
    030	import main.*;
    031	
    032	public class GamePanel extends JPanel implements Runnable
    033	{
    034		Dimension size;   // パネルの大きさ
    035		MainPanel mp;
    036		Thread td;
    037		boolean in_game = true;
    038		Hero hr;   // 主人公
    039		Road rd;   // 背景(道)
    040		double up = 0;   // 上向き加速度
    041		double down = 0;   // 下向き加速度
    042		boolean jump = false;   // ジャンプ中か?
    043				// コンストラクタ
    044		public GamePanel(Dimension size1, MainPanel mp1)
    045		{
    046			size = size1;
    047			mp   = mp1;
    048						// レイアウトマネージャの停止
    049			setLayout(null);
    050						// 背景色の設定
    051			setBackground(Color.white);
    052						// 主人公と道の生成
    053			rd = new Road(size, mp);
    054			hr = new Hero(size, rd, mp);
    055						// スレッドの生成
    056			td = new Thread(this);
    057			td.start();
    058						// マウスリスナの追加
    059			addMouseListener(new Mouse());
    060		}
    061				// スレッドの実行
    062		public void run()
    063		{
    064			while (in_game) {
    065				try {
    066					td.sleep(30);
    067				}
    068				catch (InterruptedException e) {}
    069						// 移動と再描画
    070				hr.v_y += (up - down);
    071				hr.x   += (int)hr.v_x;
    072				hr.y   -= (int)hr.v_y;
    073				rd.x   += (int)rd.v_x;
    074				repaint();
    075						// 道の状態のチェック
    076	/*
    077				int x = (hr.x - rd.x + hr.width / 2) / rd.width;
    078				int y = (hr.y + hr.height + rd.height / 2) / rd.height;
    079				if (x >= rd.col || rd.blk[y][x] == 0) {
    080					in_game = false;
    081					mp.state = 3;   // ゲームオーバー
    082				}
    083				else if (rd.blk[y][x] == 2) {
    084					in_game = false;
    085					mp.state = 2;   // ゲームクリア
    086				}
    087	*/
    088			}
    089		}
    090				// 描画
    091		public void paintComponent(Graphics g)
    092		{
    093			super.paintComponent(g);   // 親クラスの描画
    094						// 主人公の描画
    095			g.drawImage(hr.image, hr.x, hr.y, this);
    096						// 背景の描画
    097			for (int i1 = 0; i1 < rd.col; i1++) {
    098				int x = rd.x + rd.width * i1;
    099				if (x + rd.width >= 0 && x <= size.width) {
    100					for (int i2 = 0; i2 < rd.row; i2++) {
    101						if (rd.blk[i2][i1] > 0) {
    102							int y = i2 * rd.height;
    103							g.drawImage(rd.image[rd.blk[i2][i1]-1], x, y, this);
    104						}
    105					}
    106				}
    107			}
    108		}
    109				// マウスイベントの処理
    110		class Mouse extends MouseAdapter {
    111						// ボタンが押されたとき
    112			public void mousePressed(MouseEvent e) {
    113				if (!jump) {
    114					up     = 1.0;
    115					down   = 0.5;
    116					hr.v_x = 2.0;
    117					jump   = true;
    118					hr.y--;   // 着地判定のため,1 ピクセルだけジャンプさせておく
    119				}
    120			}
    121						// ボタンが離されたとき
    122			public void mouseReleased(MouseEvent e) {
    123				up   = 0.0;
    124				down = 0.5;
    125			}
    126		}
    127	}
    			
    011 行目

      主人公の垂直方向の移動速度を保存するためのフィールドを定義しています.

    040,041 行目

      主人公に対する上向きの加速度と下向きの加速度です.

    042 行目

      ジャンプ中に,さらにジャンプすることを防ぐためのフィールドです.

    070,072 行目

      設定された加速度から,現在の速度を計算しています.一般に,現在の速度を v / t,加速度を a / t2 とすると,t 時間後の速度は v + at となります.この速度を使用して,072 行目において,主人公の垂直位置を計算しています.いずれの式においても,t = 1 とみなしています.

    077 行目~ 086 行目

      この部分をそのまま残しておくと,横方向の座標が谷の部分になるとゲームオーバーになってしまうため,一時的にコメントアウトしています.

    113 行目~ 119 行目

      マウスボタンを押すと,垂直方向の加速度が設定されます( 114,115 行目).また,水平方向の速度も変化させています( 116 行目).この結果,主人公は,垂直方向は上方向に速度を増しながら,水平方向は一定速度で移動します.また,変数 jump を true に設定し,ジャンプ中であることを設定します.なお,後ほど行う処理ですが,着地した場合はこの値を再び false にします.

    122 行目~ 125 行目

      マウスボタンを離すと,上向きの加速度が 0 に設定されます( 123 行目).この結果,最終的に,主人公は,下方向に速度を増しながら落下することになります.

  4. ステップ4: 完成

      ジャンプした後,着地,ゲームクリア,ゲームオーバーなどの判定を行い,ゲームを完成します.修正するプログラムは,game パッケージ内の GamePanel クラスだけです( GamePanel.java ).

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import main.*;
    007	
    008	public class GamePanel extends JPanel implements Runnable
    009	{
    010		Dimension size;   // パネルの大きさ
    011		MainPanel mp;
    012		Thread td;
    013		boolean in_game = true;
    014		Hero hr;   // 主人公
    015		Road rd;   // 背景(道)
    016		double up = 0;   // 上向き加速度
    017		double down = 0;   // 下向き加速度
    018		boolean jump = false;   // ジャンプ中か?
    019				// コンストラクタ
    020		public GamePanel(Dimension size1, MainPanel mp1)
    021		{
    022			size = size1;
    023			mp   = mp1;
    024						// レイアウトマネージャの停止
    025			setLayout(null);
    026						// 背景色の設定
    027			setBackground(Color.white);
    028						// 主人公と道の生成
    029			rd = new Road(size, mp);
    030			hr = new Hero(size, rd, mp);
    031						// スレッドの生成
    032			td = new Thread(this);
    033			td.start();
    034						// マウスリスナの追加
    035			addMouseListener(new Mouse());
    036		}
    037				// スレッドの実行
    038		public void run()
    039		{
    040			while (in_game) {
    041				try {
    042					td.sleep(30);
    043				}
    044				catch (InterruptedException e) {}
    045						// 移動と再描画
    046				hr.v_y += (up - down);
    047				hr.x   += (int)hr.v_x;
    048				hr.y   -= (int)hr.v_y;
    049				rd.x   += (int)rd.v_x;
    050				repaint();
    051						// 道の状態のチェック
    052				int x = (hr.x - rd.x + hr.width / 2) / rd.width;
    053								// ジャンプ中
    054				if (jump) {
    055					if (x >= rd.col) {
    056						in_game = false;
    057						mp.state = 3;   // ゲームオーバー
    058					}
    059					else {
    060						int y = (hr.y + hr.height) / rd.height;
    061						if (y >= 0) {
    062							if (y < rd.row && rd.blk[y][x] > 0) {
    063								jump   = false;
    064								down   = 0.0;
    065								hr.v_x = 1.0;
    066								hr.v_y = 0.0;
    067								hr.y   = size.height - rd.height * rd.r_no - hr.height;
    068								if (rd.blk[y][x] == 2) {
    069									in_game = false;
    070									mp.state = 2;   // ゲームクリア
    071								}
    072							}
    073							else if (y >= rd.row - rd.r_no) {
    074								in_game = false;
    075								mp.state = 3;   // ゲームオーバー
    076							}
    077						}
    078					}
    079				}
    080								// ジャンプ中でない
    081				else {
    082					int y = (hr.y + hr.height + rd.height / 2) / rd.height;
    083					if (x >= rd.col || rd.blk[y][x] == 0) {
    084						in_game = false;
    085						mp.state = 3;   // ゲームオーバー
    086					}
    087					else if (rd.blk[y][x] == 2) {
    088						in_game = false;
    089						mp.state = 2;   // ゲームクリア
    090					}
    091				}
    092			}
    093		}
    094				// 描画
    095		public void paintComponent(Graphics g)
    096		{
    097			super.paintComponent(g);   // 親クラスの描画
    098						// 主人公の描画
    099			g.drawImage(hr.image, hr.x, hr.y, this);
    100						// 背景の描画
    101			for (int i1 = 0; i1 < rd.col; i1++) {
    102				int x = rd.x + rd.width * i1;
    103				if (x + rd.width >= 0 && x <= size.width) {
    104					for (int i2 = 0; i2 < rd.row; i2++) {
    105						if (rd.blk[i2][i1] > 0) {
    106							int y = i2 * rd.height;
    107							g.drawImage(rd.image[rd.blk[i2][i1]-1], x, y, this);
    108						}
    109					}
    110				}
    111			}
    112		}
    113				// マウスイベントの処理
    114		class Mouse extends MouseAdapter {
    115						// ボタンが押されたとき
    116			public void mousePressed(MouseEvent e) {
    117				if (!jump) {
    118					up     = 1.0;
    119					down   = 0.5;
    120					hr.v_x = 2.0;
    121					jump   = true;
    122					hr.y--;   // 着地判定のため,1 ピクセルだけジャンプさせておく
    123				}
    124			}
    125						// ボタンが離されたとき
    126			public void mouseReleased(MouseEvent e) {
    127				up   = 0.0;
    128				down = 0.5;
    129			}
    130		}
    131	}
    			
    052 行目

      主人公の足下が配列 blk のどの位置(配列の何列目か?)にあるかを計算しています.縦方向の位置(配列の何行目か?)は,ジャンプしている場合( 054 行目~ 079 行目 )とそうでない場合( 081 行目~ 091 行目 )によって,対応する処理が異なります.なお,ジャンプしていない場合は,今まで行ってきた処理と同じです.

    055 行目~ 058 行目

      横方向が,格子の外側に出た場合は,タイマーを止め,ゲームオーバー状態にします.

    060 行目

      主人公の足下が配列 blk のどの位置(配列の何行目か?)にあるかを計算しています.0 より小さい場合(画面の上側の外に出ている)は何もしません( 061 行目).

    062 行目~ 072 行目

      主人公がジャンプ中であり,足下が地面(何かが描かれたブロック)であった場合の処理です.ジャンプを止め,足下の座標を地面になるように調整しています.また,地面がゴールであった場合は,タイマーを止め,ゲームクリア状態にします.

    073 行目~ 076 行目

      主人公がジャンプ中であり,谷(何も描かれていないブロック)の中に入っている場合,つまり,地面より下にいる場合の処理です.タイマーを止め,ゲームオーバー状態にします.

    081 行目~ 091 行目

      ジャンプ中でない場合の処理です.082 行目において,主人公の足下が乗っている配列の行を計算しています.ゴールを過ぎていたり,その位置に何も描かれていない(谷)場合は,ゲームオーバーとします( 083 行目~ 086 行目).また,ゴール図形が描かれていた場合は,ゲームクリアとします( 087 行目~ 090 行目).

  5. ステップ4: 完成( BGM 付き)

      参考のため,BGM を付加した例を示しておきます.Game.java と同じフォルダ(ディレクトリ)に Action_BGM.mid を置き,MainPanel パッケージに BGMPlayer クラス( BGMPlayer.java )を新たに作成すると共に,MainPanel クラスの一部を修正しました.BGMPlayer.java は,18 行目のファイル名を修正すれば,他のゲームにも使用できます.MainPanel.java における追加・修正した部分は,以下の通りです.なお,BGM は,平成 25 年度に本学を卒業した斉藤亮太さんに作成してもらいました.
    23 行目,30 ~ 31 行目,51 ~ 54 行目,60 ~ 63 行目,72 行目			

    MainPanel.java

    01	package main;
    02	
    03	import java.awt.*;
    04	import java.awt.event.*;
    05	import javax.swing.*;
    06	import start.*;
    07	import game.*;
    08	import clear.*;
    09	import over.*;
    10	
    11	public class MainPanel extends JPanel implements Runnable
    12	{
    13		Dimension size;   // パネルの大きさ
    14		boolean in_game = true;   // ゲーム実行中はtrue
    15		public int state = 0;   // ゲーム状態(0:表紙,1:ゲーム,2:クリア,3:オーバー,4:終了)
    16		public int level = 1;   // ゲームレベル
    17		int old_state = 0;   // 直前のゲーム状態
    18		StartPanel sp;
    19		GamePanel gp;
    20		GameClearPanel gcp;
    21		GameOverPanel gop;
    22		Thread td;
    23		BGMPlayer BGM;
    24				// コンストラクタ
    25		public MainPanel(Dimension size1)
    26		{
    27			size = size1;
    28						// グリッドレイアウト
    29			setLayout(new GridLayout(1, 1, 0, 0));
    30						// BGM の準備
    31			BGM = new BGMPlayer(); 
    32						// ゲームパネルの生成
    33			sp = new StartPanel(size, this);   // スタート(タイトル)
    34			add(sp);
    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);   // 100 ms 毎の実施
    45				}
    46				catch (InterruptedException e) {}
    47				if (state != old_state) {
    48								// 前のパネルの削除
    49					if (old_state == 0)
    50						remove(sp);
    51					else if (old_state == 1) {
    52						remove(gp);
    53						BGM.player.stop();   // BGM を止める
    54					}
    55					else if (old_state == 2)
    56						remove(gcp);
    57					else
    58						remove(gop);
    59								// 新しいパネルの追加
    60					if (state == 4) {   // ゲーム終了
    61						in_game = false;
    62						BGM.player.close();   // BGM を閉じる
    63					}
    64					else {
    65						if (state == 0) {   // StartPanel
    66							sp = new StartPanel(size, this);
    67							add(sp);
    68						}
    69						else if (state == 1) {   // GamePanel
    70							gp = new GamePanel(size, this);
    71							add(gp);
    72							BGM.player.start();   // BGM をスタート
    73						}
    74						else if (state == 2) {   // GameClearPanel
    75							gcp = new GameClearPanel(size, this);
    76							add(gcp);
    77						}
    78						else {   // GameOverPanel
    79							gop = new GameOverPanel(size, this);
    80							add(gop);
    81						}
    82						validate();
    83						old_state = state;
    84					}
    85				}
    86			}
    87		}
    88	}
    			

    BGMPlayer.java(新規作成)

    01	package main;
    02	
    03	import java.io.*; 
    04	import javax.sound.midi.*;
    05	
    06	/**********************/
    07	/* BGM の再生(MIDI) */
    08	/**********************/
    09	public class BGMPlayer 
    10	{ 
    11		Sequencer player;
    12	
    13		BGMPlayer()
    14		{
    15			try
    16			{ 
    17				player = MidiSystem.getSequencer();
    18				Sequence sqc  = MidiSystem.getSequence(new File("Action_BGM.mid"));
    19				player.setSequence(sqc);
    20				player.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);   // 無限界演奏
    21				player.open();
    22			} 
    23			catch (Exception e) 
    24			{ 
    25				System.out.println(e); 
    26			} 
    27		}
    28	}
    			

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