Add autoload global script
This commit is contained in:
parent
b915552a64
commit
6de5819595
20
game.gd
Normal file
20
game.gd
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
extends Node2D
|
||||||
|
|
||||||
|
|
||||||
|
# Declare member variables here. Examples:
|
||||||
|
# var a = 2
|
||||||
|
# var b = "text"
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready():
|
||||||
|
pass # Replace with function body.
|
||||||
|
|
||||||
|
|
||||||
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
|
#func _process(delta):
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Button_pressed():
|
||||||
|
Global.leave_game()
|
28
game.tscn
Normal file
28
game.tscn
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
[gd_scene load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://game.gd" type="Script" id=1]
|
||||||
|
|
||||||
|
[node name="Node2D" type="Node2D"]
|
||||||
|
script = ExtResource( 1 )
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="."]
|
||||||
|
margin_left = 93.0
|
||||||
|
margin_top = 109.0
|
||||||
|
margin_right = 253.0
|
||||||
|
margin_bottom = 123.0
|
||||||
|
text = "You are playing the game"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="Label"]
|
||||||
|
margin_left = 29.0
|
||||||
|
margin_top = 177.0
|
||||||
|
margin_right = 120.0
|
||||||
|
margin_bottom = 197.0
|
||||||
|
text = "Leave Game"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[connection signal="pressed" from="Label/Button" to="." method="_on_Button_pressed"]
|
20
gen.ts
Normal file
20
gen.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
const ALFNUM = "0123456789";
|
||||||
|
const SECRET_LENGTH = 8;
|
||||||
|
|
||||||
|
function randomInt(low: number, high: number) {
|
||||||
|
return Math.floor(Math.random() * (high - low + 1) + low);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function randomId() {
|
||||||
|
const arr = new Int32Array(1);
|
||||||
|
crypto.getRandomValues(arr);
|
||||||
|
return Math.abs(arr[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function randomSecret() {
|
||||||
|
let out = "";
|
||||||
|
for (let i = 0; i < SECRET_LENGTH; i++) {
|
||||||
|
out += ALFNUM[randomInt(0, ALFNUM.length - 1)];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
22
global.gd
Normal file
22
global.gd
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
|
||||||
|
# Declare member variables here. Examples:
|
||||||
|
# var a = 2
|
||||||
|
# var b = "text"
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready():
|
||||||
|
pass # Replace with function body.
|
||||||
|
|
||||||
|
|
||||||
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
|
#func _process(delta):
|
||||||
|
# pass
|
||||||
|
|
||||||
|
func start_singleplayer_game():
|
||||||
|
print("Starting singleplayer game...")
|
||||||
|
|
||||||
|
func leave_game():
|
||||||
|
print("Leaving game...")
|
14
main.gd
14
main.gd
|
@ -1,14 +1,4 @@
|
||||||
extends Control
|
|
||||||
|
|
||||||
func _ready():
|
|
||||||
if OS.get_name() == "HTML5":
|
|
||||||
$VBoxContainer/Signaling.hide()
|
|
||||||
|
|
||||||
func _on_listen_toggled(button_pressed):
|
func _on_Singleplayer_pressed():
|
||||||
if button_pressed:
|
Global.start_singleplayer_game()
|
||||||
$Server.listen(int($VBoxContainer/Signaling/Port.value))
|
|
||||||
else:
|
|
||||||
$Server.stop()
|
|
||||||
|
|
||||||
func _on_LinkButton_pressed():
|
|
||||||
OS.shell_open("https://github.com/godotengine/webrtc-native/releases")
|
|
||||||
|
|
86
main.tscn
86
main.tscn
|
@ -1,7 +1,6 @@
|
||||||
[gd_scene load_steps=3 format=2]
|
[gd_scene load_steps=2 format=2]
|
||||||
|
|
||||||
[ext_resource path="res://main.gd" type="Script" id=1]
|
[ext_resource path="res://main.gd" type="Script" id=1]
|
||||||
[ext_resource path="res://client_ui.tscn" type="PackedScene" id=2]
|
|
||||||
|
|
||||||
[node name="Control" type="Control"]
|
[node name="Control" type="Control"]
|
||||||
anchor_left = 0.0136719
|
anchor_left = 0.0136719
|
||||||
|
@ -23,76 +22,29 @@ __meta__ = {
|
||||||
"_edit_use_anchors_": true
|
"_edit_use_anchors_": true
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="Signaling" type="HBoxContainer" parent="VBoxContainer"]
|
[node name="Singleplayer" type="Button" parent="VBoxContainer"]
|
||||||
margin_right = 995.0
|
margin_right = 995.0
|
||||||
margin_bottom = 24.0
|
margin_bottom = 20.0
|
||||||
|
text = "Start Singleplayer"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/Signaling"]
|
[node name="CreateLobbyButton" type="Button" parent="VBoxContainer"]
|
||||||
margin_top = 5.0
|
margin_top = 70.0
|
||||||
margin_right = 104.0
|
|
||||||
margin_bottom = 19.0
|
|
||||||
text = "Signaling server:"
|
|
||||||
|
|
||||||
[node name="Port" type="SpinBox" parent="VBoxContainer/Signaling"]
|
|
||||||
margin_left = 108.0
|
|
||||||
margin_right = 182.0
|
|
||||||
margin_bottom = 24.0
|
|
||||||
min_value = 1025.0
|
|
||||||
max_value = 65535.0
|
|
||||||
value = 9080.0
|
|
||||||
|
|
||||||
[node name="ListenButton" type="Button" parent="VBoxContainer/Signaling"]
|
|
||||||
margin_left = 186.0
|
|
||||||
margin_right = 237.0
|
|
||||||
margin_bottom = 24.0
|
|
||||||
toggle_mode = true
|
|
||||||
text = "Listen"
|
|
||||||
|
|
||||||
[node name="CenterContainer" type="CenterContainer" parent="VBoxContainer/Signaling"]
|
|
||||||
margin_left = 241.0
|
|
||||||
margin_right = 995.0
|
margin_right = 995.0
|
||||||
margin_bottom = 24.0
|
margin_bottom = 90.0
|
||||||
size_flags_horizontal = 3
|
text = "Create Lobby"
|
||||||
size_flags_vertical = 3
|
|
||||||
|
|
||||||
[node name="LinkButton" type="LinkButton" parent="VBoxContainer/Signaling/CenterContainer"]
|
[node name="JoinLobbyButton" type="Button" parent="VBoxContainer"]
|
||||||
margin_left = 104.0
|
margin_top = 140.0
|
||||||
margin_top = 5.0
|
|
||||||
margin_right = 650.0
|
|
||||||
margin_bottom = 19.0
|
|
||||||
text = "Make sure to download the GDNative WebRTC Plugin and place it in the project folder"
|
|
||||||
|
|
||||||
[node name="Clients" type="GridContainer" parent="VBoxContainer"]
|
|
||||||
margin_top = 74.0
|
|
||||||
margin_right = 995.0
|
margin_right = 995.0
|
||||||
margin_bottom = 579.0
|
margin_bottom = 160.0
|
||||||
size_flags_horizontal = 3
|
text = "Join Lobby"
|
||||||
size_flags_vertical = 3
|
__meta__ = {
|
||||||
custom_constants/vseparation = 15
|
"_edit_use_anchors_": false
|
||||||
custom_constants/hseparation = 15
|
}
|
||||||
columns = 2
|
|
||||||
|
|
||||||
[node name="ClientUI" parent="VBoxContainer/Clients" instance=ExtResource( 2 )]
|
|
||||||
margin_right = 490.0
|
|
||||||
margin_bottom = 245.0
|
|
||||||
|
|
||||||
[node name="ClientUI2" parent="VBoxContainer/Clients" instance=ExtResource( 2 )]
|
|
||||||
margin_left = 505.0
|
|
||||||
margin_right = 995.0
|
|
||||||
margin_bottom = 245.0
|
|
||||||
|
|
||||||
[node name="ClientUI3" parent="VBoxContainer/Clients" instance=ExtResource( 2 )]
|
|
||||||
margin_top = 260.0
|
|
||||||
margin_right = 490.0
|
|
||||||
margin_bottom = 505.0
|
|
||||||
|
|
||||||
[node name="ClientUI4" parent="VBoxContainer/Clients" instance=ExtResource( 2 )]
|
|
||||||
margin_left = 505.0
|
|
||||||
margin_top = 260.0
|
|
||||||
margin_right = 995.0
|
|
||||||
margin_bottom = 505.0
|
|
||||||
|
|
||||||
[node name="Server" type="Node" parent="."]
|
[node name="Server" type="Node" parent="."]
|
||||||
|
|
||||||
[connection signal="toggled" from="VBoxContainer/Signaling/ListenButton" to="." method="_on_listen_toggled"]
|
[connection signal="pressed" from="VBoxContainer/Singleplayer" to="." method="_on_Singleplayer_pressed"]
|
||||||
[connection signal="pressed" from="VBoxContainer/Signaling/CenterContainer/LinkButton" to="." method="_on_LinkButton_pressed"]
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ func _create_peer(id):
|
||||||
return peer
|
return peer
|
||||||
|
|
||||||
func _new_ice_candidate(mid_name, index_name, sdp_name, id):
|
func _new_ice_candidate(mid_name, index_name, sdp_name, id):
|
||||||
|
print("New ICE Candidate: ", mid_name, index_name, sdp_name, id)
|
||||||
send_candidate(id, mid_name, index_name, sdp_name)
|
send_candidate(id, mid_name, index_name, sdp_name)
|
||||||
|
|
||||||
func _offer_created(type, data, id):
|
func _offer_created(type, data, id):
|
||||||
|
|
|
@ -14,6 +14,10 @@ config/name="kdt"
|
||||||
run/main_scene="res://main.tscn"
|
run/main_scene="res://main.tscn"
|
||||||
config/icon="res://icon.png"
|
config/icon="res://icon.png"
|
||||||
|
|
||||||
|
[autoload]
|
||||||
|
|
||||||
|
Global="*res://global.gd"
|
||||||
|
|
||||||
[gdnative]
|
[gdnative]
|
||||||
|
|
||||||
singletons=[ "res://webrtc/webrtc.tres" ]
|
singletons=[ "res://webrtc/webrtc.tres" ]
|
||||||
|
|
103
server.ts
103
server.ts
|
@ -1,11 +1,12 @@
|
||||||
|
import { randomId, randomSecret } from "./gen.ts";
|
||||||
|
|
||||||
const MAX_PEERS = 4096;
|
const MAX_PEERS = 4096;
|
||||||
const MAX_LOBBIES = 1024;
|
const MAX_LOBBIES = 1024;
|
||||||
const PORT = 9080;
|
const PORT = 9080;
|
||||||
const ALFNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
|
|
||||||
const NO_LOBBY_TIMEOUT = 1000;
|
const NO_LOBBY_TIMEOUT = 10000;
|
||||||
const SEAL_CLOSE_TIMEOUT = 10000;
|
const SEAL_CLOSE_TIMEOUT = 10000;
|
||||||
const PING_INTERVAL = 10000;
|
// const PING_INTERVAL = 10000;
|
||||||
|
|
||||||
const STR_NO_LOBBY = "Have not joined lobby yet";
|
const STR_NO_LOBBY = "Have not joined lobby yet";
|
||||||
const STR_HOST_DISCONNECTED = "Room host has disconnected";
|
const STR_HOST_DISCONNECTED = "Room host has disconnected";
|
||||||
|
@ -22,23 +23,11 @@ const STR_INVALID_DEST = "Invalid destination";
|
||||||
const STR_INVALID_CMD = "Invalid command";
|
const STR_INVALID_CMD = "Invalid command";
|
||||||
const STR_TOO_MANY_PEERS = "Too many peers connected";
|
const STR_TOO_MANY_PEERS = "Too many peers connected";
|
||||||
|
|
||||||
function randomInt(low: number, high: number) {
|
// TODO: setup regular pings?
|
||||||
return Math.floor(Math.random() * (high - low + 1) + low);
|
|
||||||
}
|
|
||||||
|
|
||||||
function randomId() {
|
// state
|
||||||
const arr = new Int32Array(1);
|
const lobbies = new Map();
|
||||||
crypto.getRandomValues(arr);
|
let peersCount = 0;
|
||||||
return Math.abs(arr[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function randomSecret() {
|
|
||||||
let out = "";
|
|
||||||
for (let i = 0; i < 16; i++) {
|
|
||||||
out += ALFNUM[randomInt(0, ALFNUM.length - 1)];
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProtoError extends Error {
|
class ProtoError extends Error {
|
||||||
code: number;
|
code: number;
|
||||||
|
@ -59,11 +48,38 @@ class Peer {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.ws = ws;
|
this.ws = ws;
|
||||||
this.lobby = "";
|
this.lobby = "";
|
||||||
// Close connection after 1 sec if client has not joined a lobby
|
|
||||||
|
// close connection after 10 sec if client has not joined a lobby
|
||||||
this.timeout = setTimeout(() => {
|
this.timeout = setTimeout(() => {
|
||||||
if (!this.lobby) ws.close(4000, STR_NO_LOBBY);
|
if (!this.lobby) ws.close(4000, STR_NO_LOBBY);
|
||||||
}, NO_LOBBY_TIMEOUT);
|
}, NO_LOBBY_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
joinLobby(lobbyName: string) {
|
||||||
|
if (lobbyName === "") {
|
||||||
|
if (lobbies.size >= MAX_LOBBIES) {
|
||||||
|
throw new ProtoError(4000, STR_TOO_MANY_LOBBIES);
|
||||||
|
}
|
||||||
|
// Peer must not already be in a lobby
|
||||||
|
if (this.lobby !== "") {
|
||||||
|
throw new ProtoError(4000, STR_ALREADY_IN_LOBBY);
|
||||||
|
}
|
||||||
|
lobbyName = randomSecret();
|
||||||
|
lobbies.set(lobbyName, new Lobby(lobbyName, this.id));
|
||||||
|
console.log(`Peer ${this.id} created lobby ${lobbyName}`);
|
||||||
|
console.log(`Open lobbies: ${lobbies.size}`);
|
||||||
|
}
|
||||||
|
const lobby = lobbies.get(lobbyName);
|
||||||
|
if (!lobby) throw new ProtoError(4000, STR_LOBBY_DOES_NOT_EXISTS);
|
||||||
|
if (lobby.sealed) throw new ProtoError(4000, STR_LOBBY_IS_SEALED);
|
||||||
|
this.lobby = lobbyName;
|
||||||
|
console.log(
|
||||||
|
`Peer ${this.id} joining lobby ${lobbyName} ` +
|
||||||
|
`with ${lobby.peers.length} peers`,
|
||||||
|
);
|
||||||
|
lobby.join(this);
|
||||||
|
this.ws.send(`J: ${lobbyName}\n`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Lobby {
|
class Lobby {
|
||||||
|
@ -95,6 +111,7 @@ class Lobby {
|
||||||
});
|
});
|
||||||
this.peers.push(peer);
|
this.peers.push(peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
leave(peer: Peer) {
|
leave(peer: Peer) {
|
||||||
const idx = this.peers.findIndex((p) => peer === p);
|
const idx = this.peers.findIndex((p) => peer === p);
|
||||||
if (idx === -1) return false;
|
if (idx === -1) return false;
|
||||||
|
@ -102,9 +119,9 @@ class Lobby {
|
||||||
const close = assigned === 1;
|
const close = assigned === 1;
|
||||||
this.peers.forEach((p) => {
|
this.peers.forEach((p) => {
|
||||||
try {
|
try {
|
||||||
// Room host disconnected, must close.
|
// room host disconnected
|
||||||
if (close) p.ws.close(4000, STR_HOST_DISCONNECTED);
|
if (close) p.ws.close(4000, STR_HOST_DISCONNECTED);
|
||||||
// Notify peer disconnect.
|
// notify peers
|
||||||
else p.ws.send(`D: ${assigned}\n`);
|
else p.ws.send(`D: ${assigned}\n`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Error when leaving: ${e}`);
|
console.error(`Error when leaving: ${e}`);
|
||||||
|
@ -112,14 +129,15 @@ class Lobby {
|
||||||
});
|
});
|
||||||
this.peers.splice(idx, 1);
|
this.peers.splice(idx, 1);
|
||||||
if (close && this.closeTimer >= 0) {
|
if (close && this.closeTimer >= 0) {
|
||||||
// We are closing already.
|
// we are closing already.
|
||||||
clearTimeout(this.closeTimer);
|
clearTimeout(this.closeTimer);
|
||||||
this.closeTimer = -1;
|
this.closeTimer = -1;
|
||||||
}
|
}
|
||||||
return close;
|
return close;
|
||||||
}
|
}
|
||||||
|
|
||||||
seal(peer: Peer) {
|
seal(peer: Peer) {
|
||||||
// Only host can seal
|
// only host can seal
|
||||||
if (peer.id !== this.host) {
|
if (peer.id !== this.host) {
|
||||||
throw new ProtoError(4000, STR_ONLY_HOST_CAN_SEAL);
|
throw new ProtoError(4000, STR_ONLY_HOST_CAN_SEAL);
|
||||||
}
|
}
|
||||||
|
@ -132,7 +150,7 @@ class Lobby {
|
||||||
`with ${this.peers.length} peers`,
|
`with ${this.peers.length} peers`,
|
||||||
);
|
);
|
||||||
this.closeTimer = setTimeout(() => {
|
this.closeTimer = setTimeout(() => {
|
||||||
// Close peer connection to host (and thus the lobby)
|
// close peer connection to host (and thus the lobby)
|
||||||
this.peers.forEach((p) => {
|
this.peers.forEach((p) => {
|
||||||
p.ws.close(1000, STR_SEAL_COMPLETE);
|
p.ws.close(1000, STR_SEAL_COMPLETE);
|
||||||
});
|
});
|
||||||
|
@ -140,35 +158,6 @@ class Lobby {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const lobbies = new Map();
|
|
||||||
let peersCount = 0;
|
|
||||||
|
|
||||||
function joinLobby(peer: Peer, lobbyName: string) {
|
|
||||||
if (lobbyName === "") {
|
|
||||||
if (lobbies.size >= MAX_LOBBIES) {
|
|
||||||
throw new ProtoError(4000, STR_TOO_MANY_LOBBIES);
|
|
||||||
}
|
|
||||||
// Peer must not already be in a lobby
|
|
||||||
if (peer.lobby !== "") {
|
|
||||||
throw new ProtoError(4000, STR_ALREADY_IN_LOBBY);
|
|
||||||
}
|
|
||||||
lobbyName = randomSecret();
|
|
||||||
lobbies.set(lobbyName, new Lobby(lobbyName, peer.id));
|
|
||||||
console.log(`Peer ${peer.id} created lobby ${lobbyName}`);
|
|
||||||
console.log(`Open lobbies: ${lobbies.size}`);
|
|
||||||
}
|
|
||||||
const lobby = lobbies.get(lobbyName);
|
|
||||||
if (!lobby) throw new ProtoError(4000, STR_LOBBY_DOES_NOT_EXISTS);
|
|
||||||
if (lobby.sealed) throw new ProtoError(4000, STR_LOBBY_IS_SEALED);
|
|
||||||
peer.lobby = lobbyName;
|
|
||||||
console.log(
|
|
||||||
`Peer ${peer.id} joining lobby ${lobbyName} ` +
|
|
||||||
`with ${lobby.peers.length} peers`,
|
|
||||||
);
|
|
||||||
lobby.join(peer);
|
|
||||||
peer.ws.send(`J: ${lobbyName}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseMsg(peer: Peer, msg: string) {
|
function parseMsg(peer: Peer, msg: string) {
|
||||||
const sep = msg.indexOf("\n");
|
const sep = msg.indexOf("\n");
|
||||||
if (sep < 0) throw new ProtoError(4000, STR_INVALID_FORMAT);
|
if (sep < 0) throw new ProtoError(4000, STR_INVALID_FORMAT);
|
||||||
|
@ -178,9 +167,9 @@ function parseMsg(peer: Peer, msg: string) {
|
||||||
|
|
||||||
const data = msg.slice(sep);
|
const data = msg.slice(sep);
|
||||||
|
|
||||||
// Lobby joining.
|
// join
|
||||||
if (cmd.startsWith("J: ")) {
|
if (cmd.startsWith("J: ")) {
|
||||||
joinLobby(peer, cmd.substr(3).trim());
|
peer.joinLobby(cmd.substr(3).trim());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +177,7 @@ function parseMsg(peer: Peer, msg: string) {
|
||||||
const lobby = lobbies.get(peer.lobby);
|
const lobby = lobbies.get(peer.lobby);
|
||||||
if (!lobby) throw new ProtoError(4000, STR_SERVER_ERROR);
|
if (!lobby) throw new ProtoError(4000, STR_SERVER_ERROR);
|
||||||
|
|
||||||
// Lobby sealing.
|
// seal
|
||||||
if (cmd.startsWith("S: ")) {
|
if (cmd.startsWith("S: ")) {
|
||||||
lobby.seal(peer);
|
lobby.seal(peer);
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Reference in a new issue