ローグライクゲームを作ってみるその2 ダンジョン・プレイヤーの生成と描画

### 過去記事一覧

* [その1 タイトル画面](https://qiita.com/pizyumi/items/3526fddd4f18a462e1ae)

現在のコードについては前回の記事の最後の項を参照してください。

### プログラム

今回作ったプログラムは[http://yurinya.net/roguelike2019/2/srl.html](http://yurinya.net/roguelike2019/2/srl.html)で公開しています。良かったらアクセスしてみてください！

### ダンジョンの生成

ローグライクゲームのポイントって何点かあると思うのですが、その1つはダンジョン生成だと思います。

ランダム生成なのは勿論ですが、適度に探索しやすく、それでいて探索しがいのあるダンジョンが理想だと思います。

でも、そんなダンジョンを最初から生成するのって難しいですよね。

それより先にとりあえず動くものを作ってしまいたいです。

そこで今回はダンジョン生成については何も考えません。

空っぽの部屋を1つ作るだけにします。

今回は描画処理の実装を頑張ります。

---

まずダンジョンの（暫定的な）仕様を決めましょう。

* 2D
* 正方形のマスを縦横に敷き詰める
* 正方形のマスは縦横32ピクセル
* 床と壁から成り、床はプレイヤーが歩けるが、壁は歩けない
* 1つのフロアの大きさは25マスx25マス

とりあえずこんな感じにしましょう。

フロアの大きさを25マスx25マスにしたのは、これだとフロアの大きさは25マス*32ピクセル=800ピクセルとなり、ちょうどゲーム画面に収まるからです。

ゲーム画面に収まらないとフロアの一部しか描画できないということになり面倒なのです。

勿論最終的にはもっと広いフロアにも対応可能にしていきたいですが。

---

という訳で、コードを書いていきましょう。

まずフロアの大きさと1マスのピクセル数を定義します。

`LX`をフロアの横方向のマス数とし、`LY`をフロアの縦方向のマス数とし、`PX`をマスの幅とし、`PY`をマスの高さとします。

```js
var LX = 25;
var LY = 25;
var PX = 32;
var PY = 32;
```

マスの種類は数値で表すことにします。

床は`0`、壁は`1`とします。

```js
var B_FLOOR = 0;
var B_WALL = 1;
```

フロアを作成する関数`create_field`を作成します。

端だけ壁にし、それ以外は床にします。

`create_field`関数はフロアを表すオブジェクトを返すことにします。

フロアを表すオブジェクトでは`blocks`プロパティに配列の配列としてマスの情報を格納することにし、それぞれのマスの情報はオブジェクトとして格納することにします。

マスを表すオブジェクトでは`base`プロパティにマスの種類を格納することにします。

```js
function create_field () {
	var blocks = [];
	for (var i = 0; i < LX; i++) {
		blocks[i] = [];
		for (var j = 0; j < LY; j++) {
			if ((i === 0 || j === 0) || (i === LX -1 || j === LY - 1)) {
				blocks[i][j] = {
					base: B_WALL
				};
			}
			else {
				blocks[i][j] = {
					base: B_FLOOR
				};
			}
		}
	}

	return {
		blocks: blocks
	};
}
```

前回空にしておいた`init`関数を実装します。

`create_field`関数によりフロアを作成し、`field`変数に格納します。

```js
var field = null;

function init () {
	field = create_field();
}
```

### ダンジョンの描画

次にダンジョンの描画を行います。

`draw`関数に下のコードを追加します。

マスが床の場合には白い点をマスの中心に描画します。

また、マスが壁の場合には四角に囲まれた×を描画します。

```js
	for (var i = 0; i < LX; i++) {
		for (var j = 0; j < LY; j++) {
			var block = field.blocks[i][j];
			if (block.base === B_FLOOR) {
				con.fillStyle = 'white';
				con.beginPath();
				con.arc((i + 0.5) * PX, (j + 0.5) * PY, 1, 0, Math.PI * 2);
				con.closePath();
				con.fill();
			}
			else if (block.base === B_WALL) {
				con.strokeStyle = 'white';
				con.strokeRect(i * PX, j * PY, PX, PY);
				con.beginPath();
				con.moveTo(i * PX, j * PY);
				con.lineTo((i + 1) * PX, (j + 1) * PY);
				con.moveTo((i + 1) * PX, j * PY);
				con.lineTo(i * PX, (j + 1) * PY);
				con.closePath();
				con.stroke();
			}
		}
	}
```

これにより描画されるダンジョンは下のようになります。

![p01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10969/b01e57fc-8027-6277-d388-69453371cd52.png)

### プレイヤーの生成

次にプレイヤーを生成します。

`init`関数を下のように変更します。

プレイヤーを表すオブジェクトを作成し、`player`変数に格納します。

プレイヤーを表すオブジェクトは以下のプロパティを持ちます。

* `x`・・・プレイヤーのフロア（ダンジョン）における横方向の位置
* `y`・・・プレイヤーのフロア（ダンジョン）における縦方向の位置

プレイヤーのフロアにおける最初の位置は`(12, 7)`とすることにしました。

```js
var player = null;

function init () {
	field = create_field();
	player = {
		x: 12, 
		y: 17
	};
}
```

### プレイヤーの描画

次にプレイヤーの描画を行います。

`draw`関数に下のようなコードを追加します。

プレイヤーがいるマスに人の絵文字を赤色で描画します。

ただし、絵文字はテキストスタイルにしなければ色を指定することができませんので、絵文字をテキストスタイルに変更するために絵文字の後ろには異字体セレクタのVS15（`U+FE0E`）を付加します。

```js
	con.textBaseline = 'middle';
	con.textAlign = 'center';
	con.fillStyle = 'red';
	con.font = '24px consolas';
	con.fillText('🚶\uFE0E', player.x * PX + (PX / 2), player.y * PY + (PY / 2));
```

これにより描画されるダンジョンは下のようになります。

![p02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10969/d404966f-2c5f-ccba-e83b-ee7381eb6f3f.png)

### 今回はここまで

今回はここまでです。

`game.js`は下のようになりました。

```js
﻿var TITLE = 'シンプルローグライク';

var TEXT_START = 'はじめる';

var SCREEN_X = 1600;
var SCREEN_Y = 800;

var LX = 25;
var LY = 25;
var PX = 32;
var PY = 32;

var B_FLOOR = 0;
var B_WALL = 1;

var startf = false;

var field = null;
var player = null;

$(function(){
	var canvas = document.getElementById('game');
	var con = canvas.getContext('2d');

	var c = $('body');
	c.on('keydown', function (e) {
		if (!startf) {
			if (e.keyCode === 90) {
				startf = true;

				init();

				draw(con);
			}

			return;
		}
	});

	draw(con);
});

function init () {
	field = create_field();
	player = {
		x: 12,
		y: 17
	};
}

function create_field () {
	var blocks = [];
	for (var i = 0; i < LX; i++) {
		blocks[i] = [];
		for (var j = 0; j < LY; j++) {
			if ((i === 0 || j === 0) || (i === LX -1 || j === LY - 1)) {
				blocks[i][j] = {
					base: B_WALL
				};
			}
			else {
				blocks[i][j] = {
					base: B_FLOOR
				};
			}
		}
	}

	return {
		blocks: blocks
	};
}

function draw (con) {
	con.fillStyle = 'black';
	con.fillRect(0, 0, SCREEN_X, SCREEN_Y);

	if (!startf) {
		con.textBaseline = 'alphabetic';
		con.textAlign = 'center';
		con.fillStyle = 'white';

		con.font = "48px consolas";
		con.fillText(TITLE, SCREEN_X / 2, SCREEN_Y / 4);

		con.font = "32px consolas";
		con.fillText('> ' + TEXT_START, SCREEN_X / 2, SCREEN_Y / 4 * 3);

		return;
	}

	for (var i = 0; i < LX; i++) {
		for (var j = 0; j < LY; j++) {
			var block = field.blocks[i][j];
			if (block.base === B_FLOOR) {
				con.fillStyle = 'white';
				con.beginPath();
				con.arc((i + 0.5) * PX, (j + 0.5) * PY, 1, 0, Math.PI * 2);
				con.closePath();
				con.fill();
			}
			else if (block.base === B_WALL) {
				con.strokeStyle = 'white';
				con.strokeRect(i * PX, j * PY, PX, PY);
				con.beginPath();
				con.moveTo(i * PX, j * PY);
				con.lineTo((i + 1) * PX, (j + 1) * PY);
				con.moveTo((i + 1) * PX, j * PY);
				con.lineTo(i * PX, (j + 1) * PY);
				con.closePath();
				con.stroke();
			}
		}
	}

	con.textBaseline = 'middle';
	con.textAlign = 'center';
	con.fillStyle = 'red';
	con.font = '24px consolas';
	con.fillText('🚶\uFE0E', player.x * PX + (PX / 2), player.y * PY + (PY / 2));
}
```

次回はプレイヤーの移動を行いたいと思います。