JavaScriptBridge 單例物件

在網頁專案建置時,JavaScriptBridge 單例物件允許與 JavaScript 及瀏覽器互動,能用於實作僅限網頁平台的特殊功能。

與 JavaScript 互動

有時將 Godot 匯出為網頁專案時,可能需要與外部 JavaScript 程式碼(如第三方 SDK、函式庫)串接,或存取 Godot 未直接提供的瀏覽器功能。

JavaScriptBridge 單例物件提供方法,可將原生 JavaScript 物件包裝成 Godot 的 JavaScriptObject,讓你在 GDScript 或 C# 中能像操作 Godot 原生物件一樣操作它。

JavaScriptBridge.get_interface() 方法可取得全域作用域的物件。

extends Node

func _ready():
    # Retrieve the `window.console` object.
    var console = JavaScriptBridge.get_interface("console")
    # Call the `window.console.log()` method.
    console.log("test")

JavaScriptBridge.create_object() 方法會透過 JavaScript 的 new 建構式建立新物件。

extends Node

func _ready():
    # Call the JavaScript `new` operator on the `window.Array` object.
    # Passing 10 as argument to the constructor:
    # JS: `new Array(10);`
    var arr = JavaScriptBridge.create_object("Array", 10)
    # Set the first element of the JavaScript array to the number 42.
    arr[0] = 42
    # Call the `pop` function on the JavaScript array.
    arr.pop()
    # Print the value of the `length` property of the array (9 after the pop).
    print(arr.length)

如上所示,將 JavaScript 物件包裝成 JavaScriptObject 後,你就能像操作 Godot 原生物件一樣呼叫其方法,存取或設定其屬性。

基本型別(整數、浮點數、字串、布林值)會自動轉換(浮點數從 Godot 轉換到 JavaScript 時可能會發生精度損失)。其他型別(如物件、陣列、函式)則會視為 JavaScriptObject

回呼函式

從 Godot 呼叫 JavaScript 很方便,但有時你會需要反過來,從 JavaScript 呼叫 Godot 的函式。

這種情境會較為複雜。JavaScript 使用垃圾回收(GC),而 Godot 使用參考計數來管理記憶體。這代表你必須明確建立回呼函式(這些回呼會以 JavaScriptObject 回傳),而且要保存其參考。

由 JavaScript 傳遞給回呼函式的參數會以單一 Godot Array 傳入。

extends Node

# Here we create a reference to the `_my_callback` function (below).
# This reference will be kept until the node is freed.
var _callback_ref = JavaScriptBridge.create_callback(_my_callback)

func _ready():
    # Get the JavaScript `window` object.
    var window = JavaScriptBridge.get_interface("window")
    # Set the `window.onbeforeunload` DOM event listener.
    window.onbeforeunload = _callback_ref

func _my_callback(args):
    # Get the first argument (the DOM event in our case).
    var js_event = args[0]
    # Call preventDefault and set the `returnValue` property of the DOM event.
    js_event.preventDefault()
    js_event.returnValue = ''

警告

透過 JavaScriptBridge.get_interface() 建立的回呼方法(如上例的 _my_callback)**必須**只接受一個 Array 參數,這個參數會是 JavaScript 的 arguments 物件 轉換成的陣列。否則該回呼方法將不會被呼叫。

以下是另一個範例,會請求使用者 通知權限,若允許則會非同步送出通知:

extends Node

# Here we create a reference to the `_on_permissions` function (below).
# This reference will be kept until the node is freed.
var _permission_callback = JavaScriptBridge.create_callback(_on_permissions)

func _ready():
    # NOTE: This is done in `_ready` for simplicity, but SHOULD BE done in response
    # to user input instead (e.g. during `_input`, or `button_pressed` event, etc.),
    # otherwise it might not work.

    # Get the `window.Notification` JavaScript object.
    var notification = JavaScriptBridge.get_interface("Notification")
    # Call the `window.Notification.requestPermission` method which returns a JavaScript
    # Promise, and bind our callback to it.
    notification.requestPermission().then(_permission_callback)

func _on_permissions(args):
    # The first argument of this callback is the string "granted" if the permission is granted.
    var permission = args[0]
    if permission == "granted":
        print("Permission granted, sending notification.")
        # Create the notification: `new Notification("Hi there!")`
        JavaScriptBridge.create_object("Notification", "Hi there!")
    else:
        print("No notification permission.")

我可以在 Godot 使用我喜歡的 JavaScript 函式庫嗎?

通常可以。首先,你必須將函式庫加入網頁。你可以在匯出時自訂 Head Include,或是 撰寫自己的 HTML 範本

在下方範例中,我們自訂 Head Include,從 CDN 加入外部函式庫(如 axios),並加上一段 <script> 標籤來定義自訂函式:

<!-- Axios -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!-- Custom function -->
<script>
function myFunc() {
    alert("My func!");
}
</script>

接著我們就能如同先前範例,在 Godot 內直接存取該函式庫及自訂函式:

extends Node

# Here create a reference to the `_on_get` function (below).
# This reference will be kept until the node is freed.
var _callback = JavaScriptBridge.create_callback(_on_get)

func _ready():
    # Get the `window` object, where globally defined functions are.
    var window = JavaScriptBridge.get_interface("window")
    # Call the JavaScript `myFunc` function defined in the custom HTML head.
    window.myFunc()
    # Get the `axios` library (loaded from a CDN in the custom HTML head).
    var axios = JavaScriptBridge.get_interface("axios")
    # Make a GET request to the current location, and receive the callback when done.
    axios.get(window.location.toString()).then(_callback)

func _on_get(args):
    OS.alert("On Get")

eval 介面

eval 方法的功能與 JavaScript 同名函式類似。它接受一個字串作為參數並將其作為 JavaScript 程式碼執行。這讓你可以以 Godot 腳本實現 Godot 內建腳本語言無法做到的瀏覽器互動。

func my_func():
    JavaScriptBridge.eval("alert('Calling JavaScript per GDScript!');")

特定情況下,最後一個 JavaScript 陳述式的值會轉換為 GDScript 值並由 eval() 回傳:

  • JavaScript number 型會以 GDScript float 回傳

  • JavaScript boolean 型會以 GDScript bool 回傳

  • JavaScript string 型會以 GDScript String 回傳

  • JavaScript ArrayBufferTypedArray 以及 DataView 型會以 PackedByteArray 回傳

func my_func2():
    var js_return = JavaScriptBridge.eval("var myNumber = 1; myNumber + 2;")
    print(js_return) # prints '3.0'

其他 JavaScript 值會回傳為 null

HTML5 匯出範本可 建置 為不支援該單例的版本以提升安全性。若使用這類範本,或是在非 HTML5 平台,呼叫 JavaScriptBridge.eval 也會回傳 null。可以透過 web feature tag 來檢查單例是否可用:

func my_func3():
    if OS.has_feature('web'):
        JavaScriptBridge.eval("""
            console.log('The JavaScriptBridge singleton is available')
        """)
    else:
        print("The JavaScriptBridge singleton is NOT available")

小訣竅

GDScript 的多行字串由三個引號 """ 包圍,如上方 my_func3() 範例所示,可讓 JavaScript 程式碼更易閱讀。

eval 方法也接受第二個可選的布林參數,用於指定是否要在全域執行環境中執行程式碼。預設為 false,以避免污染全域命名空間:

func my_func4():
    # execute in global execution context,
    # thus adding a new JavaScript global variable `SomeGlobal`
    JavaScriptBridge.eval("var SomeGlobal = {};", true)

下載檔案

從 Godot 網頁版下載檔案(例如存檔)到使用者電腦雖然可以直接透過 JavaScript 實作,但由於這是非常常見的需求,Godot 也提供了專用的 JavaScriptBridge.download_buffer() 方法,讓你可以直接從腳本下載任何產生的緩衝區資料。

以下是最簡單的使用範例:

extends Node

func _ready():
    # Asks the user download a file called "hello.txt" whose content will be the string "Hello".
    JavaScriptBridge.download_buffer("Hello".to_utf8_buffer(), "hello.txt")

以下則是更完整的範例,說明如何下載先前儲存的檔案:

extends Node

# Open a file for reading and download it via the JavaScript singleton.
func _download_file(path):
    var file = FileAccess.open(path, FileAccess.READ)
    if file == null:
        push_error("Failed to load file")
        return
    # Get the file name.
    var fname = path.get_file()
    # Read the whole file to memory.
    var buffer = file.get_buffer(file.get_len())
    # Prompt the user to download the file (will have the same name as the input file).
    JavaScriptBridge.download_buffer(buffer, fname)

func _ready():
    # Create a temporary file.
    var config = ConfigFile.new()
    config.set_value("option", "one", false)
    config.save("/tmp/test.cfg")

    # Download it
    _download_file("/tmp/test.cfg")