情報学部 菅沼ホーム Java 目次 基礎技術目次 索引

跳ね返りと乱数

  1. 跳ね返り
      アニメーションやゲームにおいて,壁に当たって跳ね返る動作は良く使用されます.以下は,それを実現したプログラムです.壁に当たったか否かの判定は,ある一定時間間隔で行われますので,判定が行われる時間と壁に当たる瞬間が必ずしも一致しません.一般的には,物体が壁にめり込んだ状態で行われることになります.そこで,以下のプログラムにおいては,物体の位置を上図のように修正しています.

      また,一般的には,壁に当たることによってエネルギーが失われ,速度の大きさが変化します.それを実現するために,赤い丸においては,係数 0.8 を乗じて速度の大きさを小さくしています.また,壁との摩擦などによって跳ね返る方向も理想的にはなりませんが,このプログラムでは理想的な方向に跳ね返るものとしています.

      いずれにしろ,以下のプログラム( Test.java )は,物理的な現象を忠実にシミュレーションしたものにはなっていません.しかし,アニメーションやゲームにとっては十分であると思います.
    001	import java.awt.*;
    002	import java.awt.event.*;
    003	import javax.swing.*;
    004	
    005	public class Test {
    006		public static void main (String[] args)
    007		{
    008			Win win = new Win("跳ね返り");
    009		}
    010	}
    011	
    012	class Win extends JFrame
    013	{
    014		/******************/
    015		/* コンストラクタ */
    016		/******************/
    017		Win(String name)
    018		{
    019						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    020			super(name);
    021						// Windowの大きさ
    022			setSize(640, 470);   // +40, +70
    023						// ContentPane の取得と MainPanel の追加
    024			Dimension d  = getSize();   // Windowの大きさ
    025			d.width     -= 40;
    026			d.height    -= 70;
    027			MainPanel pn = new MainPanel(d);   // MainPanel オブジェクトの生成
    028			getContentPane().add(pn);   // MainPanel オブジェクトを ContentPane に追加
    029						// ウィンドウを表示
    030			setVisible(true);
    031						// イベントアダプタ
    032			addWindowListener(new WinEnd());
    033		}
    034	
    035		/******************************/
    036		/* 上,左,下,右の余白の設定 */
    037		/******************************/
    038		public Insets getInsets()
    039		{
    040			return new Insets(50, 20, 20, 20);
    041		}
    042	
    043		/************/
    044		/* 終了処理 */
    045		/************/
    046		class WinEnd extends WindowAdapter
    047		{
    048			public void windowClosing(WindowEvent e) {
    049				System.exit(0);
    050			}
    051		}
    052	}
    053	
    054	class MainPanel extends JPanel implements Runnable
    055	{
    056		boolean state = true;
    057		double x1, x2, y1, y2, vx1 = 50, vy1 = 55, vx2 = 50, vy2 = 55, dt = 0.1;
    058		int width = 40;
    059		Dimension d;
    060		Thread th;
    061	
    062		MainPanel(Dimension d1)
    063		{
    064			d = d1;
    065			x1 = width / 2;
    066			y1 = width / 2;
    067			x2 = x1;
    068			y2 = y1;
    069			setBackground(new Color(238, 255, 238));   // 背景色の設定
    070			th = new Thread(this);   // スレッドの生成とスタート
    071			th.start();
    072		}
    073						// 他ページへ移動の際,一時的にスレッドを停止
    074		public void stop()
    075		{
    076			state = false;
    077		}
    078						// スレッドの実行
    079		public void run()
    080		{
    081			while (state) {
    082				try {
    083					th.sleep(33);
    084				}
    085				catch (InterruptedException e) {}
    086								// 摩擦なし(緑)
    087				if (y1 + width / 2 > d.height) {
    088					vy1 *= -1.0;
    089					y1   = d.height - width / 2;   // 位置の修正
    090				}
    091				else if (y1 - width / 2 < 0) {
    092					vy1 *= -1.0;
    093					y1   = width / 2;   // 位置の修正
    094				}
    095				else if (x1 + width / 2 > d.width) {
    096					vx1 *= -1.0;
    097					x1   = d.width - width / 2;   // 位置の修正
    098				}
    099				else if (x1 - width / 2 < 0) {
    100					vx1 *= -1.0;
    101					x1   = width / 2;   // 位置の修正
    102				}
    103				x1 += vx1 * dt;
    104				y1 += vy1 * dt;
    105								// 摩擦あり(赤)
    106				if (y2 + width / 2 > d.height) {
    107					vx2 *= 0.8;
    108					vy2 *= -0.8;
    109					y2   = d.height - width / 2;   // 位置の修正
    110				}
    111				else if (y2 - width / 2 < 0) {
    112					vx2 *= 0.8;
    113					vy2 *= -0.8;
    114					y2   = width / 2;   // 位置の修正
    115				}
    116				else if (x2 + width / 2 > d.width) {
    117					vx2 *= -0.8;
    118					vy2 *= 0.8;
    119					x2   = d.width - width / 2;   // 位置の修正
    120				}
    121				else if (x2 - width / 2 < 0) {
    122					vx2 *= -0.8;
    123					vy2 *= 0.8;
    124					x2   = width / 2;   // 位置の修正
    125				}
    126				x2 += vx2 * dt;
    127				y2 += vy2 * dt;
    128	
    129				repaint();
    130			}
    131		}
    132						// 描画
    133		public void paintComponent(Graphics g)
    134		{
    135			super.paintComponent(g);   // 親クラスの描画
    136	
    137			g.setColor(Color.green);
    138			g.fillOval((int)x1-20, (int)y1-20, 40, 40);
    139			g.setColor(Color.red);
    140			g.fillOval((int)x2-20, (int)y2-20, 40, 40);
    141		}
    142	}
    			
    087 行目~ 090 行目

      摩擦が無い場合において,円が下の壁に衝突したときの処理です.y 軸方向の速度を逆方向にし( 088 行目),y 軸方向の位置を修正しています( 089 行目).

    091 行目~ 094 行目

      上と同様,円が上の壁に衝突したときの処理です.y 軸方向の速度を逆方向にし( 092 行目),y 軸方向の位置を修正しています( 093 行目).

    095 行目~ 098 行目

      上と同様,円が右の壁に衝突したときの処理です.x 軸方向の速度を逆方向にし( 096 行目),x 軸方向の位置を修正しています( 097 行目).

    099 行目~ 102 行目

      上と同様,円が左の壁に衝突したときの処理です.x 軸方向の速度を逆方向にし( 100 行目),x 軸方向の位置を修正しています( 101 行目).

    103 行目~ 104 行目

      上で変更した速度を使用して,円の現在位置を再計算しています.

    106 行目~ 127 行目

      摩擦がある場合に対して,上と同様の処理を行っています.速度の方向だけではなく,その大きさも変更しています( 0.8 をかける処理).そのため,いずれの壁に衝突した場合も,x 軸及び y 軸方向の速度の修正が必要な点に注意して下さい.片方の速度の修正だけでは,反射方向が理想的な反射方向とは異なってしまいます.

  2. 乱数

      乱数は,Random クラスnextDouble メソッドを利用して実現できます.nextDouble メソッドは,[0, 1] 区間の一様乱数を返しますので,適当な定数を乗じることによって任意の区間の一様乱数を生成可能です.以下に示すプログラム( Test.java )は,画面上部のランダムな位置から,ランダムな方向にスタートした物体が,壁に当たってランダムな方向に跳ね返る現象を表現しています.
    001	import java.awt.*;
    002	import java.awt.event.*;
    003	import javax.swing.*;
    004	import java.util.Random;
    005	
    006	public class Test {
    007		public static void main (String[] args)
    008		{
    009			Win win = new Win("乱数");
    010		}
    011	}
    012	
    013	class Win extends JFrame
    014	{
    015		/******************/
    016		/* コンストラクタ */
    017		/******************/
    018		Win(String name)
    019		{
    020						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    021			super(name);
    022						// Windowの大きさ
    023			setSize(640, 470);   // +40, +70
    024						// ContentPane の取得と MainPanel の追加
    025			Dimension d  = getSize();   // Windowの大きさ
    026			d.width     -= 40;
    027			d.height    -= 70;
    028			MainPanel pn = new MainPanel(d);   // MainPanel オブジェクトの生成
    029			getContentPane().add(pn);   // MainPanel オブジェクトを ContentPane に追加
    030						// ウィンドウを表示
    031			setVisible(true);
    032						// イベントアダプタ
    033			addWindowListener(new WinEnd());
    034		}
    035	
    036		/******************************/
    037		/* 上,左,下,右の余白の設定 */
    038		/******************************/
    039		public Insets getInsets()
    040		{
    041			return new Insets(50, 20, 20, 20);
    042		}
    043	
    044		/************/
    045		/* 終了処理 */
    046		/************/
    047		class WinEnd extends WindowAdapter
    048		{
    049			public void windowClosing(WindowEvent e) {
    050				System.exit(0);
    051			}
    052		}
    053	}
    054	
    055	class MainPanel extends JPanel implements Runnable
    056	{
    057		boolean state = true;
    058		double x, y, vx, vy, a, dt = 0.1;
    059		int width = 40;
    060		Random rn;
    061		Dimension d;
    062		Thread th;
    063	
    064		MainPanel(Dimension d1)
    065		{
    066			d = d1;
    067			rn = new Random();
    068			x  = width / 2 + rn.nextDouble() * (d.width - width);
    069			y  = width / 2;
    070			a  = rn.nextDouble() * Math.PI;
    071			vx = 70 * Math.cos(a);
    072			vy = 70 * Math.sin(a);
    073			setBackground(new Color(238, 255, 238));   // 背景色の設定
    074			th = new Thread(this);   // スレッドの生成とスタート
    075			th.start();
    076		}
    077						// 他ページへ移動の際,一時的にスレッドを停止
    078		public void stop()
    079		{
    080			state = false;
    081		}
    082						// スレッドの実行
    083		public void run()
    084		{
    085			while (state) {
    086				try {
    087					th.sleep(33);
    088				}
    089				catch (InterruptedException e) {}
    090	
    091				if (y + width / 2 > d.height) {
    092					a  = rn.nextDouble() * Math.PI;
    093					vx = 70 * Math.cos(a);
    094					vy = -70 * Math.sin(a);
    095					y  = d.height - width / 2;   // 位置の修正
    096				}
    097				else if (y - width / 2 < 0) {
    098					a  = rn.nextDouble() * Math.PI;
    099					vx = 70 * Math.cos(a);
    100					vy = 70 * Math.sin(a);
    101					y  = width / 2;   // 位置の修正
    102				}
    103				else if (x + width / 2 > d.width) {
    104					a  = rn.nextDouble() * Math.PI - 0.5 * Math.PI;
    105					vx = -70 * Math.cos(a);
    106					vy = 70 * Math.sin(a);
    107					x  = d.width - width / 2;   // 位置の修正
    108				}
    109				else if (x - width / 2 < 0) {
    110					a  = rn.nextDouble() * Math.PI - 0.5 * Math.PI;
    111					vx = 70 * Math.cos(a);
    112					vy = 70 * Math.sin(a);
    113					x  = width / 2;   // 位置の修正
    114				}
    115				x += vx * dt;
    116				y += vy * dt;
    117	
    118				repaint();
    119			}
    120		}
    121						// 描画
    122		public void paintComponent(Graphics g)
    123		{
    124			super.paintComponent(g);   // 親クラスの描画
    125	
    126			g.setColor(Color.green);
    127			g.fillOval((int)x-20, (int)y-20, 40, 40);
    128		}
    129	}
    			
    068 行目

      円の x 軸方向の初期位置は [円の半径, パネルの幅-円の半径] 区間の一様乱数によって設定されます.

    070 行目

      a は円の初期進行方向を表し,[0.π] 区間の一様乱数によって設定されます.

    071 行目~ 072 行目

      x 軸方向及び y 軸方向の初期速度です.

    091 行目~ 096 行目

      円が下の壁に衝突したときの処理です.初期進行方向や初期速度を決める処理とほぼ同様ですが,y 軸方向の速度を逆方向に設置しています( 094 行目).また,095 行目において,壁にめり込んだ位置を修正しています.これらの点は,以下の処理においても同様です.

  3. もう一つの例

      この例では,四方のランダムの位置から,ランダムな進行方向で円が出現し,壁に当たると正確に反射します.ただし,反射後の速度は,元の速度に [0.5, 2.0] 一様区間の乱数をかけた値に変化します.そのため,最終的には,速度が大きくなりすぎ描画できなくなってしまいます.

      今まで示した例においては,複数の円を描くとき,複数の変数を使用していました.しかし,その方法ですと,円の数が多くなった場合やその数を変更したい場合は大変な作業になります.この例では,各円の位置や速度を配列変数によって表現しています.そのため,058 行目における変数 n の値を変更するだけで,任意の数の円を描画することができます.
    001	import java.awt.*;
    002	import java.awt.event.*;
    003	import javax.swing.*;
    004	import java.util.Random;
    005	
    006	public class Test {
    007		public static void main (String[] args)
    008		{
    009			Win win = new Win("乱数(複数の円)");
    010		}
    011	}
    012	
    013	class Win extends JFrame
    014	{
    015		/******************/
    016		/* コンストラクタ */
    017		/******************/
    018		Win(String name)
    019		{
    020						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    021			super(name);
    022						// Windowの大きさ
    023			setSize(640, 470);   // +40, +70
    024						// ContentPane の取得と MainPanel の追加
    025			Dimension d  = getSize();   // Windowの大きさ
    026			d.width     -= 40;
    027			d.height    -= 70;
    028			MainPanel pn = new MainPanel(d);   // MainPanel オブジェクトの生成
    029			getContentPane().add(pn);   // MainPanel オブジェクトを ContentPane に追加
    030						// ウィンドウを表示
    031			setVisible(true);
    032						// イベントアダプタ
    033			addWindowListener(new WinEnd());
    034		}
    035	
    036		/******************************/
    037		/* 上,左,下,右の余白の設定 */
    038		/******************************/
    039		public Insets getInsets()
    040		{
    041			return new Insets(50, 20, 20, 20);
    042		}
    043	
    044		/************/
    045		/* 終了処理 */
    046		/************/
    047		class WinEnd extends WindowAdapter
    048		{
    049			public void windowClosing(WindowEvent e) {
    050				System.exit(0);
    051			}
    052		}
    053	}
    054	
    055	class MainPanel extends JPanel implements Runnable
    056	{
    057		boolean state = true;
    058		int n = 3;   // 物体の数
    059		double x[] = new double [n];   // 物体の位置(x)
    060		double y[] = new double [n];   // 物体の位置(y)
    061		double vx[] = new double [n];   // 物体の速度(x)
    062		double vy[] = new double [n];   // 物体の速度(y)
    063		double v = 30;   // 初期速度
    064		double dt = 0.1;   // 時間刻み幅
    065		int r = 20;   // 物体の半径
    066		Dimension d;   // パネルの幅と高さ
    067		Random rn;
    068		Thread th;
    069	
    070		MainPanel(Dimension d1)
    071		{
    072			d = d1;
    073			rn = new Random();
    074			for (int i1 = 0; i1 < n; i1++) {
    075				double p = rn.nextDouble() * 2 * (d.width + d.height);
    076				double ang;
    077				if (p < d.width) {   // 上
    078					ang   = -rn.nextDouble() * Math.PI;
    079					x[i1] = r + rn.nextDouble() * (d.width - 2 * r);
    080					y[i1] = r;
    081				}
    082				else if (p < d.width + d.height) {   // 右
    083					ang   = 0.5 * Math.PI + rn.nextDouble() * Math.PI;
    084					x[i1] = d.width - r;
    085					y[i1] = r + rn.nextDouble() * (d.height - 2 * r);
    086				}
    087				else if (p < 2 * d.width + d.height) {   // 下
    088					ang   = rn.nextDouble() * Math.PI;
    089					x[i1] = r + rn.nextDouble() * (d.width - 2 * r);
    090					y[i1] = d.height - r;
    091				}
    092				else {   // 左
    093					ang   = -0.5 * Math.PI + rn.nextDouble() * Math.PI;
    094					x[i1] = r;
    095					y[i1] = r + rn.nextDouble() * (d.height - 2 * r);
    096				}
    097				vx[i1] = v * Math.cos(ang);
    098				vy[i1] = -v * Math.sin(ang);
    099			}
    100			setBackground(new Color(238, 255, 238));   // 背景色の設定
    101			th = new Thread(this);   // スレッドの生成とスタート
    102			th.start();
    103		}
    104						// 他ページへ移動の際,一時的にスレッドを停止
    105		public void stop()
    106		{
    107			state = false;
    108		}
    109						// スレッドの実行
    110		public void run()
    111		{
    112			while (state) {
    113				try {
    114					th.sleep(33);
    115				}
    116				catch (InterruptedException e) {}
    117	
    118				for (int i1 = 0; i1 < n; i1++) {
    119					double a = 0.5 + 1.5 * rn.nextDouble();
    120					if (y[i1] + r > d.height) {   // 下の壁
    121						vx[i1] = a * vx[i1];
    122						vy[i1] = -a * vy[i1];
    123						y[i1]  = d.height - r;   // 位置の修正
    124					}
    125					else if (y[i1] - r < 0) {   // 上の壁
    126						vx[i1] = a * vx[i1];
    127						vy[i1] = -a * vy[i1];
    128						y[i1]  = r;   // 位置の修正
    129					}
    130					else if (x[i1] + r > d.width) {   // 右の壁
    131						vx[i1] = -a * vx[i1];
    132						vy[i1] = a * vy[i1];
    133						x[i1]  = d.width - r;   // 位置の修正
    134					}
    135					else if (x[i1] - r < 0) {   // 左の壁
    136						vx[i1] = -a * vx[i1];
    137						vy[i1] = a * vy[i1];
    138						x[i1]  = r;   // 位置の修正
    139					}
    140					x[i1] += vx[i1] * dt;
    141					y[i1] += vy[i1] * dt;
    142				}
    143	
    144				repaint();
    145			}
    146		}
    147						// 描画
    148		public void paintComponent(Graphics g)
    149		{
    150			super.paintComponent(g);   // 親クラスの描画
    151	
    152			g.setColor(Color.green);
    153			for (int i1 = 0; i1 < n; i1++)
    154				g.fillOval((int)x[i1]-r, (int)y[i1]-r, 2*r, 2*r);
    155		}
    156	}
    			

情報学部 菅沼ホーム Java 目次 基礎技術目次 索引