phina.jsとpixi.jsを連携させる

*追記*
ここではどちらかと言うと理論の話をしています。
実利用の際は[minimoさんの記事](http://qiita.com/minimo/items/a0aa17d05172b5bfca70)のほうが参考になるかと思います。
*追記終わり*

phina.jsというHTML5ゲーム用フレームワークを個人的に愛用していますが、今のところ（正式には）webGLに対応していないので、webGLを使いたい場合、頑張って自作するか、別の対応ライブラリを併用する必要があります。

canvas2D(phina単体)でもそこそこパフォーマンスがありますが、モバイルでの処理速度を上げたかったのと、単純な興味からpixi.jsとの連携を試してみました。

#### 使用バージョン
phina.js v0.2.0
pixi.js v3.0.11

## 方法１：canvas2DのdrawImageを利用
pixiオブジェクトを描画するためのレイヤークラス、phina.display.PixiLayerを用意します。

```js:PixiLayer.js
phina.define('phina.display.PixiLayer', {
  superClass: 'phina.display.DisplayElement',

  stage: null,
  renderer: null,

  init: function(options) {
    this.superInit();

    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(pixiObject){
    this.stage.addChild(pixiObject);
    return this;
  },

  removeChild: function(pixiObject){
    this.stage.removeChild(pixiObject);
    return this;
  }
});
```

phinaにはthree.js連携用クラス（phina.display.ThreeLayer）がありますが、その実装をほぼ真似した方法です。
内部で描画をpixiのwebGLレンダラーに任せ、結果だけをcanvas2DのdrawImageメソッドでphinaのメインcanvasに転写しています。
addChild（とremoveChild）はpixi側のcanvasに追加するよう、オーバーライドします。

アプリ土台はあくまでcanvas2Dのままなので、PixiLiayer以外にはphinaの描画系クラスをそのまま引き続き使えます。

### 使い方
上記のレイヤークラスを作って、それをphinaのSceneオブジェクトに追加します。
スプライトを追加するには、レイヤーにpixi.SpriteオブジェクトをaddChildすればOK。
optionでwidth/heightを与えて、レイヤーとSceneを同じ大きさにするようにします。

```js
phina.define('MainScene', {
  superClass: 'phina.display.DisplayScene',

  init: function(options) {

    this.superInit(options);

    // レイヤーの作成・追加(width, heightを設定したオプションを渡すこと)
    this.pixiLayer = phina.display.PixiLayer(options).addChildTo(this);

    // レイヤーにpixiのスプライト追加
    var chara = new PIXI.Sprite.fromImage(imageSrc);
    chara.position.set(100, 100);
    this.pixiLayer.addChild(chara);
  }
});
```

**[実行例](http://goo.gl/aNI7MI)**

##  方法２: pixiのwebGLRendererをそのまま使う
直接webGLRendererを使ったほうがパフォーマンス限界は上がるのでは？と思い、rendererそのものを取り替える方法も考えてみました。
この場合、アプリ生成地点でpixiのrendererを設定しなくてはならないため、専用のアプリコア生成クラス、Sceneクラスを作る必要があります。

```PixiApp.js
/* 専用コアクラス */
phina.define('phina.display.PixiApp', {
     superClass: 'phina.display.DomApp',

     renderer: null,
     domElement: null,

     init: function(options) {
          var options = (options || {}).$safe(phina.display.CanvasApp.defaults);
          this.renderer = PIXI.autoDetectRenderer(options.width, options.height);
          options.domElement  = this.renderer.view;
          
          this.superInit(options);

          this.canvas = phina.graphics.Canvas();
          this.canvas.canvas = this.canvas.domElement = this.domElement;
          this.canvas.setSize(options.width, options.height);              　　                  　　　
          document.body.appendChild(this.domElement);
          if (options.fit) this.canvas.fitScreen(true);

          this.replaceScene(phina.app.PixiScene());
     },

     _draw: function(){
          if (this.currentScene) this.renderer.render(this.currentScene.stage);
     }
});
```

```PixiScene.js
/* 専用シーンクラス */
phina.define('phina.app.PixiScene', {
  superClass: 'phina.app.Element',
  stage: null,

  init: function(options) {
    this.superInit();

    var options = ({}).$safe(options, phina.app.Object2D.defaults);

    this.stage = new PIXI.Container();
    this.width = options.width;
    this.height = options.height;
    if (options.backgroundColor) {
      this.on('enter', function() {
        this.app.renderer.backgroundColor = options.backgroundColor;
      });
    }
  },

  addChild: function(pixiObject){
    this.stage.addChild(pixiObject);
  },

  removeChild: function(pixiObject){
    this.stage.removeChild(pixiObject);
  }
});
```

本来canvas2D用rendererとcanvasを立ち上げるところをPIXIのrendererを設定するようにします。
継承元のDomAppクラスは毎フレーム_drawメソッドを実行するので、ここに描画更新処理を書きます。
描画するScene（currentScene）は素のphina.jsと同様にreplaceSceneメソッドを使って設定できますが、ここで渡すSceneはPixiSceneクラスを継承している必要があります。

（this.canvasあたりで何かややこしいことをしていますが、これはphina.graphics.CanvasクラスのもつsetSize, fitScreenというメソッドを引き継ぐためです。）

PixiSceneのほうでは先程のpixiLayerクラスと同様、addChild等をオーバーライドします。

### 使い方
コア作成方法はphinaのオリジナルクラスとほぼ同じです。（オプションは一部機能しませんが...）
PixiSceneにはpixi.js由来の描画オブジェクトしか追加できませんが、それ以外は通常のphinaシーンとそれほど変わらない...はずです。
サウンドマネージャーやローダーといった描画に無関係のphinaクラスは引き続き使えますし、input系メソッド(app.pointer.getPointing(), app.keyboard.getKeyDown()など)もそのまま使えます。

ソースが長くなってしまったので、以下リンク先でコード詳細をご確認ください。

**[実行例](http://goo.gl/b6A0pf)**

## パフォーマンス・テスト
![pixiApp-test.png](https://qiita-image-store.s3.amazonaws.com/0/81823/f2b9105b-b626-655f-0aaf-c1ff076d0d3e.png)

最後に具体的な使用例を兼ねて、それぞれのパフォーマンスをチェックしてみます。
回転・移動する64x64のスプライトを大量表示します。

1. [phina.display.Spriteで描画（phina.js単体）](https://pentamania.github.io/phina-pixi/performance-test/phinaSprite.html)
2. [PixiLayerに描画（pixi用カスタムレイヤークラス）](https://pentamania.github.io/phina-pixi/performance-test/pixiLayer.html)
3. [PixiApp&PixiSceneで描画　(renderer丸ごと取っ替え)](https://pentamania.github.io/phina-pixi/performance-test/pixiApp.html)

ちなみに自分の環境（Windows7 + intel HD Graphics 3000（オンボードGPU）＋ corei5(3.3GHz) +Google Chrome）だと、
１では1000オブジェクト前後からすでにFPSが60を割り始めますが、
２では7000オブジェクトあたりまで60FPSを保てました。
３では7500オブジェクト前後と、２より若干良くなっていますが、実装が面倒くさかった割に思ったよりも伸びませんでした...。
（２と３の処理量はGPUに大きく依存すると思います）

いずれも一長一短ありますが、弾幕STGなど高パフォーマンスを要するゲーム作りの際に参考にしてみてください。