昨天,我們成功讓第一個 Sprite 動了起來。有了控制單一物件動態的經驗後,今天我們將面臨一個更實際的問題:當一個遊戲角色由多個物件組成時,要怎麼一起控制它們?
想像一下,一個遊戲角色不僅有主角的 Sprite,還有血條、ID 名稱等。如果每次移動都要分別去設定每個物件的位置,那程式碼將會變得非常混亂。
解決這個問題的關鍵就是 Container,你可以把它想像成一個透明的容器,用來將多個遊戲物件裝在一起。當你移動或旋轉這個容器時,裡面所有的東西都會跟著一起移動。
首先,將你的 app.ts
程式碼清空,並貼上以下程式碼:
import pixi = CG.Pixi.pixi;
// 設定移動速度
let speed = 3;
async function start() {
// 載入資源(記得將資源別名修改成你自己專案的資源別名喔!)
await pixi.assets.add("ironman2025_cook.圖片.女巫").load();
// 初始化 Pixi
await pixi.initialize({ stageWidth: 960, stageHeight: 540 });
// 創建一個容器物件,用來裝所有東西,並設定位置在畫面中央
const container = new PIXI.Container();
container.position.set(pixi.stageWidth * 0.5, pixi.stageHeight * 0.5);
// 創建女巫 Sprite 物件並設定錨點為中心
const witch = pixi.assets.createSprite("ironman2025_cook.圖片.女巫");
witch.anchor.set(0.5);
// 創建一個代表血條的紅色方塊
const healthBar = new PIXI.Graphics()
.rect(-30, -3, 60, 6)
.fill(0xFF0000)
.stroke({
color: 0xFFFFFF,
width: 1
} as PIXI.StrokeStyle);
// 將血條的位置設定在女巫上方
healthBar.y = -witch.height * 0.5 - 10;
// 將 Sprite 和血條放入容器中
container.addChild(witch);
container.addChild(healthBar);
// 將容器加入舞台
pixi.root.addChild(container);
// 添加一個循環更新函數
CG.Base2.addUpdateFunction(() => {
// 更新容器的位置
container.x += speed;
// 判斷容器是否碰到舞台邊界,並反轉方向
if (container.x > pixi.stageWidth || container.x < 0) {
speed *= -1;
// 改變女巫的 x 軸縮放來讓它轉向
witch.scale.x = speed / Math.abs(speed);
}
});
}
start();
貼上程式碼並點擊「試玩遊戲」,你應該會看到一個帶著紅色血條的女巫在畫面上來回移動,並且在碰到邊界時會自動轉向。而且我們只控制了 Container
的位置,但裡面的女巫和血條卻一起跟著動了!
Container
是 PixiJS 中一種基本的顯示物件,它的主要功能是作為其他顯示物件的群組或容器。
Container
本身是不可見的,沒有顏色也沒有大小。它就像一個無形的盒子,當你把 Sprite
、Graphics
或其他 Container
放進這個盒子裡,這些子物件的位置、縮放和旋轉都會相對於這個盒子來計算。這意味著,當你移動這個盒子時,裡面的所有東西都會跟著一起移動!
我最喜歡舉這個例子了,容器與子物件的關係,就像是餐桌與桌上的餐盤、食物的關係。
這套邏輯也完全適用於程式碼的世界,我們只需要控制這個「餐桌」,就能輕鬆管理所有「餐盤」和「食物」了。
在最新的 PixiJS v8 版本中,
Container
已經完全取代了DisplayObject
,成為了所有顯示物件的基類,DisplayObject
也已經被淘汰。
這段程式碼的核心,在於我們如何將多個物件整合成一個群組來管理。
const container = new PIXI.Container();
container.position.set(pixi.stageWidth * 0.5, pixi.stageHeight * 0.5);
我們首先創建了一個新的 Container
物件,並設定它的初始位置在畫面的正中央。
接著,我們創建了兩個子物件:
witch
:Sprite
物件,正是我們前兩天重點介紹的女巫,只是名字從 sprite
改成了 witch
。healthBar
:Graphics
物件,用來繪製血條。Graphics
是一個非常實用的類別,它讓你可以用程式碼動態地繪製簡單的形狀,像是矩形(rect
)、圓形(circle
)或線條(line
)等等。目前暫時先大概了解即可,明天我們再來深入介紹它。// 將 Sprite 和血條放入容器中
container.addChild(witch);
container.addChild(healthBar);
// 將容器加入舞台
pixi.root.addChild(container);
這兩組程式碼是 Container
的精髓所在。
container.addChild()
:這行程式碼將 witch
和 healthBar
這兩個子物件放進了 container
這個容器裡。一旦被放進去,它們的位置就會相對於 container
的原點來計算。pixi.root.addChild(container)
:這行程式碼則是將整個容器 (container
) 加入到我們的主舞台 (pixi.root
)。這是一個非常重要的概念:只有被加到舞台上的物件,才會被顯示出來。而當我們把一個容器加到舞台上時,容器裡的所有子物件也會跟著被顯示。
你知道嗎?其實
pixi.root
也一個Container
。
// 更新容器的位置
container.x += speed;
// 判斷容器是否碰到舞台邊界,並反轉方向
if (container.x > pixi.stageWidth || container.x < 0) {
speed *= -1;
// 改變女巫的 x 軸縮放來讓它轉向
witch.scale.x = speed / Math.abs(speed);
}
這裡的程式碼在每次畫面更新時執行。
container.x += speed
:我們只改變了容器的 x 軸位置。但因為女巫和血條都在容器裡,它們會跟著容器一起移動,這就是 Container
的核心用途。if
判斷式:這段邏輯跟昨天一樣,用來判斷容器是否碰到左右邊界,並讓它反彈。但這次我們多加了一行 witch.scale.x = speed / Math.abs(speed);
,這行程式碼會根據 speed
的正負值,將女巫的 x 軸縮放設定為 1
或 -1
,從而實現自動轉向的效果。還記得昨天留下的謎團嗎?
scale.x = -1
,沒錯,他會直接讓顯示物件轉向,x 軸為負數時,顯示物件會水平翻轉,y 軸為負數時,顯示物件會垂直翻轉。不知道這個問題有沒有讓你半夜苦惱的睡不著覺呢?
在結束之前,讓我們再看看一個情況,將下方新的程式碼添加到原本的 addUpdateFunction
中,其他程式碼保持不變。
CG.Base2.addUpdateFunction(() => {
// ... 其餘保留 ...
// 簡單的動畫,讓女巫上下輕微浮動
const timing = Date.now() % 2000 / 2000;
const scale = Math.sin(Math.PI * timing * 2);
witch.y = 5 * scale;
});
這段程式碼會讓女巫以正弦波的方式上下輕微浮動,製造一種飄動的感覺。
Date.now()
會回傳目前的毫秒數。% 2000 / 2000
會將這個毫秒數轉換成一個介於 0 ~ 1 之間的數字,代表每 2 秒循環一次。Math.sin(Math.PI * timing * 2)
則將這個介於 0 ~ 1 的數字轉換成一個在 -1 ~ 1 之間擺動的數字。5
乘上這個擺動的數字,並設定給 witch.y
,這樣女巫就會在垂直方向上輕微浮動了。如果你覺得這段數學運算太複雜也沒關係,這只是提供給對數學有興趣的朋友參考。對於遊戲開發來說,你只需要知道,透過這段程式碼,我們成功讓子物件在跟隨父物件移動的同時,還能擁有自己的獨立動畫。
今天的重點內容其實只有 Container
,作為顯示物件的容器:
明天,我們將深入介紹 Graphics
這個類別,介紹如何用程式碼畫出各種形狀。