I need help setting up WebRTC for my two player coop platformer game

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:
:joystick: Multiplayer manager ready.
true
WebRTCLibPeerConnection
:antenna_bars: WS state:3
:joystick: Multiplayer manager ready.
true
WebRTCLibPeerConnection
:antenna_bars: WS state:3
:white_check_mark: Server awake. Connecting WS…
:antenna_bars: WS state:0
:antenna_bars: WS state:1
:incoming_envelope: Sent join for room:6GV58Z
:white_check_mark: Server awake. Connecting WS…
:antenna_bars: WS state:0
:antenna_bars: WS state:1
:incoming_envelope: Sent join for room:6GV58Z
:envelope_with_arrow: Received:{“ready”:true}
:white_check_mark: Room ready. Setting up RTC…
:white_check_mark: Client peer initialized. Waiting for offer…
:envelope_with_arrow: Received:{“ready”:true}
:white_check_mark: Room ready. Setting up RTC…
WebRTCLibPeerConnection
:white_check_mark: Host peer and data channel initialized. Offer created.
:white_check_mark: SDP created for type:offer
:envelope_with_arrow: Received:{“signal”:{“sdp”:“[mysdp]”,“type”:“offer”}}
:white_check_mark: Received offer, setting remote description…false
:white_check_mark: 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)

Update: Managed to resolve the issue, here is what i found: The module i used (see previous post) was the source of the WebRTCLib… classes. I checked out the code of these classes and noticed that create_answer() wasn’t mentioned anywhere (as expected from the error message).

Now this was obviously strange, as the module didnt contain any create_answer() function. The create_answer() function has to be called immediately after set_remote_description() (according to docs i’ve read about general WebRTC), so i checked in the source code of the module what the set_remote_description() definition was:

Error WebRTCLibPeerConnection::_set_remote_description(const String &p_type, const String &p_sdp) try {
	ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED);
	std::string sdp(p_sdp.utf8().get_data());
	std::string type(p_type.utf8().get_data());
	rtc::Description desc(sdp, type);
	peer_connection->setRemoteDescription(desc);
	// Automatically create the answer.
	if (p_type == String("offer")) {
		peer_connection->setLocalDescription(rtc::Description::Type::Answer);
	}
	return OK;
} catch (const std::exception &e) {
	ERR_PRINT(e.what());
	ERR_FAIL_V(FAILED);
}

That was my solution: create_answer() wasn’t defined because it was done automatically in set_remote description. I removed the line in which i called create_answer, and everything worked fine! :partying_face:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.