昨天,我們介紹了如何使用 Container
將多個顯示物件放在一起,輕鬆地控制它們的整體動作,而且我們還偷偷用到了 Graphics
來繪製血條,今天我們就要深入介紹這個強大的工具,介紹怎麼利用它來畫出各種形狀。
Graphics 是 PixiJS 中一個非常實用的繪圖工具,它就像是 PixiJS 裡的小畫家,讓我們可以用程式碼動態地繪製各種圖形,舉凡像是矩形、圓形、多邊形、線條等等。這對於製作介面、血條,或任何需要動態生成形狀的遊戲物件都非常方便。
同樣,將昨天 app.ts
內的程式碼清空,並貼上下列程式碼:
import pixi = CG.Pixi.pixi;
export async function start() {
// 初始化 Pixi
await pixi.initialize({ stageWidth: 960, stageHeight: 540 });
// 創建一個 Graphics 物件
const graphics = new PIXI.Graphics();
// 繪製一個紅色矩形
graphics.rect(100, 50, 200, 100).fill(0xFF0000);
// 繪製一個藍色圓形
graphics.circle(450, 100, 50).fill(0x0000FF);
// 繪製一個綠色三角形
graphics.poly([
600, 150,
700, 50,
800, 150
]).fill(0x00FF00);
// 繪製一條帶有粗細的黃色直線
graphics
.moveTo(50, 250)
.lineTo(800, 250)
.stroke({
color: 0xFFFF00,
width: 5
} as PIXI.StrokeStyle);
// 繪製一個有邊框的紫色圓角矩形
graphics.roundRect(100, 300, 200, 100, 20).stroke({
color: 0x800080,
width: 5
});
// 將 Graphics 物件加入舞台
pixi.root.addChild(graphics);
}
start();
貼上程式碼後,點擊「試玩遊戲」,你應該會看到畫面中出現了各種形狀,包含了矩形、圓形、三角形等。
const graphics = new PIXI.Graphics();
首先,我們使用 new PIXI.Graphics()
創建一個 Graphics
物件,這個物件就是我們的虛擬畫布。
每個繪圖函數後面,都可以接著使用 .fill()
或 .stroke()
來設定顏色。
fill()
:用於將圖形填充顏色。stroke()
:用於將圖形繪製邊框、線條。這兩個函數都可以傳入一個物件,來調整繪製的設定,例如顏色(color
)、不透明度(alpha
),而 stroke
還可以設定線條的粗細(width
)等。
以下是常用的繪圖函數:
矩形 rect()
graphics.rect(x, y, width, height)
x, y
:矩形的左上角座標。width, height
:矩形的寬度和高度。圓形 circle()
graphics.circle(x, y, radius)
x, y
:圓形的圓心座標。radius
:圓形的半徑。多邊形 poly()
graphics.poly([x1, y1, x2, y2, ...])
x, y
:一個包含多個點座標的陣列,Graphics
會將這些點依序連接起來,形成一個多邊形。// 繪製一個綠色三角形
raphics.poly([
600, 150,
700, 50,
800, 150
).fill(0x00FF00);
上方範例中傳入的參數是一個一維的數字陣列,通過換行我們才比較好分辨每一個點的 x 與 y 座標,但如果想要更直觀一點,也可以傳入 PIXI.PointData
陣列。PIXI.PointData
就是一個帶有 x
、y
屬性的物件,因此我們可以改成這樣。
// 繪製一個綠色三角形
raphics.poly([
{ x: 600, y: 150 },
{ x: 700, y: 50 },
{ x: 800, y: 150 }
).fill(0x00FF00);
這樣是不是就更直觀了呢?當然,你可以根據自己的習慣來決定要使用哪一種方式。
圓角矩形 roundRect()
graphics.roundRect(x, y, width, height, radius)
x, y
:矩形的左上角座標。width, height
:矩形的寬度和高度。radius
:圓角的半徑。其實還有像是繪製扇形之類的函數,不過這些我就比較少用了,通常是有需要才會去查怎麼用,有興趣的人可以參考 PixiJS 官方對於
Graphics
的 說明,以及 API。
graphics
.moveTo(50, 250)
.lineTo(800, 250)
.stroke({
color: 0xFFFF00,
width: 5
} as PIXI.StrokeStyle);
Graphics
的線條繪製方式需要搭配 moveTo()
和 lineTo()
來使用。
moveTo(x, y)
:將「畫筆」移動到指定座標,但不繪製。lineTo(x, y)
:從當前畫筆位置畫一條直線到指定座標。當你用 moveTo()
和 lineTo()
畫完線後,再使用 stroke()
方法,就可以一次性繪製所有線條。
在 PixiJS v8 之前,
lineTo
會直接把線條繪製出來,繪製前我們通常還會使用lineStyle
定義線條樣式。而現在的設計更像是「先規劃路徑,最後再統一繪製」,這種方式大大提高了渲染效能,特別是在處理大量線條時。
as PIXI.StrokeStyle
是 TypeScript 的語法,用於告訴編輯器前面物件的型別是PIXI.StrokeStyle
,這樣當我們在前面的{}
內輸入時,就會跳出對應的屬性提示訊息來輔助我們撰寫程式。
pixi.root.addChild(graphics);
就像 Sprite 和 Container 一樣,只有將 Graphics
物件加入到舞台上,它才能被顯示出來。
老實說光只是看著文字介紹,以及上面那張只是畫了幾個形狀的圖片,我肯定會看到睡著,因此我們來進行一個有趣的挑戰,讓我們來結合這幾天所學到的東西,實作一個會「動」的紅綠燈吧。
廢話少說,上 Code!
import pixi = CG.Pixi.pixi;
export async function start() {
// 初始化 Pixi
await pixi.initialize({ stageWidth: 960, stageHeight: 540 });
// 建立一個 Container 來管理紅綠燈的所有元件
const trafficLight = new PIXI.Container();
trafficLight.position.set(pixi.stageWidth * 0.5, pixi.stageHeight * 0.5);
pixi.root.addChild(trafficLight);
// 先定義好燈泡的半徑
const lightRadius = 100;
// 再計算出底座的寬高
const baseWidth = lightRadius * 2 * 3 + lightRadius; // * 2 代表燈泡的直徑,* 3 代表三個燈泡的總長
const baseHeight = lightRadius * 2 + lightRadius * 0.8;
// 繪製紅綠燈的黑色底座
// 圓角矩形的左上角座標設定為負數,是為了讓整個圖形以 (0,0) 為中心
const base = new PIXI.Graphics()
.roundRect(-baseWidth * 0.5, -baseHeight * 0.5, baseWidth, baseHeight, 10)
.fill(0x222222);
trafficLight.addChild(base);
// 繪製三個燈泡
const redLight = new PIXI.Graphics()
.circle(-lightRadius * 2, 0, lightRadius)
.fill(0xFF0000); // 創建一個 Graphics 作為紅燈
const yellowLight = new PIXI.Graphics()
.circle(0, 0, lightRadius)
.fill(0xFFFF00); // 創建一個 Graphics 作為黃燈
const greenLight = new PIXI.Graphics()
.circle(lightRadius * 2, 0, lightRadius)
.fill(0x00FF00); // 創建一個 Graphics 作為綠燈
// 將燈泡加到容器中
trafficLight.addChild(redLight);
trafficLight.addChild(yellowLight);
trafficLight.addChild(greenLight);
// 建立一個計時器來控制燈號
let timer = 0;
// 定義當前狀態為:紅燈
let currentState = 'red';
// 添加一個循環更新函數
CG.Base2.addUpdateFunction((dt: number) => {
timer += dt; // 將計時器加上每次更新畫面的間隔時間(毫秒)
// 判斷當前狀態並切換燈號
switch (currentState) {
case 'red': // 紅燈狀態
redLight.alpha = 1;
yellowLight.alpha = 0.3;
greenLight.alpha = 0.3;
if (timer >= 3000) { // 3 秒後切換
currentState = 'green';
timer = 0; // 將計時器歸零
}
break;
case 'green': // 綠燈狀態
redLight.alpha = 0.3;
yellowLight.alpha = 0.3;
greenLight.alpha = 1;
if (timer >= 3000) { // 3 秒後切換
currentState = 'yellow';
timer = 0;
}
break;
case 'yellow': // 黃燈狀態
redLight.alpha = 0.3;
yellowLight.alpha = 1;
greenLight.alpha = 0.3;
if (timer >= 1000) { // 1 秒後切換
currentState = 'red';
timer = 0;
}
break;
}
});
}
start();
這裡結合了我們所學的知識:
Graphics
繪製出底座和燈泡。Container
將所有元件組合在一起,方便我們管理紅綠燈這個物件。addUpdateFunction
來實現計時和燈號切換的邏輯。不過有些之前沒有提過的我這邊稍微補充一下。
alpha
:是 PixiJS 所有顯示物件的屬性之一,就像 position
、scale
那樣。它代表顯示物件的不透明度,是一個 0 ~ 1 之間的數字,數字越大越不透明,數字越小越透明。dt
:在 addUpdateFunction
的函數參數中,多了一個 dt
,它代表的是畫面更新的間隔時間(Delta Time),每當這個函數被執行一次,dt
就會告訴我們,距離上一次更新畫面過了多久,單位為毫秒(ms),因此我們只要不斷地加上這個數字,就可以創造出一個計時器了。其他部分就交給各位參考程式碼上的註解,自行去鑽研拉~
今天我們介紹了 Graphics,作為 PixiJS 上的小畫家,它讓我們可以輕鬆地用程式碼繪製各式各樣的圖形。
Graphics
是一個可見的顯示物件,可以被加入到 Container
或舞台中。Graphics
物件上繪製多個形狀。fill()
用於填充顏色,stroke()
用於繪製線條或邊框。明天,我們將會介紹早在 Day 02 就出現過,但一直沒有被提及的 Text,身為最基礎的文字物件,它在 PixiJS 的地位也是必不可少。