この記事は、phina.js Advent Calendar 2016 の記事です。
昨日→phina.jsとWebGLでいろいろ by daishi_hmrさん
明日→phina.jsのShapeを複数まとめて画像として使う by simiraaaaさん
pentamaniaさんの「phina.jsとpixi.jsを連携させる」を参考にしております。
#はじめに
phina.jsのスプライトは十分速いのですが、弾を沢山だしたり、エフェクトばりばりーなSTGとか作ってると、若干処理速度に不安が出てきます。(特にiPhoneとか)
何か方法は…と考えてたところ、pentamaniaさんの記事を発見し、パク参考にさせて頂くついでに使用感をphina.js側に寄せる方向で実装を行ってみました。
主な説明は、pentamaniaさんの記事を参照して頂くとして(手抜き)、こちらの記事では、「方法1:canvas2DのdrawImageを利用」を採用しております。
参考記事と同様に、pixiオブジェクトを描画するためのレイヤークラス、phina.display.PixiLayer
を用意します。
さらにphina.js側との使用感を統一する為に、PixiLayer用にスプライト操作を行う、phina.pixi.Sprite
を用意しています。
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);
}
});
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サンプル
pixi.jsのSpriteで表示するサンプル
参考記事と同様の結果となりますが、ほぼ4~5倍程度のSprite量でも60FPSを保てる様になりました。
#おしまい
ほぼ人様の記事のただ乗り見たいな感じで非常に恐縮ですが、大量に高速でスプライトを表示したい場合、こんな方法もあると言う事で参考にして頂ければと思います。
サンプルのソースはこちら[github]