JavaScriptとRot.jsでローグライクゲームをつくってみる(1)

Pocket
LINEで送る

ローグライクゲームって何?

ローグライクといわれてもピンとこない方が多いと思いますが、不思議のダンジョン系といればイメージしやすいでしょうか?トライするたびにダンジョンマップが違い、アイテムも使ってみないとわからない。死んでしまえばおしまいというゲームです。その不思議のダンジョン系のもとになったゲームがローグというゲームであり、似たようなシステムのゲームをローグライクゲームと呼びます。今でも検索すればいっぱいネットに転がっていますので、興味がある方は遊んでみてください。

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:敵
@:自分
*:宝箱
移動:↑→↓←
調べる:スペースキー(宝箱に重なって押してください)

メッセージ:{{ message }}

ソースコード

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>

最後に

次回、中身の解説をしていきたいと思います。と自分に課題を与えておきます。
では!

Pocket
LINEで送る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください