Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

WebRTC

HTML5, WebSocket, WebRTC

Um dos grandes recursos do Godot é sua capacidade de exportar para a plataforma HTML5/WebAssembly, permitindo que seu jogo rode diretamente no navegador quando um usuário visita sua página web.

Esta é uma ótima oportunidade tanto para demos quanto para jogos completos, mas costumava vir com algumas limitações. Na área de redes, os navegadores costumavam suportar apenas HTTPRequests até recentemente, quando primeiro o WebSocket e depois o WebRTC foram propostos como padrões.

WebSocket

Quando o protocolo WebSocket foi padronizado em dezembro de 2011, ele permitiu que os navegadores criassem conexões estáveis e bidirecionais com um servidor WebSocket. O protocolo é uma ferramenta muito poderosa para enviar notificações push para navegadores e tem sido usado para implementar chats, jogos baseados em turnos, etc.

Os WebSockets, no entanto, ainda usam uma conexão TCP, que é boa para confiabilidade, mas não para latência, portanto não é boa para aplicações em tempo real como VoIP e jogos de ritmo acelerado.

WebRTC

Por esse motivo, desde 2010, o Google começou a trabalhar em uma nova tecnologia chamada WebRTC, que mais tarde, em 2017, tornou-se uma recomendação candidata do W3C. O WebRTC é um conjunto muito mais complexo de especificações e depende de muitas outras tecnologias nos bastidores (ICE, DTLS, SDP) para fornecer comunicação rápida, em tempo real e segura entre dois peers.

A ideia é encontrar a rota mais rápida entre os dois peers e estabelecer, sempre que possível, uma comunicação direta (ou seja, tentar evitar um servidor de retransmissão).

No entanto, isso tem um preço, que é o fato de que algumas informações de mídia devem ser trocadas entre os dois pares antes que a comunicação possa começar (na forma de strings do Session Description Protocol - SDP). Isso geralmente assume a forma de um chamado Servidor de Sinalização WebRTC.

../../_images/webrtc_signaling.png

Os peers se conectam a um servidor de sinalização (por exemplo, um servidor WebSocket) e enviam suas informações de mídia. O servidor então retransmite essas informações para outros peers, permitindo que eles estabeleçam a comunicação direta desejada. Uma vez concluída esta etapa, os peers podem se desconectar do servidor de sinalização e manter aberta a conexão direta Peer-to-Peer (P2P).

Usando WebRTC no Godot

O WebRTC é implementado no Godot através de duas classes principais, WebRTCPeerConnection e WebRTCDataChannel, além da implementação da API multijogador WebRTCMultiplayerPeer. Veja a seção sobre multijogador de alto nível para mais detalhes.

Nota

Essas classes estão disponíveis automaticamente no HTML5, mas exigem um plugin GDExtension externo em plataformas nativas (não HTML5). Confira o repositório do plugin webrtc-native para obter instruções e baixar a última versão.

Aviso

Ao exportar para o Android, certifique-se de ativar a permissão INTERNET no preset de exportação do Android antes de exportar o projeto ou usar o deploy de um clique. Caso contrário, qualquer tipo de comunicação de rede será bloqueada pelo Android.

Exemplo de conexão mínima

Este exemplo mostrará como criar uma conexão WebRTC entre dois peers na mesma aplicação. Isso não é muito útil na vida real, mas lhe dará uma boa visão geral de como uma conexão WebRTC é configurada.

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())

Isto vai imprimir:

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

Exemplo de sinalização local

Este exemplo expande o anterior, separando os peers em duas cenas diferentes e usando um singleton como servidor de sinalização.

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())

E agora para o servidor de sinalização local:

Nota

Este servidor de sinalização local deve ser usado como um singleton para conectar dois peers na mesma cena.

# 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)

Então você pode usar assim:

# 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()))

Isso imprimirá algo semelhante a isto:

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

Sinalização remota com WebSocket

A more advanced demo using WebSocket for signaling peers and WebRTCMultiplayerPeer is available in the godot demo projects under networking/webrtc_signaling.