昨天,我們成功創建了第一個 Sprite,並將它顯示在畫面的正中央。有了這個靜態的物件後,今天,我們就要讓它活起來,學習如何控制它的位置、縮放與旋轉。
在開始之前,你可以先想想,要怎麼讓一個物件動起來呢?
答案很簡單:不斷地更新它的屬性。
在 PixiJS 中,Sprite
擁有許多屬性,其中最基礎也最重要的,就是控制位置 (position
)、縮放 (scale
) 與旋轉 (rotation
)。透過在每一幀(Frame)持續地修改這些屬性,就能創造出我們所看到的動態效果。
首先,將你的 app.ts
程式碼清空,並貼上以下程式碼:
import pixi = CG.Pixi.pixi;
// 速度
let speed = { x: 2, y: 2 };
// 旋轉速度
let rotationSpeed: number = 0.05;
async function start() {
// 載入資源(記得將資源別名修改成你自己專案的資源別名喔!)
await pixi.assets.add("ironman2025_cook.圖片.女巫").load();
// 初始化 Pixi
await pixi.initialize({ stageWidth: 960, stageHeight: 540 });
// 創建 Sprite
const sprite = pixi.assets.createSprite("ironman2025_cook.圖片.女巫");
sprite.anchor.set(0.5);
sprite.position.set(pixi.stageWidth * 0.5, pixi.stageHeight * 0.5);
// 加入舞台
pixi.root.addChild(sprite);
// 添加一個循環更新函數
CG.Base2.addUpdateFunction(() => {
// 更新位置
sprite.x += speed.x;
sprite.y += speed.y;
// 如果 Sprite 碰到舞台左右邊界,就反轉方向
if (sprite.x > pixi.stageWidth || sprite.x < 0) {
speed.x *= -1;
}
if (sprite.y > pixi.stageHeight || sprite.y < 0) {
speed.y *= -1;
}
// 更新旋轉
sprite.rotation += rotationSpeed;
});
}
start();
貼上程式碼後,點擊「試玩遊戲」,你應該會看到女巫圖片在畫面上不斷地移動並持續旋轉,這不就是螢幕保護程式嗎!?
這段程式碼主要分為兩個部分:初始化和循環更新。
前面大部分的程式碼你應該很熟悉了,包含載入資源、初始化 Pixi、創建並設定 Sprite
物件,以及將它加入舞台。
但這次,我們在程式碼最上方定義了兩個變數:
let speed = { x: 2, y: 2 };
let rotationSpeed: number = 0.05;
這裡的 let
關鍵字表示我們宣告了兩個變數,它們的值可以在程式執行過程中被改變。speed
用來控制 Sprite
的移動速度,rotationSpeed
則用來控制它的旋轉速度。
這段程式碼是今天的重點,也是所有遊戲動畫的核心:
// 添加一個循環更新函數
CG.Base2.addUpdateFunction(() => {
// 這裡的程式碼,會在每次畫面被更新時執行
});
CG.Base2.addUpdateFunction
是一個函數,它能將你寫的程式碼註冊到遊戲主迴圈中。這個迴圈會不斷地執行,每當畫面要更新時,就會呼叫一次你所註冊的函式。這也是為什麼我們的程式碼只需要寫一次,就能讓女巫持續移動和旋轉。
在 CG 上,比起使用 PixiJS 原生的
Ticker
,我們更傾向於使用addUpdateFunction
,它與原生的Ticker
並無太大的差異,使用方式也幾乎相同。由於本系列是建立在 CG 平台上,因此我這邊才會著重在介紹addUpdateFunction
,而非Ticker
,這樣當我們去查看 CG 其他專案的原始碼時,才不會有所斷層。
在每次更新畫面的時候,我們做了幾件事情:
sprite.x += speed.x;
sprite.y += speed.y;
sprite.x
和 sprite.y
屬性分別代表了 sprite
的水平和垂直位置。我們讓它們持續加上 speed
的值,就可以讓物件持續移動。
sprite.x
是sprite.position.x
的簡寫,y
亦同。
if (sprite.x > pixi.stageWidth || sprite.x < 0) {
speed.x *= -1;
}
if (sprite.y > pixi.stageHeight || sprite.y < 0) {
speed.y *= -1;
}
if
判斷式會檢查小括號 ()
內的條件式是否成立。這裡我們用來檢查 sprite
的位置是否超過了舞台的邊界。
sprite.x > pixi.stageWidth
判斷 sprite
是否移出右邊界。sprite.x < 0
判斷 sprite
是否移出左邊界。當 sprite
碰到任一邊界時,我們將對應 speed
的值變號(乘上 -1
),從而實現反彈效果。
||
代表「或」,只要左右兩邊的條件有一個成立,就會執行大括號{}
內的程式碼。
sprite.rotation += rotationSpeed;
rotation
屬性用來控制 sprite
的旋轉角度。我們持續讓它的值加上 rotationSpeed
,從而讓 sprite
永不停止地旋轉。
為了簡化教學,我將旋轉速度設為
0.05
,但sprite.rotation
的單位是弧度而非角度。一個圓是Math.PI * 2
弧度,大約是 6.28。所以如果你想讓物件旋轉一圈,就必須讓rotation
屬性累加上Math.PI * 2
。如果你習慣使用角度,可以將弧度除以180
來進行轉換,例如 30° 就是Math.PI / 180 * 30
。
我相信 position
、scale
、rotation
,其實大家應該都很容易就能理解了,但是關於 scale
其實還有一個小秘密。
我們知道 scale
的 x
、y
預設皆為 1
,代表顯示物件最原始的縮放大小,數字越大,顯示物件就越大,數字越小,顯示物件就越小,直到歸零,顯示物件看起來就像是消失了。
「但!如果數字變成負數呢?」
sprite.scale.x = -1;
你能夠想像物件的 x 軸縮放是 -1
時,他看起來會長什麼樣子嗎?這個問題,我們明天再來解答。
今天的內容看起來很簡單,但這三個屬性加上「循環更新」的觀念,就是所有遊戲動畫的核心。掌握了這個概念,你就可以讓任何物件動起來,而不只是靜靜地待在畫面上。
明天,我將會介紹如何使用 Container 這個物件,作為容器,它可以讓我們把多個顯示物件放在一起作為一個群組,不過詳細的介紹就等到明天再來說明吧!