Синглтон JavaScriptBridge
В веб-сборках синглтон JavaScriptBridge обеспечивает взаимодействие с JavaScript и веб-браузерами и может использоваться для реализации некоторых функций, уникальных для веб-платформы.
Взаимодействие с JavaScript
Иногда при экспорте Godot для Интернета (Web) может возникнуть необходимость взаимодействия с внешним кодом JavaScript, например сторонними SDK, библиотеками или просто для доступа к функциям браузера, которые Godot не предоставляет напрямую.
Синглтон JavaScriptBridge предоставляет методы для обертывания собственного объекта JavaScript в Godot JavaScriptObject, который пытается выглядеть естественно в контексте сценариев Godot (например, GDScript и C#).
Метод 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, вызывая их методы и извлекая (или даже устанавливая) их свойства.
Базовые типы (int (целые), floats (числа с плавающей точкой), strings (строки), booleans (логические значения)) автоматически преобразуются (числа с плавающей точкой могут терять точность при преобразовании из Godot в JavaScript). Всё остальное (например, objects (объекты), arrays (массивы), functions (функции)) рассматривается как объекты JavaScriptObjects.
Обратные вызовы
Вызов JavaScript кода из Godot это прекрасно, но иногда вместо этого тебе необходимо вызвать функцию Godot из JavaScript.
Этот случай немного сложнее. JavaScript использует сборку мусора, а Godot использует подсчёт ссылок для управления памятью. Это означает, что вам придётся явно создавать обратные вызовы (которые возвращаются как объекты JavaScriptObject) и сохранять ссылки на них.
Аргументы, передаваемые JavaScript в функцию обратного вызова, будут переданы как один Array Godot.
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 object, преобразованный в массив. В противном случае метод обратного вызова не будет вызван.
Вот еще один пример, который запрашивает у пользователя Разрешение на уведомление и асинхронно ожидает доставки уведомления, если разрешение предоставлено:
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.")
Могу ли я использовать свою любимую библиотеку?
Скорее всего, да. Сначала вам нужно подключить свою библиотеку к странице. Вы можете настроить Head Include во время экспорта (см. ниже) или даже write your own template (написать свой собственный шаблон).
В примере ниже мы настраиваем Head Include для добавления внешней библиотеки (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 работает аналогично одноимённой функции JavaScript. Он принимает строку в качестве аргумента и выполняет её как код JavaScript. Это позволяет взаимодействовать с браузером способами, недоступными при использовании скриптовых языков, интегрированных в Godot.
func my_func():
JavaScriptBridge.eval("alert('Calling JavaScript per GDScript!');")
private void MyFunc()
{
JavaScriptBridge.Eval("alert('Calling JavaScript per C#!');")
}
Значение последнего выражения Javascript превращается в значение GDScript и возвращается eval() при определенных обстоятельствах:
JavaScript
numberвозвращается как float (число с плавающей точкой)JavaScript
booleanвозвращается как bool (булевое значение)JavaScript
stringвозвращается как StringJavaScript
ArrayBuffer,TypedArray, иDataViewвозвращаются как PackedByteArray
func my_func2():
var js_return = JavaScriptBridge.eval("var myNumber = 1; myNumber + 2;")
print(js_return) # prints '3.0'
private void MyFunc2()
{
var jsReturn = JavaScriptBridge.Eval("var myNumber = 1; myNumber + 2;");
GD.Print(jsReturn); // prints '3.0'
}
Любое другое значение в Javascript возвращено как null (ноль).
Шаблоны экспорта HTML5 могут быть созданы без поддержки синглтона для повышения безопасности. В таких шаблонах и на платформах, отличных от HTML5, вызов JavaScriptBridge.eval также вернёт null. Доступность синглтона можно проверить с помощью тега web feature:
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")
private void MyFunc3()
{
if (OS.HasFeature("web"))
{
JavaScriptBridge.Eval("console.log('The JavaScriptBridge singleton is available')");
}
else
{
GD.Print("The JavaScriptBridge singleton is NOT available");
}
}
Совет
Многострочные строки GDScript, окруженные 3 кавычками """, как в 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)
private void MyFunc4()
{
// execute in global execution context,
// thus adding a new JavaScript global variable `SomeGlobal`
JavaScriptBridge.Eval("var SomeGlobal = {};", true);
}
Загрузка файлов
Загрузка файлов (например, сохраненной игры) из Godot Web Export на компьютер пользователя может осуществляться путем прямого взаимодействия с JavaScript, но, поскольку это очень распространенный вариант использования, Godot предоставляет эту функциональность для скриптинга через специальную функцию JavaScriptBridge.download_buffer(), которая позволяет загружать любой сгенерированный буфер.
Вот минимальный пример того, как его использовать:
расширение Узла
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")