前兩天,我們介紹了如何用循環更新來處理持續性的物件移動,以及如何用補間動畫(Tween)來實現一次性的平滑移動。而今天,我們要來介紹另一種截然不同的技術——Spritesheet。
Spritesheet 就像是遊戲世界的定格動畫,它用一系列的靜態圖片,在極短的時間內快速切換,從而創造出流暢的視覺效果。
Spritesheet 是一種將多個小圖(動畫幀)打包成一張大圖的技術。你可能在許多遊戲的資源檔中看過它,它看起來就像一張包含所有角色動作的大拼圖。

素材來源:https://despairparty.itch.io/rpgmaker-spriteface
作者:Despair Party Works
這種技術之所以被廣泛使用,主要有兩個原因:
基於上述優點,Spritesheet 就算不拿來製作動畫,也常常被用來整合所有靜態圖片資源,這是一種常見的優化方式。
要在 PixiJS 上使用 Spritesheet,我們通常需要兩個檔案:
在 CG 上,Spritesheet 被稱為圖集動畫,同樣可以作為資源素材上傳至專案中,但由於需要包含兩種檔案,因此上傳時需要先壓縮成 .zip 檔案,而且壓縮檔內的結構有所規定:
Spritesheet 名稱.zip
└── Spritesheet 名稱
    ├── Spritesheet 名稱.png
    └── Spritesheet 名稱.json
你需要將 .png 檔與 .json 檔放在一個資料夾內,然後將這個資料夾包含在 .zip 壓縮檔內,並且這四個檔案的名稱必須一模一樣,這樣才能將壓縮檔上傳至 CG 專案資源管理中。

如上圖,在 CG 的資源管理中,可以查看該 Spritesheet 的動畫資料:
素材來源:https://arks.itch.io/dungeon-platform-tileset
作者:Arks💢
如果有需要可以在 CG 載入資源時輸入Dunjo搜尋圖集動畫,載入由 cook1470 最新上傳的素材即可,也是本次介紹所使用的素材。
PIXI.Texture由於之前在創建 Sprite 的時候,都是使用 pixi.assets.createSprite("資源別名"),只需要帶入專案的資源別名即可,雖然在 Day 03 有小提示關於 Texture,但沒有深入介紹到,因此這邊稍微補充一下。
Texture 是 PixiJS 中代表一張圖片紋理的物件。當我們使用 pixi.assets.createSprite() 時,它其實在背後幫我們自動完成了兩件事:首先,取得資源別名對應的 Texture;然後,用這個 Texture 來創建一個 Sprite。
// 取得圖片資源別名對應的 Texture,並創建 Sprite
const texture: PIXI.Texture = pixi.assets.getTexture("ironman2025_cook.圖片.女巫");
const sprite: PIXI.Sprite = new PIXI.Sprite(texture);
如上,因此我們不太會需要操作到關於 Texture 的部分,但接下來的 AnimatedSprite 就會用到了。
PIXI.AnimatedSpriteAnimatedSprite 就是動畫版的 Sprite,Sprite 一次只接受一個 Texture,但它可以一次接收很多個,並藉由不斷切換不同的 Texture,從而產生動畫效果。
import pixi = CG.Pixi.pixi;
async function start() {
	// 載入資源(記得將資源別名修改成你自己專案的資源別名喔!)
	await pixi.assets.add("ironman2025_cook.圖集動畫.Dunjo").load();
	// 初始化 Pixi。
	await pixi.initialize({ stageWidth: 960, stageHeight: 540, });
	// 取得 Spritesheet 資源
	const asset: PIXI.Spritesheet = pixi.assets.getSpritesheet("ironman2025_cook.圖集動畫.Dunjo");
	// 創建 PIXI.AnimatedSprite,並設定播放速度、自動播放
	const animatedSprite = new PIXI.AnimatedSprite({
		textures: asset.animations["player"], // 使用動畫紋理 player
		animationSpeed: 0.1,				  // 設定播放速度為 0.1
		autoPlay: true, 					  // 啟用自動播放
		anchor: 0.5,
		position: { x: pixi.stageWidth * 0.5, y: pixi.stageHeight * 0.5 }
	});
	// 將 animatedSprite 加入舞台顯示
	pixi.root.addChild(animatedSprite);
}
start();
在這段程式碼中:
pixi.assets.getSpritesheet():這個方法能取得我們載入的 Spritesheet 資源物件。asset.animations["player"]:一個 Spritesheet 可以包含多個動畫(例如:走路、攻擊、死亡)。asset.animations 是一個物件,其中包含了所有動畫的 Texture 陣列。new PIXI.AnimatedSprite({...}):與 Text 相同,新版的 PixiJS 幾乎將所有能夠被 new 出來的物件,都支援了能將所有的參數(包括紋理、速度、錨點等)都放在一個物件 {} 內傳入,這樣程式碼會更清晰。
而 AnimatedSprite 包含了以下幾個常用的屬性、函數:
textures:動畫使用的 Texture 陣列。autoPlay:布林值,設定是否在創建時自動播放動畫。animationSpeed:控制動畫的播放速度,預設速度為 1。loop:布林值,設定動畫是否循環播放。play():開始播放動畫。stop():停止播放動畫。gotoAndPlay(frame):跳到指定的幀並開始播放。gotoAndStop(frame):跳到指定的幀並停止。onComplete:動畫播放完畢時觸發的回呼函式(只在 loop 為 false 時有效)。除了製作動畫,Spritesheet 另一個主要用途就是作為一個靜態資源庫。當你需要使用圖集中的某個單一圖片時,可以透過它的「圖幀名稱」來單獨取得紋理。這對於管理 UI 介面的各式按鈕、圖示、或視窗背景等小圖片非常有用。
// 取得圖幀列表的 key 紋理
const texture = asset.textures["key"];
// 使用 key 紋理創建 Sprite
const key = new PIXI.Sprite({
	texture: texture,
	anchor: 0.5,
	position: { x: pixi.stageWidth * 0.5, y: pixi.stageHeight * 0.4 }
});
// 將 key 加入舞台顯示
pixi.root.addChild(key);
我們可以從已載入的 Spritesheet 資源中,單獨取出名為 "key" 的圖片紋理,並用它來創建一個靜態的 Sprite 物件。

上圖正是我自己幫我的音樂遊戲所畫的各種圖示,也是利用 Spritesheet 的方式來整合所有的圖示,當然實際背景不會是黑色的,但因為 iThome 好像只有淺色模式,為了介紹所以加工了一下。(作為程式語言交流的平台卻沒有深色模式太奇怪了!)

順帶一提這是我以前使用今天介紹的素材,所做的一款同名 2D 橫向卷軸遊戲,並且提供了自製關卡的功能。
以上兩個遊戲專案的原始碼都是公開的,對我實際上是怎麼使用 CG 平台 + PixiJS 來製作遊戲有興趣的人,可以去翻看看,只是這兩個專案的 PixiJS 版本都是古早版的 v5,所以可能會有不少語法與本次介紹的有所不同。
今天我們學會了遊戲開發中極為重要的視覺技術——Spritesheet。我們不僅了解了它如何提升遊戲效能與載入速度,也學會了兩種主要用法:
PIXI.AnimatedSprite 實現流暢的動態效果。有了讓畫面看起來更酷的動畫效果後,接下來缺的就只剩下聲音了,明天我們將要來介紹如何播放音效、音樂,讓我們的遊戲能夠更加生動活潑!