mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2025-12-16 13:30:07 +01:00
Use static typing in all demos (#1063)
This leads to code that is easier to understand and runs faster thanks to GDScript's typed instructions. The untyped declaration warning is now enabled on all projects where type hints were added. All projects currently run without any untyped declration warnings. Dodge the Creeps and Squash the Creeps demos intentionally don't use type hints to match the documentation, where type hints haven't been adopted yet (given its beginner focus).
This commit is contained in:
@@ -4,13 +4,14 @@ var in_area: Array = []
|
||||
var from_player: int
|
||||
|
||||
# Called from the animation.
|
||||
func explode():
|
||||
func explode() -> void:
|
||||
if not is_multiplayer_authority():
|
||||
# Explode only on authority.
|
||||
return
|
||||
for p in in_area:
|
||||
|
||||
for p: Object in in_area:
|
||||
if p.has_method("exploded"):
|
||||
# Checks if there is wall in between bomb and the object
|
||||
# Checks if there is wall in between bomb and the object.
|
||||
var world_state: PhysicsDirectSpaceState2D = get_world_2d().direct_space_state
|
||||
var query := PhysicsRayQueryParameters2D.create(position, p.position)
|
||||
query.hit_from_inside = true
|
||||
@@ -20,15 +21,15 @@ func explode():
|
||||
p.exploded.rpc(from_player)
|
||||
|
||||
|
||||
func done():
|
||||
func done() -> void:
|
||||
if is_multiplayer_authority():
|
||||
queue_free()
|
||||
|
||||
|
||||
func _on_bomb_body_enter(body):
|
||||
func _on_bomb_body_enter(body: Node2D) -> void:
|
||||
if not body in in_area:
|
||||
in_area.append(body)
|
||||
|
||||
|
||||
func _on_bomb_body_exit(body):
|
||||
func _on_bomb_body_exit(body: Node2D) -> void:
|
||||
in_area.erase(body)
|
||||
|
||||
@@ -125,10 +125,10 @@ angular_velocity_max = 188.35
|
||||
scale_amount_curve = SubResource("Curve_4yges")
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||
autoplay = "anim"
|
||||
libraries = {
|
||||
"": SubResource("AnimationLibrary_h2w7m")
|
||||
}
|
||||
autoplay = "anim"
|
||||
|
||||
[connection signal="body_entered" from="." to="." method="_on_bomb_body_enter"]
|
||||
[connection signal="body_exited" from="." to="." method="_on_bomb_body_exit"]
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
extends MultiplayerSpawner
|
||||
|
||||
func _init():
|
||||
func _init() -> void:
|
||||
spawn_function = _spawn_bomb
|
||||
|
||||
|
||||
func _spawn_bomb(data):
|
||||
func _spawn_bomb(data: Array) -> Area2D:
|
||||
if data.size() != 2 or typeof(data[0]) != TYPE_VECTOR2 or typeof(data[1]) != TYPE_INT:
|
||||
return null
|
||||
var bomb = preload("res://bomb.tscn").instantiate()
|
||||
|
||||
var bomb: Area2D = preload("res://bomb.tscn").instantiate()
|
||||
bomb.position = data[0]
|
||||
bomb.from_player = data[1]
|
||||
return bomb
|
||||
|
||||
@@ -1,149 +1,156 @@
|
||||
extends Node
|
||||
|
||||
# Default game server port. Can be any number between 1024 and 49151.
|
||||
# Not on the list of registered or common ports as of November 2020:
|
||||
# Not on the list of registered or common ports as of May 2024:
|
||||
# https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
|
||||
const DEFAULT_PORT = 10567
|
||||
|
||||
# Max number of players.
|
||||
## The maximum number of players.
|
||||
const MAX_PEERS = 12
|
||||
|
||||
var peer = null
|
||||
var peer: ENetMultiplayerPeer
|
||||
|
||||
# Name for my player.
|
||||
var player_name = "The Warrior"
|
||||
## Our local player's name.
|
||||
var player_name := "The Warrior"
|
||||
|
||||
# Names for remote players in id:name format.
|
||||
var players = {}
|
||||
var players_ready = []
|
||||
var players := {}
|
||||
var players_ready: Array[int] = []
|
||||
|
||||
# Signals to let lobby GUI know what's going on.
|
||||
signal player_list_changed()
|
||||
signal connection_failed()
|
||||
signal connection_succeeded()
|
||||
signal game_ended()
|
||||
signal game_error(what)
|
||||
signal game_error(what: int)
|
||||
|
||||
# Callback from SceneTree.
|
||||
func _player_connected(id):
|
||||
func _player_connected(id: int) -> void:
|
||||
# Registration of a client beings here, tell the connected player that we are here.
|
||||
register_player.rpc_id(id, player_name)
|
||||
|
||||
|
||||
# Callback from SceneTree.
|
||||
func _player_disconnected(id):
|
||||
if has_node("/root/World"): # Game is in progress.
|
||||
func _player_disconnected(id: int) -> void:
|
||||
if has_node("/root/World"):
|
||||
# Game is in progress.
|
||||
if multiplayer.is_server():
|
||||
game_error.emit("Player " + players[id] + " disconnected")
|
||||
end_game()
|
||||
else: # Game is not in progress.
|
||||
else:
|
||||
# Game is not in progress.
|
||||
# Unregister this player.
|
||||
unregister_player(id)
|
||||
|
||||
|
||||
# Callback from SceneTree, only for clients (not server).
|
||||
func _connected_ok():
|
||||
func _connected_ok() -> void:
|
||||
# We just connected to a server
|
||||
connection_succeeded.emit()
|
||||
|
||||
|
||||
# Callback from SceneTree, only for clients (not server).
|
||||
func _server_disconnected():
|
||||
func _server_disconnected() -> void:
|
||||
game_error.emit("Server disconnected")
|
||||
end_game()
|
||||
|
||||
|
||||
# Callback from SceneTree, only for clients (not server).
|
||||
func _connected_fail():
|
||||
func _connected_fail() -> void:
|
||||
multiplayer.set_network_peer(null) # Remove peer
|
||||
connection_failed.emit()
|
||||
|
||||
|
||||
# Lobby management functions.
|
||||
@rpc("any_peer")
|
||||
func register_player(new_player_name):
|
||||
var id = multiplayer.get_remote_sender_id()
|
||||
func register_player(new_player_name: String) -> void:
|
||||
var id := multiplayer.get_remote_sender_id()
|
||||
players[id] = new_player_name
|
||||
player_list_changed.emit()
|
||||
|
||||
|
||||
func unregister_player(id):
|
||||
func unregister_player(id: int) -> void:
|
||||
players.erase(id)
|
||||
player_list_changed.emit()
|
||||
|
||||
|
||||
@rpc("call_local")
|
||||
func load_world():
|
||||
func load_world() -> void:
|
||||
# Change scene.
|
||||
var world = load("res://world.tscn").instantiate()
|
||||
var world: Node2D = load("res://world.tscn").instantiate()
|
||||
get_tree().get_root().add_child(world)
|
||||
get_tree().get_root().get_node("Lobby").hide()
|
||||
|
||||
# Set up score.
|
||||
world.get_node("Score").add_player(multiplayer.get_unique_id(), player_name)
|
||||
for pn in players:
|
||||
for pn: int in players:
|
||||
world.get_node("Score").add_player(pn, players[pn])
|
||||
get_tree().set_pause(false) # Unpause and unleash the game!
|
||||
|
||||
# Unpause and unleash the game!
|
||||
get_tree().paused = false
|
||||
|
||||
|
||||
func host_game(new_player_name):
|
||||
func host_game(new_player_name: String) -> void:
|
||||
player_name = new_player_name
|
||||
peer = ENetMultiplayerPeer.new()
|
||||
peer.create_server(DEFAULT_PORT, MAX_PEERS)
|
||||
multiplayer.set_multiplayer_peer(peer)
|
||||
|
||||
|
||||
func join_game(ip, new_player_name):
|
||||
func join_game(ip: String, new_player_name: String) -> void:
|
||||
player_name = new_player_name
|
||||
peer = ENetMultiplayerPeer.new()
|
||||
peer.create_client(ip, DEFAULT_PORT)
|
||||
multiplayer.set_multiplayer_peer(peer)
|
||||
|
||||
|
||||
func get_player_list():
|
||||
func get_player_list() -> Array:
|
||||
return players.values()
|
||||
|
||||
|
||||
func get_player_name():
|
||||
return player_name
|
||||
|
||||
|
||||
func begin_game():
|
||||
func begin_game() -> void:
|
||||
assert(multiplayer.is_server())
|
||||
load_world.rpc()
|
||||
|
||||
var world = get_tree().get_root().get_node("World")
|
||||
var player_scene = load("res://player.tscn")
|
||||
var world: Node2D = get_tree().get_root().get_node("World")
|
||||
var player_scene: PackedScene = load("res://player.tscn")
|
||||
|
||||
# Create a dictionary with peer id and respective spawn points, could be improved by randomizing.
|
||||
var spawn_points = {}
|
||||
spawn_points[1] = 0 # Server in spawn point 0.
|
||||
var spawn_point_idx = 1
|
||||
for p in players:
|
||||
# Create a dictionary with peer ID. and respective spawn points.
|
||||
# TODO: This could be improved by randomizing spawn points for players.
|
||||
var spawn_points := {}
|
||||
spawn_points[1] = 0 # Server in spawn point 0.
|
||||
var spawn_point_idx := 1
|
||||
for p: int in players:
|
||||
spawn_points[p] = spawn_point_idx
|
||||
spawn_point_idx += 1
|
||||
|
||||
for p_id in spawn_points:
|
||||
var spawn_pos = world.get_node("SpawnPoints/" + str(spawn_points[p_id])).position
|
||||
var player = player_scene.instantiate()
|
||||
for p_id: int in spawn_points:
|
||||
var spawn_pos: Vector2 = world.get_node("SpawnPoints/" + str(spawn_points[p_id])).position
|
||||
var player := player_scene.instantiate()
|
||||
player.synced_position = spawn_pos
|
||||
player.name = str(p_id)
|
||||
player.set_player_name(player_name if p_id == multiplayer.get_unique_id() else players[p_id])
|
||||
world.get_node("Players").add_child(player)
|
||||
# The RPC must be called after the player is added to the scene tree.
|
||||
player.set_player_name.rpc(player_name if p_id == multiplayer.get_unique_id() else players[p_id])
|
||||
|
||||
|
||||
func end_game():
|
||||
if has_node("/root/World"): # Game is in progress.
|
||||
# End it
|
||||
func end_game() -> void:
|
||||
if has_node("/root/World"):
|
||||
# If the game is in progress, end it.
|
||||
get_node("/root/World").queue_free()
|
||||
|
||||
game_ended.emit()
|
||||
players.clear()
|
||||
|
||||
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
multiplayer.peer_connected.connect(_player_connected)
|
||||
multiplayer.peer_disconnected.connect(_player_disconnected)
|
||||
multiplayer.connected_to_server.connect(_connected_ok)
|
||||
multiplayer.connection_failed.connect(_connected_fail)
|
||||
multiplayer.server_disconnected.connect(_server_disconnected)
|
||||
|
||||
|
||||
## Returns an unique-looking player color based on the name's hash.
|
||||
func get_player_color(p_name: String) -> Color:
|
||||
return Color.from_hsv(wrapf(p_name.hash() * 0.001, 0.0, 1.0), 0.6, 1.0)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
extends Control
|
||||
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
# Called every time the node is added to the scene.
|
||||
gamestate.connection_failed.connect(_on_connection_failed)
|
||||
gamestate.connection_succeeded.connect(_on_connection_success)
|
||||
@@ -11,11 +11,11 @@ func _ready():
|
||||
if OS.has_environment("USERNAME"):
|
||||
$Connect/Name.text = OS.get_environment("USERNAME")
|
||||
else:
|
||||
var desktop_path = OS.get_system_dir(0).replace("\\", "/").split("/")
|
||||
var desktop_path := OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP).replace("\\", "/").split("/")
|
||||
$Connect/Name.text = desktop_path[desktop_path.size() - 2]
|
||||
|
||||
|
||||
func _on_host_pressed():
|
||||
func _on_host_pressed() -> void:
|
||||
if $Connect/Name.text == "":
|
||||
$Connect/ErrorLabel.text = "Invalid name!"
|
||||
return
|
||||
@@ -24,17 +24,18 @@ func _on_host_pressed():
|
||||
$Players.show()
|
||||
$Connect/ErrorLabel.text = ""
|
||||
|
||||
var player_name = $Connect/Name.text
|
||||
var player_name: String = $Connect/Name.text
|
||||
gamestate.host_game(player_name)
|
||||
get_window().title = ProjectSettings.get_setting("application/config/name") + ": Server (%s)" % $Connect/Name.text
|
||||
refresh_lobby()
|
||||
|
||||
|
||||
func _on_join_pressed():
|
||||
func _on_join_pressed() -> void:
|
||||
if $Connect/Name.text == "":
|
||||
$Connect/ErrorLabel.text = "Invalid name!"
|
||||
return
|
||||
|
||||
var ip = $Connect/IPAddress.text
|
||||
var ip: String = $Connect/IPAddress.text
|
||||
if not ip.is_valid_ip_address():
|
||||
$Connect/ErrorLabel.text = "Invalid IP address!"
|
||||
return
|
||||
@@ -43,22 +44,23 @@ func _on_join_pressed():
|
||||
$Connect/Host.disabled = true
|
||||
$Connect/Join.disabled = true
|
||||
|
||||
var player_name = $Connect/Name.text
|
||||
var player_name: String = $Connect/Name.text
|
||||
gamestate.join_game(ip, player_name)
|
||||
get_window().title = ProjectSettings.get_setting("application/config/name") + ": Client (%s)" % $Connect/Name.text
|
||||
|
||||
|
||||
func _on_connection_success():
|
||||
func _on_connection_success() -> void:
|
||||
$Connect.hide()
|
||||
$Players.show()
|
||||
|
||||
|
||||
func _on_connection_failed():
|
||||
func _on_connection_failed() -> void:
|
||||
$Connect/Host.disabled = false
|
||||
$Connect/Join.disabled = false
|
||||
$Connect/ErrorLabel.set_text("Connection failed.")
|
||||
|
||||
|
||||
func _on_game_ended():
|
||||
func _on_game_ended() -> void:
|
||||
show()
|
||||
$Connect.show()
|
||||
$Players.hide()
|
||||
@@ -66,27 +68,27 @@ func _on_game_ended():
|
||||
$Connect/Join.disabled = false
|
||||
|
||||
|
||||
func _on_game_error(errtxt):
|
||||
func _on_game_error(errtxt: String) -> void:
|
||||
$ErrorDialog.dialog_text = errtxt
|
||||
$ErrorDialog.popup_centered()
|
||||
$Connect/Host.disabled = false
|
||||
$Connect/Join.disabled = false
|
||||
|
||||
|
||||
func refresh_lobby():
|
||||
var players = gamestate.get_player_list()
|
||||
func refresh_lobby() -> void:
|
||||
var players := gamestate.get_player_list()
|
||||
players.sort()
|
||||
$Players/List.clear()
|
||||
$Players/List.add_item(gamestate.get_player_name() + " (You)")
|
||||
for p in players:
|
||||
$Players/List.add_item(gamestate.player_name + " (you)")
|
||||
for p: String in players:
|
||||
$Players/List.add_item(p)
|
||||
|
||||
$Players/Start.disabled = not multiplayer.is_server()
|
||||
|
||||
|
||||
func _on_start_pressed():
|
||||
func _on_start_pressed() -> void:
|
||||
gamestate.begin_game()
|
||||
|
||||
|
||||
func _on_find_public_ip_pressed():
|
||||
func _on_find_public_ip_pressed() -> void:
|
||||
OS.shell_open("https://icanhazip.com/")
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
extends CharacterBody2D
|
||||
|
||||
## The player's movement speed (in pixels per second).
|
||||
const MOTION_SPEED = 90.0
|
||||
|
||||
## The delay before which you can place a new bomb (in seconds).
|
||||
const BOMB_RATE = 0.5
|
||||
|
||||
@export
|
||||
var synced_position := Vector2()
|
||||
@export var synced_position := Vector2()
|
||||
|
||||
@export
|
||||
var stunned = false
|
||||
@export var stunned := false
|
||||
|
||||
@onready
|
||||
var inputs = $Inputs
|
||||
var last_bomb_time = BOMB_RATE
|
||||
var current_anim = ""
|
||||
var last_bomb_time := BOMB_RATE
|
||||
var current_anim := ""
|
||||
|
||||
func _ready():
|
||||
@onready var inputs: Node = $Inputs
|
||||
|
||||
func _ready() -> void:
|
||||
stunned = false
|
||||
position = synced_position
|
||||
if str(name).is_valid_int():
|
||||
get_node("Inputs/InputsSync").set_multiplayer_authority(str(name).to_int())
|
||||
$"Inputs/InputsSync".set_multiplayer_authority(str(name).to_int())
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
func _physics_process(delta: float) -> void:
|
||||
if multiplayer.multiplayer_peer == null or str(multiplayer.get_unique_id()) == str(name):
|
||||
# The client which this player represent will update the controls state, and notify it to everyone.
|
||||
inputs.update()
|
||||
@@ -33,43 +34,48 @@ func _physics_process(delta):
|
||||
last_bomb_time += delta
|
||||
if not stunned and is_multiplayer_authority() and inputs.bombing and last_bomb_time >= BOMB_RATE:
|
||||
last_bomb_time = 0.0
|
||||
get_node("../../BombSpawner").spawn([position, str(name).to_int()])
|
||||
$"../../BombSpawner".spawn([position, str(name).to_int()])
|
||||
else:
|
||||
# The client simply updates the position to the last known one.
|
||||
position = synced_position
|
||||
|
||||
if not stunned:
|
||||
# Everybody runs physics. I.e. clients tries to predict where they will be during the next frame.
|
||||
# Everybody runs physics. i.e. clients try to predict where they will be during the next frame.
|
||||
velocity = inputs.motion * MOTION_SPEED
|
||||
move_and_slide()
|
||||
|
||||
# Also update the animation based on the last known player input state
|
||||
var new_anim = "standing"
|
||||
# Also update the animation based on the last known player input state.
|
||||
var new_anim := &"standing"
|
||||
|
||||
if inputs.motion.y < 0:
|
||||
new_anim = "walk_up"
|
||||
new_anim = &"walk_up"
|
||||
elif inputs.motion.y > 0:
|
||||
new_anim = "walk_down"
|
||||
new_anim = &"walk_down"
|
||||
elif inputs.motion.x < 0:
|
||||
new_anim = "walk_left"
|
||||
new_anim = &"walk_left"
|
||||
elif inputs.motion.x > 0:
|
||||
new_anim = "walk_right"
|
||||
new_anim = &"walk_right"
|
||||
|
||||
if stunned:
|
||||
new_anim = "stunned"
|
||||
new_anim = &"stunned"
|
||||
|
||||
if new_anim != current_anim:
|
||||
current_anim = new_anim
|
||||
get_node("anim").play(current_anim)
|
||||
|
||||
|
||||
func set_player_name(value):
|
||||
get_node("label").text = value
|
||||
$anim.play(current_anim)
|
||||
|
||||
|
||||
@rpc("call_local")
|
||||
func exploded(_by_who):
|
||||
func set_player_name(value: String) -> void:
|
||||
$label.text = value
|
||||
# Assign a random color to the player based on its name.
|
||||
$label.modulate = gamestate.get_player_color(value)
|
||||
$sprite.modulate = Color(0.5, 0.5, 0.5) + gamestate.get_player_color(value)
|
||||
|
||||
|
||||
@rpc("call_local")
|
||||
func exploded(_by_who: int) -> void:
|
||||
if stunned:
|
||||
return
|
||||
|
||||
stunned = true
|
||||
get_node("anim").play("stunned")
|
||||
$anim.play("stunned")
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[ext_resource type="Script" path="res://player_controls.gd" id="4_k1vfr"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="1"]
|
||||
radius = 20.0
|
||||
radius = 16.0
|
||||
|
||||
[sub_resource type="Animation" id="2"]
|
||||
resource_name = "standing"
|
||||
@@ -149,22 +149,18 @@ outline_color = Color(0, 0, 0, 1)
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_sh64w"]
|
||||
properties/0/path = NodePath(".:synced_position")
|
||||
properties/0/spawn = true
|
||||
properties/0/sync = true
|
||||
properties/0/watch = false
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath("label:text")
|
||||
properties/1/spawn = true
|
||||
properties/1/sync = false
|
||||
properties/1/watch = false
|
||||
properties/1/replication_mode = 0
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_w53uu"]
|
||||
properties/0/path = NodePath(".:motion")
|
||||
properties/0/spawn = true
|
||||
properties/0/sync = true
|
||||
properties/0/watch = false
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath(".:bombing")
|
||||
properties/1/spawn = true
|
||||
properties/1/sync = true
|
||||
properties/1/watch = false
|
||||
properties/1/replication_mode = 1
|
||||
|
||||
[node name="player" type="CharacterBody2D"]
|
||||
z_index = 10
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
extends Node
|
||||
|
||||
@export
|
||||
var motion = Vector2():
|
||||
@export var motion := Vector2():
|
||||
set(value):
|
||||
# This will be sent by players, make sure values are within limits.
|
||||
motion = clamp(value, Vector2(-1, -1), Vector2(1, 1))
|
||||
|
||||
@export
|
||||
var bombing = false
|
||||
@export var bombing := false
|
||||
|
||||
func update():
|
||||
var m = Vector2()
|
||||
if Input.is_action_pressed("move_left"):
|
||||
func update() -> void:
|
||||
var m := Vector2()
|
||||
if Input.is_action_pressed(&"move_left"):
|
||||
m += Vector2(-1, 0)
|
||||
if Input.is_action_pressed("move_right"):
|
||||
if Input.is_action_pressed(&"move_right"):
|
||||
m += Vector2(1, 0)
|
||||
if Input.is_action_pressed("move_up"):
|
||||
if Input.is_action_pressed(&"move_up"):
|
||||
m += Vector2(0, -1)
|
||||
if Input.is_action_pressed("move_down"):
|
||||
if Input.is_action_pressed(&"move_down"):
|
||||
m += Vector2(0, 1)
|
||||
|
||||
motion = m
|
||||
bombing = Input.is_action_pressed("set_bomb")
|
||||
bombing = Input.is_action_pressed(&"set_bomb")
|
||||
|
||||
@@ -23,6 +23,10 @@ config/icon="res://icon.webp"
|
||||
|
||||
gamestate="*res://gamestate.gd"
|
||||
|
||||
[debug]
|
||||
|
||||
gdscript/warnings/untyped_declaration=1
|
||||
|
||||
[display]
|
||||
|
||||
window/stretch/mode="canvas_items"
|
||||
@@ -75,6 +79,8 @@ set_bomb={
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
2d/snap/snap_2d_transforms_to_pixel=true
|
||||
2d/snap/snap_2d_vertices_to_pixel=true
|
||||
|
||||
[replication]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
extends CharacterBody2D
|
||||
|
||||
@rpc("call_local")
|
||||
func exploded(by_who):
|
||||
func exploded(by_who: int) -> void:
|
||||
$"../../Score".increase_score(by_who)
|
||||
$"AnimationPlayer".play("explode")
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
extends HBoxContainer
|
||||
|
||||
var player_labels = {}
|
||||
var player_labels := {}
|
||||
|
||||
func _process(_delta):
|
||||
var rocks_left = $"../Rocks".get_child_count()
|
||||
func _process(_delta: float) -> void:
|
||||
var rocks_left := $"../Rocks".get_child_count()
|
||||
if rocks_left == 0:
|
||||
var winner_name = ""
|
||||
var winner_score = 0
|
||||
for p in player_labels:
|
||||
var winner_name := ""
|
||||
var winner_score := 0
|
||||
for p: int in player_labels:
|
||||
if player_labels[p].score > winner_score:
|
||||
winner_score = player_labels[p].score
|
||||
winner_name = player_labels[p].name
|
||||
@@ -16,30 +16,36 @@ func _process(_delta):
|
||||
$"../Winner".show()
|
||||
|
||||
|
||||
func increase_score(for_who):
|
||||
func increase_score(for_who: int) -> void:
|
||||
assert(for_who in player_labels)
|
||||
var pl = player_labels[for_who]
|
||||
|
||||
var pl: Dictionary = player_labels[for_who]
|
||||
pl.score += 1
|
||||
pl.label.set_text(pl.name + "\n" + str(pl.score))
|
||||
|
||||
|
||||
func add_player(id, new_player_name):
|
||||
var l = Label.new()
|
||||
l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
l.set_text(new_player_name + "\n" + "0")
|
||||
l.set_h_size_flags(SIZE_EXPAND_FILL)
|
||||
var font = preload("res://montserrat.otf")
|
||||
l.set("custom_fonts/font", font)
|
||||
l.set("custom_font_size/font_size", 18)
|
||||
add_child(l)
|
||||
func add_player(id: int, new_player_name: String) -> void:
|
||||
var label := Label.new()
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.text = new_player_name + "\n" + "0"
|
||||
label.modulate = gamestate.get_player_color(new_player_name)
|
||||
label.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
label.add_theme_font_override("font", preload("res://montserrat.otf"))
|
||||
label.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||
label.add_theme_constant_override("outline_size", 9)
|
||||
label.add_theme_font_size_override("font_size", 18)
|
||||
add_child(label)
|
||||
|
||||
player_labels[id] = { name = new_player_name, label = l, score = 0 }
|
||||
player_labels[id] = {
|
||||
name = new_player_name,
|
||||
label = label,
|
||||
score = 0,
|
||||
}
|
||||
|
||||
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
$"../Winner".hide()
|
||||
set_process(true)
|
||||
|
||||
|
||||
func _on_exit_game_pressed():
|
||||
func _on_exit_game_pressed() -> void:
|
||||
gamestate.end_game()
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
[node name="TileMap" type="TileMap" parent="."]
|
||||
tile_set = ExtResource("1")
|
||||
cell_quadrant_size = 48
|
||||
rendering_quadrant_size = 48
|
||||
format = 2
|
||||
layer_0/tile_data = PackedInt32Array(0, 0, 0, 65536, 0, 0, 131072, 0, 0, 196608, 0, 0, 262144, 0, 0, 327680, 0, 0, 393216, 0, 0, 458752, 0, 0, 524288, 0, 0, 589824, 0, 0, 655360, 0, 0, 720896, 0, 0, 786432, 0, 0, 1, 0, 0, 65537, 65536, 0, 131073, 65536, 0, 196609, 65536, 0, 262145, 65536, 0, 327681, 65536, 0, 393217, 65536, 0, 458753, 65536, 0, 524289, 65536, 0, 589825, 65536, 0, 655361, 65536, 0, 720897, 65536, 0, 786433, 0, 0, 2, 0, 0, 65538, 65536, 0, 131074, 0, 0, 196610, 65536, 0, 262146, 0, 0, 327682, 65536, 0, 393218, 0, 0, 458754, 65536, 0, 524290, 0, 0, 589826, 65536, 0, 655362, 0, 0, 720898, 65536, 0, 786434, 0, 0, 3, 0, 0, 65539, 65536, 0, 131075, 65536, 0, 196611, 65536, 0, 262147, 65536, 0, 327683, 65536, 0, 393219, 65536, 0, 458755, 65536, 0, 524291, 0, 0, 589827, 65536, 0, 655363, 65536, 0, 720899, 65536, 0, 786435, 0, 0, 4, 0, 0, 65540, 65536, 0, 131076, 0, 0, 196612, 0, 0, 262148, 0, 0, 327684, 65536, 0, 393220, 0, 0, 458756, 65536, 0, 524292, 0, 0, 589828, 65536, 0, 655364, 0, 0, 720900, 65536, 0, 786436, 0, 0, 5, 0, 0, 65541, 65536, 0, 131077, 65536, 0, 196613, 65536, 0, 262149, 65536, 0, 327685, 65536, 0, 393221, 65536, 0, 458757, 65536, 0, 524293, 65536, 0, 589829, 65536, 0, 655365, 65536, 0, 720901, 65536, 0, 786437, 0, 0, 6, 0, 0, 65542, 65536, 0, 131078, 0, 0, 196614, 65536, 0, 262150, 0, 0, 327686, 0, 0, 393222, 0, 0, 458758, 65536, 0, 524294, 0, 0, 589830, 65536, 0, 655366, 0, 0, 720902, 65536, 0, 786438, 0, 0, 7, 0, 0, 65543, 65536, 0, 131079, 65536, 0, 196615, 65536, 0, 262151, 65536, 0, 327687, 65536, 0, 393223, 65536, 0, 458759, 65536, 0, 524295, 65536, 0, 589831, 65536, 0, 655367, 65536, 0, 720903, 65536, 0, 786439, 0, 0, 8, 0, 0, 65544, 65536, 0, 131080, 0, 0, 196616, 65536, 0, 262152, 0, 0, 327688, 65536, 0, 393224, 0, 0, 458760, 65536, 0, 524296, 0, 0, 589832, 65536, 0, 655368, 0, 0, 720904, 65536, 0, 786440, 0, 0, 9, 0, 0, 65545, 65536, 0, 131081, 65536, 0, 196617, 65536, 0, 262153, 65536, 0, 327689, 65536, 0, 393225, 65536, 0, 458761, 65536, 0, 524297, 65536, 0, 589833, 65536, 0, 655369, 65536, 0, 720905, 65536, 0, 786441, 0, 0, 10, 0, 0, 65546, 65536, 0, 131082, 0, 0, 196618, 0, 0, 262154, 0, 0, 327690, 65536, 0, 393226, 0, 0, 458762, 65536, 0, 524298, 0, 0, 589834, 65536, 0, 655370, 0, 0, 720906, 65536, 0, 786442, 0, 0, 11, 0, 0, 65547, 65536, 0, 131083, 0, 0, 196619, 65536, 0, 262155, 65536, 0, 327691, 65536, 0, 393227, 65536, 0, 458763, 65536, 0, 524299, 65536, 0, 589835, 65536, 0, 655371, 65536, 0, 720907, 65536, 0, 786443, 0, 0, 12, 0, 0, 65548, 65536, 0, 131084, 0, 0, 196620, 65536, 0, 262156, 0, 0, 327692, 65536, 0, 393228, 0, 0, 458764, 65536, 0, 524300, 0, 0, 589836, 65536, 0, 655372, 0, 0, 720908, 65536, 0, 786444, 0, 0, 13, 0, 0, 65549, 65536, 0, 131085, 0, 0, 196621, 65536, 0, 262157, 65536, 0, 327693, 65536, 0, 393229, 0, 0, 458765, 65536, 0, 524301, 0, 0, 589837, 65536, 0, 655373, 65536, 0, 720909, 65536, 0, 786445, 0, 0, 14, 0, 0, 65550, 65536, 0, 131086, 0, 0, 196622, 65536, 0, 262158, 0, 0, 327694, 65536, 0, 393230, 0, 0, 458766, 65536, 0, 524302, 0, 0, 589838, 65536, 0, 655374, 0, 0, 720910, 65536, 0, 786446, 0, 0, 15, 0, 0, 65551, 65536, 0, 131087, 65536, 0, 196623, 65536, 0, 262159, 65536, 0, 327695, 65536, 0, 393231, 0, 0, 458767, 65536, 0, 524303, 65536, 0, 589839, 65536, 0, 655375, 65536, 0, 720911, 65536, 0, 786447, 0, 0, 16, 0, 0, 65552, 65536, 0, 131088, 0, 0, 196624, 65536, 0, 262160, 0, 0, 327696, 65536, 0, 393232, 0, 0, 458768, 65536, 0, 524304, 0, 0, 589840, 65536, 0, 655376, 0, 0, 720912, 65536, 0, 786448, 0, 0, 17, 0, 0, 65553, 65536, 0, 131089, 65536, 0, 196625, 65536, 0, 262161, 65536, 0, 327697, 65536, 0, 393233, 65536, 0, 458769, 65536, 0, 524305, 65536, 0, 589841, 65536, 0, 655377, 65536, 0, 720913, 65536, 0, 786449, 0, 0, 18, 0, 0, 65554, 65536, 0, 131090, 0, 0, 196626, 65536, 0, 262162, 0, 0, 327698, 0, 0, 393234, 0, 0, 458770, 65536, 0, 524306, 0, 0, 589842, 65536, 0, 655378, 0, 0, 720914, 65536, 0, 786450, 0, 0, 19, 0, 0, 65555, 65536, 0, 131091, 65536, 0, 196627, 65536, 0, 262163, 65536, 0, 327699, 65536, 0, 393235, 65536, 0, 458771, 65536, 0, 524307, 65536, 0, 589843, 65536, 0, 655379, 65536, 0, 720915, 65536, 0, 786451, 0, 0, 20, 0, 0, 65556, 0, 0, 131092, 0, 0, 196628, 0, 0, 262164, 0, 0, 327700, 0, 0, 393236, 0, 0, 458772, 0, 0, 524308, 0, 0, 589844, 0, 0, 655380, 0, 0, 720916, 0, 0, 786452, 0, 0, 21, 0, 0, 65557, 0, 0, 131093, 0, 0, 196629, 0, 0, 262165, 0, 0, 327701, 0, 0, 393237, 0, 0, 458773, 0, 0, 524309, 0, 0, 589845, 0, 0, 655381, 0, 0, 720917, 0, 0, 786453, 0, 0)
|
||||
|
||||
@@ -266,6 +266,11 @@ position = Vector2(840, 456)
|
||||
|
||||
[node name="Players" type="Node2D" parent="."]
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
offset_right = 1056.0
|
||||
offset_bottom = 48.0
|
||||
color = Color(0, 0, 0, 0.501961)
|
||||
|
||||
[node name="Score" type="HBoxContainer" parent="."]
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 40.0
|
||||
@@ -298,7 +303,6 @@ text = "EXIT GAME"
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
offset = Vector2(512, 300)
|
||||
current = true
|
||||
|
||||
[node name="PlayerSpawner" type="MultiplayerSpawner" parent="."]
|
||||
_spawnable_scenes = PackedStringArray("res://player.tscn")
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
extends Area2D
|
||||
|
||||
const DEFAULT_SPEED = 100
|
||||
const DEFAULT_SPEED = 100.0
|
||||
|
||||
var direction = Vector2.LEFT
|
||||
var stopped = false
|
||||
var _speed = DEFAULT_SPEED
|
||||
var direction := Vector2.LEFT
|
||||
var stopped := false
|
||||
var _speed := DEFAULT_SPEED
|
||||
|
||||
@onready var _screen_size = get_viewport_rect().size
|
||||
@onready var _screen_size := get_viewport_rect().size
|
||||
|
||||
func _process(delta):
|
||||
func _process(delta: float) -> void:
|
||||
_speed += delta
|
||||
# Ball will move normally for both players,
|
||||
# even if it's sightly out of sync between them,
|
||||
@@ -17,24 +17,24 @@ func _process(delta):
|
||||
translate(_speed * delta * direction)
|
||||
|
||||
# Check screen bounds to make ball bounce.
|
||||
var ball_pos = position
|
||||
var ball_pos := position
|
||||
if (ball_pos.y < 0 and direction.y < 0) or (ball_pos.y > _screen_size.y and direction.y > 0):
|
||||
direction.y = -direction.y
|
||||
|
||||
if is_multiplayer_authority():
|
||||
# Only the master will decide when the ball is out in
|
||||
# the left side (it's own side). This makes the game
|
||||
# the left side (its own side). This makes the game
|
||||
# playable even if latency is high and ball is going
|
||||
# fast. Otherwise ball might be out in the other
|
||||
# fast. Otherwise, the ball might be out in the other
|
||||
# player's screen but not this one.
|
||||
if ball_pos.x < 0:
|
||||
get_parent().update_score.rpc(false)
|
||||
_reset_ball.rpc(false)
|
||||
else:
|
||||
# Only the puppet will decide when the ball is out in
|
||||
# the right side, which is it's own side. This makes
|
||||
# the right side, which is its own side. This makes
|
||||
# the game playable even if latency is high and ball
|
||||
# is going fast. Otherwise ball might be out in the
|
||||
# is going fast. Otherwise, the ball might be out in the
|
||||
# other player's screen but not this one.
|
||||
if ball_pos.x > _screen_size.x:
|
||||
get_parent().update_score.rpc(true)
|
||||
@@ -42,7 +42,7 @@ func _process(delta):
|
||||
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func bounce(left, random):
|
||||
func bounce(left: bool, random: float) -> void:
|
||||
# Using sync because both players can make it bounce.
|
||||
if left:
|
||||
direction.x = abs(direction.x)
|
||||
@@ -55,12 +55,12 @@ func bounce(left, random):
|
||||
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func stop():
|
||||
func stop() -> void:
|
||||
stopped = true
|
||||
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func _reset_ball(for_left):
|
||||
func _reset_ball(for_left: float) -> void:
|
||||
position = _screen_size / 2
|
||||
if for_left:
|
||||
direction = Vector2.LEFT
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
extends Control
|
||||
|
||||
# Default game server port. Can be any number between 1024 and 49151.
|
||||
# Not present on the list of registered or common ports as of December 2022:
|
||||
# Not present on the list of registered or common ports as of May 2024:
|
||||
# https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
|
||||
const DEFAULT_PORT = 8910
|
||||
|
||||
@onready var address = $Address
|
||||
@onready var host_button = $HostButton
|
||||
@onready var join_button = $JoinButton
|
||||
@onready var status_ok = $StatusOk
|
||||
@onready var status_fail = $StatusFail
|
||||
@onready var port_forward_label = $PortForward
|
||||
@onready var find_public_ip_button = $FindPublicIP
|
||||
@onready var address: LineEdit = $Address
|
||||
@onready var host_button: Button = $HostButton
|
||||
@onready var join_button: Button = $JoinButton
|
||||
@onready var status_ok: Label = $StatusOk
|
||||
@onready var status_fail: Label = $StatusFail
|
||||
@onready var port_forward_label: Label = $PortForward
|
||||
@onready var find_public_ip_button: LinkButton = $FindPublicIP
|
||||
|
||||
var peer = null
|
||||
var peer: ENetMultiplayerPeer
|
||||
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
# Connect all the callbacks related to networking.
|
||||
multiplayer.peer_connected.connect(_player_connected)
|
||||
multiplayer.peer_disconnected.connect(_player_disconnected)
|
||||
@@ -23,12 +23,11 @@ func _ready():
|
||||
multiplayer.connection_failed.connect(_connected_fail)
|
||||
multiplayer.server_disconnected.connect(_server_disconnected)
|
||||
|
||||
#### Network callbacks from SceneTree ####
|
||||
|
||||
#region Network callbacks from SceneTree
|
||||
# Callback from SceneTree.
|
||||
func _player_connected(_id):
|
||||
func _player_connected(_id: int) -> void:
|
||||
# Someone connected, start the game!
|
||||
var pong = load("res://pong.tscn").instantiate()
|
||||
var pong: Node2D = load("res://pong.tscn").instantiate()
|
||||
# Connect deferred so we can safely erase it from the callback.
|
||||
pong.game_finished.connect(_end_game, CONNECT_DEFERRED)
|
||||
|
||||
@@ -36,49 +35,49 @@ func _player_connected(_id):
|
||||
hide()
|
||||
|
||||
|
||||
func _player_disconnected(_id):
|
||||
func _player_disconnected(_id: int) -> void:
|
||||
if multiplayer.is_server():
|
||||
_end_game("Client disconnected")
|
||||
_end_game("Client disconnected.")
|
||||
else:
|
||||
_end_game("Server disconnected")
|
||||
_end_game("Server disconnected.")
|
||||
|
||||
|
||||
# Callback from SceneTree, only for clients (not server).
|
||||
func _connected_ok():
|
||||
func _connected_ok() -> void:
|
||||
pass # This function is not needed for this project.
|
||||
|
||||
|
||||
# Callback from SceneTree, only for clients (not server).
|
||||
func _connected_fail():
|
||||
func _connected_fail() -> void:
|
||||
_set_status("Couldn't connect.", false)
|
||||
|
||||
multiplayer.set_multiplayer_peer(null) # Remove peer.
|
||||
multiplayer.set_multiplayer_peer(null) # Remove peer.
|
||||
host_button.set_disabled(false)
|
||||
join_button.set_disabled(false)
|
||||
|
||||
|
||||
func _server_disconnected():
|
||||
func _server_disconnected() -> void:
|
||||
_end_game("Server disconnected.")
|
||||
#endregion
|
||||
|
||||
##### Game creation functions ######
|
||||
|
||||
func _end_game(with_error = ""):
|
||||
#region Game creation methods
|
||||
func _end_game(with_error: String = "") -> void:
|
||||
if has_node("/root/Pong"):
|
||||
# Erase immediately, otherwise network might show
|
||||
# errors (this is why we connected deferred above).
|
||||
get_node(^"/root/Pong").free()
|
||||
show()
|
||||
|
||||
multiplayer.set_multiplayer_peer(null) # Remove peer.
|
||||
multiplayer.set_multiplayer_peer(null) # Remove peer.
|
||||
host_button.set_disabled(false)
|
||||
join_button.set_disabled(false)
|
||||
|
||||
_set_status(with_error, false)
|
||||
|
||||
|
||||
func _set_status(text, isok):
|
||||
func _set_status(text: String, is_ok: bool) -> void:
|
||||
# Simple way to show status.
|
||||
if isok:
|
||||
if is_ok:
|
||||
status_ok.set_text(text)
|
||||
status_fail.set_text("")
|
||||
else:
|
||||
@@ -86,9 +85,10 @@ func _set_status(text, isok):
|
||||
status_fail.set_text(text)
|
||||
|
||||
|
||||
func _on_host_pressed():
|
||||
func _on_host_pressed() -> void:
|
||||
peer = ENetMultiplayerPeer.new()
|
||||
var err = peer.create_server(DEFAULT_PORT, 1) # Maximum of 1 peer, since it's a 2-player game.
|
||||
# Set a maximum of 1 peer, since Pong is a 2-player game.
|
||||
var err := peer.create_server(DEFAULT_PORT, 1)
|
||||
if err != OK:
|
||||
# Is another server running?
|
||||
_set_status("Can't host, address in use.",false)
|
||||
@@ -99,14 +99,15 @@ func _on_host_pressed():
|
||||
host_button.set_disabled(true)
|
||||
join_button.set_disabled(true)
|
||||
_set_status("Waiting for player...", true)
|
||||
get_window().title = ProjectSettings.get_setting("application/config/name") + ": Server"
|
||||
|
||||
# Only show hosting instructions when relevant.
|
||||
port_forward_label.visible = true
|
||||
find_public_ip_button.visible = true
|
||||
|
||||
|
||||
func _on_join_pressed():
|
||||
var ip = address.get_text()
|
||||
func _on_join_pressed() -> void:
|
||||
var ip := address.get_text()
|
||||
if not ip.is_valid_ip_address():
|
||||
_set_status("IP address is invalid.", false)
|
||||
return
|
||||
@@ -117,7 +118,8 @@ func _on_join_pressed():
|
||||
multiplayer.set_multiplayer_peer(peer)
|
||||
|
||||
_set_status("Connecting...", true)
|
||||
get_window().title = ProjectSettings.get_setting("application/config/name") + ": Client"
|
||||
#endregion
|
||||
|
||||
|
||||
func _on_find_public_ip_pressed():
|
||||
func _on_find_public_ip_pressed() -> void:
|
||||
OS.shell_open("https://icanhazip.com/")
|
||||
|
||||
@@ -2,14 +2,14 @@ extends Area2D
|
||||
|
||||
const MOTION_SPEED = 150
|
||||
|
||||
@export var left = false
|
||||
@export var left := false
|
||||
|
||||
var _motion = 0
|
||||
var _you_hidden = false
|
||||
var _motion := 0.0
|
||||
var _you_hidden := false
|
||||
|
||||
@onready var _screen_size_y = get_viewport_rect().size.y
|
||||
@onready var _screen_size_y := get_viewport_rect().size.y
|
||||
|
||||
func _process(delta):
|
||||
func _process(delta: float) -> void:
|
||||
# Is the master of the paddle.
|
||||
if is_multiplayer_authority():
|
||||
_motion = Input.get_axis(&"move_up", &"move_down")
|
||||
@@ -26,25 +26,25 @@ func _process(delta):
|
||||
if not _you_hidden:
|
||||
_hide_you_label()
|
||||
|
||||
translate(Vector2(0, _motion * delta))
|
||||
translate(Vector2(0.0, _motion * delta))
|
||||
|
||||
# Set screen limits.
|
||||
position.y = clamp(position.y, 16, _screen_size_y - 16)
|
||||
position.y = clampf(position.y, 16, _screen_size_y - 16)
|
||||
|
||||
|
||||
# Synchronize position and speed to the other peers.
|
||||
@rpc("unreliable")
|
||||
func set_pos_and_motion(pos, motion):
|
||||
func set_pos_and_motion(pos: Vector2, motion: float) -> void:
|
||||
position = pos
|
||||
_motion = motion
|
||||
|
||||
|
||||
func _hide_you_label():
|
||||
func _hide_you_label() -> void:
|
||||
_you_hidden = true
|
||||
get_node(^"You").hide()
|
||||
$You.hide()
|
||||
|
||||
|
||||
func _on_paddle_area_enter(area):
|
||||
func _on_paddle_area_enter(area: Area2D) -> void:
|
||||
if is_multiplayer_authority():
|
||||
# Random for new direction generated checked each peer.
|
||||
area.bounce.rpc(left, randf())
|
||||
|
||||
@@ -4,16 +4,16 @@ signal game_finished()
|
||||
|
||||
const SCORE_TO_WIN = 10
|
||||
|
||||
var score_left = 0
|
||||
var score_right = 0
|
||||
var score_left := 0
|
||||
var score_right := 0
|
||||
|
||||
@onready var player2 = $Player2
|
||||
@onready var score_left_node = $ScoreLeft
|
||||
@onready var score_right_node = $ScoreRight
|
||||
@onready var winner_left = $WinnerLeft
|
||||
@onready var winner_right = $WinnerRight
|
||||
@onready var player2: Area2D = $Player2
|
||||
@onready var score_left_node: Label = $ScoreLeft
|
||||
@onready var score_right_node: Label = $ScoreRight
|
||||
@onready var winner_left: Label = $WinnerLeft
|
||||
@onready var winner_right: Label = $WinnerRight
|
||||
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
# By default, all nodes in server inherit from master,
|
||||
# while all nodes in clients inherit from puppet.
|
||||
# set_multiplayer_authority is tree-recursive by default.
|
||||
@@ -28,7 +28,7 @@ func _ready():
|
||||
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func update_score(add_to_left):
|
||||
func update_score(add_to_left: int) -> void:
|
||||
if add_to_left:
|
||||
score_left += 1
|
||||
score_left_node.set_text(str(score_left))
|
||||
@@ -36,7 +36,7 @@ func update_score(add_to_left):
|
||||
score_right += 1
|
||||
score_right_node.set_text(str(score_right))
|
||||
|
||||
var game_ended = false
|
||||
var game_ended := false
|
||||
if score_left == SCORE_TO_WIN:
|
||||
winner_left.show()
|
||||
game_ended = true
|
||||
@@ -49,5 +49,5 @@ func update_score(add_to_left):
|
||||
$Ball.stop.rpc()
|
||||
|
||||
|
||||
func _on_exit_game_pressed():
|
||||
func _on_exit_game_pressed() -> void:
|
||||
game_finished.emit()
|
||||
|
||||
@@ -81,7 +81,6 @@ text = "Exit Game"
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
offset = Vector2(320, 200)
|
||||
current = true
|
||||
|
||||
[connection signal="pressed" from="ExitGame" to="." method="_on_exit_game_pressed"]
|
||||
|
||||
|
||||
@@ -19,35 +19,38 @@ run/main_scene="res://lobby.tscn"
|
||||
config/features=PackedStringArray("4.2")
|
||||
config/icon="res://icon.webp"
|
||||
|
||||
[debug]
|
||||
|
||||
gdscript/warnings/untyped_declaration=1
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=640
|
||||
window/size/viewport_height=400
|
||||
window/stretch/mode="canvas_items"
|
||||
window/stretch/aspect="expand"
|
||||
window/stretch/scale_mode="integer"
|
||||
|
||||
[input]
|
||||
|
||||
move_down={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":16777234,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":90,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_up={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":16777232,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":65,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":87,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":122,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
|
||||
]
|
||||
}
|
||||
move_down={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[rendering]
|
||||
|
||||
textures/canvas_textures/default_texture_filter=0
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
# A local signaling server. Add this to autoloads with name "Signaling" (/root/Signaling)
|
||||
extends Node
|
||||
|
||||
# We will store the two peers here
|
||||
var peers = []
|
||||
# We will store the two peers here.
|
||||
var peers: Array[String] = []
|
||||
|
||||
func register(path):
|
||||
func register(path: String) -> void:
|
||||
assert(peers.size() < 2)
|
||||
peers.append(path)
|
||||
peers.push_back(path)
|
||||
if peers.size() == 2:
|
||||
get_node(peers[0]).peer.create_offer()
|
||||
|
||||
|
||||
func _find_other(path):
|
||||
func _find_other(path: String) -> String:
|
||||
# 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 != "")
|
||||
func send_session(path: String, type: String, sdp: String) -> void:
|
||||
var other := _find_other(path)
|
||||
assert(not other.is_empty())
|
||||
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)
|
||||
func send_candidate(path: String, media: String, index: int, sdp: String) -> void:
|
||||
var other := _find_other(path)
|
||||
assert(not other.is_empty())
|
||||
get_node(other).peer.add_ice_candidate(media, index, sdp)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
extends Node
|
||||
# An example p2p chat client.
|
||||
# An example peer-to-peer chat client.
|
||||
|
||||
var peer = WebRTCPeerConnection.new()
|
||||
var peer := WebRTCPeerConnection.new()
|
||||
|
||||
# Create negotiated data channel.
|
||||
var channel = peer.create_data_channel("chat", {"negotiated": true, "id": 1})
|
||||
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
# Connect all functions.
|
||||
peer.ice_candidate_created.connect(_on_ice_candidate)
|
||||
peer.session_description_created.connect(_on_session)
|
||||
@@ -15,19 +15,19 @@ func _ready():
|
||||
Signaling.register(String(get_path()))
|
||||
|
||||
|
||||
func _on_ice_candidate(mid, index, sdp):
|
||||
# Send the ICE candidate to the other peer via signaling server.
|
||||
Signaling.send_candidate(String(get_path()), mid, index, sdp)
|
||||
func _on_ice_candidate(media: String, index: int, sdp: String) -> void:
|
||||
# Send the ICE candidate to the other peer via the signaling server.
|
||||
Signaling.send_candidate(String(get_path()), media, index, sdp)
|
||||
|
||||
|
||||
func _on_session(type, sdp):
|
||||
# Send the session to other peer via signaling server.
|
||||
func _on_session(type: String, sdp: String) -> void:
|
||||
# Send the session to other peer via the signaling server.
|
||||
Signaling.send_session(String(get_path()), type, sdp)
|
||||
# Set generated description as local.
|
||||
peer.set_local_description(type, sdp)
|
||||
|
||||
|
||||
func _process(delta):
|
||||
func _process(delta: float) -> void:
|
||||
# Always poll the connection frequently.
|
||||
peer.poll()
|
||||
if channel.get_ready_state() == WebRTCDataChannel.STATE_OPEN:
|
||||
@@ -35,5 +35,5 @@ func _process(delta):
|
||||
print(String(get_path()), " received: ", channel.get_packet().get_string_from_utf8())
|
||||
|
||||
|
||||
func send_message(message):
|
||||
func send_message(message: String) -> void:
|
||||
channel.put_packet(message.to_utf8_buffer())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
extends LinkButton
|
||||
|
||||
func _on_LinkButton_pressed():
|
||||
func _on_LinkButton_pressed() -> void:
|
||||
OS.shell_open("https://github.com/godotengine/webrtc-native/releases")
|
||||
|
||||
@@ -2,16 +2,16 @@ extends Node
|
||||
|
||||
const Chat = preload("res://chat.gd")
|
||||
|
||||
func _ready():
|
||||
var p1 = Chat.new()
|
||||
var p2 = Chat.new()
|
||||
func _ready() -> void:
|
||||
var p1 := Chat.new()
|
||||
var p2 := Chat.new()
|
||||
add_child(p1)
|
||||
add_child(p2)
|
||||
|
||||
# Wait a second and send message from P1
|
||||
await get_tree().create_timer(1).timeout
|
||||
# Wait a second and send message from P1.
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
p1.send_message("Hi from %s" % String(p1.get_path()))
|
||||
|
||||
# Wait a second and send message from P2
|
||||
await get_tree().create_timer(1).timeout
|
||||
# Wait a second and send message from P2.
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
p2.send_message("Hi from %s" % String(p2.get_path()))
|
||||
|
||||
@@ -2,13 +2,13 @@ extends Node
|
||||
# Main scene.
|
||||
|
||||
# Create the two peers.
|
||||
var p1 = WebRTCPeerConnection.new()
|
||||
var p2 = WebRTCPeerConnection.new()
|
||||
var ch1 = p1.create_data_channel("chat", {"id": 1, "negotiated": true})
|
||||
var ch2 = p2.create_data_channel("chat", {"id": 1, "negotiated": true})
|
||||
var p1 := WebRTCPeerConnection.new()
|
||||
var p2 := WebRTCPeerConnection.new()
|
||||
var ch1 := p1.create_data_channel("chat", { "id": 1, "negotiated": true })
|
||||
var ch2 := p2.create_data_channel("chat", { "id": 1, "negotiated": true })
|
||||
|
||||
func _ready():
|
||||
print(p1.create_data_channel("chat", {"id": 1, "negotiated": true}))
|
||||
func _ready() -> void:
|
||||
print(p1.create_data_channel("chat", { "id": 1, "negotiated": true }))
|
||||
# Connect P1 session created to itself to set local description.
|
||||
p1.session_description_created.connect(p1.set_local_description)
|
||||
# Connect P1 session and ICE created to p2 set remote description and candidates.
|
||||
@@ -32,7 +32,7 @@ func _ready():
|
||||
ch2.put_packet("Hi from P2".to_utf8_buffer())
|
||||
|
||||
|
||||
func _process(delta):
|
||||
func _process(delta: float) -> void:
|
||||
p1.poll()
|
||||
p2.poll()
|
||||
if ch1.get_ready_state() == ch1.STATE_OPEN and ch1.get_available_packet_count() > 0:
|
||||
|
||||
@@ -20,6 +20,10 @@ config/features=PackedStringArray("4.2")
|
||||
|
||||
Signaling="*res://Signaling.gd"
|
||||
|
||||
[debug]
|
||||
|
||||
gdscript/warnings/untyped_declaration=1
|
||||
|
||||
[display]
|
||||
|
||||
window/stretch/mode="canvas_items"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
extends "ws_webrtc_client.gd"
|
||||
|
||||
var rtc_mp: WebRTCMultiplayerPeer = WebRTCMultiplayerPeer.new()
|
||||
var rtc_mp := WebRTCMultiplayerPeer.new()
|
||||
var sealed := false
|
||||
|
||||
func _init():
|
||||
func _init() -> void:
|
||||
connected.connect(_connected)
|
||||
disconnected.connect(_disconnected)
|
||||
|
||||
@@ -17,7 +17,7 @@ func _init():
|
||||
peer_disconnected.connect(_peer_disconnected)
|
||||
|
||||
|
||||
func start(url, _lobby = "", _mesh := true):
|
||||
func start(url: String, _lobby: String = "", _mesh: bool = true) -> void:
|
||||
stop()
|
||||
sealed = false
|
||||
mesh = _mesh
|
||||
@@ -25,30 +25,35 @@ func start(url, _lobby = "", _mesh := true):
|
||||
connect_to_url(url)
|
||||
|
||||
|
||||
func stop():
|
||||
func stop() -> void:
|
||||
multiplayer.multiplayer_peer = null
|
||||
rtc_mp.close()
|
||||
close()
|
||||
|
||||
|
||||
func _create_peer(id):
|
||||
func _create_peer(id: int) -> WebRTCPeerConnection:
|
||||
var peer: WebRTCPeerConnection = WebRTCPeerConnection.new()
|
||||
# Use a public STUN server for moderate NAT traversal.
|
||||
# Note that STUN cannot punch through strict NATs (such as most mobile connections),
|
||||
# in which case TURN is required. TURN generally does not have public servers available,
|
||||
# as it requires much greater resources to host (all traffic goes through
|
||||
# the TURN server, instead of only performing the initial connection).
|
||||
peer.initialize({
|
||||
"iceServers": [ { "urls": ["stun:stun.l.google.com:19302"] } ]
|
||||
})
|
||||
peer.session_description_created.connect(_offer_created.bind(id))
|
||||
peer.ice_candidate_created.connect(_new_ice_candidate.bind(id))
|
||||
rtc_mp.add_peer(peer, id)
|
||||
if id < rtc_mp.get_unique_id(): # So lobby creator never creates offers.
|
||||
if id < rtc_mp.get_unique_id(): # So lobby creator never creates offers.
|
||||
peer.create_offer()
|
||||
return peer
|
||||
|
||||
|
||||
func _new_ice_candidate(mid_name, index_name, sdp_name, id):
|
||||
func _new_ice_candidate(mid_name: String, index_name: int, sdp_name: String, id: int) -> void:
|
||||
send_candidate(id, mid_name, index_name, sdp_name)
|
||||
|
||||
|
||||
func _offer_created(type, data, id):
|
||||
func _offer_created(type: String, data: String, id: int) -> void:
|
||||
if not rtc_mp.has_peer(id):
|
||||
return
|
||||
print("created", type)
|
||||
@@ -57,7 +62,7 @@ func _offer_created(type, data, id):
|
||||
else: send_answer(id, data)
|
||||
|
||||
|
||||
func _connected(id, use_mesh):
|
||||
func _connected(id: int, use_mesh: bool) -> void:
|
||||
print("Connected %d, mesh: %s" % [id, use_mesh])
|
||||
if use_mesh:
|
||||
rtc_mp.create_mesh(id)
|
||||
@@ -68,41 +73,42 @@ func _connected(id, use_mesh):
|
||||
multiplayer.multiplayer_peer = rtc_mp
|
||||
|
||||
|
||||
func _lobby_joined(_lobby):
|
||||
func _lobby_joined(_lobby: String) -> void:
|
||||
lobby = _lobby
|
||||
|
||||
|
||||
func _lobby_sealed():
|
||||
func _lobby_sealed() -> void:
|
||||
sealed = true
|
||||
|
||||
|
||||
func _disconnected():
|
||||
func _disconnected() -> void:
|
||||
print("Disconnected: %d: %s" % [code, reason])
|
||||
if not sealed:
|
||||
stop() # Unexpected disconnect
|
||||
|
||||
|
||||
func _peer_connected(id):
|
||||
print("Peer connected %d" % id)
|
||||
func _peer_connected(id: int) -> void:
|
||||
print("Peer connected: %d" % id)
|
||||
_create_peer(id)
|
||||
|
||||
|
||||
func _peer_disconnected(id):
|
||||
if rtc_mp.has_peer(id): rtc_mp.remove_peer(id)
|
||||
func _peer_disconnected(id: int) -> void:
|
||||
if rtc_mp.has_peer(id):
|
||||
rtc_mp.remove_peer(id)
|
||||
|
||||
|
||||
func _offer_received(id, offer):
|
||||
func _offer_received(id: int, offer: int) -> void:
|
||||
print("Got offer: %d" % id)
|
||||
if rtc_mp.has_peer(id):
|
||||
rtc_mp.get_peer(id).connection.set_remote_description("offer", offer)
|
||||
|
||||
|
||||
func _answer_received(id, answer):
|
||||
func _answer_received(id: int, answer: int) -> void:
|
||||
print("Got answer: %d" % id)
|
||||
if rtc_mp.has_peer(id):
|
||||
rtc_mp.get_peer(id).connection.set_remote_description("answer", answer)
|
||||
|
||||
|
||||
func _candidate_received(id, mid, index, sdp):
|
||||
func _candidate_received(id: int, mid: String, index: int, sdp: String) -> void:
|
||||
if rtc_mp.has_peer(id):
|
||||
rtc_mp.get_peer(id).connection.add_ice_candidate(mid, index, sdp)
|
||||
|
||||
@@ -1,41 +1,50 @@
|
||||
extends Node
|
||||
|
||||
enum Message {JOIN, ID, PEER_CONNECT, PEER_DISCONNECT, OFFER, ANSWER, CANDIDATE, SEAL}
|
||||
enum Message {
|
||||
JOIN,
|
||||
ID,
|
||||
PEER_CONNECT,
|
||||
PEER_DISCONNECT,
|
||||
OFFER,
|
||||
ANSWER,
|
||||
CANDIDATE,
|
||||
SEAL,
|
||||
}
|
||||
|
||||
@export var autojoin := true
|
||||
@export var lobby := "" # Will create a new lobby if empty.
|
||||
@export var mesh := true # Will use the lobby host as relay otherwise.
|
||||
@export var lobby := "" # Will create a new lobby if empty.
|
||||
@export var mesh := true # Will use the lobby host as relay otherwise.
|
||||
|
||||
var ws: WebSocketPeer = WebSocketPeer.new()
|
||||
var code = 1000
|
||||
var reason = "Unknown"
|
||||
var old_state = WebSocketPeer.STATE_CLOSED
|
||||
var ws := WebSocketPeer.new()
|
||||
var code := 1000
|
||||
var reason := "Unknown"
|
||||
var old_state := WebSocketPeer.STATE_CLOSED
|
||||
|
||||
signal lobby_joined(lobby)
|
||||
signal connected(id, use_mesh)
|
||||
signal lobby_joined(lobby: String)
|
||||
signal connected(id: int, use_mesh: bool)
|
||||
signal disconnected()
|
||||
signal peer_connected(id)
|
||||
signal peer_disconnected(id)
|
||||
signal offer_received(id, offer)
|
||||
signal answer_received(id, answer)
|
||||
signal candidate_received(id, mid, index, sdp)
|
||||
signal peer_connected(id: int)
|
||||
signal peer_disconnected(id: int)
|
||||
signal offer_received(id: int, offer: int)
|
||||
signal answer_received(id: int, answer: int)
|
||||
signal candidate_received(id: int, mid: String, index: int, sdp: String)
|
||||
signal lobby_sealed()
|
||||
|
||||
|
||||
func connect_to_url(url):
|
||||
func connect_to_url(url: String) -> void:
|
||||
close()
|
||||
code = 1000
|
||||
reason = "Unknown"
|
||||
ws.connect_to_url(url)
|
||||
|
||||
|
||||
func close():
|
||||
func close() -> void:
|
||||
ws.close()
|
||||
|
||||
|
||||
func _process(delta):
|
||||
func _process(_delta: float) -> void:
|
||||
ws.poll()
|
||||
var state = ws.get_ready_state()
|
||||
var state := ws.get_ready_state()
|
||||
if state != old_state and state == WebSocketPeer.STATE_OPEN and autojoin:
|
||||
join_lobby(lobby)
|
||||
while state == WebSocketPeer.STATE_OPEN and ws.get_available_packet_count():
|
||||
@@ -48,8 +57,8 @@ func _process(delta):
|
||||
old_state = state
|
||||
|
||||
|
||||
func _parse_msg():
|
||||
var parsed = JSON.parse_string(ws.get_packet().get_string_from_utf8())
|
||||
func _parse_msg() -> bool:
|
||||
var parsed: Dictionary = JSON.parse_string(ws.get_packet().get_string_from_utf8())
|
||||
if typeof(parsed) != TYPE_DICTIONARY or not parsed.has("type") or not parsed.has("id") or \
|
||||
typeof(parsed.get("data")) != TYPE_STRING:
|
||||
return false
|
||||
@@ -68,19 +77,19 @@ func _parse_msg():
|
||||
elif type == Message.SEAL:
|
||||
lobby_sealed.emit()
|
||||
elif type == Message.PEER_CONNECT:
|
||||
# Client connected
|
||||
# Client connected.
|
||||
peer_connected.emit(src_id)
|
||||
elif type == Message.PEER_DISCONNECT:
|
||||
# Client connected
|
||||
# Client connected.
|
||||
peer_disconnected.emit(src_id)
|
||||
elif type == Message.OFFER:
|
||||
# Offer received
|
||||
# Offer received.
|
||||
offer_received.emit(src_id, msg.data)
|
||||
elif type == Message.ANSWER:
|
||||
# Answer received
|
||||
# Answer received.
|
||||
answer_received.emit(src_id, msg.data)
|
||||
elif type == Message.CANDIDATE:
|
||||
# Candidate received
|
||||
# Candidate received.
|
||||
var candidate: PackedStringArray = msg.data.split("\n", false)
|
||||
if candidate.size() != 3:
|
||||
return false
|
||||
@@ -89,32 +98,33 @@ func _parse_msg():
|
||||
candidate_received.emit(src_id, candidate[0], candidate[1].to_int(), candidate[2])
|
||||
else:
|
||||
return false
|
||||
return true # Parsed
|
||||
|
||||
return true # Parsed.
|
||||
|
||||
|
||||
func join_lobby(lobby: String):
|
||||
func join_lobby(lobby: String) -> Error:
|
||||
return _send_msg(Message.JOIN, 0 if mesh else 1, lobby)
|
||||
|
||||
|
||||
func seal_lobby():
|
||||
func seal_lobby() -> Error:
|
||||
return _send_msg(Message.SEAL, 0)
|
||||
|
||||
|
||||
func send_candidate(id, mid, index, sdp) -> int:
|
||||
func send_candidate(id: int, mid: String, index: int, sdp: String) -> Error:
|
||||
return _send_msg(Message.CANDIDATE, id, "\n%s\n%d\n%s" % [mid, index, sdp])
|
||||
|
||||
|
||||
func send_offer(id, offer) -> int:
|
||||
func send_offer(id: int, offer: String) -> Error:
|
||||
return _send_msg(Message.OFFER, id, offer)
|
||||
|
||||
|
||||
func send_answer(id, answer) -> int:
|
||||
func send_answer(id: int, answer: String) -> Error:
|
||||
return _send_msg(Message.ANSWER, id, answer)
|
||||
|
||||
|
||||
func _send_msg(type: int, id: int, data:="") -> int:
|
||||
func _send_msg(type: int, id: int, data: String = "") -> Error:
|
||||
return ws.send_text(JSON.stringify({
|
||||
"type": type,
|
||||
"id": id,
|
||||
"data": data
|
||||
"data": data,
|
||||
}))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
extends Control
|
||||
|
||||
@onready var client = $Client
|
||||
@onready var host = $VBoxContainer/Connect/Host
|
||||
@onready var room = $VBoxContainer/Connect/RoomSecret
|
||||
@onready var mesh = $VBoxContainer/Connect/Mesh
|
||||
@onready var client: Node = $Client
|
||||
@onready var host: LineEdit = $VBoxContainer/Connect/Host
|
||||
@onready var room: LineEdit = $VBoxContainer/Connect/RoomSecret
|
||||
@onready var mesh: CheckBox = $VBoxContainer/Connect/Mesh
|
||||
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
client.lobby_joined.connect(_lobby_joined)
|
||||
client.lobby_sealed.connect(_lobby_sealed)
|
||||
client.connected.connect(_connected)
|
||||
@@ -19,62 +19,62 @@ func _ready():
|
||||
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func ping(argument):
|
||||
func ping(argument: String) -> void:
|
||||
_log("[Multiplayer] Ping from peer %d: arg: %s" % [multiplayer.get_remote_sender_id(), argument])
|
||||
|
||||
|
||||
func _mp_server_connected():
|
||||
func _mp_server_connected() -> void:
|
||||
_log("[Multiplayer] Server connected (I am %d)" % client.rtc_mp.get_unique_id())
|
||||
|
||||
|
||||
func _mp_server_disconnect():
|
||||
func _mp_server_disconnect() -> void:
|
||||
_log("[Multiplayer] Server disconnected (I am %d)" % client.rtc_mp.get_unique_id())
|
||||
|
||||
|
||||
func _mp_peer_connected(id: int):
|
||||
func _mp_peer_connected(id: int) -> void:
|
||||
_log("[Multiplayer] Peer %d connected" % id)
|
||||
|
||||
|
||||
func _mp_peer_disconnected(id: int):
|
||||
func _mp_peer_disconnected(id: int) -> void:
|
||||
_log("[Multiplayer] Peer %d disconnected" % id)
|
||||
|
||||
|
||||
func _connected(id, use_mesh):
|
||||
func _connected(id: int, use_mesh: bool) -> void:
|
||||
_log("[Signaling] Server connected with ID: %d. Mesh: %s" % [id, use_mesh])
|
||||
|
||||
|
||||
func _disconnected():
|
||||
func _disconnected() -> void:
|
||||
_log("[Signaling] Server disconnected: %d - %s" % [client.code, client.reason])
|
||||
|
||||
|
||||
func _lobby_joined(lobby):
|
||||
func _lobby_joined(lobby: String) -> void:
|
||||
_log("[Signaling] Joined lobby %s" % lobby)
|
||||
|
||||
|
||||
func _lobby_sealed():
|
||||
func _lobby_sealed() -> void:
|
||||
_log("[Signaling] Lobby has been sealed")
|
||||
|
||||
|
||||
func _log(msg):
|
||||
func _log(msg: String) -> void:
|
||||
print(msg)
|
||||
$VBoxContainer/TextEdit.text += str(msg) + "\n"
|
||||
|
||||
|
||||
func _on_peers_pressed():
|
||||
_log(multiplayer.get_peers())
|
||||
func _on_peers_pressed() -> void:
|
||||
_log(str(multiplayer.get_peers()))
|
||||
|
||||
|
||||
func _on_ping_pressed():
|
||||
func _on_ping_pressed() -> void:
|
||||
ping.rpc(randf())
|
||||
|
||||
|
||||
func _on_seal_pressed():
|
||||
func _on_seal_pressed() -> void:
|
||||
client.seal_lobby()
|
||||
|
||||
|
||||
func _on_start_pressed():
|
||||
func _on_start_pressed() -> void:
|
||||
client.start(host.text, room.text, mesh.button_pressed)
|
||||
|
||||
|
||||
func _on_stop_pressed():
|
||||
func _on_stop_pressed() -> void:
|
||||
client.stop()
|
||||
|
||||
@@ -25,92 +25,55 @@ grow_vertical = 2
|
||||
|
||||
[node name="Connect" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 31.0
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/Connect"]
|
||||
layout_mode = 2
|
||||
offset_top = 2.0
|
||||
offset_right = 89.0
|
||||
offset_bottom = 28.0
|
||||
text = "Connect to:"
|
||||
|
||||
[node name="Host" type="LineEdit" parent="VBoxContainer/Connect"]
|
||||
layout_mode = 2
|
||||
offset_left = 93.0
|
||||
offset_right = 829.0
|
||||
offset_bottom = 31.0
|
||||
size_flags_horizontal = 3
|
||||
text = "ws://localhost:9080"
|
||||
|
||||
[node name="Room" type="Label" parent="VBoxContainer/Connect"]
|
||||
layout_mode = 2
|
||||
offset_left = 833.0
|
||||
offset_right = 879.0
|
||||
offset_bottom = 31.0
|
||||
size_flags_vertical = 5
|
||||
text = "Room"
|
||||
|
||||
[node name="RoomSecret" type="LineEdit" parent="VBoxContainer/Connect"]
|
||||
layout_mode = 2
|
||||
offset_left = 883.0
|
||||
offset_right = 950.0
|
||||
offset_bottom = 31.0
|
||||
placeholder_text = "secret"
|
||||
|
||||
[node name="Mesh" type="CheckBox" parent="VBoxContainer/Connect"]
|
||||
layout_mode = 2
|
||||
offset_left = 954.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 31.0
|
||||
button_pressed = true
|
||||
text = "Mesh"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
offset_top = 35.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 66.0
|
||||
|
||||
[node name="Start" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
offset_right = 46.0
|
||||
offset_bottom = 31.0
|
||||
text = "Start"
|
||||
|
||||
[node name="Stop" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
offset_left = 50.0
|
||||
offset_right = 93.0
|
||||
offset_bottom = 31.0
|
||||
text = "Stop"
|
||||
|
||||
[node name="Seal" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
offset_left = 97.0
|
||||
offset_right = 137.0
|
||||
offset_bottom = 31.0
|
||||
text = "Seal"
|
||||
|
||||
[node name="Ping" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
offset_left = 141.0
|
||||
offset_right = 183.0
|
||||
offset_bottom = 31.0
|
||||
text = "Ping"
|
||||
|
||||
[node name="Peers" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
offset_left = 187.0
|
||||
offset_right = 280.0
|
||||
offset_bottom = 31.0
|
||||
text = "Print peers"
|
||||
|
||||
[node name="TextEdit" type="TextEdit" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
offset_top = 70.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 600.0
|
||||
size_flags_vertical = 3
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Start" to="." method="_on_start_pressed"]
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
extends Control
|
||||
|
||||
func _enter_tree():
|
||||
func _enter_tree() -> void:
|
||||
for c in $VBoxContainer/Clients.get_children():
|
||||
# So each child gets its own separate MultiplayerAPI.
|
||||
get_tree().set_multiplayer(
|
||||
MultiplayerAPI.create_default_interface(),
|
||||
NodePath("%s/VBoxContainer/Clients/%s" % [get_path(), c.name])
|
||||
MultiplayerAPI.create_default_interface(),
|
||||
NodePath("%s/VBoxContainer/Clients/%s" % [get_path(), c.name])
|
||||
)
|
||||
|
||||
func _ready():
|
||||
|
||||
func _ready() -> void:
|
||||
if OS.get_name() == "Web":
|
||||
$VBoxContainer/Signaling.hide()
|
||||
|
||||
|
||||
func _on_listen_toggled(button_pressed):
|
||||
func _on_listen_toggled(button_pressed: bool) -> void:
|
||||
if button_pressed:
|
||||
$Server.listen(int($VBoxContainer/Signaling/Port.value))
|
||||
else:
|
||||
$Server.stop()
|
||||
|
||||
|
||||
func _on_LinkButton_pressed():
|
||||
func _on_LinkButton_pressed() -> void:
|
||||
OS.shell_open("https://github.com/godotengine/webrtc-native/releases")
|
||||
|
||||
@@ -22,6 +22,7 @@ config/features=PackedStringArray("4.2")
|
||||
[debug]
|
||||
|
||||
gdscript/warnings/shadowed_variable=false
|
||||
gdscript/warnings/untyped_declaration=1
|
||||
gdscript/warnings/unused_argument=false
|
||||
|
||||
[display]
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
extends Node
|
||||
|
||||
enum Message {JOIN, ID, PEER_CONNECT, PEER_DISCONNECT, OFFER, ANSWER, CANDIDATE, SEAL}
|
||||
enum Message {
|
||||
JOIN,
|
||||
ID,
|
||||
PEER_CONNECT,
|
||||
PEER_DISCONNECT,
|
||||
OFFER,
|
||||
ANSWER,
|
||||
CANDIDATE,
|
||||
SEAL,
|
||||
}
|
||||
|
||||
const TIMEOUT = 1000 # Unresponsive clients times out after 1 sec
|
||||
const SEAL_TIME = 10000 # A sealed room will be closed after this time
|
||||
## Unresponsive clients time out after this time (in milliseconds).
|
||||
const TIMEOUT = 1000
|
||||
|
||||
## A sealed room will be closed after this time (in milliseconds).
|
||||
const SEAL_TIME = 10000
|
||||
|
||||
## All alphanumeric characters.
|
||||
const ALFNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
var _alfnum = ALFNUM.to_ascii_buffer()
|
||||
var _alfnum := ALFNUM.to_ascii_buffer()
|
||||
|
||||
var rand: RandomNumberGenerator = RandomNumberGenerator.new()
|
||||
var lobbies: Dictionary = {}
|
||||
@@ -14,13 +28,13 @@ var tcp_server := TCPServer.new()
|
||||
var peers: Dictionary = {}
|
||||
|
||||
class Peer extends RefCounted:
|
||||
var id = -1
|
||||
var lobby = ""
|
||||
var time = Time.get_ticks_msec()
|
||||
var ws = WebSocketPeer.new()
|
||||
var id := -1
|
||||
var lobby := ""
|
||||
var time := Time.get_ticks_msec()
|
||||
var ws := WebSocketPeer.new()
|
||||
|
||||
|
||||
func _init(peer_id, tcp):
|
||||
func _init(peer_id: int, tcp: StreamPeer) -> void:
|
||||
id = peer_id
|
||||
ws.accept_stream(tcp)
|
||||
|
||||
@@ -29,7 +43,7 @@ class Peer extends RefCounted:
|
||||
return ws.get_ready_state() == WebSocketPeer.STATE_OPEN
|
||||
|
||||
|
||||
func send(type: int, id: int, data:=""):
|
||||
func send(type: int, id: int, data: String = "") -> void:
|
||||
return ws.send_text(JSON.stringify({
|
||||
"type": type,
|
||||
"id": id,
|
||||
@@ -38,13 +52,13 @@ class Peer extends RefCounted:
|
||||
|
||||
|
||||
class Lobby extends RefCounted:
|
||||
var peers: = {}
|
||||
var host: int = -1
|
||||
var sealed: bool = false
|
||||
var time = 0
|
||||
var peers := {}
|
||||
var host := -1
|
||||
var sealed := false
|
||||
var time := 0 # Value is in milliseconds.
|
||||
var mesh := true
|
||||
|
||||
func _init(host_id: int, use_mesh: bool):
|
||||
func _init(host_id: int, use_mesh: bool) -> void:
|
||||
host = host_id
|
||||
mesh = use_mesh
|
||||
|
||||
@@ -52,7 +66,7 @@ class Lobby extends RefCounted:
|
||||
if sealed: return false
|
||||
if not peer.is_ws_open(): return false
|
||||
peer.send(Message.ID, (1 if peer.id == host else peer.id), "true" if mesh else "")
|
||||
for p in peers.values():
|
||||
for p: Peer in peers.values():
|
||||
if not p.is_ws_open():
|
||||
continue
|
||||
if not mesh and p.id != host:
|
||||
@@ -65,15 +79,19 @@ class Lobby extends RefCounted:
|
||||
|
||||
|
||||
func leave(peer: Peer) -> bool:
|
||||
if not peers.has(peer.id): return false
|
||||
if not peers.has(peer.id):
|
||||
return false
|
||||
|
||||
peers.erase(peer.id)
|
||||
var close = false
|
||||
var close := false
|
||||
if peer.id == host:
|
||||
# The room host disconnected, will disconnect all peers.
|
||||
close = true
|
||||
if sealed: return close
|
||||
if sealed:
|
||||
return close
|
||||
|
||||
# Notify other peers.
|
||||
for p in peers.values():
|
||||
for p: Peer in peers.values():
|
||||
if not p.is_ws_open():
|
||||
continue
|
||||
if close:
|
||||
@@ -82,53 +100,59 @@ class Lobby extends RefCounted:
|
||||
else:
|
||||
# Notify disconnection.
|
||||
p.send(Message.PEER_DISCONNECT, peer.id)
|
||||
|
||||
return close
|
||||
|
||||
|
||||
func seal(peer_id: int) -> bool:
|
||||
# Only host can seal the room.
|
||||
if host != peer_id: return false
|
||||
if host != peer_id:
|
||||
return false
|
||||
|
||||
sealed = true
|
||||
for p in peers.values():
|
||||
|
||||
for p: Peer in peers.values():
|
||||
if not p.is_ws_open():
|
||||
continue
|
||||
p.send(Message.SEAL, 0)
|
||||
|
||||
time = Time.get_ticks_msec()
|
||||
peers.clear()
|
||||
|
||||
return true
|
||||
|
||||
|
||||
func _process(delta):
|
||||
func _process(_delta: float) -> void:
|
||||
poll()
|
||||
|
||||
|
||||
func listen(port):
|
||||
if OS.get_name() == "Web":
|
||||
func listen(port: int) -> void:
|
||||
if OS.has_feature("web"):
|
||||
OS.alert("Cannot create WebSocket servers in Web exports due to browsers' limitations.")
|
||||
return
|
||||
stop()
|
||||
rand.seed = Time.get_unix_time_from_system()
|
||||
rand.seed = int(Time.get_unix_time_from_system())
|
||||
tcp_server.listen(port)
|
||||
|
||||
|
||||
func stop():
|
||||
func stop() -> void:
|
||||
tcp_server.stop()
|
||||
peers.clear()
|
||||
|
||||
|
||||
func poll():
|
||||
func poll() -> void:
|
||||
if not tcp_server.is_listening():
|
||||
return
|
||||
|
||||
if tcp_server.is_connection_available():
|
||||
var id = randi() % (1 << 31)
|
||||
var id := randi() % (1 << 31)
|
||||
peers[id] = Peer.new(id, tcp_server.take_connection())
|
||||
|
||||
# Poll peers.
|
||||
var to_remove := []
|
||||
for p in peers.values():
|
||||
for p: Peer in peers.values():
|
||||
# Peers timeout.
|
||||
if p.lobby == "" and Time.get_ticks_msec() - p.time > TIMEOUT:
|
||||
if p.lobby.is_empty() and Time.get_ticks_msec() - p.time > TIMEOUT:
|
||||
p.ws.close()
|
||||
p.ws.poll()
|
||||
while p.is_ws_open() and p.ws.get_available_packet_count():
|
||||
@@ -137,7 +161,7 @@ func poll():
|
||||
to_remove.push_back(p.id)
|
||||
p.ws.close()
|
||||
break
|
||||
var state = p.ws.get_ready_state()
|
||||
var state := p.ws.get_ready_state()
|
||||
if state == WebSocketPeer.STATE_CLOSED:
|
||||
print("Peer %d disconnected from lobby: '%s'" % [p.id, p.lobby])
|
||||
# Remove from lobby (and lobby itself if host).
|
||||
@@ -148,24 +172,24 @@ func poll():
|
||||
to_remove.push_back(p.id)
|
||||
|
||||
# Lobby seal.
|
||||
for k in lobbies:
|
||||
for k: String in lobbies:
|
||||
if not lobbies[k].sealed:
|
||||
continue
|
||||
if lobbies[k].time + SEAL_TIME < Time.get_ticks_msec():
|
||||
# Close lobby.
|
||||
for p in lobbies[k].peers:
|
||||
for p: Peer in lobbies[k].peers:
|
||||
p.ws.close()
|
||||
to_remove.push_back(p.id)
|
||||
|
||||
# Remove stale peers
|
||||
for id in to_remove:
|
||||
for id: int in to_remove:
|
||||
peers.erase(id)
|
||||
|
||||
|
||||
func _join_lobby(peer: Peer, lobby: String, mesh: bool) -> bool:
|
||||
if lobby == "":
|
||||
for _i in range(0, 32):
|
||||
lobby += char(_alfnum[rand.randi_range(0, ALFNUM.length()-1)])
|
||||
if lobby.is_empty():
|
||||
for _i in 32:
|
||||
lobby += char(_alfnum[rand.randi_range(0, ALFNUM.length() - 1)])
|
||||
lobbies[lobby] = Lobby.new(peer.id, mesh)
|
||||
elif not lobbies.has(lobby):
|
||||
return false
|
||||
@@ -179,7 +203,7 @@ func _join_lobby(peer: Peer, lobby: String, mesh: bool) -> bool:
|
||||
|
||||
func _parse_msg(peer: Peer) -> bool:
|
||||
var pkt_str: String = peer.ws.get_packet().get_string_from_utf8()
|
||||
var parsed = JSON.parse_string(pkt_str)
|
||||
var parsed: Dictionary = JSON.parse_string(pkt_str)
|
||||
if typeof(parsed) != TYPE_DICTIONARY or not parsed.has("type") or not parsed.has("id") or \
|
||||
typeof(parsed.get("data")) != TYPE_STRING:
|
||||
return false
|
||||
@@ -189,36 +213,37 @@ func _parse_msg(peer: Peer) -> bool:
|
||||
var msg := {
|
||||
"type": str(parsed.type).to_int(),
|
||||
"id": str(parsed.id).to_int(),
|
||||
"data": parsed.data
|
||||
"data": parsed.data,
|
||||
}
|
||||
|
||||
if msg.type == Message.JOIN:
|
||||
if peer.lobby: # Peer must not have joined a lobby already!
|
||||
if peer.lobby: # Peer must not have joined a lobby already!
|
||||
return false
|
||||
|
||||
return _join_lobby(peer, msg.data, msg.id == 0)
|
||||
|
||||
if not lobbies.has(peer.lobby): # Lobby not found?
|
||||
if not lobbies.has(peer.lobby): # Lobby not found?
|
||||
return false
|
||||
|
||||
var lobby = lobbies[peer.lobby]
|
||||
var lobby: Peer = lobbies[peer.lobby]
|
||||
|
||||
if msg.type == Message.SEAL:
|
||||
# Client is sealing the room
|
||||
# Client is sealing the room.
|
||||
return lobby.seal(peer.id)
|
||||
|
||||
var dest_id: int = msg.id
|
||||
if dest_id == MultiplayerPeer.TARGET_PEER_SERVER:
|
||||
dest_id = lobby.host
|
||||
|
||||
if not peers.has(dest_id): # Destination ID not connected
|
||||
if not peers.has(dest_id): # Destination ID not connected.
|
||||
return false
|
||||
|
||||
if peers[dest_id].lobby != peer.lobby: # Trying to contact someone not in same lobby
|
||||
if peers[dest_id].lobby != peer.lobby: # Trying to contact someone not in same lobby.
|
||||
return false
|
||||
|
||||
if msg.type in [Message.OFFER, Message.ANSWER, Message.CANDIDATE]:
|
||||
var source = MultiplayerPeer.TARGET_PEER_SERVER if peer.id == lobby.host else peer.id
|
||||
var source := MultiplayerPeer.TARGET_PEER_SERVER if peer.id == lobby.host else peer.id
|
||||
peers[dest_id].send(msg.type, source, msg.data)
|
||||
return true
|
||||
|
||||
return false # Unknown message
|
||||
return false # Unknown message.
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
extends Control
|
||||
|
||||
@onready var _client: WebSocketClient = $WebSocketClient
|
||||
@onready var _log_dest = $Panel/VBoxContainer/RichTextLabel
|
||||
@onready var _line_edit = $Panel/VBoxContainer/Send/LineEdit
|
||||
@onready var _host = $Panel/VBoxContainer/Connect/Host
|
||||
@onready var _log_dest: RichTextLabel = $Panel/VBoxContainer/RichTextLabel
|
||||
@onready var _line_edit: LineEdit = $Panel/VBoxContainer/Send/LineEdit
|
||||
@onready var _host: LineEdit = $Panel/VBoxContainer/Connect/Host
|
||||
|
||||
func info(msg):
|
||||
func info(msg: String) -> void:
|
||||
print(msg)
|
||||
_log_dest.add_text(str(msg) + "\n")
|
||||
|
||||
|
||||
# Client signals
|
||||
func _on_web_socket_client_connection_closed():
|
||||
var ws = _client.get_socket()
|
||||
#region Client signals
|
||||
func _on_web_socket_client_connection_closed() -> void:
|
||||
var ws := _client.get_socket()
|
||||
info("Client just disconnected with code: %s, reson: %s" % [ws.get_close_code(), ws.get_close_reason()])
|
||||
|
||||
|
||||
func _on_web_socket_client_connected_to_server():
|
||||
func _on_web_socket_client_connected_to_server() -> void:
|
||||
info("Client just connected with protocol: %s" % _client.get_socket().get_selected_protocol())
|
||||
|
||||
|
||||
func _on_web_socket_client_message_received(message):
|
||||
func _on_web_socket_client_message_received(message: String) -> void:
|
||||
info("%s" % message)
|
||||
#endregion
|
||||
|
||||
|
||||
# UI signals.
|
||||
func _on_send_pressed():
|
||||
if _line_edit.text == "":
|
||||
#region UI signals
|
||||
func _on_send_pressed() -> void:
|
||||
if _line_edit.text.is_empty():
|
||||
return
|
||||
|
||||
info("Sending message: %s" % [_line_edit.text])
|
||||
@@ -34,14 +34,17 @@ func _on_send_pressed():
|
||||
_line_edit.text = ""
|
||||
|
||||
|
||||
func _on_connect_toggled(pressed):
|
||||
func _on_connect_toggled(pressed: bool) -> void:
|
||||
if not pressed:
|
||||
_client.close()
|
||||
return
|
||||
if _host.text == "":
|
||||
|
||||
if _host.text.is_empty():
|
||||
return
|
||||
|
||||
info("Connecting to host: %s." % [_host.text])
|
||||
var err = _client.connect_to_url(_host.text)
|
||||
var err := _client.connect_to_url(_host.text)
|
||||
if err != OK:
|
||||
info("Error connecting to host: %s" % [_host.text])
|
||||
return
|
||||
#endregion
|
||||
|
||||
@@ -17,6 +17,10 @@ run/main_scene="res://combo.tscn"
|
||||
config/features=PackedStringArray("4.2")
|
||||
config/icon="res://icon.webp"
|
||||
|
||||
[debug]
|
||||
|
||||
gdscript/warnings/untyped_declaration=1
|
||||
|
||||
[display]
|
||||
|
||||
window/stretch/mode="canvas_items"
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
extends Control
|
||||
|
||||
@onready var _server: WebSocketServer = $WebSocketServer
|
||||
@onready var _log_dest = $Panel/VBoxContainer/RichTextLabel
|
||||
@onready var _line_edit = $Panel/VBoxContainer/Send/LineEdit
|
||||
@onready var _listen_port = $Panel/VBoxContainer/Connect/Port
|
||||
@onready var _log_dest: RichTextLabel = $Panel/VBoxContainer/RichTextLabel
|
||||
@onready var _line_edit: LineEdit = $Panel/VBoxContainer/Send/LineEdit
|
||||
@onready var _listen_port: SpinBox = $Panel/VBoxContainer/Connect/Port
|
||||
|
||||
func info(msg):
|
||||
func info(msg: String) -> void:
|
||||
print(msg)
|
||||
_log_dest.add_text(str(msg) + "\n")
|
||||
|
||||
|
||||
# Server signals
|
||||
func _on_web_socket_server_client_connected(peer_id):
|
||||
#region Server signals
|
||||
func _on_web_socket_server_client_connected(peer_id: int) -> void:
|
||||
var peer: WebSocketPeer = _server.peers[peer_id]
|
||||
info("Remote client connected: %d. Protocol: %s" % [peer_id, peer.get_selected_protocol()])
|
||||
_server.send(-peer_id, "[%d] connected" % peer_id)
|
||||
|
||||
|
||||
func _on_web_socket_server_client_disconnected(peer_id):
|
||||
func _on_web_socket_server_client_disconnected(peer_id: int) -> void:
|
||||
var peer: WebSocketPeer = _server.peers[peer_id]
|
||||
info("Remote client disconnected: %d. Code: %d, Reason: %s" % [peer_id, peer.get_close_code(), peer.get_close_reason()])
|
||||
_server.send(-peer_id, "[%d] disconnected" % peer_id)
|
||||
|
||||
|
||||
func _on_web_socket_server_message_received(peer_id, message):
|
||||
func _on_web_socket_server_message_received(peer_id: int, message: String) -> void:
|
||||
info("Server received data from peer %d: %s" % [peer_id, message])
|
||||
_server.send(-peer_id, "[%d] Says: %s" % [peer_id, message])
|
||||
#endregion
|
||||
|
||||
|
||||
# UI signals.
|
||||
func _on_send_pressed():
|
||||
#region UI signals
|
||||
func _on_send_pressed() -> void:
|
||||
if _line_edit.text == "":
|
||||
return
|
||||
|
||||
@@ -38,14 +38,17 @@ func _on_send_pressed():
|
||||
_line_edit.text = ""
|
||||
|
||||
|
||||
func _on_listen_toggled(pressed):
|
||||
func _on_listen_toggled(pressed: bool) -> void:
|
||||
if not pressed:
|
||||
_server.stop()
|
||||
info("Server stopped")
|
||||
return
|
||||
var port = int(_listen_port.value)
|
||||
var err = _server.listen(port)
|
||||
|
||||
var port := int(_listen_port.value)
|
||||
var err := _server.listen(port)
|
||||
|
||||
if err != OK:
|
||||
info("Error listing on port %s" % port)
|
||||
return
|
||||
info("Listing on port %s, supported protocols: %s" % [port, _server.supported_protocols])
|
||||
#endregion
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
extends Node
|
||||
class_name WebSocketClient
|
||||
class_name Node
|
||||
extends WebSocketClient
|
||||
|
||||
@export var handshake_headers: PackedStringArray
|
||||
@export var supported_protocols: PackedStringArray
|
||||
var tls_options: TLSOptions = null
|
||||
|
||||
|
||||
var socket = WebSocketPeer.new()
|
||||
var last_state = WebSocketPeer.STATE_CLOSED
|
||||
|
||||
var socket := WebSocketPeer.new()
|
||||
var last_state := WebSocketPeer.STATE_CLOSED
|
||||
|
||||
signal connected_to_server()
|
||||
signal connection_closed()
|
||||
signal message_received(message: Variant)
|
||||
|
||||
|
||||
func connect_to_url(url) -> int:
|
||||
func connect_to_url(url: String) -> int:
|
||||
socket.supported_protocols = supported_protocols
|
||||
socket.handshake_headers = handshake_headers
|
||||
var err = socket.connect_to_url(url, tls_options)
|
||||
|
||||
var err := socket.connect_to_url(url, tls_options)
|
||||
if err != OK:
|
||||
return err
|
||||
|
||||
last_state = socket.get_ready_state()
|
||||
return OK
|
||||
|
||||
|
||||
func send(message) -> int:
|
||||
func send(message: String) -> int:
|
||||
if typeof(message) == TYPE_STRING:
|
||||
return socket.send_text(message)
|
||||
return socket.send(var_to_bytes(message))
|
||||
@@ -34,13 +33,13 @@ func send(message) -> int:
|
||||
func get_message() -> Variant:
|
||||
if socket.get_available_packet_count() < 1:
|
||||
return null
|
||||
var pkt = socket.get_packet()
|
||||
var pkt := socket.get_packet()
|
||||
if socket.was_string_packet():
|
||||
return pkt.get_string_from_utf8()
|
||||
return bytes_to_var(pkt)
|
||||
|
||||
|
||||
func close(code := 1000, reason := "") -> void:
|
||||
func close(code: int = 1000, reason: String = "") -> void:
|
||||
socket.close(code, reason)
|
||||
last_state = socket.get_ready_state()
|
||||
|
||||
@@ -57,7 +56,9 @@ func get_socket() -> WebSocketPeer:
|
||||
func poll() -> void:
|
||||
if socket.get_ready_state() != socket.STATE_CLOSED:
|
||||
socket.poll()
|
||||
var state = socket.get_ready_state()
|
||||
|
||||
var state := socket.get_ready_state()
|
||||
|
||||
if last_state != state:
|
||||
last_state = state
|
||||
if state == socket.STATE_OPEN:
|
||||
@@ -68,5 +69,5 @@ func poll() -> void:
|
||||
message_received.emit(get_message())
|
||||
|
||||
|
||||
func _process(delta):
|
||||
func _process(_delta: float) -> void:
|
||||
poll()
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
extends Node
|
||||
class_name WebSocketServer
|
||||
class_name Node
|
||||
extends WebSocketServer
|
||||
|
||||
signal message_received(peer_id: int, message)
|
||||
signal message_received(peer_id: int, message: String)
|
||||
signal client_connected(peer_id: int)
|
||||
signal client_disconnected(peer_id: int)
|
||||
|
||||
@export var handshake_headers := PackedStringArray()
|
||||
@export var supported_protocols: PackedStringArray
|
||||
@export var supported_protocols := PackedStringArray()
|
||||
@export var handshake_timout := 3000
|
||||
@export var use_tls := false
|
||||
@export var tls_cert: X509Certificate
|
||||
@@ -23,7 +23,7 @@ class PendingPeer:
|
||||
var connection: StreamPeer
|
||||
var ws: WebSocketPeer
|
||||
|
||||
func _init(p_tcp: StreamPeerTCP):
|
||||
func _init(p_tcp: StreamPeerTCP) -> void:
|
||||
tcp = p_tcp
|
||||
connection = p_tcp
|
||||
connect_time = Time.get_ticks_msec()
|
||||
@@ -39,17 +39,17 @@ func listen(port: int) -> int:
|
||||
return tcp_server.listen(port)
|
||||
|
||||
|
||||
func stop():
|
||||
func stop() -> void:
|
||||
tcp_server.stop()
|
||||
pending_peers.clear()
|
||||
peers.clear()
|
||||
|
||||
|
||||
func send(peer_id, message) -> int:
|
||||
var type = typeof(message)
|
||||
func send(peer_id: int, message: String) -> int:
|
||||
var type := typeof(message)
|
||||
if peer_id <= 0:
|
||||
# Send to multiple peers, (zero = brodcast, negative = exclude one)
|
||||
for id in peers:
|
||||
# Send to multiple peers, (zero = broadcast, negative = exclude one).
|
||||
for id: int in peers:
|
||||
if id == -peer_id:
|
||||
continue
|
||||
if type == TYPE_STRING:
|
||||
@@ -59,30 +59,30 @@ func send(peer_id, message) -> int:
|
||||
return OK
|
||||
|
||||
assert(peers.has(peer_id))
|
||||
var socket = peers[peer_id]
|
||||
var socket: WebSocketPeer = peers[peer_id]
|
||||
if type == TYPE_STRING:
|
||||
return socket.send_text(message)
|
||||
return socket.send(var_to_bytes(message))
|
||||
|
||||
|
||||
func get_message(peer_id) -> Variant:
|
||||
func get_message(peer_id: int) -> Variant:
|
||||
assert(peers.has(peer_id))
|
||||
var socket = peers[peer_id]
|
||||
var socket: WebSocketPeer = peers[peer_id]
|
||||
if socket.get_available_packet_count() < 1:
|
||||
return null
|
||||
var pkt = socket.get_packet()
|
||||
var pkt: PackedByteArray = socket.get_packet()
|
||||
if socket.was_string_packet():
|
||||
return pkt.get_string_from_utf8()
|
||||
return bytes_to_var(pkt)
|
||||
|
||||
|
||||
func has_message(peer_id) -> bool:
|
||||
func has_message(peer_id: int) -> bool:
|
||||
assert(peers.has(peer_id))
|
||||
return peers[peer_id].get_available_packet_count() > 0
|
||||
|
||||
|
||||
func _create_peer() -> WebSocketPeer:
|
||||
var ws = WebSocketPeer.new()
|
||||
var ws := WebSocketPeer.new()
|
||||
ws.supported_protocols = supported_protocols
|
||||
ws.handshake_headers = handshake_headers
|
||||
return ws
|
||||
@@ -91,72 +91,83 @@ func _create_peer() -> WebSocketPeer:
|
||||
func poll() -> void:
|
||||
if not tcp_server.is_listening():
|
||||
return
|
||||
|
||||
while not refuse_new_connections and tcp_server.is_connection_available():
|
||||
var conn = tcp_server.take_connection()
|
||||
var conn: StreamPeerTCP = tcp_server.take_connection()
|
||||
assert(conn != null)
|
||||
pending_peers.append(PendingPeer.new(conn))
|
||||
|
||||
var to_remove := []
|
||||
|
||||
for p in pending_peers:
|
||||
if not _connect_pending(p):
|
||||
if p.connect_time + handshake_timout < Time.get_ticks_msec():
|
||||
# Timeout
|
||||
# Timeout.
|
||||
to_remove.append(p)
|
||||
continue # Still pending
|
||||
continue # Still pending.
|
||||
|
||||
to_remove.append(p)
|
||||
for r in to_remove:
|
||||
|
||||
for r: RefCounted in to_remove:
|
||||
pending_peers.erase(r)
|
||||
|
||||
to_remove.clear()
|
||||
for id in peers:
|
||||
|
||||
for id: int in peers:
|
||||
var p: WebSocketPeer = peers[id]
|
||||
var packets = p.get_available_packet_count()
|
||||
p.poll()
|
||||
|
||||
if p.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||
client_disconnected.emit(id)
|
||||
to_remove.append(id)
|
||||
continue
|
||||
|
||||
while p.get_available_packet_count():
|
||||
message_received.emit(id, get_message(id))
|
||||
for r in to_remove:
|
||||
|
||||
for r: int in to_remove:
|
||||
peers.erase(r)
|
||||
to_remove.clear()
|
||||
|
||||
|
||||
func _connect_pending(p: PendingPeer) -> bool:
|
||||
if p.ws != null:
|
||||
# Poll websocket client if doing handshake
|
||||
# Poll websocket client if doing handshake.
|
||||
p.ws.poll()
|
||||
var state = p.ws.get_ready_state()
|
||||
var state := p.ws.get_ready_state()
|
||||
if state == WebSocketPeer.STATE_OPEN:
|
||||
var id = randi_range(2, 1 << 30)
|
||||
var id := randi_range(2, 1 << 30)
|
||||
peers[id] = p.ws
|
||||
client_connected.emit(id)
|
||||
return true # Success.
|
||||
return true # Success.
|
||||
elif state != WebSocketPeer.STATE_CONNECTING:
|
||||
return true # Failure.
|
||||
return false # Still connecting.
|
||||
return true # Failure.
|
||||
return false # Still connecting.
|
||||
elif p.tcp.get_status() != StreamPeerTCP.STATUS_CONNECTED:
|
||||
return true # TCP disconnected.
|
||||
return true # TCP disconnected.
|
||||
elif not use_tls:
|
||||
# TCP is ready, create WS peer
|
||||
# TCP is ready, create WS peer.
|
||||
p.ws = _create_peer()
|
||||
p.ws.accept_stream(p.tcp)
|
||||
return false # WebSocketPeer connection is pending.
|
||||
return false # WebSocketPeer connection is pending.
|
||||
|
||||
else:
|
||||
if p.connection == p.tcp:
|
||||
assert(tls_key != null and tls_cert != null)
|
||||
var tls = StreamPeerTLS.new()
|
||||
var tls := StreamPeerTLS.new()
|
||||
tls.accept_stream(p.tcp, TLSOptions.server(tls_key, tls_cert))
|
||||
p.connection = tls
|
||||
p.connection.poll()
|
||||
var status = p.connection.get_status()
|
||||
var status: StreamPeerTLS.Status = p.connection.get_status()
|
||||
if status == StreamPeerTLS.STATUS_CONNECTED:
|
||||
p.ws = _create_peer()
|
||||
p.ws.accept_stream(p.connection)
|
||||
return false # WebSocketPeer connection is pending.
|
||||
return false # WebSocketPeer connection is pending.
|
||||
if status != StreamPeerTLS.STATUS_HANDSHAKING:
|
||||
return true # Failure.
|
||||
return true # Failure.
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func _process(delta):
|
||||
func _process(_delta: float) -> void:
|
||||
poll()
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
extends Node
|
||||
|
||||
# The URL we will connect to.
|
||||
var websocket_url = "ws://localhost:9080"
|
||||
## The URL we will connect to.
|
||||
var websocket_url := "ws://localhost:9080"
|
||||
|
||||
var socket := WebSocketPeer.new()
|
||||
|
||||
|
||||
func log_message(message):
|
||||
var time = "[color=#aaaaaa] %s [/color]" % Time.get_time_string_from_system()
|
||||
func log_message(message: String) -> void:
|
||||
var time := "[color=#aaaaaa] %s |[/color] " % Time.get_time_string_from_system()
|
||||
%TextClient.text += time + message + "\n"
|
||||
|
||||
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
if socket.connect_to_url(websocket_url) != OK:
|
||||
log_message("Unable to connect.")
|
||||
set_process(false)
|
||||
|
||||
|
||||
func _process(_delta):
|
||||
func _process(_delta: float) -> void:
|
||||
socket.poll()
|
||||
|
||||
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
||||
@@ -24,9 +24,9 @@ func _process(_delta):
|
||||
log_message(socket.get_packet().get_string_from_ascii())
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
func _exit_tree() -> void:
|
||||
socket.close()
|
||||
|
||||
|
||||
func _on_button_ping_pressed():
|
||||
func _on_button_ping_pressed() -> void:
|
||||
socket.send_text("Ping")
|
||||
|
||||
@@ -16,6 +16,10 @@ config/tags=PackedStringArray("demo", "network", "official")
|
||||
run/main_scene="res://Main.tscn"
|
||||
config/features=PackedStringArray("4.2")
|
||||
|
||||
[debug]
|
||||
|
||||
gdscript/warnings/untyped_declaration=1
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=600
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
extends Node
|
||||
|
||||
# The port we will listen to.
|
||||
## The port the server will listen on.
|
||||
const PORT = 9080
|
||||
|
||||
var tcp_server := TCPServer.new()
|
||||
var socket := WebSocketPeer.new()
|
||||
|
||||
|
||||
func log_message(message):
|
||||
var time = "[color=#aaaaaa] %s [/color]" % Time.get_time_string_from_system()
|
||||
func log_message(message: String) -> void:
|
||||
var time := "[color=#aaaaaa] %s |[/color] " % Time.get_time_string_from_system()
|
||||
%TextServer.text += time + message + "\n"
|
||||
|
||||
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
if tcp_server.listen(PORT) != OK:
|
||||
log_message("Unable to start server.")
|
||||
set_process(false)
|
||||
|
||||
|
||||
func _process(_delta):
|
||||
func _process(_delta: float) -> void:
|
||||
while tcp_server.is_connection_available():
|
||||
var conn: StreamPeerTCP = tcp_server.take_connection()
|
||||
assert(conn != null)
|
||||
@@ -30,10 +30,10 @@ func _process(_delta):
|
||||
log_message(socket.get_packet().get_string_from_ascii())
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
func _exit_tree() -> void:
|
||||
socket.close()
|
||||
tcp_server.stop()
|
||||
|
||||
|
||||
func _on_button_pong_pressed():
|
||||
func _on_button_pong_pressed() -> void:
|
||||
socket.send_text("Pong")
|
||||
|
||||
@@ -17,6 +17,10 @@ run/main_scene="res://scene/combo.tscn"
|
||||
config/features=PackedStringArray("4.2")
|
||||
config/icon="res://icon.webp"
|
||||
|
||||
[debug]
|
||||
|
||||
gdscript/warnings/untyped_declaration=1
|
||||
|
||||
[display]
|
||||
|
||||
window/stretch/mode="canvas_items"
|
||||
|
||||
@@ -1,50 +1,42 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dqxum77awcw6u"]
|
||||
|
||||
[ext_resource path="res://script/game.gd" type="Script" id=1]
|
||||
[ext_resource type="Script" path="res://script/game.gd" id="1"]
|
||||
|
||||
[node name="Game" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
mouse_filter = 1
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
mouse_filter = 1
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="RichTextLabel" type="RichTextLabel" parent="HBoxContainer"]
|
||||
offset_right = 510.0
|
||||
offset_bottom = 600.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"]
|
||||
offset_left = 514.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 600.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer"]
|
||||
offset_right = 510.0
|
||||
offset_bottom = 14.0
|
||||
layout_mode = 2
|
||||
text = "Players:"
|
||||
|
||||
[node name="ItemList" type="ItemList" parent="HBoxContainer/VBoxContainer"]
|
||||
offset_top = 18.0
|
||||
offset_right = 510.0
|
||||
offset_bottom = 576.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
same_column_width = true
|
||||
|
||||
[node name="Action" type="Button" parent="HBoxContainer/VBoxContainer"]
|
||||
offset_top = 580.0
|
||||
offset_right = 510.0
|
||||
offset_bottom = 600.0
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "Do Action!"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://c240icwf4uov8"]
|
||||
|
||||
[ext_resource type="Script" path="res://script/main.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://scene/game.tscn" id="2"]
|
||||
[ext_resource type="PackedScene" uid="uid://dqxum77awcw6u" path="res://scene/game.tscn" id="2"]
|
||||
|
||||
[node name="Main" type="Control"]
|
||||
layout_mode = 3
|
||||
@@ -15,14 +15,14 @@ size_flags_vertical = 3
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
anchors_preset = 15
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
|
||||
anchors_preset = 15
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 20.0
|
||||
@@ -33,84 +33,55 @@ grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_right = 1112.0
|
||||
offset_bottom = 31.0
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Panel/VBoxContainer/HBoxContainer"]
|
||||
offset_top = 2.0
|
||||
offset_right = 369.0
|
||||
offset_bottom = 28.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Name"
|
||||
|
||||
[node name="NameEdit" type="LineEdit" parent="Panel/VBoxContainer/HBoxContainer"]
|
||||
offset_left = 373.0
|
||||
offset_right = 1112.0
|
||||
offset_bottom = 31.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "A Godot User"
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_top = 35.0
|
||||
offset_right = 1112.0
|
||||
offset_bottom = 66.0
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer/HBoxContainer2"]
|
||||
offset_right = 369.0
|
||||
offset_bottom = 31.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Host" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
|
||||
offset_right = 44.0
|
||||
offset_bottom = 31.0
|
||||
layout_mode = 2
|
||||
text = "Host"
|
||||
|
||||
[node name="Control" type="Control" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_left = 48.0
|
||||
offset_right = 273.0
|
||||
offset_bottom = 31.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Connect" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
|
||||
offset_left = 277.0
|
||||
offset_right = 369.0
|
||||
offset_bottom = 31.0
|
||||
layout_mode = 2
|
||||
text = "Connect to"
|
||||
|
||||
[node name="Disconnect" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
|
||||
visible = false
|
||||
offset_left = 68.0
|
||||
offset_right = 152.0
|
||||
offset_bottom = 24.0
|
||||
layout_mode = 2
|
||||
text = "Disconnect"
|
||||
|
||||
[node name="Hostname" type="LineEdit" parent="Panel/VBoxContainer/HBoxContainer2"]
|
||||
offset_left = 373.0
|
||||
offset_right = 1112.0
|
||||
offset_bottom = 31.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "localhost"
|
||||
placeholder_text = "localhost"
|
||||
|
||||
[node name="Control" type="Control" parent="Panel/VBoxContainer"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_top = 70.0
|
||||
offset_right = 1112.0
|
||||
offset_bottom = 70.0
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Game" parent="Panel/VBoxContainer" instance=ExtResource("2")]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_top = 74.0
|
||||
offset_right = 1112.0
|
||||
offset_bottom = 608.0
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AcceptDialog" type="AcceptDialog" parent="."]
|
||||
dialog_text = "Connection closed"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
extends Control
|
||||
|
||||
var paths := []
|
||||
var paths: Array[NodePath] = []
|
||||
|
||||
func _enter_tree():
|
||||
|
||||
func _enter_tree() -> void:
|
||||
for ch in $GridContainer.get_children():
|
||||
paths.append(NodePath(str(get_path()) + "/GridContainer/" + str(ch.name)))
|
||||
# Sets a dedicated Multiplayer API for each branch.
|
||||
@@ -10,7 +11,7 @@ func _enter_tree():
|
||||
get_tree().set_multiplayer(MultiplayerAPI.create_default_interface(), path)
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
func _exit_tree() -> void:
|
||||
# Clear the branch-specific Multiplayer API.
|
||||
for path in paths:
|
||||
get_tree().set_multiplayer(null, path)
|
||||
|
||||
@@ -2,39 +2,40 @@ extends Control
|
||||
|
||||
const _crown = preload("res://img/crown.png")
|
||||
|
||||
@onready var _list = $HBoxContainer/VBoxContainer/ItemList
|
||||
@onready var _action = $HBoxContainer/VBoxContainer/Action
|
||||
@onready var _list: ItemList = $HBoxContainer/VBoxContainer/ItemList
|
||||
@onready var _action: Button = $HBoxContainer/VBoxContainer/Action
|
||||
|
||||
const ACTIONS = ["roll", "pass"]
|
||||
|
||||
var _players = []
|
||||
var _turn = -1
|
||||
var _players: Array[int] = []
|
||||
var _turn := -1
|
||||
|
||||
|
||||
@rpc
|
||||
func _log(what):
|
||||
$HBoxContainer/RichTextLabel.add_text(what + "\n")
|
||||
func _log(message: String) -> void:
|
||||
$HBoxContainer/RichTextLabel.add_text(message + "\n")
|
||||
|
||||
|
||||
@rpc("any_peer")
|
||||
func set_player_name(p_name):
|
||||
func set_player_name(p_name: String) -> void:
|
||||
if not is_multiplayer_authority():
|
||||
return
|
||||
var sender = multiplayer.get_remote_sender_id()
|
||||
var sender := multiplayer.get_remote_sender_id()
|
||||
update_player_name.rpc(sender, p_name)
|
||||
|
||||
|
||||
@rpc("call_local")
|
||||
func update_player_name(player, p_name):
|
||||
var pos = _players.find(player)
|
||||
func update_player_name(player: int, p_name: String) -> void:
|
||||
var pos := _players.find(player)
|
||||
if pos != -1:
|
||||
_list.set_item_text(pos, p_name)
|
||||
|
||||
|
||||
@rpc("any_peer")
|
||||
func request_action(action):
|
||||
func request_action(action: String) -> void:
|
||||
if not is_multiplayer_authority():
|
||||
return
|
||||
var sender = multiplayer.get_remote_sender_id()
|
||||
var sender := multiplayer.get_remote_sender_id()
|
||||
if _players[_turn] != sender:
|
||||
_log.rpc("Someone is trying to cheat! %s" % str(sender))
|
||||
return
|
||||
@@ -46,88 +47,97 @@ func request_action(action):
|
||||
next_turn()
|
||||
|
||||
|
||||
func do_action(action):
|
||||
var player_name = _list.get_item_text(_turn)
|
||||
var val = randi() % 100
|
||||
func do_action(action: String) -> void:
|
||||
var player_name := _list.get_item_text(_turn)
|
||||
var val := randi() % 100
|
||||
_log.rpc("%s: %ss %d" % [player_name, action, val])
|
||||
|
||||
|
||||
@rpc("call_local")
|
||||
func set_turn(turn):
|
||||
func set_turn(turn: int) -> void:
|
||||
_turn = turn
|
||||
if turn >= _players.size():
|
||||
return
|
||||
for i in range(0, _players.size()):
|
||||
|
||||
for i in _players.size():
|
||||
if i == turn:
|
||||
_list.set_item_icon(i, _crown)
|
||||
else:
|
||||
_list.set_item_icon(i, null)
|
||||
|
||||
_action.disabled = _players[turn] != multiplayer.get_unique_id()
|
||||
|
||||
|
||||
@rpc("call_local")
|
||||
func del_player(id):
|
||||
var pos = _players.find(id)
|
||||
func del_player(id: int) -> void:
|
||||
var pos := _players.find(id)
|
||||
|
||||
if pos == -1:
|
||||
return
|
||||
|
||||
_players.remove_at(pos)
|
||||
_list.remove_item(pos)
|
||||
|
||||
if _turn > pos:
|
||||
_turn -= 1
|
||||
|
||||
if multiplayer.is_server():
|
||||
set_turn.rpc(_turn)
|
||||
|
||||
|
||||
@rpc("call_local")
|
||||
func add_player(id, pname=""):
|
||||
func add_player(id: int, p_name: String = "") -> void:
|
||||
_players.append(id)
|
||||
if pname == "":
|
||||
if p_name == "":
|
||||
_list.add_item("... connecting ...", null, false)
|
||||
else:
|
||||
_list.add_item(pname, null, false)
|
||||
_list.add_item(p_name, null, false)
|
||||
|
||||
|
||||
func get_player_name(pos):
|
||||
func get_player_name(pos: int) -> String:
|
||||
if pos < _list.get_item_count():
|
||||
return _list.get_item_text(pos)
|
||||
else:
|
||||
return "Error!"
|
||||
|
||||
|
||||
func next_turn():
|
||||
func next_turn() -> void:
|
||||
_turn += 1
|
||||
if _turn >= _players.size():
|
||||
_turn = 0
|
||||
set_turn.rpc(_turn)
|
||||
|
||||
|
||||
func start():
|
||||
func start() -> void:
|
||||
set_turn(0)
|
||||
|
||||
|
||||
func stop():
|
||||
func stop() -> void:
|
||||
_players.clear()
|
||||
_list.clear()
|
||||
_turn = 0
|
||||
_action.disabled = true
|
||||
|
||||
|
||||
func on_peer_add(id):
|
||||
func on_peer_add(id: int) -> void:
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
for i in range(0, _players.size()):
|
||||
|
||||
for i in _players.size():
|
||||
add_player.rpc_id(id, _players[i], get_player_name(i))
|
||||
|
||||
add_player.rpc(id)
|
||||
set_turn.rpc_id(id, _turn)
|
||||
|
||||
|
||||
func on_peer_del(id):
|
||||
func on_peer_del(id: int) -> void:
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
|
||||
del_player.rpc(id)
|
||||
|
||||
|
||||
func _on_Action_pressed():
|
||||
func _on_Action_pressed() -> void:
|
||||
if multiplayer.is_server():
|
||||
if _turn != 0:
|
||||
return
|
||||
|
||||
@@ -3,21 +3,21 @@ extends Control
|
||||
const DEF_PORT = 8080
|
||||
const PROTO_NAME = "ludus"
|
||||
|
||||
@onready var _host_btn = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Host
|
||||
@onready var _connect_btn = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Connect
|
||||
@onready var _disconnect_btn = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Disconnect
|
||||
@onready var _name_edit = $Panel/VBoxContainer/HBoxContainer/NameEdit
|
||||
@onready var _host_edit = $Panel/VBoxContainer/HBoxContainer2/Hostname
|
||||
@onready var _game = $Panel/VBoxContainer/Game
|
||||
@onready var _host_btn: Button = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Host
|
||||
@onready var _connect_btn: Button = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Connect
|
||||
@onready var _disconnect_btn: Button = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Disconnect
|
||||
@onready var _name_edit: LineEdit = $Panel/VBoxContainer/HBoxContainer/NameEdit
|
||||
@onready var _host_edit: LineEdit = $Panel/VBoxContainer/HBoxContainer2/Hostname
|
||||
@onready var _game: Control = $Panel/VBoxContainer/Game
|
||||
|
||||
var peer = WebSocketMultiplayerPeer.new()
|
||||
var peer := WebSocketMultiplayerPeer.new()
|
||||
|
||||
|
||||
func _init():
|
||||
func _init() -> void:
|
||||
peer.supported_protocols = ["ludus"]
|
||||
|
||||
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
multiplayer.peer_connected.connect(_peer_connected)
|
||||
multiplayer.peer_disconnected.connect(_peer_disconnected)
|
||||
multiplayer.server_disconnected.connect(_close_network)
|
||||
@@ -30,11 +30,11 @@ func _ready():
|
||||
if OS.has_environment("USERNAME"):
|
||||
_name_edit.text = OS.get_environment("USERNAME")
|
||||
else:
|
||||
var desktop_path = OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP).replace("\\", "/").split("/")
|
||||
var desktop_path := OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP).replace("\\", "/").split("/")
|
||||
_name_edit.text = desktop_path[desktop_path.size() - 2]
|
||||
|
||||
|
||||
func start_game():
|
||||
func start_game() -> void:
|
||||
_host_btn.disabled = true
|
||||
_name_edit.editable = false
|
||||
_host_edit.editable = false
|
||||
@@ -43,7 +43,7 @@ func start_game():
|
||||
_game.start()
|
||||
|
||||
|
||||
func stop_game():
|
||||
func stop_game() -> void:
|
||||
_host_btn.disabled = false
|
||||
_name_edit.editable = true
|
||||
_host_edit.editable = true
|
||||
@@ -52,7 +52,7 @@ func stop_game():
|
||||
_game.stop()
|
||||
|
||||
|
||||
func _close_network():
|
||||
func _close_network() -> void:
|
||||
stop_game()
|
||||
$AcceptDialog.popup_centered()
|
||||
$AcceptDialog.get_ok_button().grab_focus()
|
||||
@@ -60,20 +60,20 @@ func _close_network():
|
||||
peer.close()
|
||||
|
||||
|
||||
func _connected():
|
||||
func _connected() -> void:
|
||||
_game.set_player_name.rpc(_name_edit.text)
|
||||
|
||||
|
||||
func _peer_connected(id):
|
||||
func _peer_connected(id: int) -> void:
|
||||
_game.on_peer_add(id)
|
||||
|
||||
|
||||
func _peer_disconnected(id):
|
||||
func _peer_disconnected(id: int) -> void:
|
||||
print("Disconnected %d" % id)
|
||||
_game.on_peer_del(id)
|
||||
|
||||
|
||||
func _on_Host_pressed():
|
||||
func _on_Host_pressed() -> void:
|
||||
multiplayer.multiplayer_peer = null
|
||||
peer.create_server(DEF_PORT)
|
||||
multiplayer.multiplayer_peer = peer
|
||||
@@ -81,11 +81,11 @@ func _on_Host_pressed():
|
||||
start_game()
|
||||
|
||||
|
||||
func _on_Disconnect_pressed():
|
||||
func _on_Disconnect_pressed() -> void:
|
||||
_close_network()
|
||||
|
||||
|
||||
func _on_Connect_pressed():
|
||||
func _on_Connect_pressed() -> void:
|
||||
multiplayer.multiplayer_peer = null
|
||||
peer.create_client("ws://" + _host_edit.text + ":" + str(DEF_PORT))
|
||||
multiplayer.multiplayer_peer = peer
|
||||
|
||||
Reference in New Issue
Block a user