Some code for WebRTC if you wanna learn:
Signaling server (js):
import WebSocket, { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: 9396 });
const messages = {
ASSIGN_ID: 0,
JOIN_LOBBY: 1,
PLAYERS_FOUND: 2,
SEND_ICE_CANDIDATE: 3,
SEND_DESCRIPTION: 4,
RECEIVE_ICE_CANDIDATE: 5,
RECEIVE_DESCRIPTION: 6,
};
let players = {};
let waiting_players = [];
let waiting_player_ids = [];
let next_player_id = 2;
const PLAYERS_IN_GAME = 3;
wss.on("connection", function connection(ws) {
const player_id = next_player_id;
players[player_id] = ws;
next_player_id += 1;
ws.on("close", function close() {
delete players[player_id];
console.log("Player left :(");
});
ws.on("error", console.error);
ws.on("message", function message(data) {
try {
const json_data = JSON.parse(data);
console.log(JSON.stringify(json_data));
if (json_data.message == messages.JOIN_LOBBY) {
console.log("Player wants to join lobby.");
waiting_players.push(ws);
waiting_player_ids.push(player_id);
if (waiting_players.length >= PLAYERS_IN_GAME) {
console.log("Lobby found!");
console.log(waiting_players);
/*
let lowest_id = -1;
for (let i = 0; i < PLAYERS_IN_GAME; i++) {
if (waiting_player_ids[i] < lowest_id || lowest_id == -1) {
lowest_id = waiting_player_ids[i];
}
}
for (let i = 0; i < PLAYERS_IN_GAME; i++) {
if (waiting_player_ids[i] == lowest_id) {
waiting_player_ids[i] = 1;
}
}
*/
for (let i = 0; i < PLAYERS_IN_GAME; i++) {
console.log(waiting_players[i]);
waiting_players[i].send(
JSON.stringify({
message: messages.PLAYERS_FOUND,
players: waiting_player_ids,
})
);
}
waiting_players = [];
waiting_player_ids = [];
}
}
if (json_data.message == messages.SEND_ICE_CANDIDATE) {
players[json_data.target].send(
JSON.stringify({
message: messages.RECEIVE_ICE_CANDIDATE,
from: player_id,
mid_name: json_data.mid_name,
index_name: json_data.index_name,
sdp_name: json_data.sdp_name,
})
);
}
if (json_data.message == messages.SEND_DESCRIPTION) {
console.log(json_data);
players[json_data.target].send(
JSON.stringify({
message: messages.RECEIVE_DESCRIPTION,
from: player_id,
data: json_data.data,
type: json_data.type,
})
);
}
} catch (e) {
console.log('Got strange data: "' + data + '"');
console.error(e);
}
});
ws.send(JSON.stringify({ message: messages.ASSIGN_ID, id: player_id }));
});
Client signaling (gdscript):
extends Node
class_name Signaling
@onready var web_rtc_manager : WebRTCManager = $"../WebRTC"
@onready var network_manager : NetworkManager = get_parent()
var socket = WebSocketPeer.new()
var waiting_for_join_request : bool = false
var my_id : int
var is_host : bool
var host_id : int = -1
enum messages {
ASSIGN_ID,
JOIN_LOBBY,
PLAYERS_FOUND,
SEND_ICE_CANDIDATE,
SEND_DESCRIPTION,
RECEIVE_ICE_CANDIDATE,
RECEIVE_DESCRIPTION,
}
func _ready():
socket.connect_to_url("ws://localhost:9396")
waiting_for_join_request = true
func _process(_delta):
socket.poll()
var state = socket.get_ready_state()
if state == WebSocketPeer.STATE_OPEN:
if waiting_for_join_request:
socket.send_text(JSON.stringify({"message":messages.JOIN_LOBBY}))
waiting_for_join_request = false
while socket.get_available_packet_count():
var packet_data : Dictionary = JSON.parse_string(socket.get_packet().get_string_from_utf8())
if packet_data.message == messages.ASSIGN_ID:
my_id = packet_data.id
web_rtc_manager.my_id = my_id
network_manager.id = my_id
web_rtc_manager.connected(my_id)
elif packet_data.message == messages.PLAYERS_FOUND:
web_rtc_manager.players_in_game = len(packet_data.players)
is_host = true
for player_id : int in packet_data.players:
web_rtc_manager.connect_to_player(player_id)
if player_id > my_id:
is_host = false
if player_id < host_id or host_id == -1:
host_id = player_id
network_manager.is_host = is_host
network_manager.host_id = host_id
print(packet_data.players)
print("Found players!")
elif packet_data.message == messages.RECEIVE_ICE_CANDIDATE:
web_rtc_manager.add_ice_candidate(packet_data.mid_name, packet_data.index_name, packet_data.sdp_name, packet_data.from)
elif packet_data.message == messages.RECEIVE_DESCRIPTION:
web_rtc_manager.set_remote_description(packet_data.type, packet_data.data, packet_data.from)
elif state == WebSocketPeer.STATE_CLOSING:
# Keep polling to achieve proper close.
pass
elif state == WebSocketPeer.STATE_CLOSED:
var code = socket.get_close_code()
var reason = socket.get_close_reason()
print("WebSocket closed with code: %d, reason %s. Clean: %s" % [code, reason, code != -1])
set_process(false) # Stop processing.
func send(message : messages, data : Dictionary, target : int):
data["message"] = message
data["target"] = target
socket.send_text(JSON.stringify(data))
func send_description(type : String, data : String, target : int):
send(messages.SEND_DESCRIPTION, {"data": data, "type": type}, target)
func send_ice_candidate(mid_name, index_name, sdp_name, target : int):
send(messages.SEND_ICE_CANDIDATE, {"mid_name":mid_name, "index_name":index_name, "sdp_name":sdp_name}, target)
WebRTC manager (gdscript):
extends Node
class_name WebRTCManager
@onready var signaling : Signaling = $"../Signaling"
var peer : WebRTCMultiplayerPeer = WebRTCMultiplayerPeer.new()
var my_id : int
var players_in_game : int
var connected_peers : int = 0
func _ready():
multiplayer.peer_connected.connect(peer_connected)
func connect_to_player(id : int):
var player_peer : WebRTCPeerConnection = WebRTCPeerConnection.new()
player_peer.initialize({
"iceServers" : [{ "urls": ["stun:stun.l.google.com:19302"] }]
})
player_peer.session_description_created.connect(offer_created.bind(id))
player_peer.ice_candidate_created.connect(ice_candidate_created.bind(id))
peer.add_peer(player_peer, id)
if id < my_id:
player_peer.create_offer()
func offer_created(type : String, data : String, id : int):
signaling.send_description(type, data, id)
func ice_candidate_created(mid_name, index_name, sdp_name, id : int):
signaling.send_ice_candidate(mid_name, index_name, sdp_name, id)
func add_ice_candidate(mid_name, index_name, sdp_name, id : int):
peer.get_peer(id).connection.add_ice_candidate(mid_name, index_name, sdp_name)
func set_remote_description(type : String, data : String, id : int):
peer.get_peer(id).connection.set_remote_description(type, data)
func connected(id):
peer.create_mesh(id)
multiplayer.multiplayer_peer = peer
func peer_connected(id):
print("Connection with " + str(id))
connected_peers += 1
if connected_peers == players_in_game - 1:
get_parent().connected_to_everyone()
@rpc("any_peer", "call_local")
func ping():
print("I got pinged!")
func _on_ping_button_pressed():
ping.rpc()
Yes, it's kinda complicated but it works.