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

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

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

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

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

    1. Game クラス

        「ゲーム枠の作成」における Game クラスと,Window の大きさを変更した以外,ほとんど同じプログラムです( 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("アクションゲーム(その1)");
      	}
      }
      
      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 ).「ゲーム枠の作成」における 62 行目( sp.repaint(); )は記述していません.

      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	
      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 行目~ 062 行目, Hero.java : 064 行目~ 085 行目,Road.java : 087 行目~ 119 行目 ).

      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			g.drawImage(rd.image, rd.x, rd.y, this);
      052			g.drawImage(hr.image, hr.x, hr.y, this);
      053		}
      054				// マウスイベントの処理
      055		class Mouse extends MouseAdapter {
      056						// ボタンが押されたとき
      057			public void mousePressed(MouseEvent e) {
      058				in_game  = false;
      059				mp.state = 2;
      060			}
      061		}
      062	}
      063	
      064	package game;
      065	
      066	import java.awt.*;
      067	import main.*;
      068	
      069	class Hero
      070	{
      071		Image image;   // 主人公画像
      072		int x, y;   // 主人公の位置
      073		double v_x = 1;   // 主人公の水平方向移動速度
      074		int width = 32;   // 主人公画像の幅
      075		int height = 52;   // 主人公画像の高さ
      076				// コンストラクタ
      077		Hero(Dimension size, Road rd, MainPanel mp)
      078		{
      079						// 主人公画像の読み込み
      080			image = mp.getToolkit().getImage("game/image/char.jpg");
      081						// 主人公の初期位置
      082			x = 0;
      083			y = size.height - rd.height - height;
      084		}
      085	}
      086	
      087	package game;
      088	
      089	import java.awt.*;
      090	import main.*;
      091	
      092	class Road
      093	{
      094		Image image;   // 背景
      095		int x, y;   // 背景の位置
      096		double v_x = -2;   // 背景の水平方向移動速度
      097		int width;   // 背景画像の幅
      098		int height;   // 背景画像の高さ
      099				// コンストラクタ
      100		public Road(Dimension size, MainPanel mp)
      101		{
      102						// 背景の読み込み
      103			if (mp.level == 1) {
      104				image  = mp.getToolkit().getImage("game/image/road1.jpg");
      105				width  = 643;
      106				height = 102;
      107			}
      108			else {
      109				image  = mp.getToolkit().getImage("game/image/road2.jpg");
      110				width  = 557;
      111				height = 102;
      112			}
      113						// 背景の初期位置
      114			x = 0;
      115			y = size.height - height;
      116		}
      117	}
      				
      026,027 行目

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

      032 行目

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

      038 行目~ 044 行目

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

      064 行目~ 085 行目

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

      087 行目~ 117 行目

        背景(道)に対するクラス( Road クラス)の定義です.image は背景画像,x, y は背景の位置,また,v_x は背景の水平方向移動速度を表しています.ここでは,レベルを 2 までとしていますので,使用する画像は 2 つだけです.

    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 クラスと Road クラスです( GamePanel.java : 043 行目~ 120 行目,Road.java : 001 行目~ 041 行目 ).

    001	package game;
    002	
    003	import java.awt.*;
    004	import main.*;
    005	
    006	class Road
    007	{
    008		Image image;   // 背景
    009		int x, y;   // 背景の位置
    010		double v_x = -2;   // 背景の水平方向移動速度
    011		int width;   // 背景画像の幅
    012		int height;   // 背景画像の高さ
    013		int s1[][] = {{147, 0}, {204, 1}, {352, 0}, {494, 1}, {642, 10}};
    014		int s2[][] = {{73, 0}, {139, 1}, {213, 0}, {330, 1}, {404, 0}, {482, 1}, {556, 10}};
    015		int state[][];   // 背景(道)の状態
    016		                 //   state[i][0] : 状態変化の終了位置
    017		                 //   state[i][1] : 道の状態
    018		                 //                   =0 : 道
    019		                 //                   =1 : 谷
    020		                 //                   =10 : ゴール
    021				// コンストラクタ
    022		public Road(Dimension size, MainPanel mp)
    023		{
    024						// 背景の読み込みと状態設定
    025			if (mp.level == 1) {
    026				image  = mp.getToolkit().getImage("game/image/road1.jpg");
    027				state  = s1;
    028				width  = 643;
    029				height = 102;
    030			}
    031			else {
    032				image  = mp.getToolkit().getImage("game/image/road2.jpg");
    033				state  = s2;
    034				width  = 557;
    035				height = 102;
    036			}
    037						// 背景の初期位置
    038			x = 0;
    039			y = size.height - height;
    040		}
    041	}
    042	
    043	package game;
    044	
    045	import java.awt.*;
    046	import java.awt.event.*;
    047	import javax.swing.*;
    048	import main.*;
    049	
    050	public class GamePanel extends JPanel implements Runnable
    051	{
    052		Dimension size;   // パネルの大きさ
    053		MainPanel mp;
    054		Thread td;
    055		boolean in_game = true;
    056		Hero hr;   // 主人公
    057		Road rd;   // 背景(道)
    058				// コンストラクタ
    059		public GamePanel(Dimension size1, MainPanel mp1)
    060		{
    061			size = size1;
    062			mp   = mp1;
    063						// レイアウトマネージャの停止
    064			setLayout(null);
    065						// 背景色の設定
    066			setBackground(Color.white);
    067						// 主人公と道の生成
    068			rd = new Road(size, mp);
    069			hr = new Hero(size, rd, mp);
    070						// スレッドの生成
    071			td = new Thread(this);
    072			td.start();
    073						// マウスリスナの追加
    074			addMouseListener(new Mouse());
    075		}
    076				// スレッドの実行
    077		public void run()
    078		{
    079			while (in_game) {
    080				try {
    081					td.sleep(30);
    082				}
    083				catch (InterruptedException e) {}
    084						// 移動と再描画
    085				hr.x += (int)hr.v_x;
    086				rd.x += (int)rd.v_x;
    087				repaint();
    088						// 道の状態のチェック
    089				int p = hr.x - rd.x + hr.width / 2, sw = -1;
    090				for (int i1 = 0; i1 < rd.state.length && sw < 0; i1++) {
    091					if (p <= rd.state[i1][0])
    092						sw = rd.state[i1][1];
    093				}
    094				if (sw < 0)
    095					sw = 1;
    096				if (sw > 0) {
    097					in_game = false;
    098					if (sw == 1)
    099						mp.state = 3;   // ゲームオーバー
    100					else
    101						mp.state = 2;   // ゲームクリア
    102				}
    103			}
    104		}
    105				// 描画
    106		public void paintComponent(Graphics g)
    107		{
    108			super.paintComponent(g);   // 親クラスの描画
    109			g.drawImage(rd.image, rd.x, rd.y, this);
    110			g.drawImage(hr.image, hr.x, hr.y, this);
    111		}
    112				// マウスイベントの処理
    113		class Mouse extends MouseAdapter {
    114						// ボタンが押されたとき
    115			public void mousePressed(MouseEvent e) {
    116				in_game  = false;
    117				mp.state = 2;
    118			}
    119		}
    120	}
    			
    013 行目~ 014 行目,27 行目,33 行目

      道の状態を表すための 2 次元配列を定義し,初期設定しています.2 次元配列の初期設定において,内側の括弧でくくられた 2 つの数値は行に相当します.013 行目では,フィールド s1( 5 行 2 列)に対してレベル 1 における道の状態,また,014 行目では,フィールド s2( 7 行 2 列)に対してレベル 2 における道の状態が設定されます.これらの状態は,027,033 行目において,現在のレベルに従ってフィールド state に代入されます.例えば,レベル 1 の場合,state ( s1 )の内容とその意味する所は以下に示すとおりです.
    	state[0][0] = 147;   // 0 から 147 ピクセルまでは道
    	state[0][1] = 0;
    	state[1][0] = 204;   // 148 から 204 ピクセルまでは谷
    	state[1][1] = 1;
    		・・・・・
    	state[4][0] = 642;   // 495 から 642 ピクセルまではゴール
    	state[4][1] = 10;				
      一般に,多次元配列は,配列の各要素を配列として定義することによって可能です.例えば,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};				
    のような方法で行います.

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

    089 行目

      変数 p には,主人公の現時点における道上の位置を設定しています.なお,hr.width は,主人公画像の幅です.従って,変数 p は,主人公の中心の位置に相当します.

    090 行目~ 093 行目

      Road クラスのフィールド state と変数 p の値に基づき,現在位置の状態を調べています.道の上にいる場合は sw = 0,谷にいる場合は sw = 1,ゴールにいる場合(現時点ではあり得ませんが)は,sw = 10 になります.094,095 行目は,ゴールを通り過ぎた状態を意味します.rd.state.length における length は,配列のサイズ(この場合は,行の数)を表しています.

    096 行目~ 102 行目

      谷,または,ゴールにいる場合は,ゲームの実行を止め( 097 行目),対応するゲーム状態に変更しています.

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

      主人公がジャンプできるようにプログラムを修正します.修正するプログラムは,game パッケージ内の GamePanel クラスと Hero クラスです( GamePanel.java : 025 行目~ 119 行目, 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 - 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 p = hr.x - rd.x + hr.width / 2, sw = -1;
    078				for (int i1 = 0; i1 < rd.state.length && sw < 0; i1++) {
    079					if (p <= rd.state[i1][0])
    080						sw = rd.state[i1][1];
    081				}
    082				if (sw < 0)
    083					sw = 1;
    084				if (sw > 0) {
    085					in_game = false;
    086					if (sw == 1)
    087						mp.state = 3;   // ゲームオーバー
    088					else
    089						mp.state = 2;   // ゲームクリア
    090				}
    091	*/
    092			}
    093		}
    094				// 描画
    095		public void paintComponent(Graphics g)
    096		{
    097			super.paintComponent(g);   // 親クラスの描画
    098			g.drawImage(rd.image, rd.x, rd.y, this);
    099			g.drawImage(hr.image, hr.x, hr.y, this);
    100		}
    101				// マウスイベントの処理
    102		class Mouse extends MouseAdapter {
    103						// ボタンが押されたとき
    104			public void mousePressed(MouseEvent e) {
    105				if (!jump) {
    106					up     = 1.0;
    107					down   = 0.5;
    108					hr.v_x = 2.0;
    109					jump   = true;
    110					hr.y--;   // 着地判定のため,1 ピクセルだけジャンプさせておく
    111				}
    112			}
    113						// ボタンが離されたとき
    114			public void mouseReleased(MouseEvent e) {
    115				up   = 0.0;
    116				down = 0.5;
    117			}
    118		}
    119	}
    			
    011 行目

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

    040,041 行目

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

    042 行目

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

    070,72 行目

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

    077 行目~ 090 行目

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

    105 行目~ 112 行目

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

    114 行目~ 117 行目

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

  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								// 水平位置のチェック
    053				int p  = hr.x - rd.x + hr.width / 2, sw = -1;
    054				for (int i1 = 0; i1 < rd.state.length && sw < 0; i1++) {
    055					if (p <= rd.state[i1][0])
    056						sw = rd.state[i1][1];
    057				}
    058				if (sw < 0)   // ゴールを通過
    059					sw = 1;
    060								// 垂直位置のチェック
    061				else {
    062					if (jump) {
    063						if (hr.y >= size.height - rd.height - hr.height) {
    064							if (sw == 0 || sw == 10) {
    065								jump   = false;
    066								down   = 0.0;
    067								hr.v_x = 1.0;
    068								hr.v_y = 0.0;
    069								hr.y   = size.height - rd.height - hr.height;
    070							}
    071						}
    072						else
    073							sw = 0;
    074					}
    075				}
    076								// ゲーム状態変更
    077				if (sw > 0) {
    078					in_game = false;
    079					if (sw == 1)
    080						mp.state = 3;   // ゲームオーバー
    081					else
    082						mp.state = 2;   // ゲームクリア
    083				}
    084			}
    085		}
    086				// 描画
    087		public void paintComponent(Graphics g)
    088		{
    089			super.paintComponent(g);   // 親クラスの描画
    090			g.drawImage(rd.image, rd.x, rd.y, this);
    091			g.drawImage(hr.image, hr.x, hr.y, this);
    092		}
    093				// マウスイベントの処理
    094		class Mouse extends MouseAdapter {
    095						// ボタンが押されたとき
    096			public void mousePressed(MouseEvent e) {
    097				if (!jump) {
    098					up     = 1.0;
    099					down   = 0.5;
    100					hr.v_x = 2.0;
    101					jump   = true;
    102					hr.y--;   // 着地判定のため,1 ピクセルだけジャンプさせておく
    103				}
    104			}
    105						// ボタンが離されたとき
    106			public void mouseReleased(MouseEvent e) {
    107				up   = 0.0;
    108				down = 0.5;
    109			}
    110		}
    111	}
    			
    053 行目~ 059 行目

      ステップ2の場合と同様,Road クラスのフィールド state と変数 p の値に基づき,現在位置の水平方向の状態を調べています.変数 sw が負の値の場合は,ゴール位置を通り過ぎたことを意味しますので,ゲームオーバーとなります( 058,059 行目).

    063 行目~ 073 行目

      主人公がジャンプ中であった場合の処理です(ジャンプ中でない場合は,ステップ2と同様の扱いで良い).063 行目は,主人公が地面より下に位置するかをチェックしています.正確さを求めるならば,ちょうど着地したか,つまり,
    	if (hr.y == size.height - rd.height - hr.height) {				
    という判定にすべきですが,時間的なタイミングから,この条件を満たすことは非常に難しくなります.そこで,最初に地面を通過した段階で着地処理を行っています.勿論,その位置が谷であった場合はゲームオーバーとなりますので,064 行目において谷でないことをチェックしています.着地した場合は,再びジャンプできるようにし( 065 行目),加速度,速度,垂直位置を再設定しています.なお,主人公が地面より上にいる場合は,現在の状態をそのまま継続させます( 072,073 行目).

  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 目次 索引