Pixi.js でゲームを作ってみる vol.2

この記事では Pixi.js を使ってブラウザで動く簡単な2Dゲームを作ってみたいと思います。
爆弾を設置して敵を倒す、某爆弾男ゲームのようなシステムを作ってみようかと。 あくまで簡易的な。

Pixi.js でゲームを作ってみる vol.1 の続きです。


OUTLINE

  • キーボードで操作する
    • キャラクターの方向転換
    • キャラクターの移動
  • キャラクターの移動を制限する

キーボードで操作する

キャラクターの方向転換

addEventListenerkeydown のイベントを取得し、対応した方向へテクスチャの向きを変更させてみました。
PIXI.extras.AnimatedSprite のテクスチャは、 インスタンス変数 textures で変更できます。

index.js
  //   ・
  //   ・
  //   ・

  /*
   * jsonのパス、読み込み完了の関数を与える
   */
  loader
    .add('sprite', '../_assets/img/sprite.json')
    .once('complete', function(){

      var
      /**
       * テクスチャの配列(Left: 0, Back: 1, Right: 2, Front: 3)
       */
      ttCharacter = [],
      /**
       * キャラクター要素
       */
      elmCharacter,
      i, j, k;

      for (i = 0; i < 4; i++) {
        // 4方向
        ttCharacter[i] = [];

        for (j = 0; j < 4; j++) {
          // 4フレーム
          var frame = j === 0 ? 0:
                      j === 1 ? 1:
                      j === 2 ? 0:
                      j === 3 ? 2:
                      0;
          ttCharacter[i].push(PIXI.Texture.fromFrame('character-' + i + '-' + frame));
        }
      }

      /**
       * キャラクターの生成(Left: 0, Back: 1, Right: 2, Front: 3)
       */
      elmCharacter = new PIXI.extras.AnimatedSprite(ttCharacter[3]);
      elmCharacter.play();
      elmCharacter.animationSpeed = 0.1;

      /**
       * キャラクターの基準点、位置を設定
       */
      elmCharacter.anchor.set(0.5);
      elmCharacter.position.set(640 / 2, 360 / 2);

      stage.addChild(elmCharacter);

      /*
       * キーボードが押されたイベント
       */
      document.addEventListener('keydown', handleKeyDown);

      function handleKeyDown(e){
        var key = e.key;

        /**
         * キーボードの矢印キーに対応したテクスチャに変更
         */
        switch (key) {

          case 'ArrowLeft':
            console.log('left');
            elmCharacter.textures = ttCharacter[0];
            break;

          case 'ArrowUp':
            console.log('up');
            elmCharacter.textures = ttCharacter[1];
            break;

          case 'ArrowRight':
            console.log('right');
            elmCharacter.textures = ttCharacter[2];
            break;

          case 'ArrowDown':
            console.log('down');
            elmCharacter.textures = ttCharacter[3];
            break;

          default:
            break;
        }
      }
  });

  /*
   * 読み込む
   */
  loader.load();

動くんだけど、テクスチャの切り替えがやたら遅い。。。
textures で別のテクスチャを指定してから アニメーションの play() が1周終わるまで、テクスチャ切り替えはお預けのよう。。。


なので別の案を。 新たに PIXI.Container を作って、4種類のアニメーションを全部ぶっこんでおく。 方向切り替えのタイミングで該当するアニメーションのみ visible = true で表示、 その他は false で非表示にしておく。

index.js
  //   ・
  //   ・
  //   ・

  /*
   * jsonのパス、読み込み完了の関数を与える
   */
  loader
    .add('sprite', '../_assets/img/sprite.json')
    .once('complete', function(){

      var
      /**
       * テクスチャの配列(Left: 0, Back: 1, Right: 2, Front: 3)
       */
      ttCharacter = [],
      /**
       * キャラクターアニメーションの配列
       */
      elmAnimationCharacter = [],
      /**
       * キャラクター要素
       */
      elmCharacter = new PIXI.Container(),
      i, j, k;

      for (i = 0; i < 4; i++) {
        // 4方向
        ttCharacter[i] = [];

        for (j = 0; j < 4; j++) {
          // 4フレーム
          var frame = j === 0 ? 0:
                      j === 1 ? 1:
                      j === 2 ? 0:
                      j === 3 ? 2:
                      0;
          ttCharacter[i].push(PIXI.Texture.fromFrame('character-' + i + '-' + frame));
        }
      }

      for (k = 0; k < 4; k++) {
        elmAnimationCharacter.push(new PIXI.extras.AnimatedSprite(ttCharacter[k]));
        elmAnimationCharacter[k].play();
        elmAnimationCharacter[k].animationSpeed = 0.1;
        elmAnimationCharacter[k].anchor.set(0.5);
        elmAnimationCharacter[k].visible = false;
        elmCharacter.addChild(elmAnimationCharacter[k]);
      }

      /**
       * 正面(Front: 3)を表示する
       */
      elmAnimationCharacter[3].visible = true;

      elmCharacter.position.set(640 / 3, 360 / 2);

      stage.addChild(elmCharacter);

      /**
       * キーボードが押されたイベント
       */
      document.addEventListener('keydown', handleKeyDown);

      function handleKeyDown(e){
        var
        key = e.key,
        i;

        /**
         * 全てのアニメーションを非表示
         */
        for (i = 0; i < 4; i++) {
          elmAnimationCharacter[i].visible = false;
        }

        /**
         * 矢印キーの方向のアニメーションのみ表示
         */
        switch (key) {
          case 'ArrowLeft':
            console.log('left');
            elmAnimationCharacter[0].visible = true;
            break;

          case 'ArrowUp':
            console.log('up');
            elmAnimationCharacter[1].visible = true;
            break;

          case 'ArrowRight':
            console.log('right');
            elmAnimationCharacter[2].visible = true;
            break;

          case 'ArrowDown':
            console.log('down');
            elmAnimationCharacter[3].visible = true;
            break;

          default:
            break;
        }
      }
  });

  /*
   * 読み込む
   */
  loader.load();

これでスムーズに切り替わるようになりました!

続きますよ( ˘⊖˘)


キャラクターの移動

単純に押された方向にキャラクターを移動させるだけなら下記のように書けます。 押された方向に PIXI.Container の position をズラすだけです。

index.js
  //   ・
  //   ・
  //   ・

        /**
         * 矢印キーの方向に移動
         * 矢印キーの方向のアニメーションのみ表示
         */
        switch (key) {
          case 'ArrowLeft':
            console.log('left');
            elmAnimationCharacter[0].visible = true;
            elmCharacter.position.x -= 16; // 左に16pxズラす
            break;

          case 'ArrowUp':
            console.log('up');
            elmAnimationCharacter[1].visible = true;
            elmCharacter.position.y -= 16; // 上に16pxズラす
            break;

          case 'ArrowRight':
            console.log('right');
            elmAnimationCharacter[2].visible = true;
            elmCharacter.position.x += 16; // 右に16pxズラす
            break;

          case 'ArrowDown':
            console.log('down');
            elmAnimationCharacter[3].visible = true;
            elmCharacter.position.y += 16; // 下に16pxズラす
            break;

          default:
            break;
        }

  //   ・
  //   ・
  //   ・

が、キーボードを押しっぱなしにした場合に、 イベント keydown が起こるタイミングがゲームとして致命的な感じです。 テキストエディタでカーソルを移動させるときのように、ピッ……ピッピッピッピッ……という感じで、初回の入力後に若干の間が生じた後、ようやく連続っぽい動作をします。

そこで、 こちらのブログを参考にしました。
初心者がモチベーション上げながらプログラミングをしてシューティング(っぽい)ゲームを1本作る! – Λlisue’s blog
keydownkeyup のイベントのタイミングでキー入力状態を一旦保存。 常時その入力状態をチェックするループ関数で移動を行う感じです。

index.js
  //   ・
  //   ・
  //   ・

      /**
       * キーの状態を保存(true: 押されている, false: 押されていない)
       */
      var
      keyStatus = [],
      i;
      for (i = 0; i < 4; i++) {
        keyStatus[i] = false;
      }

      /**
       * キーボードが押されたイベント
       */
      document.addEventListener('keydown', function (e){
        keyStatus[e.keyCode - 37] = true;
      }, false);
      document.addEventListener('keyup', function (e){
        keyStatus[e.keyCode - 37] = false;
      }, false);

      /*
       * checkInput 関数を定義
       */
      var checkInput = function () {
        var
        i,
        KEY_LEFT  = 0,
        KEY_UP    = 1,
        KEY_RIGHT = 2,
        KEY_DOWN  = 3,
        /**
         * 全てのアニメーションを非表示
         */
        hideAnimation = function () {
          for (i = 0; i < 4; i++) {
            elmAnimationCharacter[i].visible = false;
          }
        };

        /**
         * 矢印キーの方向に移動
         * 矢印キーの方向のアニメーションのみ表示
         */
        if (keyStatus[KEY_LEFT]) {
          hideAnimation();
          elmAnimationCharacter[0].visible = true;
          elmCharacter.position.x -= 2;
        }
        if (keyStatus[KEY_UP]) {
          hideAnimation();
          elmAnimationCharacter[1].visible = true;
          elmCharacter.position.y -= 2;
        }
        if (keyStatus[KEY_RIGHT]) {
          hideAnimation();
          elmAnimationCharacter[2].visible = true;
          elmCharacter.position.x += 2;
        }
        if (keyStatus[KEY_DOWN]) {
          hideAnimation();
          elmAnimationCharacter[3].visible = true;
          elmCharacter.position.y += 2;
        }

        requestAnimationFrame(checkInput);
      };

      checkInput();

  //   ・
  //   ・
  //   ・

遅延等なく動きもスムーズ! また上記のような方法だと、 同時押しも検出できるため斜め移動も可能です。 斜め方向だと移動速度が速く見えてしまいますが、今回は目をつむります。 スーファミでもよくあるしね。。。

DEMO vol.2

続きますよ( ˘⊖˘)


キャラクターの移動を制限する

まだゲームステージもできてないので、とりあえず画面外には行けないように制限をかけます。

index.js
  //   ・
  //   ・
  //   ・

      /*
       * checkInput 関数を定義
       */
      var checkInput = function () {
        var
        i,
        KEY_LEFT  = 0,
        KEY_UP    = 1,
        KEY_RIGHT = 2,
        KEY_DOWN  = 3,
        /**
         * 全てのアニメーションを非表示
         */
        hideAnimation = function () {
          for (i = 0; i < 4; i++) {
            elmAnimationCharacter[i].visible = false;
          }
        },
        /**
         * 移動を制限
         */
        restrictMovement = function () {
          if (elmCharacter.x - 40 <= 0) {
            elmCharacter.x = 40;
          } else if (elmCharacter.x + 40 >= 640 * 2) {
            elmCharacter.x = 640 * 2 - 40;
          }

          if (elmCharacter.y - 80 <= 0) {
            elmCharacter.y = 80;
          } else if (elmCharacter.y >= 360 * 2) {
            elmCharacter.y = 360 * 2;
          }
        };

        /**
         * 矢印キーの方向に移動
         * 矢印キーの方向のアニメーションのみ表示
         */
        if (keyStatus[KEY_LEFT]) {
          hideAnimation();
          elmAnimationCharacter[0].visible = true;
          elmCharacter.position.x -= 4;
          restrictMovement();
        }
        if (keyStatus[KEY_UP]) {
          hideAnimation();
          elmAnimationCharacter[1].visible = true;
          elmCharacter.position.y -= 4;
          restrictMovement();
        }
        if (keyStatus[KEY_RIGHT]) {
          hideAnimation();
          elmAnimationCharacter[2].visible = true;
          elmCharacter.position.x += 4;
          restrictMovement();
        }
        if (keyStatus[KEY_DOWN]) {
          hideAnimation();
          elmAnimationCharacter[3].visible = true;
          elmCharacter.position.y += 4;
          restrictMovement();
        }
        requestAnimationFrame(checkInput);
      };


  //   ・
  //   ・
  //   ・

ゲームステージを作る段階でグリッドを導入するので、その時にもうちょっとちゃんと制限かけようと思います。
あとモジュール分けたり、リファクタリングしないと汚くなってきた。。。

vol.2はここまで( ˘⊖˘)
続きは Pixi.js でゲームを作ってみる vol.3 から。

NEWER POST
OLDER POST