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

最近 Pixi.js を使う機会が多くなってきました。
WebGLを使ったビジュアルを簡単に実装できるので、しばらくの間お世話になりまくると思います。この記事では Pixi.jsを使ってブラウザで動く簡単な2Dゲームを作ってみたいと思います。
爆弾を設置して敵を倒す、某爆弾男ゲームのようなシステムを作ってみようかと。 あくまで簡易的な。

OUTLINE

  • Getting started
  • 画面を生成する
  • キャラクターを表示する
    • 画像を表示
    • スプライトシートを生成
    • スプライトシートを表示
  • アニメーションさせる

Getting started

まずはベースとなるhtml、css、jsを準備。

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>pixi-game</title>
    <link rel="stylesheet" href="./_assets/css/bundle.css">
  </head>
  <body>
    <h1>pixi-game</h1>
    <div class="stage" id="stage"></div>
    <script src="./_assets/js/bundle.js"></script>
  </body>
</html>

bundle.css
body {
  margin: 0;
}

h1 {
  position: absolute;
  width: 100%;
  top: 50%;
  font-size: 24px;
  color: #34363c;
  margin-top: -244px;
  text-align: center;
}

.stage {
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: -320px;
  margin-top: -180px;
  width: 640px;
  height: 360px;
  background: #34363c;
  overflow: hidden;
}

.stage canvas {
  display: block;
  width: 100%;
  height: auto;
}

以降、htmlとcssは基本的には変わらず。 jsのみ編集していきます。

Pixi.js は 4.3.0 を使用。 今回、JavaScriptモジュールのビルドに webpack を使用しました。 下記の記述は、 require でpixi.js も index.js にまとめてますが、外部ファイルとして使う場合は 自前のコードの前にスクリプトタグでpixi.jsを読み込ませます。

index.js
require('pixi.js'); // pixi.js 4.3.0

(function () {

  // ここにコアな処理
  // ここにコアな処理
  // ここにコアな処理

}());

アクセスするとこんな感じ。 まだCSSのスタイルが当たっているだけです。



画面を生成する

描画するための画面を生成します。(コアな処理のみ抜粋しています)

index.js
  /**
  * STEP.1 元となるコンテナを用意。画面に描画される要素は全てこの下にぶら下がる
  */
  var stage = new PIXI.Container();

  /**
   * STEP.2 描画するためのレンダラーを用意。引数は描画領域の幅、高さ、オプション
   */
  var renderer = PIXI.autoDetectRenderer(640, 360, {
    antialias:        true,     // アンチエイリアスをONに
    backgroundColor : 0x00ffd4, // 背景色
    //  transparent:      true,     // 背景を透過にしたい場合はこちらを指定
  });

  /**
   * STEP.3 #stage のDOM要素に view を追加
   */
  document.getElementById('stage').appendChild(renderer.view);

  /**
   * animation関数を定義
   */
  var animation = function () {
    // 再帰的に次のアニメーションフレームで animation関数を呼び出す
    requestAnimationFrame(animation);

    // 描画
    renderer.render(stage);
  };

  /**
   * animation関数を呼び出す
   */
  animation();

PIXI.Container は Photoshop でいうレイヤーグループのようなものでしょうか。 PIXI.DisplayObject という pixi.js で扱う基本的なオブジェクトを継承しています。 今後扱う、画像オブジェクト( PIXI.Sprite )や図形オブジェクト( PIXI.Graphics )、テキストオブジェクト( PIXI.Text )も同様です。 (正確には画像、図形は PIXI.Container を継承、 テキストは PIXI.Sprite を継承しています。)
メソッド addChild などを使って、子要素を持つことができる(このあたりがレイヤーグループっぽい)ので、

var stage = new PIXI.Container();

上記のコンテナが、最も上位に位置する PIXI.Container になります。

実行すると、 先程 CSS でグレーが当たっていた領域が、レンダラーで指定した背景色に変わっているかと思います。


続きますよ( ˘⊖˘)


キャラクターを表示する

画像を表示

キャラクターの素材を用意するため、フリーのRPG作成ツール WOLF RPGエディターグラフィック合成器 を使用しました。 8方向歩行アニメーション付きのドット絵が簡単に作れるツールです。ありがたい……。

オレンジっぽい服 + エプロンを着た女の子をジェネレート。 8方向もいらないので上下左右の4方向のみ切り出しました。最終的にはこの子に爆弾男になってもらいます。


pixi.js で画像を扱う場合、

  • PIXI.Texture を準備する
  • そのテクスチャから PIXI.Sprite を作る
  • そのスプライトを親要素となる PIXI.DisplayObject追加 する

という手順が一般かなと思います。

とりあえず正面のみ切り出した下記画像を表示させます。 画像を読み込んでテクスチャを作るには、 PIXI.Texture.fromImage('画像パス') を使います。 更に PIXI.Sprite(テクスチャ) でSpriteオブジェクトを生成します。

character_front.png

index.js
  //   ・
  //   ・
  //   ・
  // 前回の続き

  /**
   * animation関数を呼び出す
   */
  animation();

  /**
   * 画像パスを指定し、テクスチャを生成する
   */
  var ttrGirl = PIXI.Texture.fromImage('../_assets/img/character_front.png');

  /**
   * テクスチャからスプライトを生成する
   */
  var sprGirl = new PIXI.Sprite(ttrGirl);

  /**
   * スプライトを、コンテナであるstageの子要素として追加する
   */
  stage.addChild(sprGirl);

キャラクター正面の画像が表示されました!


スプライトシートを生成

使用する画像を1枚1枚 fromImage で読み込むのはダルすぎです。 pixi.js では css sprite のように、たくさんの画像を結合した1枚のスプライトシートからテクスチャを生成できます。 結合した画像の幅, 高さ, 画像ID等は JSONファイルに記述しします。

スプライトシートの生成は node.js でらくちん生成です。
使用するパッケージは spritesmith-clispritesmith-texturepacker です。

  $ npm -D install spritesmith-cli spritesmith-texturepacker

まず、 spritesmith の設定ファイルを作成。 細かな設定については spritesmith-cli に。

.spritesmith.js
'use strict';

var util = require('util');

module.exports = [
  {
    src: './app/develop/img/*.{png,gif,jpg}', // 元となる画像
    destImage: './app/public/_assets/img/sprite.png', // 生成されるスプライト画像
    destCSS: './app/public/_assets/img/sprite.json', // 生成されるスプライト情報のJSONファイル
    cssTemplate: require('spritesmith-texturepacker'), // 使用するテンプレート
    padding: 4, // 個々の画像の間隔[px]
    algorithmOpts: { sort: false }, // 画像の並べ方
  }
];

下記コマンドを叩けば、スプライト画像とそのJSONファイルができあがります。 –rc は設定ファイルのパスを変えるオプション。

  $ spritesmith --rc ./config/.spritesmith.js

ドット絵のグラフィック合成器が吐き出した画像のままだとスプライトシートの情報が無いので、メンドいですが一度バラバラにし spritesmith でくっつけます。 個々の元ファイル名がそのまま画像のIDになるので注意。 あとでfor文で回しやすいように、 character-{方向}-{フレーム番号}.png というファイルネームです。 (方向は、Left: 0, Back: 1, Right: 2, Front: 3)


生成されたものがこちら。並び順は結構バラつきますが位置情報はJSONで持ってるので問題ないです。


同様に生成されたJSONはこんな感じです。

sprite.json
{
    "frames": {
        "character-0-0": {
            "frame": {
                "x": 84,
                "y": 0,
                "w": 80,
                "h": 88
            },
            "rotated": false,
            "trimmed": false,
            "spriteSourceSize": {
                "x": 0,
                "y": 0,
                "w": 80,
                "h": 88
            },
            "sourceSize": {
                "w": 80,
                "h": 88
            }
        },
      
      
      
    },
    "meta": {
        "app": "https://github.com/Ensighten/spritesmith",
        "image": "sprite.png",
        "format": "RGBA8888",
        "size": {
            "w": 332,
            "h": 272
        },
        "scale": 1
    }
}

スプライトシートを表示

PIXI.loaders.Loader() でローダーを生成、 スプライトシートのJSONパスを与えます。 PIXI.Texture.fromImage('画像パス') ではなく PIXI.Texture.fromFrame(元画像の拡張子なしファイル名) でテクスチャを生成できます。

PIXI.DisplayObject のインスタンス変数 x, y でオブジェクトの位置を変更できるので、見やすい位置にしてみました。 position.set(x, y) で設定もできます。

index.js
  //   ・
  //   ・
  //   ・
  // 前回の続き

  /**
   * animation関数を呼び出す
   */
  animation();

  /**
   * ローダーを生成
   */
  var loader = new PIXI.loaders.Loader();

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

      /**
       * 画像IDを指定し、テクスチャを作成する
       */
      var ttCharacterBack = PIXI.Texture.fromFrame('character-1-0');
      var ttCharacterLeft = PIXI.Texture.fromFrame('character-0-0');

      /**
       * テクスチャからスプライトを作成する
       */
      var sprCharacterBack = new PIXI.Sprite(ttCharacterBack);
      var sprCharacterLeft = new PIXI.Sprite(ttCharacterLeft);

      /**
       * スプライトの位置を指定する
       */
      sprCharacterBack.position.set(100, 100);
      sprCharacterLeft.position.set(300, 200);

      /**
       * スプライトを、コンテナであるstageの子要素として追加する
       */
      stage.addChild(sprCharacterBack, sprCharacterLeft);
  });

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

後ろ向きと左向きが表示されました。


続きますよ( ˘⊖˘)


アニメーションさせる

PIXI.extras.AnimatedSprite にテクスチャを配列で渡すと、アニメーションになります。 (version 4.2.0 から PIXI.extras.MovieClip がリネームされて PIXI.extras.AnimatedSprite になりました)
例えば正面向きの素材は3種類ですが、 テクスチャを character-3-0(ニュートラル) → character-3-1(右足あげ) → character-3-0(ニュートラル) → character-3-2(左足あげ) で表示させると一つのアニメーションが完成します。 あとはループするのでOK。

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

      var
      /**
       * テクスチャの配列(Left: 0, Back: 1, Right: 2, Front: 3)
       */
      ttCharacter = [],
      /**
       * アニメーション要素の配列
       */
      mvCharacter = [],
      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++) {
        /**
         * アニメーション要素の生成
         */
        mvCharacter.push(new PIXI.extras.AnimatedSprite(ttCharacter[k]));

        /**
         * アニメーションスタート
         */
        mvCharacter[k].play();

        /**
         * スピード設定
         */
        mvCharacter[k].animationSpeed = 0.1;

      }

      mvCharacter[0].position.set(130, 130);
      mvCharacter[1].position.set(230, 130);
      mvCharacter[2].position.set(330, 130);
      mvCharacter[3].position.set(430, 130);

      stage.addChild(mvCharacter[0], mvCharacter[1], mvCharacter[2], mvCharacter[3]);

一気にゲームのキャラっぽくなりました!


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

NEWER POST
OLDER POST