The JavaScriptBridge singleton
In Web-Builds ermöglicht das JavaScriptBridge-Singleton die Interaktion mit JavaScript und Web-Browsern und kann zur Implementierung einiger für die Web-Plattform einzigartiger Funktionen verwendet werden.
Mit JavaScript interagieren
Beim Exportieren von Godot für das Web kann es manchmal notwendig sein, eine Schnittstelle zu externem JavaScript-Code wie SDKs oder Bibliotheken von Drittanbietern einzurichten oder einfach auf Browserfunktionen zuzugreifen, die nicht direkt von Godot bereitgestellt werden.
Das JavaScriptBridge-Singleton bietet Methoden, um ein natives JavaScript-Objekt in ein Godot-JavaScriptObject zu verpacken, das versucht, sich im Kontext von Godot-Skripten (z.B. GDScript und C#) natürlich anzufühlen.
Die Methode JavaScriptBridge.get_interface() ruft ein Objekt im globalen Gültigkeitsbereich ab.
extends Node
func _ready():
# Retrieve the `window.console` object.
var console = JavaScriptBridge.get_interface("console")
# Call the `window.console.log()` method.
console.log("test")
Die JavaScriptBridge.create_object() erzeugt ein neues Objekt über den JavaScript new-Konstruktor.
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)
Wie Sie sehen, können Sie mit JavaScript-Objekten interagieren, indem Sie sie in JavaScriptObject verpacken, als wären sie native Godot-Objekte, ihre Methoden aufrufen und ihre Propertys abfragen (oder sogar setzen).
Basistypen (Ints, Floats, Strings, Bools) werden automatisch konvertiert (Floats können bei der Konvertierung von Godot nach JavaScript an Präzision verlieren). Alles andere (z.B. Objekte, Arrays, Funktionen) wird als JavaScriptObjects selbst betrachtet.
Callbacks
Der Aufruf von JavaScript-Code aus Godot ist praktisch, aber manchmal muss man stattdessen eine Godot-Funktion aus JavaScript aufrufen.
Dieser Fall ist ein wenig komplizierter. JavaScript verlässt sich auf Garbage Collection, während Godot Referenzzählung für die Speicherverwaltung verwendet. Das bedeutet, dass Sie explizit Callbacks erstellen müssen (die selbst als JavaScriptObjects zurückgegeben werden), und dass Sie ihre Referenz behalten müssen.
Argumente, die von JavaScript an das Callback übergeben werden, werden als ein einziges Godot Array übergeben.
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 = ''
Warnung
Callback methods created via JavaScriptBridge.get_interface()
(_my_callback in the above example) must take exactly one Array
argument, which is going to be the JavaScript arguments object
converted to an array. Otherwise, the callback method will not be called.
Hier ist ein weiteres Beispiel, das den Benutzer um die Benachrichtigungserlaubnis bittet und asynchron wartet, um eine Benachrichtigung zu liefern, wenn die Erlaubnis erteilt wird:
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.")
Kann ich meine Lieblingsbibliothek benutzen?
You most likely can. First, you have to include your library in the page. You can customize the Head Include during export (see below), or even write your own template.
Im folgenden Beispiel passen wir den Head Include an, um eine externe Bibliothek (axios) aus einem Content Delivery Network hinzuzufügen, und einen zweiten <script>-Tag, um unsere eigene Funktion zu definieren:
<!-- Axios -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!-- Custom function -->
<script>
function myFunc() {
alert("My func!");
}
</script>
Wir können dann sowohl auf die Bibliothek als auch auf die Funktion von Godot aus zugreifen, wie wir es in den vorherigen Beispielen getan haben:
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")
Die Eval-Schnittstelle
Die Methode eval arbeitet ähnlich wie die gleichnamige JavaScript-Funktion. Sie nimmt einen String als Argument und führt ihn als JavaScript-Code aus. Dies ermöglicht die Interaktion mit dem Browser auf eine Weise, die mit den in Godot integrierten Skriptsprachen nicht möglich ist.
func my_func():
JavaScriptBridge.eval("alert('Calling JavaScript per GDScript!');")
private void MyFunc()
{
JavaScriptBridge.Eval("alert('Calling JavaScript per C#!');")
}
Der Wert der letzten JavaScript-Anweisung wird in einen GDScript-Wert konvertiert und unter bestimmten Umständen von eval() zurückgegeben:
JavaScript
numberwird als float zurückgegebenJavaScript
booleanwird als bool zurückgegebenJavaScript
stringwird als String zurückgegebenJavaScript
ArrayBuffer,TypedArrayundDataViewwerden als PackedByteArray zurückgegeben
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'
}
Jeder andere JavaScript-Wert wird zurückgegeben als null.
HTML5-Exportvorlagen können gebaut werden ohne Unterstützung für das Singleton, um die Sicherheit zu verbessern. Mit solchen Vorlagen und auf anderen Plattformen als HTML5 wird der Aufruf von JavaScriptBridge.eval ebenfalls null zurückgeben. Die Verfügbarkeit des Singletons kann mit dem web feature tag überprüft werden:
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");
}
}
Tipp
Die mehrzeiligen Strings von GDScript, umgeben von 3 Anführungszeichen """ wie in my_func3() oben, sind nützlich, um JavaScript-Code lesbar zu halten.
Die Methode eval akzeptiert auch ein zweites, optionales boolesches Argument, das angibt, ob der Code im globalen Ausführungskontext ausgeführt werden soll. Der Default ist false, um den globalen Namespace nicht zu vermüllen:
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);
}
Herunterladen von Dateien
Das Herunterladen von Dateien (z.B. eines Spielstands) vom Godot-Web-Export auf den Computer des Benutzers kann durch direkte Interaktion mit JavaScript erfolgen. Da dies jedoch ein sehr häufiger Anwendungsfall ist, stellt Godot diese Funktionalität der Skripterstellung über eine dedizierte Funktion JavaScriptBridge.download_buffer() zur Verfügung, mit der Sie jeden generierten Puffer herunterladen können.
Hier ist ein Minimalbeispiel für die Verwendung:
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")
Und hier ist ein ausführlicheres Beispiel für das Herunterladen einer zuvor gespeicherten Datei:
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")