iT邦幫忙

2025 iThome 鐵人賽

DAY 6
1

昨天,我們介紹了如何使用 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();

貼上程式碼後,點擊「試玩遊戲」,你應該會看到畫面中出現了各種形狀,包含了矩形、圓形、三角形等。

Pixi Graphics 預覽

▸ 拆解程式碼

1. 創建 Graphics 物件

const graphics = new PIXI.Graphics();

首先,我們使用 new PIXI.Graphics() 創建一個 Graphics 物件,這個物件就是我們的虛擬畫布。

2. 繪製與填色

每個繪圖函數後面,都可以接著使用 .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 就是一個帶有 xy 屬性的物件,因此我們可以改成這樣。

    // 繪製一個綠色三角形
    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

3. 繪製線條

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,這樣當我們在前面的 {} 內輸入時,就會跳出對應的屬性提示訊息來輔助我們撰寫程式。

4. 加入舞台

pixi.root.addChild(graphics);

就像 SpriteContainer 一樣,只有將 Graphics 物件加入到舞台上,它才能被顯示出來。

點我查看範例程式碼

▸ Bonus 關卡:紅綠燈 Demo

老實說光只是看著文字介紹,以及上面那張只是畫了幾個形狀的圖片,我肯定會看到睡著,因此我們來進行一個有趣的挑戰,讓我們來結合這幾天所學到的東西,實作一個會「」的紅綠燈吧。

廢話少說,上 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 來實現計時和燈號切換的邏輯。

Pixi 紅綠燈 預覽

不過有些之前沒有提過的我這邊稍微補充一下。

  • alpha:是 PixiJS 所有顯示物件的屬性之一,就像 positionscale 那樣。它代表顯示物件的不透明度,是一個 0 ~ 1 之間的數字,數字越大越不透明,數字越小越透明
  • dt:在 addUpdateFunction 的函數參數中,多了一個 dt,它代表的是畫面更新的間隔時間(Delta Time),每當這個函數被執行一次,dt 就會告訴我們,距離上一次更新畫面過了多久,單位為毫秒(ms),因此我們只要不斷地加上這個數字,就可以創造出一個計時器了。

其他部分就交給各位參考程式碼上的註解,自行去鑽研拉~

點我查看範例程式碼

▸ 總結

今天我們介紹了 Graphics,作為 PixiJS 上的小畫家,它讓我們可以輕鬆地用程式碼繪製各式各樣的圖形。

  • Graphics 是一個可見的顯示物件,可以被加入到 Container 或舞台中。
  • 你可以在同一個 Graphics 物件上繪製多個形狀。
  • fill() 用於填充顏色stroke() 用於繪製線條或邊框

明天,我們將會介紹早在 Day 02 就出現過,但一直沒有被提及的 Text,身為最基礎的文字物件,它在 PixiJS 的地位也是必不可少。


上一篇
Day 05:物件的收納盒 - Container
系列文
用 PixiJS 寫遊戲!告別繁瑣設定,在 Code.Gamelet 打造你的第一個遊戲6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言