跳到內容

Web Player API

Cavalry Web Player 和 API 目前處於測試階段,因此可能會有變更。

Cavalry Web Player 可用於在網頁瀏覽器中播放和互動 Cavalry 場景。本文件提供了完整的整合指南和 API 參考。

Web Player 由兩個主要部分組成。

  1. WebAssembly 執行環境
  2. JavaScript 模組

JavaScript 模組用於實例化 Web Player。Web Player 包含 4 個方面。

  1. Module — 匯入的 CavalryWasm 模組的實例
  2. Player — 控制播放、資源管理等功能的 Cavalry 播放器(類似於 Cavalry 的指令碼 API
  3. Canvas — 用作視圖的 canvas 元素
  4. 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);

使用以下方式匯入模組:

// 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);

將字型從虛擬檔案系統載入到執行環境字型登錄表中。

參數:

  • 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" });

取得已載入字型的詳細元資料,包括所有可用樣式及其 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"

從給定的 URL 載入字型資料並將其載入到執行環境字型登錄表中。

參數:

  • url(字串): 擷取字型資料的 URL。
// Load a font from a URLModule.loadFontData("/url/to/font.ttf");

使用以下方式建立實例:

const player = Module.Cavalry.Make("scene.cv");

當透過 MakeWithPath 載入場景時,其資源將位於 pendingAssets 陣列中。然後可以透過遍歷該陣列來載入資源。

有關實作範例,請參見載入資源

const player = Module.Cavalry.MakeWithPath("scene.cv");console.log(Module.pendingAssets)

向此物件新增 canvas 元素允許 CavalryWasm 模組在 canvas 位於 Shadow DOM 中時找到它。

請參見 Shadow DOM

const id = "my-canvas"const canvas = document.createElement("canvas")canvas.id = idModule.specialHTMLTargets[id] = canvas

構造並回傳一個新的 Cavalry 類別實例。

參數:

  • scenePath(可選): 虛擬檔案系統中場景檔案的路徑。預設為 "scene.cv" 以保持向後相容。
// Load default scene.cvconst player = Module.Cavalry.Make();// Load a specific scene fileconst player = Module.Cavalry.Make("myAnimation.cv");

構造並回傳一個新的 Cavalry 類別實例。

這是 Make 的替代方法。

參數:

  • scenePath: 虛擬檔案系統中場景檔案的路徑。
// Load a specific scene fileconst player = Module.Cavalry.MakeWithPath("scene.cv");

將目前畫格渲染到指定的 WebGL Skia 表面。

player.render(surface);

取得目前合成畫面的解析度。

回傳值: 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);

回傳目前活動合成畫面的 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);

設定合成畫面的時間倍率。將其設定為 2.0 意味著合成畫面將以兩倍速度播放。

player.setCompTimeMultiplier(0.5);

透過 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: 要設定的值。支援的類型:
    • 數字: 根據屬性類型自動檢測為 doubleintenum
    • 字串: 用於文字和富文字屬性
    • 布林值: 用於布林屬性
    • 陣列: [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 }});

取得給定字型的可變字型軸資訊。可變字型包含可調節的軸(如寬度、字重、傾斜度),允許對字型的外觀進行精細控制。此方法回傳有關每個可用軸的元資料,包括其名稱、標籤、範圍和目前值。

參數:

  • 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();

從目前畫格位置開始播放。在啟動 requestAnimationFrame 迴圈之前呼叫此方法。

player.play();runPlaybackLoop();

停止播放。目前畫格位置被保留,因此再次呼叫 play() 將從停止的位置恢復播放。

player.stop();cancelAnimationFrame(animationFrameId);

切換播放狀態。如果正在播放,則停止播放。如果已停止,則開始播放。

player.toggle();console.log(player.isPlaying() ? "Stop" : "Play");

回傳目前是否正在播放。

console.log("Playback is running?", player.isPlaying());

根據經過的時間推進播放並渲染目前畫格。這應該從 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);}

設定目前畫格。畫格號會被限制在有效的播放範圍內(getStartFrame()getEndFrame() 之間)。

注意: 如果正在播放,請在設定畫格之前停止播放,然後可以選擇重新啟動。

// Seek to a specific frameconst wasPlaying = player.isPlaying();player.stop();player.setFrame(50);player.render(surface);if (wasPlaying) { player.play(); runPlaybackLoop();}

回傳目前畫格號。

const currentFrame = player.getFrame();console.log("Current frame", currentFrame);

回傳目前合成畫面播放範圍的起始畫格。

const startFrame = player.getStartFrame();console.log(startFrame);

回傳目前合成畫面播放範圍的結束畫格。

const endFrame = player.getEndFrame();console.log(endFrame);

回傳目前合成畫面的每秒畫格數(FPS)。

const fps = player.getFPS();console.log(`Scene runs at ${fps} FPS`);

回傳活動播放期間測量的播放 FPS。這是在 1 秒視窗內計算的,可用於監控播放效能。

const actualFps = player.getActualFPS();console.log(`${actualFps.toFixed(1)} FPS`);

設定播放模式。

參數:

  • mode: 0 為精確模式(預設),1 為平滑模式。
// Use Smooth mode for frame-accurate previewplayer.setPlaybackMode(1);// Use Accurate mode for real-time playbackplayer.setPlaybackMode(0);

回傳目前播放模式。0 = 精確模式,1 = 平滑模式。

const mode = player.getPlaybackMode();console.log(mode === 0 ? "Accurate" : "Smooth");

設定播放到達結束畫格時是否應循環播放。

參數:

  • loop: true 啟用循環(預設),false 在結束時停止。
player.setLoop(false);

回傳是否啟用了播放循環。

console.log("Playback will loop?", player.isLooping());

手動推進到下一畫格。這是一個用於手動畫格控制的低階方法 — 對於大多數用例,請使用 tick() 代替,它會自動處理計時。

player.incrementFrame();player.render(surface);
interface Resolution { width: number; height: number;}

表示場景或畫布解析度。

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});
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});
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 });
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 objects
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}; }`);});
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)
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`)}
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 會自動為某些與資源相關的操作派發事件。這些事件允許自動載入資源和其他整合場景。

事件類型: 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}`); }});

工作原理:

  1. 當載入 Cavalry 場景(.cv 檔案)時,Web Player 會掃描資源定義
  2. 具有 "type": "file" 和受支援擴展名的檔案資源會觸發此事件:
    • 圖片檔案: png、jpg、jpeg、exr、webp
    • 字型檔案: ttf、otf、woff、woff2
  3. 事件提供資源 ID 和提取的檔案名稱供應用程式處理
  4. 事件處理程式應從 Web 伺服器擷取檔案並將其寫入虛擬檔案系統
  5. 然後呼叫適當的 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/ 中的自訂字型範例