ローグライクゲームって何?
ローグライクといわれてもピンとこない方が多いと思いますが、不思議のダンジョン系といればイメージしやすいでしょうか?トライするたびにダンジョンマップが違い、アイテムも使ってみないとわからない。死んでしまえばおしまいというゲームです。その不思議のダンジョン系のもとになったゲームがローグというゲームであり、似たようなシステムのゲームをローグライクゲームと呼びます。今でも検索すればいっぱいネットに転がっていますので、興味がある方は遊んでみてください。
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>
最後に
次回、中身の解説をしていきたいと思います。と自分に課題を与えておきます。
では!