Web Player API
Cavalry Web Player 和 API 目前處於測試階段,因此可能會有變更。
Cavalry Web Player 可用於在網頁瀏覽器中播放和互動 Cavalry 場景。本文件提供了完整的整合指南和 API 參考。
Web Player 由兩個主要部分組成。
- WebAssembly 執行環境
- JavaScript 模組
JavaScript 模組用於實例化 Web Player。Web Player 包含 4 個方面。
- Module — 匯入的
CavalryWasm模組的實例 - Player — 控制播放、資源管理等功能的 Cavalry 播放器(類似於 Cavalry 的指令碼 API)
- Canvas — 用作視圖的
canvas元素 - Surface — 將場景渲染到
canvas的 WebGL 表面
基本範例
标题为“基本範例”的章节// Import the Cavalry moduleconst wasm = await import("../wasm-lib/CavalryWasm.js")// Initialise the moduleconst Module = await wasm.default({ // `locateFile` is required for the module to load the wasm files locateFile: (path) => `../wasm-lib/${path}`, print: (text) => console.log(text), printErr: (text) => console.error(text),})// Load a scene fileconst response = await fetch("scene.cv")const sceneData = await response.arrayBuffer()Module.FS.writeFile("scene.cv", new Uint8Array(sceneData))// Create a player instanceconst player = Module.Cavalry.MakeWithPath("scene.cv")// Read the scene resolutionconst scene = player.getSceneResolution()const canvas = document.getElementById("canvas")canvas.width = scene.widthcanvas.height = scene.height// Set up a rendering surfaceconst surface = Module.makeWebGLSurfaceFromElement( canvas, scene.width, scene.height,)// Render the current frameplayer.render(surface)// Use `requestAnimationFrame` for efficient playbacklet animationFrameId = 0const runPlaybackLoop = () => { const tick = (timestamp) => { // `tick` advances the frame based on the elapsed time const status = player.tick(surface, timestamp); // Optionally update the UI when a frame changes if (status.frameChanged) { console.log("Frame", status.currentFrame); } animationFrameId = requestAnimationFrame(tick) } animationFrameId = requestAnimationFrame(tick)}runPlaybackLoop()player.play()// To stop playback// player.stop();// cancelAnimationFrame(animationFrameId);Module
标题为“Module”的章节使用以下方式匯入模組:
// Import the Cavalry moduleconst wasm = await import("../wasm-lib/CavalryWasm.js")// Initialise the moduleconst Module = await wasm.default({ // `locateFile` is required for the module to load the wasm files locateFile: (path) => `../wasm-lib/${path}`, // Logs diagnostic data to the console print: (text) => console.log(text), // Logs errors to the console printErr: (text) => console.error(text),})makeWebGLSurface(canvasId:string, width:number, height:number) → SkSurface
标题为“makeWebGLSurface(canvasId:string, width:number, height:number) → SkSurface”的章节使用指定的 canvas 元素初始化 WebGL2 上下文和 Skia 表面。
參數:
canvasId: 畫布 HTML 元素的 ID。width: 渲染表面的寬度。height: 渲染表面的高度。
回傳值: 綁定到 WebGL 上下文的 Skia SkSurface。
const surface = Module.makeWebGLSurface("canvas", 800, 600);makeWebGLSurfaceFromElement(canvas:emscripten::val, width:number, height:number) → SkSurface
标题为“makeWebGLSurfaceFromElement(canvas:emscripten::val, width:number, height:number) → SkSurface”的章节使用指定的 canvas 元素初始化 WebGL2 上下文和 Skia 表面。
參數:
canvas: 畫布 HTML 元素。width: 渲染表面的寬度。height: 渲染表面的高度。
回傳值: 綁定到 WebGL 上下文的 Skia SkSurface。
const surface = Module.makeWebGLSurfaceFromElement(canvas, canvas.width, canvas.height);loadFont(path:string, alias?:string) → string
标题为“loadFont(path:string, alias?:string) → string”的章节將字型從虛擬檔案系統載入到執行環境字型登錄表中。
參數:
path: 虛擬檔案系統中字型檔案的路徑(例如.ttf、.otf、.woff、.woff2)。alias(可選): 註冊字型時使用的自訂名稱。如果提供,字型將使用此別名而不是其內部族名進行註冊。這對於匹配外部命名系統(例如 CSS font-family 名稱)非常有用。
回傳值: 註冊的族名(如果提供了別名則為別名,否則為字型的內部族名)。
// Load font using its internal family nameModule.FS.writeFile("Montserrat.ttf", fontBytes);const familyName = Module.loadFont("Montserrat.ttf", "");// familyName = "Montserrat" (from font's internal name)// Load font with a custom aliasModule.FS.writeFile("AC-Compacta.woff", fontBytes);const familyName = Module.loadFont("AC-Compacta.woff", "my-custom-id");// familyName = "my-custom-id"// Use the alias with other font APIsconst styles = player.getFontStyles("my-custom-id");player.setAttributeFont("textShape#1", "font", { font: "my-custom-id", style: "Regular" });getFontMetadata(familyName:string) → FontMetadata
标题为“getFontMetadata(familyName:string) → FontMetadata”的章节取得已載入字型的詳細元資料,包括所有可用樣式及其 CSS 相容值。
參數:
familyName: 註冊的族名(來自loadFont()或queryFonts())。
回傳值: 包含字型名稱變體和樣式資訊的 FontMetadata 物件。
// Load a fontconst familyName = Module.loadFont("Inter.ttf", "");// Get metadataconst metadata = Module.getFontMetadata(familyName);console.log("Family:", metadata.registeredFamily);console.log("Styles:", metadata.styles);// Use style info for CSS mappingmetadata.styles.forEach(style => { console.log(`${style.name}: weight=${style.weight}, slant=${style.slant}`);});// Output: "Regular: weight=400, slant=normal"// Output: "Bold: weight=700, slant=normal"// Output: "Italic: weight=400, slant=italic"loadFontData(url:string)
标题为“loadFontData(url:string)”的章节從給定的 URL 載入字型資料並將其載入到執行環境字型登錄表中。
參數:
url(字串): 擷取字型資料的 URL。
// Load a font from a URLModule.loadFontData("/url/to/font.ttf");Cavalry
标题为“Cavalry”的章节使用以下方式建立實例:
const player = Module.Cavalry.Make("scene.cv");pendingAssets
标题为“pendingAssets”的章节當透過 MakeWithPath 載入場景時,其資源將位於 pendingAssets 陣列中。然後可以透過遍歷該陣列來載入資源。
有關實作範例,請參見載入資源。
const player = Module.Cavalry.MakeWithPath("scene.cv");console.log(Module.pendingAssets)specialHTMLTargets
标题为“specialHTMLTargets”的章节向此物件新增 canvas 元素允許 CavalryWasm 模組在 canvas 位於 Shadow DOM 中時找到它。
請參見 Shadow DOM。
const id = "my-canvas"const canvas = document.createElement("canvas")canvas.id = idModule.specialHTMLTargets[id] = canvasMake(scenePath?:string) → Cavalry
标题为“Make(scenePath?:string) → Cavalry”的章节構造並回傳一個新的 Cavalry 類別實例。
參數:
scenePath(可選): 虛擬檔案系統中場景檔案的路徑。預設為"scene.cv"以保持向後相容。
// Load default scene.cvconst player = Module.Cavalry.Make();// Load a specific scene fileconst player = Module.Cavalry.Make("myAnimation.cv");MakeWithPath(scenePath:string) → Cavalry
标题为“MakeWithPath(scenePath:string) → Cavalry”的章节構造並回傳一個新的 Cavalry 類別實例。
這是 Make 的替代方法。
參數:
scenePath: 虛擬檔案系統中場景檔案的路徑。
// Load a specific scene fileconst player = Module.Cavalry.MakeWithPath("scene.cv");render(surface:SkSurface)
标题为“render(surface:SkSurface)”的章节將目前畫格渲染到指定的 WebGL Skia 表面。
player.render(surface);getSceneResolution() → Resolution
标题为“getSceneResolution() → Resolution”的章节取得目前合成畫面的解析度。
回傳值: Resolution 類型的物件。
const resolution = player.getSceneResolution();console.log(resolution.width, resolution.height);replaceImageAsset(filePath:string, assetId:string)
标题为“replaceImageAsset(filePath:string, assetId:string)”的章节將 assetId 處的圖片資源替換為給定路徑的圖片。
Module.FS.writeFile("image.png", imageBytes);player.replaceImageAsset("image.png", "asset#2");player.render(surface);replaceImageAssetData(assetId:string, image:string)
标题为“replaceImageAssetData(assetId:string, image:string)”的章节將 assetId 處的圖片資源替換為虛擬檔案系統中的圖片。
player.replaceImageAssetData("asset#2", "test.png");player.render(surface);replaceFontAsset(filePath:string, assetId:string)
标题为“replaceFontAsset(filePath:string, assetId:string)”的章节將 assetId 處的字型資源替換為虛擬檔案系統中的字型。
player.replaceFontAsset("font.ttf", "asset#2");player.render(surface);replaceCSVAsset(filePath:string, assetId:string)
标题为“replaceCSVAsset(filePath:string, assetId:string)”的章节將 assetId 處的 CSV 資源替換為給定路徑的 CSV。
輕量快速的 Web CSV 解析器與 Cavalry 內建的大型全功能解析器相比功能較為基礎。目前不支援自訂分隔符號和引號。如果您在使用 CSV 檔案時遇到問題,請告知我們。
Module.FS.writeFile("test.csv", csvBytes);player.replaceCSVAsset("text.csv", "asset#2");player.render(surface);replaceCSVAssetData(assetId:string, csvData:string)
标题为“replaceCSVAssetData(assetId:string, csvData:string)”的章节將 assetId 處的 CSV 資源替換為 CSV 資料。
請注意 — 輕量快速的 Web CSV 解析器與 Cavalry 內建的大型全功能解析器相比功能較為基礎。目前不支援自訂分隔符號和引號。如果您在使用 CSV 檔案時遇到問題,請告知我們。
player.replaceCSVAssetData("asset#2", "test.csv");player.render(surface);replaceExcelAsset(filePath:string, assetId:string)
标题为“replaceExcelAsset(filePath:string, assetId:string)”的章节將 assetId 處的 Excel 資源替換為虛擬檔案系統中的 Excel 資源。
player.replaceExcelAsset("sheet.xlsx", "asset#2");player.render(surface);replaceExcelAssetData(assetId:string, csvData:string)
标题为“replaceExcelAssetData(assetId:string, csvData:string)”的章节將 assetId 處的 CSV 資源替換為 CSV 資料。
player.replaceExcelAssetData("asset#2", "test.csv");player.render(surface);replaceSVGAsset(filePath:string, assetId:string)
标题为“replaceSVGAsset(filePath:string, assetId:string)”的章节將 assetId 處的 SVG 資源替換為虛擬檔案系統中的 SVG 資源。
player.replaceExcelAsset("image.svg", "asset#2");player.render(surface);replaceSVGAssetData(assetId:string, svgData:string)
标题为“replaceSVGAssetData(assetId:string, svgData:string)”的章节將 assetId 處的 SVG 資源替換為 SVG 資料。
player.replaceExcelAssetData("asset#2", "image.svg");player.render(surface);replaceGoogleSheet(sheetUrl:string, assetId:string)
标题为“replaceGoogleSheet(sheetUrl:string, assetId:string)”的章节Google Sheets 會自動載入。使用 replaceGoogleSheet 在執行時替換 Google Sheet 資源。
將 Google Sheet 的共用權限設定為「知道連結的任何人」以避免錯誤。
const sheetId = "UNIQUE_ID_OF_GOOGLE_SHEET"const url = `https://docs.google.com/spreadsheets/d/${sheetId}`player.replaceGoogleSheet(url, assetId);getActiveComp() → string
标题为“getActiveComp() → string”的章节回傳目前活動合成畫面的 ID。
const compId = player.getActiveComp();getInConnection(layerId:string, attrId:string) → string
标题为“getInConnection(layerId:string, attrId:string) → string”的章节回傳屬性的輸入連線。如果該屬性沒有輸入,則回傳空字串。
const path = player.getInConnection("basicShape#2", "scale");disconnect(fromLayerId:string, fromAttrId:string, toLayerId:string, toAttrId:string)
标题为“disconnect(fromLayerId:string, fromAttrId:string, toLayerId:string, toAttrId:string)”的章节移除屬性之間的連線。當連線被移除時回傳空字串。
player.disconnect("basicShape#2", "position", "basicShape#2", "scale");const path = player.getInConnection("basicShape#2", "scale"); console.log("Connection for Scale Attribute after disconnect is", path);setCompTimeMultiplier(multiplier:number)
标题为“setCompTimeMultiplier(multiplier:number)”的章节設定合成畫面的時間倍率。將其設定為 2.0 意味著合成畫面將以兩倍速度播放。
player.setCompTimeMultiplier(0.5);setActiveComp(compId:string)
标题为“setActiveComp(compId:string)”的章节透過 ID 設定活動合成畫面。如果找不到該合成畫面,則會記錄警告。
setAttributeViaStringify(layerId:string, jsonString:string)
标题为“setAttributeViaStringify(layerId:string, jsonString:string)”的章节透過傳遞 JSON 字串來設定圖層上的一個或多個屬性。
參數:
layerId: 要修改的圖層的 ID。jsonString: 將屬性名稱對應到值的 JSON 字串。
const color = JSON.stringify({ backgroundColor: { r: 255, g: 0, b: 128 } });player.setAttributeViaStringify(player.getActiveComp(), color);player.render(surface);const playbackStep = JSON.stringify({ playbackStep: 2 });player.setAttributeViaStringify(player.getActiveComp(), playbackStep);setAttribute(layerId:string, attrId:string, value:any)
标题为“setAttribute(layerId:string, attrId:string, value:any)”的章节設定圖層上的屬性。值類型會自動檢測並轉換為適當的 C++ 類型。
參數:
layerId: 要修改的圖層的 ID。attrId: 要修改的屬性。value: 要設定的值。支援的類型:- 數字: 根據屬性類型自動檢測為
double、int或enum - 字串: 用於文字和富文字屬性
- 布林值: 用於布林屬性
- 陣列:
[x, y]用於 2D 向量,[x, y, z]用於 3D 向量,[r, g, b]或[r, g, b, a]用於顏色 - 物件:
{x, y}或{x, y, z}用於向量,{r, g, b}或{r, g, b, a}用於顏色
- 數字: 根據屬性類型自動檢測為
// Numbers (auto-detected as double, int, or enum based on attribute type)player.setAttribute("textShape#1", "fontSize", 24.5);player.setAttribute("duplicator#1", "count", 10);player.setAttribute("dropdown#1", "alignment", 2);// Stringsplayer.setAttribute("textShape#1", "text", "Hello World!");// Booleansplayer.setAttribute("shape#1", "visible", true);// 2D vectors (array or object syntax)player.setAttribute("shape#1", "position", [100, 200]);player.setAttribute("shape#1", "scale", {x: 1.5, y: 2.0});// 3D vectors (array or object syntax)player.setAttribute("camera#1", "position", [10, 20, 30]);player.setAttribute("transform#1", "rotation", {x: 0, y: 45, z: 0});// Colours (array or object syntax, values 0-255)player.setAttribute("shape#1", "fillColor", [255, 0, 0, 255]);player.setAttribute("shape#1", "material.materialColor", {r: 128, g: 64, b: 255, a: 255});// Composition background colourplayer.setAttribute(player.getActiveComp(), "backgroundColor", {r: 30, g: 30, b: 30});getAttribute(layerId:string, attrId:string) → any
标题为“getAttribute(layerId:string, attrId:string) → any”的章节從圖層取得屬性值。回傳類型取決於屬性類型。
參數:
layerId: 要查詢的圖層的 ID。attrId: 要取得的屬性。
回傳值: 屬性值。回傳類型:
- double/int/enum:
number - bool:
boolean - string/richText:
string - double2/int2:
[x, y]陣列 - double3/int3:
[x, y, z]陣列 - color:
{r, g, b, a}物件,值範圍為 0-255
// Numbersconst fontSize = player.getAttribute("textShape#1", "fontSize");const count = player.getAttribute("duplicator#1", "count");// Stringsconst text = player.getAttribute("textShape#1", "text");// Booleansconst isVisible = player.getAttribute("shape#1", "visible");// 2D vectors (returns array)const position = player.getAttribute("shape#1", "position");console.log(`Position: ${position[0]}, ${position[1]}`);// 3D vectors (returns array)const rotation = player.getAttribute("transform#1", "rotation");console.log(`Rotation: ${rotation[0]}, ${rotation[1]}, ${rotation[2]}`);// Colours (returns object)const color = player.getAttribute("shape#1", "material.materialColor");console.log(`Color: R=${color.r}, G=${color.g}, B=${color.b}, A=${color.a}`);// Convert colour to CSS hexconst hex = "#" + [color.r, color.g, color.b] .map(x => x.toString(16).padStart(2, "0")) .join("");getAttributeViaStringify(layerId:string, attrId:string) → string
标题为“getAttributeViaStringify(layerId:string, attrId:string) → string”的章节將任何屬性值作為 JSON 字串取得。這是沒有特定 getter 的複雜屬性類型的備用方法。
參數:
layerId: 要查詢的圖層的 ID。attrId: 要取得的屬性。
回傳值: 屬性值的 JSON 字串表示。
const jsonString = player.getAttributeViaStringify("basicShape#1", "customData");const data = JSON.parse(jsonString);getAttributeName(layerId:string, attrId:string) → string
标题为“getAttributeName(layerId:string, attrId:string) → string”的章节取得屬性名稱。可以在 Cavalry 中透過在Attribute Editor中右鍵點擊屬性並選擇「Rename…」來設定屬性名稱。
參數:
layerId: 要查詢的圖層的 ID。attrId: 要取得的屬性。
回傳值: 屬性名稱或屬性 ID。
const name = player.getAttributeName("basicShape#1", "position");getAttributeDefinition(layerId:string, attrId:string) → AttributeDefinition
标题为“getAttributeDefinition(layerId:string, attrId:string) → AttributeDefinition”的章节取得有關屬性的詳細元資料,包括其類型、約束、預設值和結構。這對於建立需要了解屬性特徵的動態使用者介面特別有用。
參數:
layerId: 要查詢的圖層的 ID。attrId: 要檢查的屬性。
回傳值: 包含全面屬性元資料的 AttributeDefinition 物件。
// Get definition for a numeric attribute with constraintsconst sizeDef = player.getAttributeDefinition("pixelateFilter#1", "size");console.log(`Type: ${sizeDef.type}`);console.log(`Default: ${sizeDef.defaultValue}`);console.log(`Min: ${sizeDef.numericInfo.hardMin}, Max: ${sizeDef.numericInfo.hardMax}`);// Use definition to create appropriate UIif (sizeDef.type === "int" && sizeDef.numericInfo.hasHardMin && sizeDef.numericInfo.hasHardMax) { // Create slider with proper min/max values const slider = document.createElement("input"); slider.type = "range"; slider.min = sizeDef.numericInfo.hardMin; slider.max = sizeDef.numericInfo.hardMax; slider.value = JSON.parse(sizeDef.defaultValue);}AttributeDefinition 屬性:
interface AttributeDefinition { attrId: string; // Attribute id type: string; // Data type (int, double, bool, string, Color, etc.) prefix: string; // UI prefix text (e.g X, or R) placeholder: string; // UI placeholder text for string attributes defaultValue: string; // JSON-serialized default value isArray: boolean; // Whether this is an array attribute isCompound: boolean; // Whether this has child attributes isDynamic: boolean; // Whether this is dynamically created isAttrReadOnly: boolean; // Whether this is read-only allowsConstrainProportions: boolean; // Whether proportions can be constrained multiline: boolean; // Whether text should be multiline numericInfo: NumericInfo; // Numeric constraint information enumValues: number[]; // Enum option values children: AttributeDefinition[]; // Child attribute definitions}interface NumericInfo { hardMin: number; // Hard minimum constraint hardMax: number; // Hard maximum constraint softMin: number; // Soft minimum constraint softMax: number; // Soft maximum constraint step: number; // Step increment hasHardMin: boolean; // Whether hardMin is set hasHardMax: boolean; // Whether hardMax is set hasSoftMin: boolean; // Whether softMin is set hasSoftMax: boolean; // Whether softMax is set hasStep: boolean; // Whether step is set}getControlCentreAttributes(compId:string) → [string]
标题为“getControlCentreAttributes(compId:string) → [string]”的章节取得給定合成畫面的所有控制中心屬性路徑清單。
參數:
compId: 要查詢的合成畫面的 ID(通常從getActiveComp()取得)。
回傳值: 表示屬性路徑的字串陣列,格式為 "layerId.attributeId"。
// Get active composition and its control centre attributesconst activeComp = player.getActiveComp();const attributes = player.getControlCentreAttributes(activeComp);console.log("Control Centre Attributes:", attributes);// Output example: [ "pixelateFilter#1.size", "pixelateFilter#1.outline", "pixelateFilter#1.borderColor" ]// Generate dynamic UI for each Attributeattributes.forEach((attrPath) => { const [layerId, attrId] = attrPath.split("."); // Get attribute definition to determine UI type const definition = player.getAttributeDefinition(layerId, attrId); // Create appropriate control based on type switch (definition.type) { case "int": // Create integer input or slider break; case "bool": // Create checkbox break; case "color": // Create color picker break; // ... handle other types }});getFontAxisInfo(fontName:string, fontStyle:string) → FontAxisInfo
标题为“getFontAxisInfo(fontName:string, fontStyle:string) → FontAxisInfo”的章节取得給定字型的可變字型軸資訊。可變字型包含可調節的軸(如寬度、字重、傾斜度),允許對字型的外觀進行精細控制。此方法回傳有關每個可用軸的元資料,包括其名稱、標籤、範圍和目前值。
參數:
fontName: 字型族名稱(例如 “Roboto”、“Inter”)。fontStyle: 字型樣式(例如 “Regular”、“Bold”)。
回傳值: FontAxisInfo 物件陣列,每個可變字型軸對應一個。如果字型不是可變字型或沒有軸,則回傳空陣列。
// Query available fonts and their stylesconst fonts = player.queryFonts();const styles = player.getFontStyles(fonts[0]);// Get axis information for a variable fontconst axisInfo = player.getFontAxisInfo("Roboto Flex", "Regular");if (!axesInfo.length) { console.warn("This font has no variable axes (not a variable font)");} else { console.log(`Font has ${axisInfo.length} variable axes`); console.log(axisInfo) // Create sliders for each axis axisInfo.forEach(axis => { const slider = document.createElement("input"); slider.type = "range"; slider.min = axis.minValue; slider.max = axis.maxValue; slider.value = axis.currentValue; slider.addEventListener("input", (e) => { // Update font axis value when slider changes console.log(`${axis.axisName} set to ${e.target.value}`); }) })}fetchAndProcess(zipUrl:string) → Promise<void>
标题为“fetchAndProcess(zipUrl:string) → Promise<void>”的章节擷取並處理 ZIP 檔案,解壓其內容並在播放器中建立圖片序列資源。
參數:
zipUrl(字串): 要擷取和處理的 ZIP 檔案的 URL。
await player.fetchAndProcess("/url/to/ImageSequence.zip");播放控制
标题为“播放控制”的章节Web Player 提供了一個原生播放管理器來處理畫格計時和渲染。
播放模式
标题为“播放模式”的章节提供兩種播放模式:
- 精確模式(預設): 基於牆上時鐘的計時,跳過畫格以保持正確的時序。最適合音訊同步和即時播放。
- 平滑模式: 按順序顯示每一畫格,但在複雜場景上可能比即時播放慢。最適合逐畫格精確預覽。
基本播放模式
标题为“基本播放模式”的章节// Start playbackplayer.play();// Run the playback loopfunction runPlaybackLoop() { const tick = (timestamp) => { if (!player.isPlaying()) { return; } const status = player.tick(surface, timestamp); // Update UI if needed if (status.frameChanged) { console.log(status) } requestAnimationFrame(tick); }; requestAnimationFrame(tick);}runPlaybackLoop();// Stop playbackplayer.stop();play()
标题为“play()”的章节從目前畫格位置開始播放。在啟動 requestAnimationFrame 迴圈之前呼叫此方法。
player.play();runPlaybackLoop();stop()
标题为“stop()”的章节停止播放。目前畫格位置被保留,因此再次呼叫 play() 將從停止的位置恢復播放。
player.stop();cancelAnimationFrame(animationFrameId);toggle()
标题为“toggle()”的章节切換播放狀態。如果正在播放,則停止播放。如果已停止,則開始播放。
player.toggle();console.log(player.isPlaying() ? "Stop" : "Play");isPlaying() → boolean
标题为“isPlaying() → boolean”的章节回傳目前是否正在播放。
console.log("Playback is running?", player.isPlaying());tick(surface:SkSurface, timestampMs:number) → PlaybackStatus
标题为“tick(surface:SkSurface, timestampMs:number) → PlaybackStatus”的章节根據經過的時間推進播放並渲染目前畫格。這應該從 requestAnimationFrame 回呼中呼叫。
參數:
surface: 要渲染到的 Skia 表面。timestampMs: 來自requestAnimationFrame的時間戳記(DOMHighResTimeStamp)。
回傳值: 包含目前播放狀態的 PlaybackStatus 物件。
const tick = (timestamp) => { if (!player.isPlaying()) { return; } const status = player.tick(surface, timestamp); if (status.frameChanged) { console.log("Frame", status.currentFrame); } requestAnimationFrame(tick);}setFrame(frame:number)
标题为“setFrame(frame:number)”的章节設定目前畫格。畫格號會被限制在有效的播放範圍內(getStartFrame() 和 getEndFrame() 之間)。
注意: 如果正在播放,請在設定畫格之前停止播放,然後可以選擇重新啟動。
// Seek to a specific frameconst wasPlaying = player.isPlaying();player.stop();player.setFrame(50);player.render(surface);if (wasPlaying) { player.play(); runPlaybackLoop();}getFrame() → number
标题为“getFrame() → number”的章节回傳目前畫格號。
const currentFrame = player.getFrame();console.log("Current frame", currentFrame);getStartFrame() → number
标题为“getStartFrame() → number”的章节回傳目前合成畫面播放範圍的起始畫格。
const startFrame = player.getStartFrame();console.log(startFrame);getEndFrame() → number
标题为“getEndFrame() → number”的章节回傳目前合成畫面播放範圍的結束畫格。
const endFrame = player.getEndFrame();console.log(endFrame);getFPS() → number
标题为“getFPS() → number”的章节回傳目前合成畫面的每秒畫格數(FPS)。
const fps = player.getFPS();console.log(`Scene runs at ${fps} FPS`);getActualFPS() → number
标题为“getActualFPS() → number”的章节回傳活動播放期間測量的播放 FPS。這是在 1 秒視窗內計算的,可用於監控播放效能。
const actualFps = player.getActualFPS();console.log(`${actualFps.toFixed(1)} FPS`);setPlaybackMode(mode:number)
标题为“setPlaybackMode(mode:number)”的章节設定播放模式。
參數:
mode:0為精確模式(預設),1為平滑模式。
// Use Smooth mode for frame-accurate previewplayer.setPlaybackMode(1);// Use Accurate mode for real-time playbackplayer.setPlaybackMode(0);getPlaybackMode() → number
标题为“getPlaybackMode() → number”的章节回傳目前播放模式。0 = 精確模式,1 = 平滑模式。
const mode = player.getPlaybackMode();console.log(mode === 0 ? "Accurate" : "Smooth");setLoop(loop:boolean)
标题为“setLoop(loop:boolean)”的章节設定播放到達結束畫格時是否應循環播放。
參數:
loop:true啟用循環(預設),false在結束時停止。
player.setLoop(false);isLooping() → boolean
标题为“isLooping() → boolean”的章节回傳是否啟用了播放循環。
console.log("Playback will loop?", player.isLooping());incrementFrame()
标题为“incrementFrame()”的章节手動推進到下一畫格。這是一個用於手動畫格控制的低階方法 — 對於大多數用例,請使用 tick() 代替,它會自動處理計時。
player.incrementFrame();player.render(surface);資料類型
标题为“資料類型”的章节Resolution
标题为“Resolution”的章节interface Resolution { width: number; height: number;}表示場景或畫布解析度。
ColorRGBA
标题为“ColorRGBA”的章节interface ColorRGBA { r: number; // Red component (0-255) g: number; // Green component (0-255) b: number; // Blue component (0-255) a: number; // Alpha component (0-255)}表示 RGBA 顏色值,各分量範圍為 0-255。透過 getAttribute() 取得顏色屬性時回傳。
const color = player.getAttribute("shape#1", "fillColor");// Convert to CSS hex colourconst hex = "#" + [color.r, color.g, color.b] .map(x => x.toString(16).padStart(2, "0")) .join("");// Set a colour using an objectplayer.setAttribute("shape#1", "fillColor", {r: 255, g: 128, b: 0, a: 255});Double2
标题为“Double2”的章节interface Double2 { x: number; // X component y: number; // Y component}表示雙精度 2D 向量。請注意,getAttribute() 為了方便,將 2D 向量作為陣列 [x, y] 回傳。
// getAttribute returns an array [x, y]const position = player.getAttribute("shape#1", "position");console.log(`Position: (${position[0]}, ${position[1]})`);// Set a new position using array syntaxplayer.setAttribute("shape#1", "position", [position[0] + 10, position[1] + 20]);// Or use object syntaxplayer.setAttribute("shape#1", "position", {x: 100, y: 200});Double3
标题为“Double3”的章节interface Double3 { x: number; // X component y: number; // Y component z: number; // Z component}表示雙精度 3D 向量。請注意,getAttribute() 為了方便,將 3D 向量作為陣列 [x, y, z] 回傳。
// getAttribute returns an array [x, y, z]const cameraPos = player.getAttribute("camera#1", "position");console.log(`Camera at: (${cameraPos[0]}, ${cameraPos[1]}, ${cameraPos[2]})`);// Set camera position using array syntaxplayer.setAttribute("camera#1", "position", [ cameraPos[0], cameraPos[1], cameraPos[2] + 100]);// Or use object syntaxplayer.setAttribute("camera#1", "position", { x: 10, y: 20, z: 30 });FontMetadata
标题为“FontMetadata”的章节interface FontMetadata { registeredFamily: string; // The name used for registration (use with getFontStyles) registeredStyle: string; // Default style name fullName: string; // Full font name from the font file preferredFamily: string; // Preferred family name (may differ from registeredFamily) preferredSubfamily: string; // Preferred subfamily name postscriptName: string; // PostScript name wwsFamilyName: string; // WWS (Weight Width Slope) family name styles: FontStyleInfo[]; // All available styles with CSS-compatible values}包含字型元資料,包括字型名稱表中的名稱變體和所有可用樣式。由 getFontMetadata() 回傳。
const metadata = Module.getFontMetadata("Inter");console.log(metadata.registeredFamily); // "Inter"console.log(metadata.styles); // Array of FontStyleInfo objectsFontStyleInfo
标题为“FontStyleInfo”的章节interface FontStyleInfo { name: string; // Style name (e.g., "Regular", "Bold Italic") weight: number; // CSS font-weight: 100-900 (400=normal, 700=bold) slant: string; // CSS font-style: "normal", "italic", or "oblique" width: number; // CSS font-stretch: 1-9 (5=normal, 3=condensed, 7=expanded)}表示具有 CSS 相容值的字型樣式。使用這些值將 Cavalry 字型樣式對應到 CSS 屬性。
const metadata = Module.getFontMetadata("Roboto");metadata.styles.forEach(style => { // Create CSS font-face rules or match styles console.log(`@font-face { font-family: "${style.name}"; font-weight: ${style.weight}; font-style: ${style.slant}; }`);});FontAxisInfo
标题为“FontAxisInfo”的章节interface FontAxisInfo { axisName: string; // Human-readable name (e.g., "Weight", "Width") axisTag: string; // Four-character axis tag (e.g., "wght", "wdth") minValue: number; // Minimum allowed value for this axis maxValue: number; // Maximum allowed value for this axis defaultValue: number; // Default value for this axis currentValue: number; // Current value for this axis}表示可變字型軸的資訊。可變字型可以有多個軸,允許對字型的外觀進行精細控制。常見的軸包括字重(wght)、寬度(wdth)、傾斜度(slnt)和光學尺寸(opsz)。
// Get variable font axis informationconst axisInfo = player.getFontAxisInfo("Inter", "Regular");console.log(axisInfo)PlaybackStatus
标题为“PlaybackStatus”的章节interface PlaybackStatus { isPlaying: boolean; // Whether playback is currently active currentFrame: number; // The current frame number frameChanged: boolean; // Whether the frame changed since the last tick actualFPS: number; // Measured playback FPS}由 tick() 回傳,提供有關目前播放狀態的資訊。使用 frameChanged 來確定何時更新 UI 元素。
const status = player.tick(surface, timestamp);if (status.frameChanged) { console.log(status.currentFrame); console.log("Frame", status.currentFrame);}if (status.actualFPS > 0) { console.log(`${status.actualFPS.toFixed(1)} FPS`)}PlaybackMode
标题为“PlaybackMode”的章节enum PlaybackMode { Accurate = 0, // Wall-clock based, skips frames to maintain timing Smooth = 1 // Shows every frame, may run slow}定義播放管理器如何推進畫格:
- 精確模式(0): 根據經過的牆上時鐘時間計算正確的畫格。如果渲染較慢,將跳過畫格以保持正確的時序。最適合音訊同步。
- 平滑模式(1): 當足夠的時間過去時,一次精確推進一畫格。從不跳畫格,但在複雜場景上播放可能比即時更慢。
// Check current modeconsole.log(player.getPlaybackMode());// Set to Smooth mode for frame-by-frame previewplayer.setPlaybackMode(1);// Set to Accurate mode for real-time playbackplayer.setPlaybackMode(0);Cavalry Web Player 會自動為某些與資源相關的操作派發事件。這些事件允許自動載入資源和其他整合場景。
cavalryAutoLoadAsset
标题为“cavalryAutoLoadAsset”的章节事件類型: CustomEvent
當 Cavalry Web Player 檢測到場景檔案中需要從 Web 伺服器載入到虛擬檔案系統的圖片或字型資源時,自動派發。
事件詳情:
interface CavalryAutoLoadAssetDetail { type: "image" | "font" | "csv" | "svg" | "excel" | "googlesheet"; assetId: string; // Asset ID from the scene file (e.g., "asset#2") filename: string; // Extracted filename (e.g., "3.jpg" or "font.ttf")}// Listen for automatic asset loading eventswindow.addEventListener("cavalryAutoLoadAsset", async (event) => { console.log("Auto-loading asset:", event.detail); const path = "/path/to/assets/" if (event.detail.type === "image") { const { assetId, filename } = event.detail; const response = await fetch(path + filename); if (!response.ok) { throw new Error(`Could not find image ${filename}`); } const imageData = await response.arrayBuffer(); // Write to virtual filesystem Module.FS.writeFile(filename, new Uint8Array(imageData)); // Load the asset player.replaceImageAsset(filename, assetId); // Re-render to show the image player.render(surface); console.log(`Auto-loaded image: ${filename} for ${assetId}`); } if (event.detail.type === "font") { const { assetId, filename } = event.detail; const response = await fetch(path + filename); if (!response.ok) { throw new Error(`Could not find font ${filename}`); } const fontData = await response.arrayBuffer(); // Write to virtual filesystem Module.FS.writeFile(filename, new Uint8Array(fontData)); // Load the font into the font registry Module.loadFont(filename); // Re-render to show font changes player.render(surface); console.log(`Auto-loaded font: ${filename} for ${assetId}`); }});工作原理:
- 當載入 Cavalry 場景(.cv 檔案)時,Web Player 會掃描資源定義
- 具有
"type": "file"和受支援擴展名的檔案資源會觸發此事件:- 圖片檔案: png、jpg、jpeg、exr、webp
- 字型檔案: ttf、otf、woff、woff2
- 事件提供資源 ID 和提取的檔案名稱供應用程式處理
- 事件處理程式應從 Web 伺服器擷取檔案並將其寫入虛擬檔案系統
- 然後呼叫適當的 API 載入資源:
- 圖片:
replaceImageAsset(filename, assetId) - 字型:
Module.loadFont(filename)
- 圖片:
自動載入:
- Google Sheets 資源 會自動處理,不需要此事件
- 圖片和字型資源 需要 JavaScript 處理,因為它們需要從 Web 伺服器擷取
檔案路徑解析: Web Player 從 Cavalry 的內部路徑中提取檔案名稱,例如:
"@assets/image.jpg"→"image.jpg""@assets/Changa_One/ChangaOne-Regular.ttf"→"ChangaOne-Regular.ttf""subfolder/font.ttf"→"font.ttf"
事件處理程式應嘗試公共位置,如根目錄(./filename)和 Assets 資料夾(./Assets/filename)。
範例實作:
- 圖片載入: 請參見
src/wasm/cavalry-web-player/image-asset/中的圖片資源範例 - 字型載入: 請參見
src/wasm/cavalry-web-player/custom-font/中的自訂字型範例