WebRTC

HTML5, WebSocket, WebRTC

Однією з чудових функцій Godot є його здатність експортувати на платформу HTML5/WebAssembly, що дозволяє вашій грі запускатися безпосередньо у браузері, коли користувач відвідує вашу веб-сторінку.

Це чудова можливість як для демонстраційних, так і для повноцінних ігор, але раніше вони мали певні обмеження. У сфері мереж донедавна браузери підтримували лише HTTPRequests, коли спочатку в якості стандартів були запропоновані WebSocket, а потім WebRTC.

WebSocket

Коли протокол WebSocket був стандартизований у грудні 2011 року, він дозволив браузерам створювати стабільні та двонаправлені з’єднання з сервером WebSocket. Цей протокол досить простий, але дуже потужний, інструмент для надсилання push-повідомлень у браузери, і його використовували для реалізації чатів, покрокових ігор тощо.

Однак WebSocket-и все ще використовують TCP-з’єднання, яке надійне, але повільне, і тому не підходить для додатків у реальному часі, таких як VoIP та інші динамічні ігри.

WebRTC

З цієї причини з 2010 року Google розпочала роботу над новою технологією під назвою WebRTC, яка згодом, у 2017 році, стала можливою рекомендацією від W3C. WebRTC — це набагато складніший набір специфікацій, який покладається на багато інших закулісних технологій (ICE, DTLS, SDP), щоб забезпечити швидке, безпечне спілкування в реальному часі між двома одноранговими вузлами.

Ідея полягає в тому, щоб знайти найшвидший маршрут між двома одноранговими вузлами та встановити, коли це можливо, прямий зв’язок (тобто спробувати уникнути сервер-ретранслятор).

Однак це має свою ціну, яка полягає в тому, що, перш ніж розпочнеться зв’язок, два однорангові вузли мають обмінятися між собою деякою медіа-інформацією (у формі Протоколу Опису Сеансу - тексти SDP). Зазвичай це має форму так званого Сервера Сигналізації WebRTC.

../../_images/webrtc_signaling.png

Однорангові вузли підключаються до сервера сигналізації (наприклад, сервера WebSocket) і надсилають свою медіа-інформацію. Потім сервер передає цю інформацію іншим одноранговим вузлам, дозволяючи їм встановити потрібний прямий зв’язок. Після виконання цього кроку однорангові вузли можуть відключитися від сервера сигналізації та зберегти пряме однорангове з’єднання (P2P) відкритим.

Використання WebRTC в Godot

WebRTC реалізовано в Godot через два головні класи WebRTCPeerConnection та WebRTCDataChannel, а також реалізацію багатокористувацького API WebRTCMultiplayer. Додаткову інформацію дивіться у розділі про багатокористувацьку гру високого рівня.

Примітка

Ці класи доступні автоматично в HTML5, але вимагають зовнішнього плагіна GDNative на рідних (не HTML5) платформах. Перегляньте `репозиторій плагінів webrtc-native<https://github.com/godotengine/webrtc-native>`__, для інструкцій та отримайте останню `версію<https://github.com/godotengine/webrtc-native/releases>`__.

Попередження

Під час експорту в Android обов’язково ввімкніть дозвіл INTERNET у попередньо налаштованих експортах Android, перш ніж експортувати проект, або використовувати розгортання одним клацанням мишки. Інакше Android заблокує будь-який мережевий зв’язок.

Мінімальний приклад підключення

Цей приклад покаже вам, як створити з’єднання WebRTC між двома одноранговими вузлами в одній програмі. Це не дуже корисно в реальному житті, але дасть вам хороший огляд того, як налаштовано з’єднання WebRTC.

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.connect("session_description_created", p1, "set_local_description")
    # Connect P1 session and ICE created to p2 set remote description and candidates
    p1.connect("session_description_created", p2, "set_remote_description")
    p1.connect("ice_candidate_created", p2, "add_ice_candidate")

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

    # Let P1 create the offer
    p1.create_offer()

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

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

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

Буде виведено:

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

Приклад локальної сигналізації

Цей приклад розширює попередній, розділяючи однорангові вузли у двох різних сценах та використовуючи синглтон, як сервер сигналізації.

# An example P2P chat client (chat.gd)
extends Node

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.connect("ice_candidate_created", self, "_on_ice_candidate")
    peer.connect("session_description_created", self, "_on_session")

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

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

func _on_session(type, sdp):
    # Send the session to other peer via signaling server
    Signaling.send_session(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(get_path(), " received: ", channel.get_packet().get_string_from_utf8())

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

А тепер для локального сервера сигналізації:

Примітка

Цей локальний сервер сигналізації має використовуватися, як синглтон для з’єднання двох однорангових вузлів в одній сцені.

# 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 it's the second one, create an offer
    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)

Тоді ви можете використовувати його так:

# 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)
    yield(get_tree().create_timer(1), "timeout")
    p1.send_message("Hi from %s" % p1.get_path())

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

Це надрукує щось подібне до цього:

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

Віддалена сигналізація за допомогою WebSocket

Більш просунута демонстрація з використанням WebSocket для сигналізаційних однорангових вузлів та WebRTCMultiplayer доступна в `демонстраційних проектах godot<https://github.com/godotengine/godot-demo-projects>`_ у розділі networking/webrtc_signaling.