8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-07-24

追記
ここではどちらかと言うと理論の話をしています。
実利用の際はminimoさんの記事のほうが参考になるかと思います。
追記終わり

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

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

使用バージョン

phina.js v0.2.0
pixi.js v3.0.11

方法1:canvas2DのdrawImageを利用

pixiオブジェクトを描画するためのレイヤークラス、phina.display.PixiLayerを用意します。

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を同じ大きさにするようにします。

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);
  }
});

実行例

方法2: 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()など)もそのまま使えます。

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

実行例

パフォーマンス・テスト

pixiApp-test.png

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

  1. phina.display.Spriteで描画(phina.js単体)
  2. PixiLayerに描画(pixi用カスタムレイヤークラス)
  3. PixiApp&PixiSceneで描画 (renderer丸ごと取っ替え)

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

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

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?