Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

WebRTC

HTML5, WebSocket, WebRTC

Eine der großartigen Features von Godot ist die Möglichkeit, auf die HTML5/WebAssembly-Plattform zu exportieren, sodass Ihr Spiel direkt im Browser ausgeführt werden kann, wenn ein Benutzer Ihre Webseite besucht.

Dies ist eine großartige Gelegenheit sowohl für Demos als auch für vollständige Spiele, war jedoch mit einigen Einschränkungen verbunden. Im Bereich Netzwerkfunktionen unterstützten Browser bis vor kurzem nur HTTPRequests, als zuerst WebSocket und dann WebRTC als Standards vorgeschlagen wurden.

WebSocket

Als das WebSocket-Protokoll im Dezember 2011 standardisiert wurde, ermöglichte es Browsern, stabile und bidirektionale Verbindungen zu einem WebSocket-Server herzustellen. Das Protokoll ist ein sehr leistungsfähiges Werkzeug, um Push-Nachrichten an Browser zu senden, und wurde zur Implementierung von Chats, rundenbasierten Spielen usw. verwendet.

WebSockets verwenden jedoch weiterhin eine TCP-Verbindung, die sich positiv auf die Zuverlässigkeit, aber nicht auf die Latenz auswirkt und daher nicht für Echtzeitanwendungen wie VoIP und schnelle Spiele geeignet ist.

WebRTC

Aus diesem Grund begann Google seit 2010 mit der Arbeit an einer neuen Technologie namens WebRTC, die später im Jahr 2017 zu einer Empfehlung für W3C-Kandidaten wurde. WebRTC ist ein viel komplexerer Satz von Spezifikationen und stützt sich auf viele andere Technologien hinter den Kulissen (ICE, DTLS, SDP), um eine schnelle, Echtzeit- und sichere Kommunikation zwischen zwei Peers zu ermöglichen.

Die Idee ist, den schnellsten Weg zwischen den beiden Peers zu finden und wann immer möglich eine direkte Kommunikation herzustellen (d.h. einen Relay-Server zu vermeiden).

Dies hat jedoch einen Preis: Einige Medieninformationen müssen zwischen den beiden Peers ausgetauscht werden, bevor die Kommunikation beginnen kann (in Form von Session Description Protocol - SDP-Zeichenfolgen). Dies geschieht in der Regel in Form eines sogenannten WebRTC-Signalisierungsservers.

../../_images/webrtc_signaling.png

Peers stellen eine Verbindung zu einem Signalisierungsserver (z.B. einem WebSocket-Server) her und senden ihre Medieninformationen. Der Server leitet diese Informationen dann an andere Peers weiter, sodass diese die gewünschte direkte Kommunikation herstellen können. Sobald dieser Schritt abgeschlossen ist, können Peers die Verbindung zum Signalisierungsserver trennen und die direkte Peer-to-Peer-Verbindung (P2P) offen halten.

Nutzung von WebRTC in Godot

WebRTC wird in Godot über zwei Hauptklassen WebRTCPeerConnection und WebRTCDataChannel, sowie die Multiplayer-API-Implementierung WebRTCMultiplayerPeer implementiert. Siehe Abschnitt über High-Level-Multiplayer für weitere Details.

Bemerkung

Diese Klassen sind automatisch in HTML5 verfügbar, benötigen aber ein externes GDExtension-Plugin auf nativen (nicht-HTML5) Plattformen. Schauen Sie sich das webrtc-native plugin repository an, um Anweisungen zu erhalten und das neueste release zu erhalten.

Warnung

Wenn Sie nach Android exportieren, stellen Sie sicher, dass Sie die Berechtigung INTERNET in der Android-Exportvorgabe aktivieren, bevor Sie das Projekt exportieren oder die Ein-Klick-Auslieferung verwenden. Andernfalls wird jede Art von Netzwerkkommunikation von Android blockiert.

Minimales Verbindungs-Beispiel

Dieses Beispiel zeigt Ihnen, wie Sie eine WebRTC-Verbindung zwischen zwei Peers in derselben Anwendung erstellen. Dies ist in einem echten Projekt nicht sehr nützlich, gibt Ihnen jedoch einen guten Überblick über den Aufbau einer WebRTC-Verbindung.

extends Node

# Create the two peers
var p1 = WebRTCPeerConnection.new()
var p2 = WebRTCPeerConnection.new()
# And a negotiated channel for each each peer
var ch1 = p1.create_data_channel("chat", {"id": 1, "negotiated": true})
var ch2 = p2.create_data_channel("chat", {"id": 1, "negotiated": true})

func _ready():
    # Connect P1 session created to itself to set local description.
    p1.session_description_created.connect(p1.set_local_description)
    # Connect P1 session and ICE created to p2 set remote description and candidates.
    p1.session_description_created.connect(p2.set_remote_description)
    p1.ice_candidate_created.connect(p2.add_ice_candidate)

    # Same for P2
    p2.session_description_created.connect(p2.set_local_description)
    p2.session_description_created.connect(p1.set_remote_description)
    p2.ice_candidate_created.connect(p1.add_ice_candidate)

    # Let P1 create the offer
    p1.create_offer()

    # Wait a second and send message from P1.
    await get_tree().create_timer(1).timeout
    ch1.put_packet("Hi from P1".to_utf8_buffer())

    # Wait a second and send message from P2.
    await get_tree().create_timer(1).timeout
    ch2.put_packet("Hi from P2".to_utf8_buffer())

func _process(_delta):
    # Poll connections
    p1.poll()
    p2.poll()

    # Check for messages
    if ch1.get_ready_state() == ch1.STATE_OPEN and ch1.get_available_packet_count() > 0:
        print("P1 received: ", ch1.get_packet().get_string_from_utf8())
    if ch2.get_ready_state() == ch2.STATE_OPEN and ch2.get_available_packet_count() > 0:
        print("P2 received: ", ch2.get_packet().get_string_from_utf8())

Dies gibt aus:

P1 received: Hi from P1
P2 received: Hi from P2

Lokales Signalisierungs-Beispiel

Dieses Beispiel erweitert das vorherige Beispiel, trennt die Peers in zwei verschiedenen Szenen und verwendet ein Singleton als Signalisierungsserver.

extends Node
# An example p2p chat client.

var peer = WebRTCPeerConnection.new()

# Create negotiated data channel.
var channel = peer.create_data_channel("chat", {"negotiated": true, "id": 1})

func _ready():
    # Connect all functions.
    peer.ice_candidate_created.connect(self._on_ice_candidate)
    peer.session_description_created.connect(self._on_session)

    # Register to the local signaling server (see below for the implementation).
    Signaling.register(String(get_path()))


func _on_ice_candidate(mid, index, sdp):
    # Send the ICE candidate to the other peer via signaling server.
    Signaling.send_candidate(String(get_path()), mid, index, sdp)


func _on_session(type, sdp):
    # Send the session to other peer via signaling server.
    Signaling.send_session(String(get_path()), type, sdp)
    # Set generated description as local.
    peer.set_local_description(type, sdp)


func _process(delta):
    # Always poll the connection frequently.
    peer.poll()
    if channel.get_ready_state() == WebRTCDataChannel.STATE_OPEN:
        while channel.get_available_packet_count() > 0:
            print(String(get_path()), " received: ", channel.get_packet().get_string_from_utf8())


func send_message(message):
    channel.put_packet(message.to_utf8_buffer())

Kommen wir jetzt zum lokalen Signalisierungsserver:

Bemerkung

Dieser lokale Signalisierungsserver soll als Singleton verwendet werden um zwei Peers in derselben Szene zu verbinden.

# A local signaling server. Add this to autoloads with name "Signaling" (/root/Signaling)
extends Node

# We will store the two peers here
var peers = []

func register(path):
    assert(peers.size() < 2)
    peers.append(path)
    if peers.size() == 2:
        get_node(peers[0]).peer.create_offer()


func _find_other(path):
    # Find the other registered peer.
    for p in peers:
        if p != path:
            return p
    return ""


func send_session(path, type, sdp):
    var other = _find_other(path)
    assert(other != "")
    get_node(other).peer.set_remote_description(type, sdp)


func send_candidate(path, mid, index, sdp):
    var other = _find_other(path)
    assert(other != "")
    get_node(other).peer.add_ice_candidate(mid, index, sdp)

Nun können Sie es so verwenden:

# Main scene (main.gd)
extends Node

const Chat = preload("res://chat.gd")

func _ready():
    var p1 = Chat.new()
    var p2 = Chat.new()
    add_child(p1)
    add_child(p2)

    # Wait a second and send message from P1
    await get_tree().create_timer(1).timeout
    p1.send_message("Hi from %s" % String(p1.get_path()))

    # Wait a second and send message from P2
    await get_tree().create_timer(1).timeout
    p2.send_message("Hi from %s" % String(p2.get_path()))

Dies wird etwas ähnliches wie das folgende ausgeben:

/root/main/@@3 received: Hi from /root/main/@@2
/root/main/@@2 received: Hi from /root/main/@@3

Remote-Signalisierung mit WebSocket

Eine fortgeschrittene Demo, die WebSocket für die Signalisierung von Peers und WebRTCMultiplayerPeer verwendet, ist in den Godot-Demoprojekten unter networking/webrtc_signaling verfügbar.