追記
ここではどちらかと言うと理論の話をしています。
実利用の際は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を用意します。
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クラスを作る必要があります。
/* 専用コアクラス */
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);
}
});
/* 専用シーンクラス */
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()など)もそのまま使えます。
ソースが長くなってしまったので、以下リンク先でコード詳細をご確認ください。
パフォーマンス・テスト
最後に具体的な使用例を兼ねて、それぞれのパフォーマンスをチェックしてみます。
回転・移動する64x64のスプライトを大量表示します。
- phina.display.Spriteで描画(phina.js単体)
- PixiLayerに描画(pixi用カスタムレイヤークラス)
- 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など高パフォーマンスを要するゲーム作りの際に参考にしてみてください。