From 911699e9563a8791d72173b46fd8860ce15bb91a Mon Sep 17 00:00:00 2001 From: Daniel Weipert Date: Thu, 10 Oct 2024 13:16:33 +0200 Subject: intermediate commit --- Game/Client.gd | 32 +++++------ Game/Network.gd | 154 +++++++++++++---------------------------------------- Game/Network.tscn | 13 +++++ Game/Player.gd | 33 +++++++----- Game/Player.tscn | 24 +++++++++ Stages/Stage.gd | 9 +++- Towers/Tower.gd | 23 +++++--- Towers/Tower.tscn | 2 - UI/Lobby.gd | 4 +- UI/players_list.gd | 9 ++-- Units/Unit.gd | 17 +----- project.godot | 2 +- 12 files changed, 138 insertions(+), 184 deletions(-) create mode 100644 Game/Network.tscn create mode 100644 Game/Player.tscn diff --git a/Game/Client.gd b/Game/Client.gd index dbaf69e..dc18765 100644 --- a/Game/Client.gd +++ b/Game/Client.gd @@ -9,17 +9,16 @@ var state: State : stage_state_changed.emit(value) var stage: Stage -var player: Player = Player.new() - -func _ready(): - #player = Player.new() - player.id = multiplayer.get_unique_id() +var _player: Player # workaround for MultiplayerSpawner +var player: Player: + get(): + if _player: return _player + else: return Network.get_player(multiplayer.get_unique_id()) -func update_player(): - Network.players[multiplayer.get_unique_id()] = player - Network.update_player.rpc(Network.to_rpc_object(player)) +func _ready(): + _player = preload("res://Game/Player.tscn").instantiate() func initialize_stage(current_stage: Stage): @@ -37,25 +36,20 @@ func place_tower(tower: Tower, position: Vector2): #Network.place_tower.rpc(Network.to_rpc_object(tower), position) player.towers[position] = tower - player.score += 1 - update_player() + #player.score += 1 func remove_tower(tower: Tower): if tower.owner_id == multiplayer.get_unique_id(): destroy_tower(tower) - player.score -= 1 - update_player() + #player.score -= 1 func destroy_tower(tower: Tower): - if multiplayer.is_server(): - stage.destroy_tower(tower) - else: - Network.destroy_tower.rpc_id(1, tower.global_position) + stage.destroy_tower(tower) + Network.destroy_tower.rpc(tower.global_position) player.towers.erase(tower.global_position) - update_player() func select_tower(tower: Tower): @@ -69,8 +63,8 @@ func deselect_tower(): tower.is_selected = false -func update_tower(tower: Tower, data: Dictionary): - Network.update_tower.rpc(tower.get_path(), data) +func update_tower(path: NodePath, data: Tower.NetworkData): + Network.update_tower.rpc(path, inst_to_dict(data)) func spawn_unit(unit: Unit, spawn: Spawn): diff --git a/Game/Network.gd b/Game/Network.gd index 48558ce..5f8be5c 100644 --- a/Game/Network.gd +++ b/Game/Network.gd @@ -19,140 +19,66 @@ func host_game(port): peer.create_server(int(port)) multiplayer.multiplayer_peer = peer - players[1] = Client.player + + Client.player.id = multiplayer.get_unique_id() + %Players.add_child(Client.player) func join_game(ip, port): var peer = ENetMultiplayerPeer.new() peer.create_client(ip, int(port)) multiplayer.multiplayer_peer = peer + + Client.player.id = multiplayer.get_unique_id() func _on_connected_to_server(): - print("connected to server") + print(multiplayer.get_unique_id(), ": connected to server") func _on_peer_connected(id): - print("peer connected: ", id) - add_to_players.rpc(to_rpc_object(Client.player)) + print(multiplayer.get_unique_id(), ": peer connected: ", id) + + if id == 1: # tell host to add player + add_to_players.rpc_id(1, inst_to_dict(Client.player)) + Client._player = null # TODO: add existing towers to new peers func _on_peer_disconnected(id): - print("peer disconnected: ", id) + print(multiplayer.get_unique_id(), ": peer disconnected: ", id) # TODO: move towers owned by peer to host - if id == 1: + if id == 1: # if host disconnected go back to Lobby get_tree().change_scene_to_file("res://UI/Lobby.tscn") -func get_ordered_player_ids(): - var keys = players.keys() - keys.sort_custom(func(a, b): +func get_player(id: int) -> Player: + return %Players.get_node(str(id)) + +func get_ordered_player_ids(): # TODO: return type needed for players_list.gd "find" method? + var keys = %Players.get_children().map(func(item: Player): + return item.id + ) + keys = keys.filter(func(item: int): # workaround for MultiplayerSpawner + Synchronizer + return item != 0 + ) + keys.sort_custom(func(a: int, b: int): return int(str(a).substr(0, 8)) < int(str(b).substr(0, 8)) ) return keys -func to_rpc_object(object: Variant): - var remote_object = {} - var properties = object.get_rpc_properties() - for property in properties: - var property_class = properties[property] - if object[property] is Dictionary: - remote_object[property] = {} - for key in object[property]: - remote_object[property][key] = to_rpc_object(object[property][key]) - elif property_class is String and property_class.begins_with("node://"): - remote_object[property] = object[property] - elif property_class: - remote_object[property] = to_rpc_object(object[property]) - else: - remote_object[property] = object[property] - - return remote_object - - -func from_rpc_object(remote_object: Dictionary, remote_class_path: String): - var object - var remote_class = load(remote_class_path) - if remote_class is PackedScene: - object = remote_class.instantiate() - elif remote_class is GDScript: - object = remote_class.new() - else: - assert(false, "unexpected remote class type") - - var properties = object.get_rpc_properties() - for property in properties: - var property_class = properties[property] - if object[property] is Dictionary: - for key in object[property]: - object[property][key] = from_rpc_object(remote_object[property], property_class) - elif property_class is String and property_class.begins_with("node://"): - var node_path = property_class.substr(7) # after node:// - object.remove_child(object.get_node(node_path)) - remote_object[property].name = node_path.get_basename() - object[property] = remote_object[property] - object.add_child(remote_object[property]) - elif property_class: - object[property] = from_rpc_object(remote_object[property], property_class) - else: - object[property] = remote_object[property] - - return object - - -func merge_with_rpc_object(object: Variant, remote_object: Dictionary): - var properties = object.get_rpc_properties() - for property in properties: - var property_class = properties[property] - if object[property] is Dictionary: - object[property] = {} - for key in remote_object[property]: - object[property][key] = from_rpc_object(remote_object[property][key], property_class) - elif property_class: - object[property] = merge_with_rpc_object(object[property], remote_object[property]) - else: - object[property] = remote_object[property] - - return object - - -@rpc("call_local", "any_peer") -func add_to_players(remote_player: Dictionary): - var id = multiplayer.get_remote_sender_id() - var player = from_rpc_object(remote_player, "res://Game/Player.gd") - player.id = id - players[id] = player - players_changed.emit() - - -@rpc("call_local", "any_peer") -func update_player(remote_player: Dictionary): +@rpc("any_peer") +func add_to_players(remote_data: Dictionary): var id = multiplayer.get_remote_sender_id() - var player = merge_with_rpc_object(players[id], remote_player) + + var player = preload("res://Game/Player.tscn").instantiate() player.id = id - players[id] = player - players_changed.emit() - - -@rpc("any_peer", "reliable") -func update_node(node_path: NodePath, properties: Dictionary): - var root_node = get_tree().root.get_node(node_path) + player.username = remote_data.username - for property in properties: - if property.contains(":"): - var child_node = get_nested_node_and_property(root_node, property) - child_node.node[child_node.property] = properties[property] - else: - root_node[property] = properties[property] - -func get_nested_node_and_property(node: Node, property: String) -> Dictionary: - if property.contains(":"): - var nested_node_path = property.substr(0, property.find(":")) - var nested_property = property.substr(property.find(":") + 1) - return get_nested_node_and_property(node.get_node(nested_node_path), nested_property) - return { "node": node, "property": property } + %Players.add_child(player) + + players_changed.emit() @rpc("any_peer") @@ -164,7 +90,7 @@ func place_tower(remote_data: Dictionary): var tower = Tower.from_network_data(data) tower.owner_id = remote_player_id - players[remote_player_id].towers[tower.global_position] = tower + Network.get_player(remote_player_id).towers[tower.global_position] = tower Client.stage.place_tower(tower, tower.global_position) @@ -173,23 +99,19 @@ func place_tower(remote_data: Dictionary): #func destroy_tower(remote_tower: Dictionary): func destroy_tower(position: Vector2): var owner_id = multiplayer.get_remote_sender_id() - var player = players[owner_id] as Player + var player = get_player(owner_id) var tower = player.towers.get(position) Client.stage.destroy_tower(tower) @rpc("any_peer") -func update_tower(remote_tower_node_path, data): +func update_tower(remote_tower_node_path: NodePath, remote_data: Dictionary): + var data: Tower.NetworkData = dict_to_inst(remote_data) var tower: Tower = get_tree().current_scene.get_node_or_null(remote_tower_node_path) + if tower: - if "components" in data: - for c in tower.components.duplicate(): - tower.remove_component(c.name) - for c in data.components: - tower.add_component( - load("res://Towers/Components/" + c + "Component.gd").new() - ) + tower.update_with_network_data(data) @rpc("any_peer") diff --git a/Game/Network.tscn b/Game/Network.tscn new file mode 100644 index 0000000..5e7c131 --- /dev/null +++ b/Game/Network.tscn @@ -0,0 +1,13 @@ +[gd_scene load_steps=2 format=3 uid="uid://dknyh6isiv5w4"] + +[ext_resource type="Script" path="res://Game/Network.gd" id="1_p1jvi"] + +[node name="Network" type="Node"] +script = ExtResource("1_p1jvi") + +[node name="Players" type="Node" parent="."] +unique_name_in_owner = true + +[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="."] +_spawnable_scenes = PackedStringArray("res://Game/Player.tscn") +spawn_path = NodePath("../Players") diff --git a/Game/Player.gd b/Game/Player.gd index dce19db..57eb73b 100644 --- a/Game/Player.gd +++ b/Game/Player.gd @@ -1,22 +1,25 @@ class_name Player -extends Resource +extends Node signal score_changed -var id := 1 -var name := "" +@export var username: String -var towers: Dictionary : - set(value): - towers = value - -var score: int : +@export var score: int : set(value): score = value score_changed.emit() +@export var id: int: + set(value): + id = value + name = str(value) + var units: Array[Unit] +var towers: Dictionary : + set(value): + towers = value func get_color(): @@ -30,9 +33,11 @@ func get_color(): return Color(rng.randf(), rng.randf(), rng.randf()) -func get_rpc_properties() -> Dictionary: - return { - "id": null, - "name": null, - "score": null, - } +func _on_multiplayer_synchronizer_synchronized() -> void: + Network.players_changed.emit() + +func _on_multiplayer_synchronizer_delta_synchronized() -> void: + Network.players_changed.emit() + +func _on_score_changed() -> void: + Network.players_changed.emit() diff --git a/Game/Player.tscn b/Game/Player.tscn new file mode 100644 index 0000000..38e1d48 --- /dev/null +++ b/Game/Player.tscn @@ -0,0 +1,24 @@ +[gd_scene load_steps=3 format=3 uid="uid://fvspuiqj0osm"] + +[ext_resource type="Script" path="res://Game/Player.gd" id="1_37njm"] + +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_6h1lm"] +properties/0/path = NodePath(".:score") +properties/0/spawn = true +properties/0/replication_mode = 2 +properties/1/path = NodePath(".:username") +properties/1/spawn = true +properties/1/replication_mode = 2 +properties/2/path = NodePath(".:id") +properties/2/spawn = true +properties/2/replication_mode = 0 + +[node name="Player" type="Node"] +script = ExtResource("1_37njm") + +[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_config = SubResource("SceneReplicationConfig_6h1lm") + +[connection signal="score_changed" from="." to="." method="_on_score_changed"] +[connection signal="delta_synchronized" from="MultiplayerSynchronizer" to="." method="_on_multiplayer_synchronizer_delta_synchronized"] +[connection signal="synchronized" from="MultiplayerSynchronizer" to="." method="_on_multiplayer_synchronizer_synchronized"] diff --git a/Stages/Stage.gd b/Stages/Stage.gd index 1f73a09..47f42a2 100644 --- a/Stages/Stage.gd +++ b/Stages/Stage.gd @@ -37,18 +37,23 @@ func _ready() -> void: func place_tower(tower: Tower, position: Vector2): - var player: Player = Network.players[tower.owner_id] + var player: Player = Network.get_player(tower.owner_id) tower.get_node("Sprite2D").modulate = player.get_color() tower.get_node("ComponentsAnchor").modulate = player.get_color() tower.global_position = position fill_tower_region(tower, true) towers.add_child(tower, true) + + player.score += 1 func destroy_tower(tower: Tower): fill_tower_region(tower, false) tower.queue_free() + + var player: Player = Network.get_player(tower.owner_id) + player.score -= 1 func fill_tower_region(tower: Tower, solid = true): @@ -75,7 +80,7 @@ func fill_tower_region(tower: Tower, solid = true): @warning_ignore("shadowed_variable") func spawn_unit(unit: Unit):#, _spawn: Spawn): - var player: Player = Network.players[unit.owner_id] + var player: Player = Network.get_player(unit.owner_id) unit.get_node("Sprite2D").modulate = player.get_color() units.add_child(unit, true) diff --git a/Towers/Tower.gd b/Towers/Tower.gd index 4262af1..8e22c3e 100644 --- a/Towers/Tower.gd +++ b/Towers/Tower.gd @@ -225,10 +225,7 @@ func _on_h_box_container_gui_input(event: InputEvent) -> void: if not found: tower.add_component(preload("res://Towers/Components/FrostComponent.gd").new()) - var data = {"components": []} - for c in tower.components: - data["components"].append(c.name) - Client.update_tower(tower, data) + Client.update_tower(tower.get_path(), tower.to_network_data()) func _on_h_box_container_2_gui_input(event: InputEvent) -> void: @@ -247,24 +244,34 @@ func _on_h_box_container_2_gui_input(event: InputEvent) -> void: if not found: tower.add_component(preload("res://Towers/Components/BurnComponent.gd").new()) - var data = {"components": []} - for c in tower.components: - data["components"].append(c.name) - Client.update_tower(tower, data) + Client.update_tower(tower.get_path(), tower.to_network_data()) class NetworkData extends Resource: var name: String var position: Vector2 + var components: Array func to_network_data() -> NetworkData: var data = NetworkData.new() data.name = name data.position = global_position + data.components = components.map(func(item: TowerComponent): + return item.name + ) return data +func update_with_network_data(data: NetworkData): + for component in components.duplicate(): + remove_component(component.name) + + for component_name in data.components: + add_component( + load("res://Towers/Components/" + component_name + "Component.gd").new() + ) + static func from_network_data(data: NetworkData) -> Tower: var tower: Tower = preload("res://Towers/Tower.tscn").instantiate() diff --git a/Towers/Tower.tscn b/Towers/Tower.tscn index 6bff2e3..43c3c35 100644 --- a/Towers/Tower.tscn +++ b/Towers/Tower.tscn @@ -115,8 +115,6 @@ text = "Burn" replication_config = SubResource("SceneReplicationConfig_spp26") [connection signal="input_event" from="." to="." method="_on_input_event"] -[connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"] -[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"] [connection signal="tree_exiting" from="." to="." method="_on_tree_exiting"] [connection signal="body_entered" from="Range" to="." method="_on_range_body_entered"] [connection signal="body_exited" from="Range" to="." method="_on_range_body_exited"] diff --git a/UI/Lobby.gd b/UI/Lobby.gd index 68d843c..ba76ecc 100644 --- a/UI/Lobby.gd +++ b/UI/Lobby.gd @@ -24,12 +24,12 @@ func get_port(): return port func _on_host_pressed() -> void: - Client.player.name = %Name.text + Client.player.username = %Name.text Network.host_game(get_port()) get_tree().change_scene_to_file("res://Stages/world.tscn") func _on_join_pressed() -> void: - Client.player.name = %Name.text + Client.player.username = %Name.text Network.join_game(get_ip(), get_port()) get_tree().change_scene_to_file("res://Stages/world.tscn") diff --git a/UI/players_list.gd b/UI/players_list.gd index 04bfe6b..6cfd26c 100644 --- a/UI/players_list.gd +++ b/UI/players_list.gd @@ -6,6 +6,7 @@ extends PanelContainer func _ready() -> void: #players_list_container.visible = false + Client.player.score_changed.connect(update_players) Network.players_changed.connect(update_players) update_players() @@ -13,8 +14,8 @@ func _ready() -> void: func update_players(): - for id in Network.players.keys(): - var player: Player = Network.players[id] + for id in Network.get_ordered_player_ids(): + var player: Player = Network.get_player(id) var control: Control = list.get_node_or_null(str(id)) if not control: @@ -24,9 +25,9 @@ func update_players(): control.get_node("%Indicator").visible = id == multiplayer.get_unique_id() list.add_child(control) - control.get_node("%ID").text = str(player.name) + control.get_node("%ID").text = str(player.username) control.get_node("%Score").text = str(player.score) - list.move_child(control, Network.get_ordered_player_ids().find(id) + 1) + #list.move_child(control, Network.get_ordered_player_ids().find(id) + 1) func remove_player(id): diff --git a/Units/Unit.gd b/Units/Unit.gd index 3b91d73..06cf346 100644 --- a/Units/Unit.gd +++ b/Units/Unit.gd @@ -68,6 +68,7 @@ func _ready(): if not target: target = Client.stage.get_node("%Goal").path_position + line.name = "Line@" + name Client.stage.units.add_child(line) reset_path() @@ -160,7 +161,6 @@ func _on_navigation_base_area_entered(area: Area2D): if area.is_in_group("goal"): Client.player.score += 1 - Client.update_player() queue_free() if area.is_in_group("path"): var path_node: PathNode = area.get_parent() @@ -279,24 +279,9 @@ func _on_tree_exiting() -> void: is_selected = false line.queue_free() - #if get_multiplayer_authority() == multiplayer.get_unique_id(): Network.remove_unit.rpc(get_path()) -func get_rpc_properties(): - return { - #"name": null, - #"global_position": null, - #"target": "res://Stages/Paths/PathNode.tscn", - #"hp": null, - #"speed": null, - #"current_path": null, - #"current_path_idx": null, - #"sprite": "node://Sprite2D", - #"owner_id": null, - } - - class NetworkData extends Resource: var name: String var position: Vector2 diff --git a/project.godot b/project.godot index 7bad496..95486a1 100644 --- a/project.godot +++ b/project.godot @@ -17,7 +17,7 @@ config/icon="res://icon.svg" [autoload] -Network="*res://Game/Network.gd" +Network="*res://Game/Network.tscn" Client="*res://Game/Client.gd" [display] -- cgit v1.2.3