phina.jsでスプライトをpixi.jsを利用して描画する

この記事は、[phina.js Advent Calendar 2016](http://qiita.com/advent-calendar/2016/phinajs) の記事です。
昨日→[phina.jsとWebGLでいろいろ by daishi_hmrさん](http://github.dev7.jp/b/2016/12/22/phinajsadvcal20161218/)
明日→[phina.jsのShapeを複数まとめて画像として使う by simiraaaaさん](http://qiita.com/simiraaaa/items/7355ed8037ebd7d99bcb)

[pentamania](http://qiita.com/pentamania)さんの「[phina.jsとpixi.jsを連携させる](http://qiita.com/pentamania/items/017b2f2b55a3aff88d5f)」を参考にしております。

#はじめに
phina.jsのスプライトは十分速いのですが、弾を沢山だしたり、エフェクトばりばりーなSTGとか作ってると、若干処理速度に不安が出てきます。（特にiPhoneとか）
何か方法は…と考えてたところ、[pentamania](http://qiita.com/pentamania)さんの[記事](http://qiita.com/pentamania/items/017b2f2b55a3aff88d5f)を発見し、~~パク~~参考にさせて頂くついでに使用感をphina.js側に寄せる方向で実装を行ってみました。

主な説明は、pentamaniaさんの記事を参照して頂くとして（手抜き）、こちらの記事では、「[方法１：canvas2DのdrawImageを利用](http://qiita.com/pentamania/items/017b2f2b55a3aff88d5f#%E6%96%B9%E6%B3%95%EF%BC%91canvas2d%E3%81%AEdrawimage%E3%82%92%E5%88%A9%E7%94%A8)」を採用しております。

参考記事と同様に、pixiオブジェクトを描画するためのレイヤークラス、`phina.display.PixiLayer`を用意します。
さらにphina.js側との使用感を統一する為に、PixiLayer用にスプライト操作を行う、`phina.pixi.Sprite`を用意しています。

```JavaScript:phina.display.PixiLayer
phina.define('phina.display.PixiLayer', {
  superClass: 'phina.display.Layer',

  stage: null,
  renderer: null,

  /** 子供を 自分のCanvasRenderer で描画するか */
  renderChildBySelf: true,

  init: function(options) {
    this.superInit();
    options = (options || {}).$safe({
      width: 640,
      height: 640
    });

    this.stage = new PIXI.Container();
    this.renderer = PIXI.autoDetectRenderer(options.width, options.height, {transparent: true});

    this.on('enterframe', function() {
      this.renderer.render(this.stage);
    });
  },

  draw: function(canvas) {
    var domElement = this.renderer.view;
    canvas.context.drawImage(domElement, 0, 0, domElement.width, domElement.height);
  },

  addChild: function(child){
    if (child.pixiObject) {
      this.stage.addChild(child.pixiObject);
    }
    return phina.display.Layer.prototype.addChild.apply(this, arguments);
  },

  removeChild: function(child){
    if (child.pixiObject) {
      this.stage.removeChild(child.pixiObject);
    }
    return phina.display.Layer.prototype.removeChild.apply(this, arguments);
  }
});
```

```JavaScript:phina.pixi.Sprite

phina.pixi = phina.pixi || {};

phina.define('phina.pixi.Sprite', {
  superClass: 'phina.display.Sprite',

  pixiObject: null,

  init: function(image, width, height) {
    this.superInit(image, width, height);

    this.pixiObject = new PIXI.Sprite.fromImage(this.image.src);
    this.pixiObject.anchor.set(0.5, 0.5);

    this.pixiObject.texture.baseTexture.width = this.image.domElement.width;
    this.pixiObject.texture.baseTexture.height = this.image.domElement.height;

    this.on('enterframe', function(e) {
      // Elementと必要な情報を同期
      this.pixiObject.position.set(this.x, this.y);
      this.pixiObject.rotation = this.rotation.toRadian();
      this.pixiObject.scale.set(this.scaleX, this.scaleY);
      this.pixiObject.anchor.set(this.originX, this.originY);
      this.pixiObject.alpha = this.alpha;
    });
  },

  setFrameIndex: function(index, width, height) {
    phina.display.Sprite.prototype.setFrameIndex.apply(this, arguments);
    this.pixiObject.texture.frame = new PIXI.Rectangle(this.srcRect.x, this.srcRect.y, this.srcRect.width, this.srcRect.height);
    return this;
  },

  setImage: function(newImage, width, height) {
    this._image = newImage;
    this.pixiObject = new PIXI.Sprite.fromImage(newImage.src);
    this.pixiObject.texture.baseTexture.width = this.image.domElement.width;
    this.pixiObject.texture.baseTexture.height = this.image.domElement.height;
    this.pixiObject.texture.frame = new PIXI.Rectangle(this.srcRect.x, this.srcRect.y, this.srcRect.width, this.srcRect.height);
    return this;
  },

  setPosition: function(x, y) {
    this.pixiObject.position.set(x, y);
    return phina.display.Sprite.prototype.setPosition.apply(this, arguments);
  },

  setOrigin: function(x, y) {
    this.pixiObject.anchor.set(x, y);
    return phina.display.Sprite.prototype.setOrigin.apply(this, arguments);
  },

  setScale: function(x, y) {
    y = y || x;
    this.pixiObject.scale.set(x, y);
    return phina.display.Sprite.prototype.setOrigin.apply(this, arguments);
  },
});
```

コードはちょっと長いですが、やっている事は至極単純です。
`phina.pixi.Sprite`はメンバとして、pixiのオブジェクトを保持しており、`phina.display.Layer`には`phina.pixi.Sprite`を追加された際に、pixiのオブジェクトを自身のレンダラに追加する処理を加えています。
また、`phina.pixi.Sprite`にSpriteに対する操作をphina.display.Spriteと同様に行えるようにして、従来のSpriteと同様に違和感無く使用出来るようにしてあります（とりあえずサンプルに必要な機能だけですが）

#パフォーマンスは？

[無改造Spriteサンプル](http://minimo.github.io/phina.js_advent_2016_1/index_phina.html)
[pixi.jsのSpriteで表示するサンプル](http://minimo.github.io/phina.js_advent_2016_1/index.html)
参考記事と同様の結果となりますが、ほぼ４～５倍程度のSprite量でも60FPSを保てる様になりました。

#おしまい
ほぼ人様の記事のただ乗り見たいな感じで非常に恐縮ですが、大量に高速でスプライトを表示したい場合、こんな方法もあると言う事で参考にして頂ければと思います。

サンプルのソースはこちら[[github](https://github.com/minimo/phina.js_advent_2016_1)]