凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Canvas を使ってみんなもお手軽に弾幕作ろうぜ!! - 其の参 -

はじめに

前回、敵を表示したので、今回はプレイヤーを表示して操作するところまで作ります。↓のような感じです。

プレイヤーの表示

こんな画像を用意しました。

まずは今までと同じように、

  • Player の画像やその他情報を Materials に登録
  • Player クラスを Mover クラスを継承して作成
  • Player インスタンスを突っ込む Container を作成

でプレイヤーを表示しましょう。

/* ------------------------------------------------------------------------- */
//  Resource
/* ------------------------------------------------------------------------- */
Materials.add({
	key    : 'player.reimu',
	path   : 'img/char/yukkuri_reimu.png',
	width  : 32,
	height : 32,
	cd     : 2,
});

/* ------------------------------------------------------------------------- */
// Player
/* ------------------------------------------------------------------------- */
var Player = function(player) {
	Mover.call(this, {
		x   : ('x' in player) ? player.x : WIDTH/2,
		y   : ('y' in player) ? player.y : HEIGHT - 60,
		key : 'player.reimu',
	});
}
Player.prototype = $.extend({}, Mover.prototype, {
	move: function() {
		Mover.prototype.move.apply(this);
		this.shot();
	},
	draw: function() {
		this.sx = this.w * (((this.cnt/5)|0) % 3);
		Mover.prototype.draw.call(this);
	},
	shot: function() {},
});

/* ------------------------------------------------------------------------- */
//  Containers
/* ------------------------------------------------------------------------- */
var Players        = new Container();

/* ------------------------------------------------------------------------- */
//  Main
/* ------------------------------------------------------------------------- */
function main() {
	var frame = 0;

	Players.add(new Player({}));
	console.log(Players);
	setInterval(function(){
		if (frame%60 === 0) {
			Enemies.add(
				new Enemy3Way({
					x   : (Math.random() + 0.5)*WIDTH/2,
					y   : -MARGIN,
					vx  : (Math.random() - 0.5)*5,
					vy  : 2,
					key : 'enemy.red'
				})
			);
		}

		var containers = [Players, BulletCurtains, Enemies, Bullets];
		Canvas.clear();
		for (var i in containers) {
			containers[i].move();
			containers[i].draw();
		}
		++frame;
	}, 1000/FPS);
}


アニメーションして表示されました。

プレイヤーの操作

ただこれでは止まっているだけですので、動かしてみましょう。

var Canvas = {
	// ...(略)
	calcPosFromPageXY : function(x0, y0) {
		var x = (x0 - $('#canvas').position().left) * WIDTH  / $('#canvas').width();
		var y = (y0 - $('#canvas').position().top ) * HEIGHT / $('#canvas').height() - 30;
		return {x: x, y: y};
	}
};

/* ------------------------------------------------------------------------- */
//  Control
/* ------------------------------------------------------------------------- */
var Mouse = {
	move : function(e) {
		e.preventDefault();
		if (Players.array.length === 0) return;

		var player = Players.array[0];
		var pos = Canvas.calcPosFromPageXY(e.pageX, e.pageY);
		player.x = pos.x;
		player.y = pos.y;
	}
};

$('#canvas').bind({
	mousemove  : Mouse.move
});


これでマウスに追従してキャラが動くようになります。
タッチイベントは Window 上での位置で受け取るので、これをキャンバス上の位置に変換する Canvas.calcPosFromPageXY() を追加します。そしてこれを用いて、Mouse.move コールバックを作成し、mousemove イベントハンドラに登録する、という形になっています。

当たり判定の追加

Mover.prototype に collision を追加します。

Mover.prototype = {
	// ...(略)
	collision : function(mover) {
		var dx = mover.x - this.x;
		var dy = mover.y - this.y;
		var dr = this.cd + mover.cd;
		return (dx*dx + dy*dy < dr*dr);
	},
	// ...(略)
};

collision メソッドは、それぞれのオブジェクトの当たり判定が半径 cd の円で表現されているとして、相手の円と自分の円が重なってしまったら true を返すようなものになっています。
そしてこれを Container の collision メソッド内から呼び出すようにします。

Container.prototype = {
	// ...(略)
	collision : function(container) {
		for (var i in this.array) {
			for (var j in container.array) {
				if (this.array[i].collision(container.array[j])) {
					this.array[i].hit(container.array[j]);
				}
			}
		}
	}
};

当たり判定を調べる相手の Container の中身と自身のコンテナの中身をすべて collision で調べ、 当たった! となったときは、hit メソッドを呼ぶような形になっています。今回は、Player と Bullet が接触したかどうかを調べるところを作りたいので、Player に hit を実装してみます。

Player.prototype = $.extend({}, Mover.prototype, {
	// ...(略)
	hit : function(enemy) {
		this.flag = false;
	}
});

これだけです。あとは、main の中身で Container.collision を実行します。

function main() {

	setInterval(function(){
		// ...(略)
		Players.collision(Bullets);
	}, 1000/FPS);
}

これで当たったら消えるようになります。

ショットを発射する

また同じような作業を繰り返します。まぁでも同じような作業を繰り返すだけで色々追加できるっていうのは良いですね。

こんな画像をショットにします。
更新箇所については省略します。ついでに、死んだままではアレなのでクリックすると復活するようにしておきました。

敵にダメージを与えられるようにする

Shot.hit メソッドから着弾相手の Enemy.hit を呼ぶようにします。 敵は一定のダメージを受けると死亡するようにするために、Enemy に hp というプロパティを追加します。つまり以下のような感じにします。

Shot.prototype = $.extend({}, Mover.prototype, {
	hit : function(enemy) {
		this.flag = false;
		enemy.hit(this.atk); // <-- 追加
	}
});

var Enemy = function(enemy) {
	Mover.call(this, enemy);
	this.hp = ('hp' in enemy) ? enemy.hp : 100; // <-- 追加
};
Enemy.prototype = $.extend({}, Mover.prototype, {
	// ...(ry
	hit  : function(damage) {
		this.hp -= damage;
		console.log(this.hp);
		if (this.hp < 0) {
			this.flag = false;
		}
	}
});

これで敵を倒せるようになります!

敵/プレイヤーが被弾したときのエフェクトを追加する

最後に、被弾したことが分かるように被弾時に何か当たってるっぽいエフェクトを追加します。
お察しの通り、Effects Container を制作して、この中に Mover を継承した Effect を突っ込んで、各所でよしなに new Effect すればそれっぽくなります。

プレイヤーが敵に突っ込むとダメージを与えられたり、被弾した弾は消えるようにするなど細かいところも修正しました。
被弾時の素材は作り忘れていたので、龍神録プログラミングの館 からお借りさせていただいております。

次回

次回はマルチタッチ対応について書きます。