【JavaScript】当たり判定を行う

下はJavaScriptで実装した円形当たり判定のデモ。赤円をドラッグし青円に当てると「HIT!」と表示される。

円形の当たり判定はピタゴラスの定理を使い斜線の距離から割り出す。ピタゴラスの定理とは、直角三角形の斜辺(c)の2乗は他2辺(a, b)をそれぞれ2乗した値の和に等しいという定理。

a² + b² = c²

斜辺以外の2辺の長さが分かれば斜辺の長さも分かる(a + bの値を素因数分解)。下図のように2点間の円形の当たり判定を求める場合、まず直角三角形を作る。

点と点の場合、cが0以下か0を超えるかで判定。円は半径があるため、互いの半径の合計と比較。比較対象が2乗されているので半径も2乗する。当たり判定を求めるだけなら、素因数分解までして正確なcは求めずとも大丈夫。素因数分解して正確な距離を出したい場合はMath.sqrt()を使う。

let distance = Math.sqrt((ax – bx) ** 2 + (ay – by) ** 2);

**はべき乗演算子。左のオペランドを、右のオペランドの累乗にした結果を返す。ここでは2乗している。

まず、円を表示するだけのCircleクラス。

/**
 * 真円を描画(circle.js)
 */
export class Circle {
	constructor(parent = undefined, x = 0, y = 0, radius = 15) {
		if (parent !== undefined) {
			this._parent = parent;
		}
		this._x = x;
		this._y = y;
		this._radius = radius;
		this._color = "#ff0000";
		
		this._init();
	}

	//////////////////////////////////
	// Private and protected
	//////////////////////////////////

	_init() {
		if (this._parent !== undefined) {
			this._ctx = this._parent;
		}
		this.draw();
	}

	//////////////////////////////////
	// Public
	//////////////////////////////////

	draw() {
		this._ctx.save();
		this._ctx.fillStyle = this._color;
		this._ctx.beginPath();
		this._ctx.arc(this._x, this._y, this._radius, 0, (Math.PI * 2), true);
		this._ctx.closePath();
		this._ctx.fill();
		this._ctx.restore();
	}

	//////////////////////////////////
	// Getters/Setters
	//////////////////////////////////

	get x() {
		return this._x;
	}
	set x(x) {
		this._x = x;
	}

	get y() {
		return this._y;
	}
	set y(y) {
		this._y = y;
	}

	get radius() {
		return this._radius;
	}
	set radius(radius) {
		this._radius = radius;
	}

	set color(color) {
		this._color = color;
		this.draw();
	}
}
スポンサーリンク

次に、当たり判定を行HitTestクラス。

import { Circle } from "./circle.js";

/**
 * 当たり判定を行う(hittest.js)
 */
export class Hittest {
	constructor() {
		this._cvs = document.getElementById('canvas');
		this._ctx = this._cvs.getContext('2d');
		this._circle1;
		this._circle2;
		this._isMouseDown = 0;
		this._cvs.addEventListener('mousedown', this._onMouseDown.bind(this));
		this._onMouseUp = this._onMouseUp.bind(this);
		this._onMouseMove = this._onMouseMove.bind(this);
		// タイマー関連
		this._animID;
		this._isAnim = 0;
		this._FPS = 60;
		this._frame = 0;
		this._startTime;
		this._nowTime;
		
		this._init();
	}

	//////////////////////////////////
	// Private and protected
	//////////////////////////////////

	_init() {
		this._cvs.style.backgroundColor = "#eeeeee";
		this._circle1 = new Circle(this._ctx, 30, 30, 30);
		this._circle2 = new Circle(this._ctx, 250, 250, 60);
		this._circle2.color = "#0000ff";
		this._ctx.font = "30px serif";

		this._startTime = performance.now();
		this._mainLoop();
	}

	_mainLoop() {
		this._nowTime = performance.now();
		let elapsedTime = this._nowTime - this._startTime;
		let idealTime = this._frame * (1000 / this._FPS);
		if (idealTime < elapsedTime) {
			this._ctx.clearRect(0, 0, this._cvs.width, this._cvs.height);

			// ①2つの円の当たり判定を行う
			let dist = (this._circle1.x - this._circle2.x) ** 2 + (this._circle1.y - this._circle2.y) ** 2;
			if (dist <= (this._circle1.radius + this._circle2.radius) ** 2) {
				this._ctx.fillText("HIT!", 10, 390);
			} else {
				this._ctx.fillText("-", 10, 390);
			}
			this._circle1.draw();
			this._circle2.draw();

			this._frame++;
			
			if (elapsedTime >= 1000) {
				this._startTime = this._nowTime;
				this._frame = 0;
			}
		}
	
		this._animID = requestAnimationFrame(this._mainLoop.bind(this));
	}

	//////////////////////////////////
	// Handlers
	//////////////////////////////////

	_onMouseDown(e) {
		this._isMouseDown = 1 - this._isMouseDown;
		this._cvs.addEventListener('mouseup', this._onMouseUp);
		this._cvs.addEventListener('mousemove', this._onMouseMove);
	}

	_onMouseUp(e) {
		this._isMouseDown = 1 - this._isMouseDown;
		this._cvs.removeEventListener('mouseup', this._onMouseUp);
		this._cvs.removeEventListener('mousemove', this._onMouseMove);
	}

	_onMouseMove(e) {
		this._circle1.x = e.clientX;
		this._circle1.y = e.clientY;
	}
}

①最初にピタゴラスの定理を使い、this._circle1とthis._circle2との距離を測定。当たり判定だけなら2乗したままの値でよい。

次に、距離と2つの円の半径を足して2乗した値を比較。距離の値の方が小さければ当たっている。

requestAnimationFrameを使ったゲームループの実装は、「【JavaScript】requestAnimationFrameでゲームループを作る」を参照。

参考図書

コメント

タイトルとURLをコピーしました