Blender 新歡 Armory3D - WebAssembly + Zui Canvas UI

本文繼續(xù)講解 Armory Traits 混入式編程的?5 種類型,本文著重介紹其中 Wasm 與 Canvas UI:
1. **haXe** 腳本代碼文件;
2. **Nodes** 使用 Logic Node Editor 可視化編程工具定義的節(jié)點(diǎn)樹;
3. **UI** - User Interface (Canvas trait),使用 Armory2D 用戶界面編輯進(jìn)行可視化編輯;
4. **Bundled** Armory Engine 預(yù)定義 Haxe 腳本,是 `Trait` 類型的擴(kuò)展;
5. **Wasm** 使用 WebAssembly 字節(jié)碼程序;
# ? 源文檔 https://github.com/Jeangowhy/opendocs/blob/main/Haxe.md
??WebAssembly (Wasm)
WebAssembly 字節(jié)碼文件稱為一個(gè) Module,可以使用 Rust/C/C++/Emscripten 編譯得到 WASM 文件。Armory 使用 Wasm 程序以獲取高性的代碼執(zhí)行,WebAssembly modules 只在 HTML5 和使用 Krom 的桌面平臺(tái)有支持,包括 Windows, Linux, macOS。
配置 Render 屬性面板:Armory Project - Modules - Append Khafile 指定要附加到 khafile.js 構(gòu)建腳本的內(nèi)容,指定 Blender 中編寫的腳本名稱即可。比如 khaconfig 腳本,
內(nèi)容如下,用來編譯 Wasm 字節(jié)碼:
官方 wasm_trait_c 示例提供的一個(gè) Wasm 程序,它直接與 Armory API 交互:
Armory examples wasm_call 示范中,就沒有在 Armory Scene Traits 或者 Armory Traits
面板中添加 Wasm 模塊,而是在 Haxe Script 代碼中引用 Bundled 目錄下的 main.wasm 字節(jié)碼文件。`Bundled` 是一個(gè)專用目錄,Armory 用來存放數(shù)據(jù)文件,其路徑硬編碼在源代碼中。數(shù)據(jù)文件,比如 UI 配置文件,canvas.json,在官方 ui_canvas 示例中就可以看到。當(dāng)然也可以存放 Wasm 文件,然后通過 `Data.getBlob(name)` 方法獲取數(shù)據(jù)。
添加 `Wasm` 模塊到 Armory Traits 列表時(shí),如果直接點(diǎn)擊 `New Module` 按鈕,就會(huì)跳轉(zhuǎn)到 Web 在線編譯 Wasm 的服務(wù),webassembly.studio。但是界內(nèi)環(huán)境可能用不了,可以按前面介紹的方法調(diào)用本地安裝好的編譯器生成 Wasm。然后將 Wasm 文件放到 `Bundled` 目錄下,再點(diǎn)擊 `Refresh` 刷新 Armory Traits 中的類型列表,就可以看到相應(yīng)的模塊名出現(xiàn)。
1. [WebAssembly Studio](https://github.com/wasdk/WebAssemblyStudio)
2. [WebAssembly Studio](https://esmbly.github.io/WebAssemblyStudio/)
Bundled 這個(gè)目錄下的文件不需手動(dòng)引用,Armory 會(huì)自動(dòng)向 khafile.js 中添加引用:
官方示范中的 .blend 體積非常小,場景同樣只包含一個(gè) Cube,示范案例中不到 100kb,而新創(chuàng)建的文件可能接收 1MB。因?yàn)楣俜降奈募謇淼袅藷o用數(shù)據(jù),Outliner - Orphans Data,但是 Worlds Arm 不能刪除,Armory 需要使用這個(gè)對(duì)象包含的數(shù)據(jù)。另外,使用喜好配置的文件壓縮保存也可以適當(dāng)減小文件,Preferences - File - Save & Load。如果文件已經(jīng)采用非壓縮式保存,那么可以使用 Save As,指定另存選項(xiàng)中的 Compress 即可以重新啟用壓縮式保存。
添加 Armory Traits 腳本時(shí),如果工程 Sources 目錄下已有腳本文件,可以在 Class 列表中指定要使用的 Traits 類型定義,如果列表沒有類型數(shù)據(jù)記錄,可以點(diǎn)擊 Refresh 按鈕刷新以讀取類型信息。
? Armory2D Canvas UI
? * [UI Editor](https://github.com/armory3d/armory/wiki/ui_editor)
? * https://github.com/BlackGoku36/armory-tutorial-download/tree/master/Canvas
Armory2D 是一個(gè)可視化 UI 編輯編輯器,使用 **Zui** UI 框架,此構(gòu)架受 imgui 啟發(fā),Immediate Mode 即時(shí)刷新模式 UI 構(gòu)架,使用 Haxe 和 Kha 實(shí)現(xiàn)。
Zui 源代碼文件有 5 個(gè):
1. Zui.hx 是整個(gè) UI 構(gòu)架的程序邏輯,每一個(gè)用于繪圖函數(shù)就是一個(gè)控件。
2. Nodes.hx 定義了節(jié)點(diǎn)類型結(jié)構(gòu),以及具體的繪圖邏輯。
3. Id.hx 提供了 Zui 控件狀態(tài)數(shù)據(jù)的獲取,CanvasScript.getHandle() 方法返回的控件數(shù)據(jù)。
4. Ext.hx 擴(kuò)展了 `Zui` 類型,增加了 textArea、fileBrowser、inlineRadio、colorWheel 等功能。
5. Themes.hx 提供主題功能。
Armory 對(duì) Zui 進(jìn)行了封裝,添加了一些類型定義,畫布包裝成 `Canvas` 類型,還有彈窗 `Popup`:
并且在 armory.ui.Canvas 內(nèi)置默認(rèn)的字體設(shè)置:
BG-36's Tutorials - Armory Canvas UI 教程中使用了舊版的類型系統(tǒng),應(yīng)該使用 Armory 類空間:
? ? import zui.Canvas.TElement;
? ? import armory.ui.Canvas;
使用 Armory2D 編輯器進(jìn)行可視化界面設(shè)計(jì),Bundled 目錄下 JSON 文件中保存。
Zui 是一個(gè) Immediate Mode UI Library,其本身缺少布局容器的設(shè)計(jì),但是控件可以設(shè)置 Anchor 來影響其放置的位置,Anchor 屬性值與對(duì)位方式如下,錨點(diǎn)的參數(shù)原點(diǎn)由控件的 x y 坐標(biāo)指定:
文字內(nèi)容對(duì)齊方式由 Alignment 屬性指定,0 - Left,1 - Center,2 - Right。
Blender Render 屬性面板中設(shè)置 Armory Project - Window - Resizable 可以啟用窗口的大小調(diào)整功能,以觀察不同的錨點(diǎn)位置下,控件的定位效果。例如,控件的坐標(biāo)設(shè)置為 0 點(diǎn),錨點(diǎn)設(shè)置為 Center,那么控件就會(huì)隨著窗口的大小調(diào)整,而更新為當(dāng)前的居中位置。
JSON 配置文件中 assets 字段記錄導(dǎo)入的圖像、字體資源,Image 控件使用 asset 屬性顯示圖像資源。Button 等等控件可以設(shè)置字體資源,以改變文字外觀。注意,資源文件路徑變動(dòng)可能導(dǎo)致 Armory2D 界面純黑顯示不了任何內(nèi)容。
顏色屬性有四種基本形式:
1. color 背景顏色
1. color_text 文字顏色
2. color_hover 懸停狀態(tài)顏色
3. color_press 按下狀態(tài)顏色
因?yàn)椴捎秘?fù)值色彩模型,-1 表示白色,-16645630 表示黑色,-16713472 表示綠色,默認(rèn)值為 null。
Armory3D SDK 本身已經(jīng)包含 Armory2D 工具,要從 Blender 中啟動(dòng) Armory2D editor,只需要添加一個(gè) UI Trait,并編輯它。例如,Scene 屬性面板中向 Armory Scene Traits 列表添加一個(gè) UI Trait。然后點(diǎn)擊編輯按鈕打開 Armory2D 編輯器。
或者直接執(zhí)行 Krom 加載 Armory2D:
? ? Krom.exe C:\HaxeToolkit\armsdk\lib\armory_tools\armory2d\d3d11
添加 UI 配置時(shí),點(diǎn)擊 `New Canvas`,比如命名為 MyCanvas,就會(huì)生成一個(gè)畫布配置文件:Bundled\canvas\MyCanvas.json,內(nèi)容包括畫布的名稱、坐標(biāo)、寬高、主題、元素、資源文件:
點(diǎn)擊 `Edit Canvas` 打開 Armory2D 工具,界面如下:

1. 左側(cè),是可用的 UI 組件列表,如 Button、Images、Text、Slider 等等;
2. 中間,是畫面設(shè)計(jì)區(qū),可以將左側(cè)的組件拖放到設(shè)計(jì)區(qū),進(jìn)行 UI 布局設(shè)計(jì);
3. 右側(cè),UI 工程保存,組件屬性、主題設(shè)置、資源文件導(dǎo)入,以及喜好配置,如 UI 界面縮放、參考格大小;
Project 面板中的按鈕使用,及注意事項(xiàng):
- **Current File** 指定當(dāng)前操作的配置文件,如果在設(shè)置了此值,相對(duì)路徑是 Krom 主程序所在目錄。
- **Save** 按鈕保存配置文件,如果指定的 Current File,就應(yīng)該使用絕對(duì)路徑。
- **Load** 按鈕用來加載配置文件,如果 Current File 使用了相對(duì)路徑,則可能導(dǎo)致程序異常。
- **New**? 按鈕在 Canvas 分組,用來創(chuàng)建新的 Canvas 配置,Width 和 Height 指定畫布大小。
另外,資源文件路徑錯(cuò)誤也可能導(dǎo)致 Armory2D 界面純黑,不顯示控件。
保存 UI 設(shè)計(jì)后,就會(huì)生成主題配置文件和資源列表文件 MyCanvas.files。
導(dǎo)入的資源文件,如圖像可以作為 Image 控件的背景。
Canvas UI 控件的 Script - Event 屬性指定一個(gè)事件名,點(diǎn)擊時(shí)會(huì)觸發(fā)此事件,比如 `save_btn`。然后,在需要處理此事件的 Haxe 腳本中調(diào)用 `Event.add("save_btn", save);` 注冊(cè)一個(gè)處理函數(shù)。
所有激活狀態(tài)的 UI 界面都會(huì)在程序運(yùn)行時(shí)顯示,控件的事件處理使用的是觀察者編程模式,即需要處理什么事件,就使用 `Event.add()` 方法注冊(cè)相應(yīng)的事件處理函數(shù)。Observer Pattern 用來解耦 UI 與程序邏輯的非常好用的工具,UI 控件可以任意指定事件名稱,處理與否完全取決于程序邏輯。
Canvas UI 控件可以通過 `CanvasScript` 對(duì)象來獲取引用,將對(duì)控制的屬性進(jìn)行修改,就像 Web 腳本編程中對(duì) HTML 元素的操作一樣。但是場景中可能存在激活多個(gè) Canvas UI 的情況,通過 Scene API 獲取到的 CanvasScript 對(duì)象總是為 Armory Traits 列表中最上面(靠前)的那一個(gè)。
以下是 CanvasTrait.hx 演示代碼,假定場景中 Canvas UI 設(shè)計(jì)包含 Image 和 Button 控件各一個(gè)。點(diǎn)擊 Button 觸發(fā) on_click() 處理函數(shù),并切換 Image 的顯示狀態(tài):
Haxe 4.0.0 開始支持 Arrow functions,也支持省略參數(shù)和 Nullability。但是也不能將在可省略參數(shù)的函數(shù)當(dāng)作 'onEvent' 傳給 Event.add() 方法,這是基本的函數(shù)簽名要求。直接調(diào)用則完全可以。
Zui 作為一個(gè)立即模式的 UI 框架,特點(diǎn)就表現(xiàn)在控件的屬性值的獲取上,另一個(gè)特點(diǎn)就是事件的處理機(jī)制。
GUI 設(shè)計(jì)有兩種模式:
1. RMGUI(Retained Mode Graphics User Interface),絕大多數(shù)應(yīng)用程序是用的這種模式。
2. IMGUI(Immediate Mode Graphics User Interface),絕大多數(shù)游戲是這類模式。
保留模式,是指 UI 控件的狀態(tài)保留,IMGUI 則不會(huì)保留控件狀態(tài),因此代碼實(shí)現(xiàn)更簡潔。由于 IMGUI 控件無狀態(tài),所以就不能直接給控件注冊(cè)事件處理函數(shù),而需要使用 Observer Pattern 編程模式用來解耦 UI 與程序邏輯,UI 控件可以任意指定事件名稱,處理與否完全取決于程序邏輯。所有激活狀態(tài)的 UI 界面都會(huì)在程序運(yùn)行時(shí)顯示,控件的事件處理使用的是觀察者編程模式,即需要處理什么事件,就使用 `Event.add()` 方法注冊(cè)相應(yīng)的事件處理函數(shù),同一個(gè) trait 中同名事件的第一個(gè)注冊(cè)的處理函數(shù)才會(huì)獲得調(diào)用機(jī)會(huì)。
立即模式下,所以控件在每一幀中都會(huì)重新繪制,這就導(dǎo)致它占用更高的 CPU 時(shí)間,當(dāng)然這是相對(duì)的,只要保持界面整潔,并不需要多少的額外 CPU 時(shí)間。另一方面,由于它更簡潔,使得它在游戲開發(fā)領(lǐng)域中備受歡迎。
因此,要獲取 Zui 控件的值,比如 Slider、ProgressBar、InputText 等等控件的值,首先就需要保存到內(nèi)存的一個(gè)位置上,以 `Handle` 類型的形式,這個(gè)位置可以通過 CanvasScript.getHandle()?方法返回的控件數(shù)據(jù) 方法獲取,根據(jù)控件類型從返回的數(shù)據(jù)中獲取相應(yīng)的字段:
Zui 定義的 Handle 類型的數(shù)據(jù)字段:
ComboBox 和 Radio 控件需要在 Text 屬性中指定選項(xiàng)字符串,使用分號(hào)分隔,如 Bad; Apple,獲取控件的 position 值,默認(rèn)為 0,表示選擇第一個(gè)選項(xiàng)。
進(jìn)度條有兩種,Progress_bar,CProgress_bar,但它們都沒有狀態(tài)數(shù)據(jù),getHandle() 獲取不到數(shù)據(jù)。而要繪制出指定狀態(tài)的控件,比如環(huán)形進(jìn)行條的百分進(jìn)度值,就需要在更新回調(diào)方法中設(shè)置相應(yīng)的值:
另外,Slider、Keyinput、TextArea 等等都不會(huì)觸發(fā)事件,只有 TextInput 在內(nèi)容改變后按回車時(shí)觸發(fā)。
Armory 可以使用主題功能,默認(rèn)為 Default Light 主題,主要是設(shè)置控件的色彩屬性,替換其默認(rèn)值。如果控件設(shè)置了相應(yīng)的色彩,則主題配置的項(xiàng)目不起作用。主題涉及的控件屬性有三類:
- Text 控件的字體顏色;
- Elements 元素:BUTTON、ACCENT;
- Others 其它:PANEL_BG_COL;
Armory2D 目前還有些未曾完成的功能,其中就有 Timeline 時(shí)間軸動(dòng)畫的支持缺失。
最后注意,傳入方法的 id 沒有對(duì)應(yīng)控件時(shí),getHandle() 方法觸發(fā)異常,getElement() 返回 null:
? ? Trace: TypeError: Cannot read property 'id' of null
? ? ? ? at armory_trait_internal_CanvasScript.getHandle (<anonymous>:8132:34)