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信号服务器的形式.
对等体连接到信号服务器(例如 WebSocket 服务器)并发送其媒介信息.然后,服务器将此信息转发到其他对等体,允许它们建立所需的直接通信.这一步完成后,对等体可以断开与信号服务器的连接,并保持直接的点对点(P2P)连接打开状态.
在Godot中使用WebRTC
在Godot中,WebRTC是通过两个主要的类来实现的 WebRTCPeerConnection,加上多人游戏API实现 WebRTCMultiplayer.更多细节请参见 :ref:`high-level multiplayer <doc_high_level_multiplayer>`章节.
注解
这些类在HTML5中自动可用,但 需要在本地(非HTML5)平台上使用外部GDNative插件 .查看 `webrtc-native插件库<https://github.com/godotengine/webrtc-native>`__ ,以获取说明和最新的 `发布<https://github.com/godotengine/webrtc-native/releases>`__ .
最小连接示例
这个例子将向您展示如何在同一应用程序中的两个对等体之间创建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
本地信号示例
这个例子在上一个例子的基础上进行了扩展,将对等体分离在两个不同的场景中,并使用 :ref:`singleton <doc_singletons_autoload>`作为信号服务器.
# 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())
现在是本地信号服务器:
注解
这个本地信号服务器应该是作为一个 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 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 下提供.