シーングラフ
PixiJSは、フレームごとにシーングラフを更新してレンダリングしています。シーングラフの内容と、プロジェクトの開発にどのように影響するかについて説明します。以前にゲームを開発したことがある場合は、これはすべて非常になじみ深いものですが、HTMLやDOMから来た場合は、レンダリングできる特定の種類のオブジェクトについて説明する前に理解しておく価値があります。
シーングラフはツリーです
シーングラフのルートノードは、アプリケーションによって管理され、`app.stage` で参照されるコンテナです。スプライトまたはその他のレンダリング可能なオブジェクトをステージの子として追加すると、シーングラフに追加され、レンダリングされてインタラクティブになります。PixiJSの`Container`にも子を持つことができ、より複雑なシーンを構築するにつれて、アプリのステージをルートとする親子関係のツリーが形成されます。
(プロジェクトを探索するのに役立つツールは、Chrome用のPixi.js devtoolsプラグインです。実行中にシーングラフをリアルタイムで表示および操作できます!)
親と子
親が移動すると、子も移動します。親が回転すると、子も回転します。親を非表示にすると、子も非表示になります。複数のスプライトで構成されるゲームオブジェクトがある場合は、コンテナの下にまとめて、ワールド内の単一のオブジェクトとして扱って、まとめて移動および回転させることができます。
PixiJSは、フレームごとに、ルートからすべての子を通ってリーフまでシーングラフを実行し、各オブジェクトの最終的な位置、回転、可視性、透明度などを計算します。親のアルファが0.5(50%透明)に設定されている場合、すべての子も50%透明で開始されます。子が0.5アルファに設定されている場合、50%透明ではなく、0.5 x 0.5 = 0.25アルファ、つまり75%透明になります。同様に、オブジェクトの位置は親を基準としているため、親のx位置が50ピクセルに設定され、子のx位置が100ピクセルに設定されている場合、150ピクセル(50 + 100)の画面オフセットで描画されます。
例を示します。それぞれが最後の子である3つのスプライトを作成し、それらの位置、回転、スケール、およびアルファをアニメーション化します。各スプライトのプロパティは同じ値に設定されていますが、親子チェーンは各変更を増幅します
// Create the application helper and add its render target to the page
const app = new Application();
await app.init({ width: 640, height: 360 })
document.body.appendChild(app.canvas);
// Add a container to center our sprite stack on the page
const container = new Container({
x:app.screen.width / 2,
y:app.screen.height / 2
});
app.stage.addChild(container);
// load the texture
await Assets.load('assets/images/sample.png');
// Create the 3 sprites, each a child of the last
const sprites = [];
let parent = container;
for (let i = 0; i < 3; i++) {
let wrapper = new Container();
let sprite = Sprite.from('assets/images/sample.png');
sprite.anchor.set(0.5);
wrapper.addChild(sprite);
parent.addChild(wrapper);
sprites.push(wrapper);
parent = wrapper;
}
// Set all sprite's properties to the same value, animated over time
let elapsed = 0.0;
app.ticker.add((delta) => {
elapsed += delta.deltaTime / 60;
const amount = Math.sin(elapsed);
const scale = 1.0 + 0.25 * amount;
const alpha = 0.75 + 0.25 * amount;
const angle = 40 * amount;
const x = 75 * amount;
for (let i = 0; i < sprites.length; i++) {
const sprite = sprites[i];
sprite.scale.set(scale);
sprite.alpha = alpha;
sprite.angle = angle;
sprite.x = x;
}
});
シーングラフ内の任意のノードの累積的な平行移動、回転、スケール、およびスキューは、オブジェクトの `worldTransform` プロパティに格納されます。同様に、累積アルファ値は `worldAlpha` プロパティに格納されます。
レンダリング順序
描画するもののツリーがあります。最初に描画されるのは誰ですか?
PixiJSは、ルートからツリーをレンダリングします。各レベルで、現在のオブジェクトがレンダリングされ、次に各子が挿入順にレンダリングされます。そのため、2番目の子は最初の子の上に、3番目の子は2番目の上にレンダリングされます。
2つの親オブジェクトAとD、およびAの下に2つの子BとCがあるこの例を確認してください
// Create the application helper and add its render target to the page
const app = new Application();
await app.init({ width: 640, height: 360 })
document.body.appendChild(app.canvas);
// Label showing scene graph hierarchy
const label = new Text({
text:'Scene Graph:\n\napp.stage\n ┗ A\n ┗ B\n ┗ C\n ┗ D',
style:{fill: '#ffffff'},
position: {x: 300, y: 100}
});
app.stage.addChild(label);
// Helper function to create a block of color with a letter
const letters = [];
function addLetter(letter, parent, color, pos) {
const bg = new Sprite(Texture.WHITE);
bg.width = 100;
bg.height = 100;
bg.tint = color;
const text = new Text({
text:letter,
style:{fill: "#ffffff"}
});
text.anchor.set(0.5);
text.position = {x: 50, y: 50};
const container = new Container();
container.position = pos;
container.visible = false;
container.addChild(bg, text);
parent.addChild(container);
letters.push(container);
return container;
}
// Define 4 letters
let a = addLetter('A', app.stage, 0xff0000, {x: 100, y: 100});
let b = addLetter('B', a, 0x00ff00, {x: 20, y: 20});
let c = addLetter('C', a, 0x0000ff, {x: 20, y: 40});
let d = addLetter('D', app.stage, 0xff8800, {x: 140, y: 100});
// Display them over time, in order
let elapsed = 0.0;
app.ticker.add((ticker) => {
elapsed += ticker.deltaTime / 60.0;
if (elapsed >= letters.length) { elapsed = 0.0; }
for (let i = 0; i < letters.length; i ++) {
letters[i].visible = elapsed >= i;
}
});
子オブジェクトの順序を変更する場合は、`setChildIndex()`を使用できます。親のリストの特定のポイントに子を追加するには、 `addChildAt()`を使用します。最後に、`sortableChildren`オプションと各子に`zIndex`プロパティを設定することで、オブジェクトの子の自動ソートを有効にできます。
レンダーグループ
PixiJSを深く掘り下げると、レンダーグループと呼ばれる強力な機能に出くわします。レンダーグループは、シーングラフ内の特殊なコンテナと考えてください。それらは、それ自体がミニシーングラフのように機能します。プロジェクトでレンダーグループを効果的に使用するために知っておく必要があることは次のとおりです。詳細については、レンダーグループの概要をご覧ください。
カリング
シーンオブジェクトの大部分が画面外にあるプロジェクト(たとえば、横スクロールゲーム)を構築している場合は、それらのオブジェクトを*カリング*する必要があります。カリングとは、オブジェクト(またはその子!)が画面上にあるかどうかを評価し、そうでない場合はレンダリングをオフにするプロセスです。画面外のオブジェクトをカリングしないと、ピクセルが画面に表示されない場合でも、レンダラーはそれらを描画します。
PixiJSは、ビューポートカリングの組み込みサポートを提供していませんが、ニーズに合ったサードパーティのプラグインを見つけることができます。または、独自のカリングシステムを構築する場合は、各ティック中にオブジェクトを実行し、描画する必要のないオブジェクトで`renderable`をfalseに設定するだけです。
ローカル座標とグローバル座標
スプライトをステージに追加すると、デフォルトで画面の左上隅に表示されます。それが、PixiJSで使用されるグローバル座標空間の原点です。すべてのオブジェクトがステージの子である場合、それが心配する必要がある唯一の座標です。ただし、コンテナと子を導入すると、事態はより複雑になります。子オブジェクトが[50, 100]にある場合、*親から*右に50ピクセル、下に100ピクセルです。
これら2つの座標系を「グローバル」座標と「ローカル」座標と呼びます。オブジェクトで `position.set(x、y)`を使用する場合、常にオブジェクトの親を基準としたローカル座標で作業しています。
問題は、オブジェクトのグローバル位置を知りたい場合が多いことです。たとえば、レンダリング時間を節約するために画面外のオブジェクトをカリングする場合、特定の子がビューの四角形の外にあるかどうかを知る必要があります。
ローカル座標からグローバル座標に変換するには、`toGlobal()`関数を使用します。使用方法の例を次に示します
// Get the global position of an object, relative to the top-left of the screen
let globalPos = obj.toGlobal(new Point(0,0));
このスニペットは、`globalPos`を、[0, 0]を基準としたグローバル座標系での子オブジェクトのグローバル座標に設定します。
グローバル座標とスクリーン座標
プロジェクトがホストオペレーティングシステムまたはブラウザで動作している場合、3番目の座標系である「スクリーン」座標(別名「ビューポート」座標)が使用されます。スクリーン座標は、PixiJSがレンダリングしているcanvas要素の左上を基準とした位置を表します。DOMやネイティブマウスクリックイベントなどは、スクリーン空間で機能します。
多くの場合、スクリーン空間はワールド空間に相当します。これは、canvasのサイズが、`Application`を作成するときに指定したレンダービューのサイズと同じである場合に当てはまります。デフォルトでは、これが当てはまります。たとえば、800x600のアプリケーションウィンドウを作成してHTMLページに追加すると、そのサイズのままになります。ワールド座標の100ピクセルは、スクリーン空間の100ピクセルに相当します。しかし!レンダリングされたビューを画面全体に表示するようにストレッチしたり、低解像度でレンダリングして速度を上げるためにアップスケールしたりするのが一般的です。その場合、canvas要素のスクリーンサイズは(たとえば、CSSを介して)変更されますが、基になるレンダービューは*変更されない*ため、ワールド座標とスクリーン座標の間に不一致が生じます。