ローグライクゲームって何?
ローグライクといわれてもピンとこない方が多いと思いますが、不思議のダンジョン系といればイメージしやすいでしょうか?トライするたびにダンジョンマップが違い、アイテムも使ってみないとわからない。死んでしまえばおしまいというゲームです。その不思議のダンジョン系のもとになったゲームがローグというゲームであり、似たようなシステムのゲームをローグライクゲームと呼びます。今でも検索すればいっぱいネットに転がっていますので、興味がある方は遊んでみてください。
RPGの歴史
ローグが開発された時期(1980年頃、不思議ダンジョンのデビューより10年以上前!)というのはコンピュータRPG黎明期で、代表的な作品が3つあります。
ローグ UNIX
ウルティマ Apple II
ウィザードリィ Apple II
ローグは不思議ダンジョン、ウルティマは2Dマップ見下ろし型、ウィザードリィはパーティー制で3Dダンジョンを探索と、すでにこの時期にコンピュータRPGの基本的スタイルが存在していたというのが驚きですよね!
実はコンピュータRPGの前にテーブルトークRPGという、コンピュータではなく人間が進行を務めるゲームもありました(今でもあります)。その説明は割愛しますが、RPGはほぼ半世紀前から存在していたんです。でも、ロールプレイングゲームとは、その役になりきってプレイするゲーム(要はいい大人が魔法使いごっことかするわけですよ)のことなので、コンピュータRPGはちょっとごっこ遊び感が薄れている気はしますけど・・・。
ゲームを作ろうと思ったきっかけ
最近JavaScriptに興味をもちまして、ライブラリをいろいろ調べていたところたまたまrot.jsを見つけました。ただ日本語でrot.jsの詳細を説明しているサイトが見つからなかったので、私の勉強の記録もかねて紹介したいと思った次第です。
ちなみに不思議ダンジョン系と聞いて、グラフィカルなゲームをイメージされる方がほとんどだと思いますが、ローグはUNIXで開発されたものですので、自分や敵、アイテム、壁、通路などすべて文字です!!
とりあえず実物を!
ローグライクゲームエンジンのrot.jsを使います。またメッセージなんかはvue.jsを使って表示しようかと思います。
今回はチュートリアルの内容をほぼそのまま張り付けただけですので、オリジナリティは全くありません。何回かに分けて機能追加していければなーと思っています。
ルール:敵に捕まる前に宝箱に入った宝物を見つけてください。
P:敵
@:自分
*:宝箱
移動:↑→↓←
調べる:スペースキー(宝箱に重なって押してください)
ソースコード
game.txtとか適当にテキストファイルを作って、下記を張り付けて、.txtを.htmlに変更して保存してください。
開けば普通に動くと思います。(wクリックで全選択できます。)
<!doctype html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/rot-js@2.0.3/dist/rot.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app1">メッセージ:{{ message }}</div>
<div id="gamediv"></div>
<script>
//vueの設定
var app1 = new Vue({
el: '#app1',
data: {
message: "開始!"
}
})
//rot.jsの設定
var Game = {
display: null,
map: {},
engine: null,
player: null,
pedro: null,
ananas: null,
init: function () {
this.display = new ROT.Display({
spacing: 1.1
});
var gamediv = document.getElementById('gamediv');
gamediv.appendChild(this.display.getContainer());
this._generateMap();
var scheduler = new ROT.Scheduler.Simple();
scheduler.add(this.player, true);
scheduler.add(this.pedro, true);
this.engine = new ROT.Engine(scheduler);
this.engine.start();
},
_generateMap: function () {
var digger = new ROT.Map.Digger();
var freeCells = [];
var digCallback = function (x, y, value) {
if (value) {
return;
}
var key = x + "," + y;
this.map[key] = ".";
freeCells.push(key);
};
digger.create(digCallback.bind(this));
this._generateBoxes(freeCells);
this._drawWholeMap();
this.player = this._createBeing(Player, freeCells);
this.pedro = this._createBeing(Pedro, freeCells);
},
_createBeing: function (what, freeCells) {
var index = Math.floor(ROT.RNG.getUniform() * freeCells.length);
var key = freeCells.splice(index, 1)[0];
var parts = key.split(",");
var x = parseInt(parts[0]);
var y = parseInt(parts[1]);
return new what(x, y);
},
_generateBoxes: function (freeCells) {
for (var i = 0; i < 10; i++) {
var index = Math.floor(ROT.RNG.getUniform() * freeCells.length);
var key = freeCells.splice(index, 1)[0];
this.map[key] = "*";
if (!i) {
this.ananas = key;
}
/* first box
contains an ananas */
}
},
_drawWholeMap: function () {
for (var key in this.map) {
var parts = key.split(",");
var x = parseInt(parts[0]);
var y = parseInt(parts[1]);
this.display.draw(x, y, this.map[key]);
}
}
};
var Player = function (x, y) {
this._x = x;
this._y = y;
this._draw();
};
Player.prototype.getSpeed = function () {
return 100;
};
Player.prototype.getX = function () {
return this._x;
};
Player.prototype.getY = function () {
return this._y;
};
Player.prototype.act = function () {
Game.engine.lock();
window.addEventListener("keydown", this);
};
Player.prototype.handleEvent = function (e) {
var keyCode = e.keyCode;
if (keyCode == 13 || keyCode == 32) {
this._checkBox();
return;
}
var keyMap = {};
keyMap[38] = 0;
keyMap[33] = 1;
keyMap[39] = 2;
keyMap[34] = 3;
keyMap[40] = 4;
keyMap[35] = 5;
keyMap[37] = 6;
keyMap[36] = 7; /* one of numpad directions? */
if (!(keyCode in keyMap)) {
return;
} /* is there a free space? */
var dir = ROT.DIRS[8][keyMap[keyCode]];
var newX = this._x + dir[0];
var newY = this._y + dir[1];
var newKey = newX + "," + newY;
if (!(newKey in Game.map)) {
return;
}
Game.display.draw(this._x, this._y, Game.map[this._x + "," + this._y]);
this._x = newX;
this._y = newY;
this._draw();
window.removeEventListener("keydown", this);
Game.engine.unlock();
};
Player.prototype._draw = function () {
Game.display.draw(this._x, this._y, "@", "#ff0");
};
Player.prototype._checkBox = function () {
var key = this._x + "," + this._y;
if (Game.map[key] != "*") {
app1.message = "ここには何もありませ!!";
} else if (key == Game.ananas) {
app1.message = "宝物が見つかりました。あなたの勝ちです!!";
Game.engine.lock();
window.removeEventListener("keydown", this);
} else {
app1.message = "空っぽでした・・・(・ε・)";
}
};
var Pedro = function (x, y) {
this._x = x;
this._y = y;
this._draw();
};
Pedro.prototype.getSpeed = function () {
return 100;
};
Pedro.prototype.act = function () {
var x = Game.player.getX();
var y = Game.player.getY();
var passableCallback = function (x, y) {
return x + "," + y in Game.map;
};
var astar = new ROT.Path.AStar(x, y, passableCallback, {
topology: 4
});
var path = [];
var pathCallback = function (x, y) {
path.push([x, y]);
};
astar.compute(this._x, this._y, pathCallback);
path.shift();
console.log(path.length);
mes = path.length;
if (path.length <= 1) {
Game.engine.lock();
app1.message = "捕まってしまいました。残念!!";
console.log(mes);
} else {
x = path[0][0];
y = path[0][1];
Game.display.draw(this._x, this._y, Game.map[this._x + "," + this._y]);
this._x = x;
this._y = y;
this._draw();
}
};
Pedro.prototype._draw = function () {
Game.display.draw(this._x, this._y, "P", "red");
};
Game.init();
</script>
</body>
</html>
最後に
次回、中身の解説をしていきたいと思います。と自分に課題を与えておきます。
では!
