Godot Version
4.4.1
Question
I’ve been trying for a long time to get WebRTC to work in a simple platformer game. I keep getting the error “Invalid call. Nonexistent function ‘create_answer’ in base ‘WebRTCLibPeerConnection’.” in line 161. All of the webrtc logic gets handled in a single ‘multiplayermanager’ script:
extends Node
var room_code := ""
var is_host := false
var has_sent_join := false
var has_printed_ready := false
var ws := WebSocketPeer.new()
var prev_ws_state := -1
var rtc_peer : WebRTCPeerConnection
var data_channel : WebRTCDataChannel
func _ready() -> void:
print("🕹 Multiplayer manager ready.")
print(ClassDB.class_exists("WebRTCPeerConnection"))
var test_peer = WebRTCPeerConnection.new()
print(test_peer.get_class())
func _process(_delta: float) -> void:
# 1) Watch WebSocket state changes
var st = ws.get_ready_state()
if st != prev_ws_state:
print("📶 WS state:", st)
prev_ws_state = st
# 2) Poll transports
ws.poll()
if rtc_peer:
rtc_peer.poll()
if data_channel and data_channel.get_ready_state() == WebRTCDataChannel.STATE_OPEN:
if not has_printed_ready:
print("✅ Data channel is OPEN!")
has_printed_ready = true
while data_channel.get_available_packet_count() > 0:
var msg = data_channel.get_packet().get_string_from_utf8()
print("📩 Received:", msg)
# 3) Send join message once WS is open
if st == WebSocketPeer.STATE_OPEN and not has_sent_join and room_code != "":
var join_msg = { "join": room_code, "host": is_host }
ws.send_text(JSON.stringify(join_msg))
has_sent_join = true
print("📨 Sent join for room:", room_code)
# 4) Handle incoming WS messages
while ws.get_available_packet_count() > 0:
var pkt = ws.get_packet().get_string_from_utf8()
print("📩 Received:", pkt)
_handle_ws_message(pkt)
func wakeup_server(code: String, is_host_mode: bool) -> void:
if ws.get_ready_state() != WebSocketPeer.STATE_CLOSED:
ws.close()
room_code = code
is_host = is_host_mode
has_sent_join = false
ws = WebSocketPeer.new()
var http = HTTPRequest.new()
add_child(http)
http.request_completed.connect(_on_http_response)
http.request("https://signaling-server-bs9q.onrender.com/ping")
func _on_http_response(_res, code, _hdrs, body) -> void:
if code == 200 and body.get_string_from_utf8() == "pong":
print("✅ Server awake. Connecting WS…")
var err = ws.connect_to_url("[the url of my signaling server]")
if err != OK:
print("❌ WS connect failed:", err)
else:
print("❌ Ping failed or bad response.")
func _handle_ws_message(json_str: String) -> void:
var msg = JSON.parse_string(json_str)
if msg == null:
print("⚠️ Invalid JSON")
return
if msg.get("ready", false):
print("✅ Room ready. Setting up RTC…")
if is_host:
start_host()
else:
start_client()
elif msg.get("left", false):
print("🚪 Peer left.")
elif msg.get("error", "") == "room_not_found":
print("❌ Room not found.")
elif msg.has("signal"):
var sgnl = msg["signal"]
if sgnl.has("sdp") and sgnl.has("type"):
handle_offer_or_answer(sgnl["sdp"], sgnl["type"])
elif sgnl.has("candidate"):
# Ensure rtc_peer is valid before adding ICE candidate
if rtc_peer:
rtc_peer.add_ice_candidate(
sgnl["sdpMid"],
sgnl["sdpMLineIndex"],
sgnl["candidate"]
)
else:
print("⚠️ rtc_peer is null when trying to add ICE candidate.")
func start_host():
var config = {
"iceServers": [
{"urls": ["stun:stun.l.google.com:19302"]}
]
}
rtc_peer = WebRTCPeerConnection.new()
rtc_peer.initialize(config)
rtc_peer.ice_candidate_created.connect(_on_ice_candidate)
rtc_peer.session_description_created.connect(_on_session_description)
# The host creates the data channel
data_channel = rtc_peer.create_data_channel("chat")
print(rtc_peer) # This will print the object reference, e.g., "[WebRTCLibPeerConnection:1234]"
var err = rtc_peer.create_offer()
if err != OK:
print("❌ create_offer failed:", err)
else:
print("✅ Host peer and data channel initialized. Offer created.")
has_printed_ready = false
func start_client():
var config = {
"iceServers": [
{"urls": ["stun:stun.l.google.com:19302"]}
]
}
rtc_peer = WebRTCPeerConnection.new()
rtc_peer.initialize(config)
rtc_peer.ice_candidate_created.connect(_on_ice_candidate)
rtc_peer.session_description_created.connect(_on_session_description)
rtc_peer.data_channel_received.connect(_on_data_channel_received)
print("✅ Client peer initialized. Waiting for offer...")
func handle_offer_or_answer(sdp: String, typ: String) -> void:
if typ == "offer":
print("✅ Received offer, setting remote description...", is_host)
var err = rtc_peer.set_remote_description("offer", sdp)
if err != OK:
print("❌ set_remote_description failed:", err)
return
print("✅ Remote description set, creating answer...", is_host)
# REMOVED: await get_tree().create_timer(5).timeout
# The timer was likely causing the rtc_peer object to become invalid.
# Check if rtc_peer is still valid before calling create_answer
if rtc_peer:
print("Attempting to create answer on valid rtc_peer object.")
err = rtc_peer.create_answer()
if err != OK:
print("❌ create_answer failed:", err)
else:
print("❌ rtc_peer is null before calling create_answer. This is the problem!")
elif typ == "answer":
print("✅ Received answer, setting remote description...", is_host)
var err = rtc_peer.set_remote_description("answer", sdp)
if err != OK:
print("❌ set_remote_description failed:", err)
func _on_session_description(typ: String, sdp: String) -> void:
print("✅ SDP created for type:", typ)
var err = rtc_peer.set_local_description(typ, sdp)
if err != OK:
print("❌ set_local_description failed:", err)
return
_send_signal({ "type": typ, "sdp": sdp })
func _on_ice_candidate(mid: String, idx: int, cand: String) -> void:
_send_signal({
"candidate": cand,
"sdpMLineIndex": idx,
"sdpMid": mid
})
func _send_signal(data: Dictionary) -> void:
if ws.get_ready_state() == WebSocketPeer.STATE_OPEN:
ws.send_text(JSON.stringify({ "signal": data }))
func _on_data_channel_received(channel: WebRTCDataChannel) -> void:
print("✅ Received data channel from host.")
data_channel = channel
func close_room() -> void:
if ws.get_ready_state() == WebSocketPeer.STATE_OPEN:
ws.send_text(JSON.stringify({ "leave": room_code }))
ws.close()
room_code = ""
has_sent_join = false
is_host = false
print("🔌 Room closed.")
The output log shows:
Multiplayer manager ready.
true
WebRTCLibPeerConnection
WS state:3
Multiplayer manager ready.
true
WebRTCLibPeerConnection
WS state:3
Server awake. Connecting WS…
WS state:0
WS state:1
Sent join for room:6GV58Z
Server awake. Connecting WS…
WS state:0
WS state:1
Sent join for room:6GV58Z
Received:{“ready”:true}
Room ready. Setting up RTC…
Client peer initialized. Waiting for offer…
Received:{“ready”:true}
Room ready. Setting up RTC…
WebRTCLibPeerConnection
Host peer and data channel initialized. Offer created.
SDP created for type:offer
Received:{“signal”:{“sdp”:“[mysdp]”,“type”:“offer”}}
Received offer, setting remote description…false
Remote description set, creating answer…false
Attempting to create answer on valid rtc_peer object.
Does anyone know what is going on?
PS: I use this addon: Releases · godotengine/webrtc-native · GitHub. I have tried to disable it but then i got even more errors about functions needing to be overwritten. I have also tried to add a delay before calling create_answer() because it seemed like it was maybe being called too early. Then it seemed to work until i got the same error again, but the host did receive a message containing an answer (the create_answer function had never been called because it still crashed on that function, so i have no idea how the host was able to receive that message)