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

Godot的一大特點是它能夠匯出到HTML5/WebAssembly平臺, 當使用者存取您的網頁時, 您的遊戲可以直接在瀏覽器中運作.

這對於演示和完整的遊戲來說都是一個很好的機會, 但過去有一些限制, 在網路領域, 瀏覽器過去只支援HTTPRequests, 直到最近, 首先是WebSocket, 然後是WebRTC被提出為標準.

WebSocket

當WebSocket協定在2011年12月被標準化後, 它允許瀏覽器與WebSocket伺服器建立穩定的雙向連接. 該協議相當簡單, 但卻是一個非常強大的向瀏覽器發送推送通知的工具, 並已被用於實作聊天, 回合制遊戲等.

不過,WebSockets仍然使用TCP連接, 這對可靠性有好處, 但對減少延遲沒有好處, 所以不適合即時應用, 比如VoIP和快節奏的遊戲.

WebRTC

為此, 從2010年開始, 穀歌開始研究一項名為WebRTC的新技術, 後來在2017年, 這項技術成為W3C候選推薦.WebRTC是一套複雜得集合規範, 並且在後臺依靠許多其他技術(ICE, DTLS, SDP)來提供兩個對等體之間快速, 即時, 安全的通信.

其想法是找到兩個對等體之間最快的路線, 並盡可能建立直接通信(儘量避開中繼伺服器).

然而, 這是有代價的, 那就是在通信開始之前, 兩個對等體之間必須交換一些媒介資訊(以工作階段描述通訊協定--SDP字串的形式). 這通常採取所謂的WebRTC訊號伺服器的形式.

../../_images/webrtc_signaling.png

對等體連接到訊號伺服器(例如 WebSocket 伺服器)並行送其媒介資訊. 然後, 伺服器將此資訊轉發到其他對等體, 允許它們建立所需的直接通信. 這一步完成後, 對等體可以斷開與訊號伺服器的連接, 並保持直接的點對點(P2P)連接打開狀態.

在 Godot 中使用 WebRTC

在Godot中,WebRTC是通過兩個主要的類來實作的 WebRTCPeerConnectionWebRTCDataChannel, 加上多人遊戲API實作 WebRTCMultiplayer. 更多細節請參見 high-level multiplayer 章節.

備註

這些類在HTML5中自動可用, 但 需要在本地(非HTML5)平臺上使用外部GDNative外掛程式 . 查看 webrtc-native 外掛程式庫 , 以獲取說明和最新的 發行 .

警告

當匯出到 Android 時,在匯出專案或使用一鍵部署之前,確保在 Android 匯出預設中啟用 INTERNET 許可權。否則,任何形式的網路通信都會被 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.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())

這將列印:

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

本地訊號範例

這個例子在上一個例子的基礎上進行了擴充, 將對等體分離在兩個不同的場景中, 並使用 singleton 作為訊號伺服器.

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

現在是本地訊號伺服器:

備註

這個本地訊號伺服器應該是作為一個 singleton 來連接同一場景中的兩個對等體.

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

然後, 您可以這樣使用它:

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

將列印出類似這樣的內容:

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

使用 WebSocket 進行遠端訊號傳輸

一個更高級的演示,使用 WebSocket 作為訊號對等體和 WebRTCMultiplayergodot 演示專案 networking/webrtc_signaling 下提供。