跳至主要內容
版本:v8.x

資源

資源檔案包

資源檔案包是舊 Loader 類別的一種現代替代。這是一個基於承諾的資源管理方案,可下載、快取和剖析你的資源,變為可用。下載可以同時在背景執行,表示你的應用程式啟動時間更快,而快取則確保你從未兩次下載同一項資源,而且可延伸的剖析器系統使你能夠輕鬆延伸和客製適合你需求的作業流程。

入門

Assets 極度依賴所有現代瀏覽器都支援的 JavaScript 承諾,然而,如果你的目標瀏覽器不支援承諾,你應考慮多填補他們

宣告我們的第一個資源承諾

要快速使用 Assets 實例,你只需要呼叫 Assets.load 並傳入一項資源即可。這會傳回一個承諾,解決時將產生你尋求的價值。在這個範例中,我們將載入紋理然後將它變成精靈。

import { Application, Assets, Sprite } from 'pixi.js';

// Create a new application
const app = new Application();

// Initialize the application
await app.init({ background: '#1099bb', resizeTo: window });

// Append the application canvas to the document body
document.body.appendChild(app.canvas);

// Start loading right away and create a promise
const texturePromise = Assets.load('https://pixijs.dev.org.tw/assets/bunny.png');

// When the promise resolves, we have the texture!
texturePromise.then((resolvedTexture) =>
{
// create a new Sprite from the resolved loaded Texture
const bunny = Sprite.from(resolvedTexture);

// center the sprite's anchor point
bunny.anchor.set(0.5);

// move the sprite to the center of the screen
bunny.x = app.screen.width / 2;
bunny.y = app.screen.height / 2;

app.stage.addChild(bunny);
});

在使用 Assets 時,一個非常重要的注意事項是,所有的要求都會被快取,如果 URL 相同,傳回的承諾也會相同。以下列程式碼來說明

promise1 = Assets.load('bunny.png')
promise2 = Assets.load('bunny.png')
// promise1 === promise2

未經包裝,可以載入下列資源類型,而不需要使用外部外掛程式

  • 紋理 (avifwebppngjpggif)
  • 精靈表 (json)
  • 點陣字型 (xmlfnttxt)
  • 網頁字型 (ttfwoffwoff2)
  • Json 檔案 (json)
  • 文字檔案 (txt)

透過建立其他載入器剖析器,可以輕易地加入更多類型。

處理無法辨識的 URL

使用基本語法時,資產類型會透過其檔案副檔名識別 - 例如 https://pixijs.dev.org.tw/assets/bunny.png.png 結尾,因此 Assets.load 可以找出應該使用材質加載器。

在某些情況下,你無法控制 URL,並且必須使用無法辨識副檔名的不明 URL。在此時,你可以指定一個明確的加載器

promise = Assets.load({
src: 'https://example.com/ambiguous-file-name',
loader: 'loadTextures'
})

以下列出你可以使用的 loader 值:

  • 材質:loadTextures
  • 網路字體:loadWebFont
  • Json 檔案:loadJson
  • 文字檔案:loadTxt

關於已解決 Promise 的警告

當下載某項資產時,它會快取為 Assets 實體中的 Promise,且如果你嘗試再次下載,你會取得對已解析 Promise 的參考。然而,Promise 處理器 .then(...)/.catch(...)/.finally(...) 始終都是非同步的,這表示即使 Promise 已經解析,.then(...)/.catch(...)/.finally(...) 下的程式碼也會在裡面的程式碼之前執行。請看這個範例

console.log(1);
alreadyResolvedPromise.then(() => console.log(2));
console.log(3);

// Console output:
// 1
// 3
// 2

若要深入了解為何會發生此情況,你必須了解 微任務,然而,使用非同步函式應該可以減輕此問題。

使用非同步/暫停

有一種處理 Promise 的方式更直覺且好理解:async/await

若要使用它,我們首先需要建立一個函式/方法並將它標示為 async

async function test() {
// ...
}

此函式現在會將回傳值包在 Promise 中,並且讓我們可以在 Promise 之前使用 await 關鍵字,以在 Promise 解析前暫停程式碼執行,並提供給我們該值。

請看這個範例

// Create a new application
const app = new Application();
// Initialize the application
await app.init({ background: '#1099bb', resizeTo: window });
// Append the application canvas to the document body
document.body.appendChild(app.canvas);
const texture = await Assets.load('https://pixijs.dev.org.tw/assets/bunny.png');
// Create a new Sprite from the awaited loaded Texture
const bunny = Sprite.from(texture);
// Center the sprite's anchor point
bunny.anchor.set(0.5);
// Move the sprite to the center of the screen
bunny.x = app.screen.width / 2;
bunny.y = app.screen.height / 2;
app.stage.addChild(bunny);

texture 變數現在不是一個 Promise,而是在等待此 Promise 解析後產生的解析材質。

const texture = await Assets.load('examples/assets/bunny.png');

這讓我們可以撰寫更易讀的程式碼,而不會落入回呼函式地獄,也讓我們可以更細膩地考慮我們的程式何時暫停和讓步。

載入多項資產

我們可以將資產新增到快取,然後利用 Assets.add(...) 讓它們同時載入,再以你想要載入的所有金鑰呼叫 Assets.load(...)。請看下列範例

// Append the application canvas to the document body
document.body.appendChild(app.canvas);
// Add the assets to load
Assets.add({ alias: 'flowerTop', src: 'https://pixijs.dev.org.tw/assets/flowerTop.png' });
Assets.add({ alias: 'eggHead', src: 'https://pixijs.dev.org.tw/assets/eggHead.png' });
// Load the assets and get a resolved promise once both are loaded
const texturesPromise = Assets.load(['flowerTop', 'eggHead']); // => Promise<{flowerTop: Texture, eggHead: Texture}>
// When the promise resolves, we have the texture!
texturesPromise.then((textures) =>
{
// Create a new Sprite from the resolved loaded Textures
const flower = Sprite.from(textures.flowerTop);
flower.anchor.set(0.5);
flower.x = app.screen.width * 0.25;
flower.y = app.screen.height / 2;
app.stage.addChild(flower);
const egg = Sprite.from(textures.eggHead);
egg.anchor.set(0.5);
egg.x = app.screen.width * 0.75;
egg.y = app.screen.height / 2;
app.stage.addChild(egg);
});

然而,如果你想要充分利用 @pixi/Assets,你應該使用套件。套件只是將資產分組在一起的一種方法,且可以透過呼叫 Assets.addBundle(...)/Assets.loadBundle(...) 手動新增。

  Assets.addBundle('animals', {
bunny: 'bunny.png',
chicken: 'chicken.png',
thumper: 'thumper.png',
});

const assets = await Assets.loadBundle('animals');

然而,處理套件的最佳方式是使用明細,並使用該明細 (或指向該明細的 URL,更好) 呼叫 Assets.init({manifest})。將我們的資產分割成對應於應用程式畫面或階段的套件,會在使用者使用應用程式時很有用,因為這樣可以於背景中載入,而非讓它們鎖定在單一的單體載入畫面中。

{
"bundles":[
{
"name":"load-screen",
"assets":[
{
"alias":"background",
"src":"sunset.png"
},
{
"alias":"bar",
"src":"load-bar.{png,webp}"
}
]
},
{
"name":"game-screen",
"assets":[
{
"alias":"character",
"src":"robot.png"
},
{
"alias":"enemy",
"src":"bad-guy.png"
}
]
}
]
}
Assets.init({manifest: "path/manifest.json"});

注意,你只能呼叫一次init

記住,重複 URL 沒有缺點,因為它們都會被快取,所以如果你在兩個套件中需要相同的資產,你可以複製請求,完全沒有額外的成本!

背景載入

關於載入,舊有的做法是使用 Loader 在應用程式開頭載入所有資產,但使用者現在的耐心越來越少,而且希望內容能立即提供,因此做法正在轉向載入顯示使用者一些內容時所需的最低載入量,在他們互動的同時,我們會持續在背景中載入後續內容。

很幸運地,Assets 提供了我們一套系統,讓我們可以在背景中載入所有內容,而且如果我們現在需要一些資產,可以將它們放到佇列最上方,因此我們可以縮短載入時間。

要達到這個目的,我們有方法 Assets.backgroundLoad(...)Assets.backgroundLoadBundle(...),可以在背景中被動地開始載入這些資產。因此當你最後載入它們時,你會取得一個立即解決為載入資產的承諾。

當你最後需要顯示這些資產時,可以呼叫一般的 Assets.load(...)Assets.loadBundle(...),你會取得對應的承諾。

執行這項工作的最佳方式是使用套件,請看以下範例

import { Application, Assets, Sprite } from 'pixi.js';

// Create a new application
const app = new Application();

async function init()
{
// Initialize the application
await app.init({ background: '#1099bb', resizeTo: window });

// Append the application canvas to the document body
document.body.appendChild(app.canvas);

// Manifest example
const manifestExample = {
bundles: [
{
name: 'load-screen',
assets: [
{
alias: 'flowerTop',
src: 'https://pixijs.dev.org.tw/assets/flowerTop.png',
},
],
},
{
name: 'game-screen',
assets: [
{
alias: 'eggHead',
src: 'https://pixijs.dev.org.tw/assets/eggHead.png',
},
],
},
],
};

await Assets.init({ manifest: manifestExample });

// Bundles can be loaded in the background too!
Assets.backgroundLoadBundle(['load-screen', 'game-screen']);
}

init();

我們為遊戲中的每個畫面建立一個套件,並設定它們在我們應用程式的開頭開始下載。如果使用者在我們應用程式中夠緩慢地進行,他們就不會看到第一個載入畫面外,其他載入畫面!