跳至主要內容
版本:v8.x

場景圖

每個畫面,PixiJS 都會更新並渲染場景圖。讓我們來討論場景圖中有哪些,以及它如何影響你開發專案的方式。如果你曾製作過遊戲,這些資訊你應該都已很熟悉,但如果你來自 HTML 和 DOM,在我們深入探討你可以渲染的特定物件類型之前,值得先來了解一下。

場景圖是一棵樹

場景圖的根節點是由應用程式維護的容器,並由 app.stage 參考。當你將一個精靈或其他可渲染物件作為一個子節點加入到舞台時,它會被加入到場景圖中,並且會被渲染和能與之互動。PixiJS 容器 也可以有子節點,因此當你建置更複雜的場景時,你最後將會得到一棵基於應用程式舞台的父子關係樹。

(一個探索專案的實用工具是 適用於 Chrome 的 Pixi.js devtools 外掛程式,它允許你在執行時即時查看和操作場景圖!)

父母和子女

當父母移動時,它的子女也會移動。當父母旋轉時,它的子女也會旋轉。隱藏父母,子女也會被隱藏。如果你有一個由多個精靈組成的遊戲物件,你可以將它們收集在一個容器下,作為世界中的單一物件處理,作為一個整體進行移動和旋轉。

PixiJS 會從根節點向下,遍歷場景圖中所有子女到葉子節點,以計算每個物件的最終位置、旋轉、可見度、透明度等等資訊。如果父母的 alpha 值設定為 0.5(使其透明度為 50%),那麼它的所有子女也都會一開始為 50% 透明度。如果一個子女接著被設定為 0.5 alpha,它不會是 50% 透明度,它會是 0.5 x 0.5 = 0.25 alpha,或 75% 透明度。類似地,一個物件的位置相對於它的父母,所以如果一個父母被設定為 x 位置為 50 個畫素,而子女被設定為 x 位置為 100 個畫素,它會繪製在 150 個畫素或 50 + 100 的螢幕偏移量上。

以下是一個範例。我們將建立三個精靈,每個都是前一個的子女,並對它們的位置、旋轉、大小和 alpha 進行動畫處理。儘管每個精靈的屬性都被設定為相同的值,但父子鏈會放大每個變化

// 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 屬性中。類似地,累加 alpha 值儲存在 worldAlpha 屬性中。

變繪順序

我們有一顆要繪製的物件樹,哪個會先被繪製?

從根節點向下,PixiJS 會繪製此樹狀結構。在每個層級中,目前的物件會先被繪製,然後依插入順序繪製每個子節點。因此,第二個子節點繪製在第一個子節點上方,第三個在第二個子節點上方。

請參閱此範例,其中包含兩個父物件 A 和 D,以及 A 底下的兩個子節點 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 沒有提供內建的視窗裁剪支援,但您可以找到符合您需求的第三方外掛程式。或者,如果您想要自行建置裁剪系統,您只需要在每個 tick 時執行物件,並對不需要繪製的任何物件將 renderable 設定為 false 即可。

區域和全域座標

如果將 sprite 新增到舞台,這個 sprite 預設會顯示在螢幕的左上角。這是 PixiJS 使用的全域座標系統的起點。如果您的所有物件都是舞台的子節點,那是您唯一需要擔心的座標。但一旦您引入容器和子節點,所有的事情都會變得更為複雜。一個位於[50, 100]的子物件會從其父節點的右側 50 像素處和下方 100 像素處開始。

我們稱這兩個座標系統為「區域」和「全域」座標。當您對物件使用 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]在全域座標系統中的關係。

全域和螢幕座標

當你的專案與主機作業系統或瀏覽器配合運作時,會用到第三個座標系統,也就是「螢幕」(或稱「視窗」)座標系統。螢幕座標代表 PixiJS 所繪製畫布元素的左上角相對位置。例如 DOM 與原生滑鼠點擊事件,便是在螢幕空間中運作。

在許多狀況下,螢幕空間等同於世界空間。當你建立 應用程式 時,畫布大小若與指定的繪製檢視大小相同,便是如此。預設情況下會如此,例如你將建立 800 x 600 應用程式視窗,並將其新增至你的 HTML 頁面,且維持該大小。在世界座標中,100 像素等同於螢幕空間中的 100 像素。但是!通常會將繪製的檢視區拉伸至填滿螢幕,或以較低解析度繪製並提升規模以加快速度。在這種情況下,畫布元素的螢幕大小會變更(例如透過 CSS),但底層繪製檢視不會變更,導致世界座標與螢幕座標之間產生不匹配。