座標

座標

canvasの座標について調べてみましょう。

window.onload = () => {
	const w = 500;
	const h = 500;
	// Canvas の章で作成した Canvas.js
	const Canvas = new CanvasClass();
	Canvas.setCanvasSize(w, h);
	const ctx = Canvas.getContext();
	ctx.fillStyle = 'rgb(191, 255, 191)';
	ctx.fillRect(0, 0, w, h);
	ctx.fillStyle = 'rgb(0, 0, 0)';
	ctx.beginPath();
	// (0, 0) に半径50の円を描く
	ctx.arc(0, 0, 50, calcRadian(0), calcRadian(360));
	ctx.stroke();
	ctx.beginPath();
	// (250, 250) に半径50の円を描く
	ctx.arc(250, 250, 50, calcRadian(0), calcRadian(360), true);
	ctx.stroke();
	ctx.beginPath();
	// (500, 500) に半径50の円を描く
	ctx.arc(500, 500, 50, calcRadian(0), calcRadian(360), false);
	ctx.fill();

	// degree(度数法)をradian(弧度法)に変換して返す
	function calcRadian(deg) {
		return deg * Math.PI / 180;
	}
}

使用しているCanvasClass.jsはこちらにあります。
実行結果

左上、中央、右下の三箇所に円を描きました。

JavaScriptのcanvasの座標はこのようになっています。
言語やフレームワーク、ゲームエンジンによっては左下になったりもします。

テキストの座標

さきほどのaxis.jsにテキストの描画処理を追記します。

	// テキストを描画してみる
	ctx.font = '16px serif';
	ctx.fillStyle = 'rgb(250, 0, 100)';
	ctx.fillText('(0, 50)に描画中', 0, 50);
	ctx.fillText('(250, 200)に描画中', 250, 200);
	ctx.fillText('(350, 450)に描画中', 350, 450);

実行結果

テキストを描画しました。
描画座標はテキストの通りです。
テキストを描画する際、テキストの座標の始点になるのは左下になります。
座標を決める点(テキストなら左下)のことをアンカーと呼びます。
言語やフレームワーク、ゲームエンジンによってはアンカーを自分で設定することができるものもあります。

ボールを動かしてみる

さて、このアンカー、なぜ必要になるのかを試してみます。
自分で作成しながら読み進める場合には、次にどのような作業をするのかを書きますので、読み進める前に自分で実装してみましょう。
何はともあれ、ボールクラスを作ります。

class BallClass {
	// canvas の context をコンストラクターの引数に渡す
	constructor(x) {
		this.ctx = x;
		this.posX = 50;
		this.posY = 15;
		this.radius = 15;
		this.drawStartDig = this.calcRadian(0);
		this.drawEndDig = this.calcRadian(360);
		this.ctx.fillStyle = 'rgb(0, 0, 0)';
	}

	// ボールの描画
	drawBall() {
		this.ctx.beginPath();
		this.ctx.arc(this.posX, this.posY, this.radius, this.drawStartDig, this.drawEndDig);
		this.ctx.stroke();
	}

	// degree(度数法)をradian(弧度法)に変換して返す
	calcRadian(deg) {
		return deg * Math.PI / 180;
	}
}
window.onload = () => {
	const w = 500;
	const h = 500;
	// Canvas の章で作成した Canvas.js
	const Canvas = new CanvasClass();
	Canvas.setCanvasSize(w, h);
	const ctx = Canvas.getContext();
	ctx.fillStyle = 'rgb(191, 255, 191)';
	ctx.fillRect(0, 0, w, h);
	const Ball = new BallClass(ctx);
	Ball.drawBall();
}

実行結果

座標(50, 15)、半径15のボールが描画されます。
CanvasClassとBallClassとを利用しました。
次はボールを下方向に動かしてみましょう。
下方向はcanvasでいうと y軸の + 方向です。
まずは、BallClassを修正します。

class BallClass {
	// canvas の context をコンストラクターの引数に渡す
	constructor(x) {
		this.ctx = x;
		this.posX = 50;
		this.posY = 15;
		this.moveSpeed = 5;
		this.radius = 15;
		this.drawStartDig = this.calcRadian(0);
		this.drawEndDig = this.calcRadian(360);
		// this.ctx.fillStyle = 'rgb(0, 0, 0)';
	}

	// ボールの描画
	drawBall() {
		this.ctx.fillStyle = 'rgb(0, 0, 0)';
		this.ctx.beginPath();
		this.ctx.arc(this.posX, this.posY, this.radius, this.drawStartDig, this.drawEndDig);
		this.ctx.stroke();
	}

	// ボールを動かす
	moveBall() {
		this.posY += this.moveSpeed;
	}

	// degree(度数法)をradian(弧度法)に変換して返す
	calcRadian(deg) {
		return deg * Math.PI / 180;
	}
}

続いて、moveBallを修正します。

window.onload = () => {
	const w = 500;
	const h = 500;
	// Canvas の章で作成した Canvas.js
	const Canvas = new CanvasClass();
	Canvas.setCanvasSize(w, h);
	const ctx = Canvas.getContext();
	// - ctx.fillStyle = 'rgb(191, 255, 191)';
	// - ctx.fillRect(0, 0, w, h);
	const Ball = new BallClass(ctx);
	// - Ball.drawBall();
	// 周期関数の実装
	// setInterval(関数, ミリ秒単位の実行間隔)
	// setInterval(func, delay)
	// 100ms毎にcanvasに色を塗り、ボールを描画する
	setInterval(function() {
		ctx.fillStyle = 'rgb(191, 255, 191)';
		ctx.fillRect(0, 0, w, h);
		Ball.moveBall();
		Ball.drawBall();
	}, 100);
}

修正した箇所をハイライトしています。
実行結果はこうなります。

setInterval()についてはこちらの章で説明しています。
ボールが下方向に移動していきます。

ボールを反射させてみる

しかし、下方向に行きっぱなしでは面白くないので、canvasの下部にいったら上方向へ動かしてみましょう。
また、canvasの上部にいったら下方向へ動かす処理を作成します。
BallClassを修正します。

class BallClass {
	// canvas の context をコンストラクターの引数に渡す
	constructor(x) {
		this.ctx = x;
		this.posX = 50;
		this.posY = 15;
		this.moveSpeed = 5;
		this.radius = 15;
		this.drawStartDig = this.calcRadian(0);
		this.drawEndDig = this.calcRadian(360);
	}

	// ボールの描画
	drawBall() {
		this.ctx.fillStyle = 'rgb(0, 0, 0)';
		this.ctx.beginPath();
		this.ctx.arc(this.posX, this.posY, this.radius, this.drawStartDig, this.drawEndDig);
		this.ctx.stroke();
	}

	// ボールを動かす
	moveBall() {
		this.posY += this.moveSpeed;
		if(this.posY >= this.ctx.canvas.height || this.posY <= 0) this.moveSpeed *= -1;
	}

	// degree(度数法)をradian(弧度法)に変換して返す
	calcRadian(deg) {
		return deg * Math.PI / 180;
	}
}

実行結果

ボールが上下に移動するようになりました。
処理の内容としては、ボールの座標がcanvasよりも下部、もしくは上部に行った場合、ボールの速さに-1を乗算しています。
さて、私は一つ気になることがあります。
それは、ボールが方向転換する際に、画面外にボールが飛び出してしまうことです。
ということで、ボールが画面外に出る前に、方向転換させてみます。
相も変わらずBallClassを修正します。

class BallClass {
	// canvas の context をコンストラクターの引数に渡す
	constructor(x) {
		this.ctx = x;
		this.posX = 50;
		this.posY = 15;
		this.moveSpeed = 5;
		this.radius = 15;
		this.drawStartDig = this.calcRadian(0);
		this.drawEndDig = this.calcRadian(360);
	}

	// ボールの描画
	drawBall() {
		this.ctx.fillStyle = 'rgb(0, 0, 0)';
		this.ctx.beginPath();
		this.ctx.arc(this.posX, this.posY, this.radius, this.drawStartDig, this.drawEndDig);
		this.ctx.stroke();
	}

	// ボールを動かす
	moveBall() {
		this.posY += this.moveSpeed;
		if(this.posY + this.radius >= this.ctx.canvas.height || this.posY - this.radius <= 0) this.moveSpeed *= -1;
	}

	// degree(度数法)をradian(弧度法)に変換して返す
	calcRadian(deg) {
		return deg * Math.PI / 180;
	}
}

修正したのはBallClass.jsの24行目のみです。
方向転換をする条件にをボールのy座標に、ボールの半径を加算・減算しています。

図にしてみると整理しやすくなります。
ボールはcontext.arc()という関数を利用して描いています。
この関数は、中心と半径(r)を利用しています。
つまり、中心からボールの上部までの長さはrということにります。
なので、ボールの上部の座標は、ボールのy座標にボールの半径を減算すれば求まります。
ボールの下部の座標は、ボールのy座標にボールの半径を加算すれば求まります。

最後に

座標とアンカーについて説明してみました。
座標の中心やアンカーはゲームにおいて重要になります。
様々なオブジェクト(画像やテキスト・図形など)の基準点となるので、操作する際に必要になるからです。

最後に、ボールが左右上下に動き回るものを実装したものをおいておきます。

class BallClass {
	// canvas の context をコンストラクターの引数に渡す
	constructor(x) {
		this.ctx = x;
		this.posX = 50;
		this.posY = 15;
		this.moveSpeedY = 5;
		this.moveSpeedX = 10;
		this.radius = 15;
		this.drawStartDig = this.calcRadian(0);
		this.drawEndDig = this.calcRadian(360);
	}

	// ボールの描画
	drawBall() {
		this.ctx.fillStyle = 'rgb(0, 0, 0)';
		this.ctx.beginPath();
		this.ctx.arc(this.posX, this.posY, this.radius, this.drawStartDig, this.drawEndDig);
		this.ctx.stroke();
	}

	// ボールを動かす
	moveBall() {
		this.posY += this.moveSpeedY;
		this.posX += this.moveSpeedX;
		if(this.posY + this.radius >= this.ctx.canvas.height || this.posY - this.radius <= 0) this.moveSpeedY *= -1;
		if(this.posX + this.radius >= this.ctx.canvas.width || this.posX - this.radius <= 0) this.moveSpeedX *= -1;
	}

	// degree(度数法)をradian(弧度法)に変換して返す
	calcRadian(deg) {
		return deg * Math.PI / 180;
	}
}
window.onload = () => {
	const w = 500;
	const h = 500;
	// Canvas の章で作成した Canvas.js
	const Canvas = new CanvasClass();
	Canvas.setCanvasSize(w, h);
	const ctx = Canvas.getContext();
	const Ball = new BallClass(ctx);
	// 周期関数の実装
	// setInterval(関数, ミリ秒単位の実行間隔)
	// setInterval(func, delay)
	// 100ms毎にcanvasに色を塗り、ボールを描画する
	setInterval(function() {
		ctx.fillStyle = 'rgb(191, 255, 191)';
		ctx.fillRect(0, 0, w, h);
		Ball.moveBall();
		Ball.drawBall();
	}, 100);
}