Edited at
phina.jsDay 19

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

More than 1 year has passed since last update.

この記事は、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.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);
}
});



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サンプル

pixi.jsのSpriteで表示するサンプル

参考記事と同様の結果となりますが、ほぼ4~5倍程度のSprite量でも60FPSを保てる様になりました。


おしまい

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

サンプルのソースはこちら[github]