From 4b0a90be0e856e1e86664df29e10d5ae00df24af Mon Sep 17 00:00:00 2001 From: Sofia Date: Sat, 18 Jul 2026 02:26:09 +0300 Subject: [PATCH] Add spawning players --- godot/maps/default_map.tres | 2 +- godot/maps/default_map_2.tres | 7 --- godot/scenes/game_manager.tscn | 5 +- godot/scenes/local_player.tscn | 1 + godot/scenes/map0.tscn | 16 +++-- godot/scenes/remote_player.tscn | 5 +- rust/src/game_manager.rs | 103 ++++++++++++++++++++++++++++++-- rust/src/lib.rs | 1 + rust/src/map.rs | 87 +++++++++++++++++++++++++++ rust/src/map_resource.rs | 4 +- rust/src/net/mod.rs | 1 + rust/src/net/network_manager.rs | 68 +++++++++++++++------ rust/src/net/util.rs | 25 ++++++++ rust/src/player.rs | 62 +++++++++++++++++-- rust/src/ui/lobby.rs | 2 +- 15 files changed, 338 insertions(+), 51 deletions(-) delete mode 100644 godot/maps/default_map_2.tres create mode 100644 rust/src/map.rs create mode 100644 rust/src/net/util.rs diff --git a/godot/maps/default_map.tres b/godot/maps/default_map.tres index 0091705..f2f4881 100644 --- a/godot/maps/default_map.tres +++ b/godot/maps/default_map.tres @@ -1,4 +1,4 @@ -[gd_resource type="Map" format=3 uid="uid://b4rw48yjf7q3q"] +[gd_resource type="MapResource" format=3 uid="uid://c7n1tacc7a73t"] [ext_resource type="PackedScene" uid="uid://7iooamjot1tg" path="res://scenes/map0.tscn" id="1_k5804"] diff --git a/godot/maps/default_map_2.tres b/godot/maps/default_map_2.tres deleted file mode 100644 index 79c11a4..0000000 --- a/godot/maps/default_map_2.tres +++ /dev/null @@ -1,7 +0,0 @@ -[gd_resource type="Map" format=3 uid="uid://crti664evgfmj"] - -[ext_resource type="PackedScene" uid="uid://lahirbqfppvw" path="res://scenes/main.tscn" id="1_gr7ev"] - -[resource] -name = "Default Map 2" -scene = ExtResource("1_gr7ev") diff --git a/godot/scenes/game_manager.tscn b/godot/scenes/game_manager.tscn index c906cae..46eef7c 100644 --- a/godot/scenes/game_manager.tscn +++ b/godot/scenes/game_manager.tscn @@ -1,7 +1,6 @@ [gd_scene format=3 uid="uid://bii6018k3ip3t"] -[ext_resource type="Map" uid="uid://b4rw48yjf7q3q" path="res://maps/default_map.tres" id="1_glms7"] -[ext_resource type="Map" uid="uid://crti664evgfmj" path="res://maps/default_map_2.tres" id="2_udu3i"] +[ext_resource type="MapResource" uid="uid://c7n1tacc7a73t" path="res://maps/default_map.tres" id="1_glms7"] [node name="game_manager" type="GameManager" unique_id=1442044769] -maps = Array[Map]([ExtResource("1_glms7"), ExtResource("2_udu3i")]) +maps = Array[MapResource]([ExtResource("1_glms7")]) diff --git a/godot/scenes/local_player.tscn b/godot/scenes/local_player.tscn index 5a94d5a..7469cda 100644 --- a/godot/scenes/local_player.tscn +++ b/godot/scenes/local_player.tscn @@ -8,6 +8,7 @@ look_sensitivity = 1.5 camera = NodePath("camera_3d") move_speed = 5.0 +collision_mask = 2 [node name="mesh_instance_3d" type="MeshInstance3D" parent="." unique_id=700336478] mesh = SubResource("CapsuleMesh_1cgct") diff --git a/godot/scenes/map0.tscn b/godot/scenes/map0.tscn index 172b2e9..80bc77a 100644 --- a/godot/scenes/map0.tscn +++ b/godot/scenes/map0.tscn @@ -1,6 +1,7 @@ [gd_scene format=3 uid="uid://7iooamjot1tg"] -[ext_resource type="PackedScene" uid="uid://ct70bpbca8one" path="res://scenes/local_player.tscn" id="1_y4ph1"] +[ext_resource type="PackedScene" uid="uid://ct70bpbca8one" path="res://scenes/local_player.tscn" id="1_yjvui"] +[ext_resource type="PackedScene" uid="uid://kfxc0migw80o" path="res://scenes/remote_player.tscn" id="2_kcncr"] [sub_resource type="ImageTexture" id="ImageTexture_3vyb7"] @@ -13,7 +14,10 @@ size = Vector2(100, 100) [sub_resource type="BoxShape3D" id="BoxShape3D_1cgct"] size = Vector3(100, 0.01, 100) -[node name="map_0" type="Node3D" unique_id=1710628675] +[node name="map_0" type="Map" unique_id=914577955 node_paths=PackedStringArray("spawner")] +spawner = NodePath("spawner") +local_player = ExtResource("1_yjvui") +remote_player = ExtResource("2_kcncr") [node name="net_stats_label" type="NetStatsLabel" parent="." unique_id=1821474986] anchors_preset = 2 @@ -29,12 +33,14 @@ material_override = SubResource("StandardMaterial3D_u8vuu") mesh = SubResource("PlaneMesh_1cgct") [node name="static_body_3d" type="StaticBody3D" parent="terrain" unique_id=203785193] +collision_layer = 3 +collision_mask = 3 [node name="collision_shape_3d" type="CollisionShape3D" parent="terrain/static_body_3d" unique_id=1466526153] shape = SubResource("BoxShape3D_1cgct") -[node name="player" parent="." unique_id=1910472598 instance=ExtResource("1_y4ph1")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.4560637, 0) - [node name="directional_light_3d" type="DirectionalLight3D" parent="." unique_id=793524416] transform = Transform3D(1, 0, 0, 0, 0.7274598, 0.6861503, 0, -0.6861503, 0.7274598, -5.1267624, 3.3997262, 3.715582) + +[node name="spawner" type="Node3D" parent="." unique_id=1364438299] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.028572083, 3.9666176, 0.031027317) diff --git a/godot/scenes/remote_player.tscn b/godot/scenes/remote_player.tscn index 5abe115..fe5cdff 100644 --- a/godot/scenes/remote_player.tscn +++ b/godot/scenes/remote_player.tscn @@ -6,13 +6,10 @@ [node name="player" type="RemotePlayer" unique_id=845968872] move_speed = 5.0 +collision_mask = 2 [node name="mesh_instance_3d" type="MeshInstance3D" parent="." unique_id=700336478] mesh = SubResource("CapsuleMesh_1cgct") [node name="collision_shape_3d" type="CollisionShape3D" parent="." unique_id=1110995513] shape = SubResource("CapsuleShape3D_1cgct") - -[node name="camera_3d" type="Camera3D" parent="." unique_id=1773137418] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5391109, 0) -current = true diff --git a/rust/src/game_manager.rs b/rust/src/game_manager.rs index 0b8349f..c99da4e 100644 --- a/rust/src/game_manager.rs +++ b/rust/src/game_manager.rs @@ -1,11 +1,13 @@ use std::net::SocketAddr; -use godot::{prelude::*, tools::get_autoload_by_name}; +use godot::{classes::CharacterBody3D, prelude::*, tools::get_autoload_by_name}; use serde::{Deserialize, Serialize}; use crate::{ - map_resource::Map, + map::Map, + map_resource::MapResource, net::network_manager::{NetworkManager, Package, PeerKind}, + player::{IPlayer, NetPlayerTransform}, }; pub const GAME_MANAGER_GLOBAL: &str = "GameManagerGlobal"; @@ -17,7 +19,7 @@ pub struct GameManager { base: Base, #[export] - pub maps: Array>, + pub maps: Array>, } #[godot_api] @@ -70,8 +72,8 @@ impl GameManager { #[class(base=Node, init)] pub struct Game { pub selected_map_idx: u8, - pub selected_map: Option>, - pub maps: Array>, + pub selected_map: Option>, + pub maps: Array>, is_host: bool, player_counter: u16, @@ -79,6 +81,8 @@ pub struct Game { pub players: Vec, game_started: bool, + pub current_map: Option>, + base: Base, } @@ -145,6 +149,7 @@ impl Game { name, ping: 0, ready: false, + character: None, }); self.run_deferred(move |s| s.signals().on_new_player().emit(id)); id @@ -165,6 +170,10 @@ impl Game { self.players.iter().find(|p| p.id == player_id) } + pub fn find_player_mut(&mut self, player_id: u16) -> Option<&mut Player> { + self.players.iter_mut().find(|p| p.id == player_id) + } + pub fn remove_player(&mut self, id: u16) { self.players.retain(|p| p.id != id); self.run_deferred(move |s| s.signals().on_player_disconnected().emit(id)); @@ -190,13 +199,95 @@ impl Game { } self.run_deferred(|s| s.signals().on_players_updated().emit()); } + + pub fn on_map_loaded(&mut self, map: Gd) { + self.current_map = Some(map); + if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { + if let PeerKind::Client(peer, to) = peer { + peer.send_reliable(to, Package::MapLoaded); + } else { + self.run_deferred(|s| { + for player in &mut s.players { + if let Some(map) = &mut s.current_map { + let character = map.bind_mut().spawn_player( + player, + player.id == s.self_id.unwrap_or(0), + None, + ); + player.character = character; + } + } + }); + } + } + } + + pub fn spawn_player(&mut self, id: u16, transform: NetPlayerTransform) { + if let Some(map) = &mut self.current_map { + if let Some(player) = self.players.iter_mut().find(|p| p.id == id) { + let character = map.bind_mut().spawn_player( + player, + player.id == self.self_id.unwrap_or(0), + Some(transform), + ); + player.character = character; + } + } + } } -#[derive(Clone, Deserialize, Serialize, Debug)] +#[derive(Clone, Debug)] pub struct Player { pub id: u16, pub connection_addr: SocketAddr, pub name: String, pub ping: u128, pub ready: bool, + pub character: Option>, +} + +impl Player { + pub fn get_transform(&self) -> Option { + if let Some(character) = &self.character { + Some(character.dyn_bind().get_transform()) + } else { + None + } + } + + pub fn set_transform(&mut self, transform: NetPlayerTransform) { + if let Some(character) = &mut self.character { + character.dyn_bind_mut().apply_transform(transform); + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct NetPlayer { + pub id: u16, + pub name: String, + pub ping: u128, +} + +impl From for Player { + fn from(value: NetPlayer) -> Self { + Player { + id: value.id, + connection_addr: SocketAddr::from(([0, 0, 0, 0], 0)), + name: value.name, + ping: value.ping, + ready: false, + character: None, + } + } +} + +impl From for NetPlayer { + fn from(value: Player) -> Self { + NetPlayer { + id: value.id, + name: value.name, + ping: value.ping, + } + } } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e401f7b..20a86bd 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -3,6 +3,7 @@ use std::net::SocketAddr; use godot::prelude::*; pub mod game_manager; +pub mod map; pub mod map_resource; pub mod net; pub mod player; diff --git a/rust/src/map.rs b/rust/src/map.rs new file mode 100644 index 0000000..6b04b98 --- /dev/null +++ b/rust/src/map.rs @@ -0,0 +1,87 @@ +use godot::{classes::CharacterBody3D, prelude::*}; + +use crate::{ + game_manager::{Game, Player}, + player::{IPlayer, LocalPlayer, NetPlayerTransform, RemotePlayer}, +}; + +#[derive(GodotClass)] +#[class(base=Node3D, init)] +pub struct Map { + #[export] + pub spawner: Option>, + #[export] + pub local_player: Option>, + #[export] + pub remote_player: Option>, + + base: Base, +} + +#[godot_api] +impl INode3D for Map { + fn ready(&mut self) { + if let Some(game) = &mut Game::singleton() { + game.bind_mut().on_map_loaded(self.to_gd()); + } + } +} + +#[godot_api] +impl Map { + #[signal] + pub fn on_loaded(); + + pub fn spawn_player( + &mut self, + player: &Player, + is_local: bool, + transform: Option, + ) -> Option> { + if is_local { + if let Some(local_player) = &self.local_player { + let mut node = local_player.instantiate_as::(); + node.bind_mut().player_id = Some(player.id); + self.base_mut().add_child(&node); + if let Some(transform) = transform { + node.bind_mut() + .base_mut() + .set_global_position(transform.location.into()); + node.bind_mut() + .base_mut() + .set_global_rotation(transform.rotation.into()); + } else { + if let Some(spawner) = &self.spawner { + node.bind_mut() + .base_mut() + .set_global_position(spawner.get_global_position()); + } + } + Some(node.into_dyn::().upcast()) + } else { + None + } + } else if let Some(remote_player) = &self.remote_player { + let mut node = remote_player.instantiate_as::(); + node.bind_mut().player_id = Some(player.id); + self.base_mut().add_child(&node); + if let Some(transform) = transform { + node.bind_mut() + .base_mut() + .set_global_position(transform.location.into()); + node.bind_mut() + .base_mut() + .set_global_rotation(transform.rotation.into()); + } else { + if let Some(spawner) = &self.spawner { + node.bind_mut() + .base_mut() + .set_global_position(spawner.get_global_position()); + } + } + Some(node.into_dyn::().upcast()) + } else { + None + } + } +} diff --git a/rust/src/map_resource.rs b/rust/src/map_resource.rs index 46547f8..081d8a2 100644 --- a/rust/src/map_resource.rs +++ b/rust/src/map_resource.rs @@ -2,7 +2,7 @@ use godot::prelude::*; #[derive(GodotClass)] #[class(base=Resource, init, tool)] -pub struct Map { +pub struct MapResource { #[export] pub name: GString, #[export] @@ -12,7 +12,7 @@ pub struct Map { } #[godot_api] -impl IResource for Map { +impl IResource for MapResource { fn on_notification(&mut self, what: godot::classes::notify::ObjectNotification) {} fn to_string(&self) -> GString { diff --git a/rust/src/net/mod.rs b/rust/src/net/mod.rs index c305f1e..0ff753d 100644 --- a/rust/src/net/mod.rs +++ b/rust/src/net/mod.rs @@ -1,2 +1,3 @@ pub mod net_stats; pub mod network_manager; +pub mod util; diff --git a/rust/src/net/network_manager.rs b/rust/src/net/network_manager.rs index 4e318ea..cd0dd76 100644 --- a/rust/src/net/network_manager.rs +++ b/rust/src/net/network_manager.rs @@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize}; use teanet::{Peer, PeerConfig, PeerMessage, connections::Connection}; use crate::{ - game_manager::{Game, GameManager, Player}, + game_manager::{Game, GameManager, NetPlayer, Player}, net::net_stats::Stats, + player::NetPlayerTransform, ui::cli::{CliColor, CommandLinePanel}, }; @@ -152,7 +153,12 @@ impl INode for NetworkManager { CliColor::Info, ); if let Some(game) = &mut Game::singleton() { - game.bind_mut().set_players(players); + game.bind_mut().set_players( + players + .into_iter() + .map(|p| p.clone().into()) + .collect(), + ); game.bind_mut().set_self(self_id); } } @@ -206,6 +212,11 @@ impl INode for NetworkManager { game.bind_mut().start_game(); } } + Package::SpawnPlayer(id, transform) => { + if let Some(game) = &mut Game::singleton() { + game.bind_mut().spawn_player(id, transform); + } + } _ => {} }, } @@ -232,20 +243,25 @@ impl INode for NetworkManager { String::new(), None, ); - let mut sent_players = game.bind().players.clone(); - for player in &mut sent_players { - player.connection_addr = - SocketAddr::from(([0, 0, 0, 0], 0)) - } peer.send_reliable( &connection.address, - Package::Players(sent_players, self_id), + Package::Players( + game.bind() + .players + .clone() + .into_iter() + .map(|p| p.into()) + .collect(), + self_id, + ), ); - let mut sent_player = - game.bind().find_player(self_id).unwrap().clone(); - sent_player.connection_addr = - SocketAddr::from(([0, 0, 0, 0], 0)); - peer.broadcast_reliable(Package::NewPlayer(sent_player)); + peer.broadcast_reliable(Package::NewPlayer( + game.bind() + .find_player(self_id) + .unwrap() + .clone() + .into(), + )); peer.send_reliable( &connection.address, @@ -271,7 +287,11 @@ impl INode for NetworkManager { .map(|p| p.id); if let Some(player_id) = player_id { peer.broadcast_reliable(Package::PlayerLeft( - game.bind().find_player(player_id).unwrap().clone(), + game.bind() + .find_player(player_id) + .unwrap() + .clone() + .into(), )); game.bind_mut().remove_player(player_id); } @@ -314,6 +334,18 @@ impl INode for NetworkManager { } } } + Package::MapLoaded => { + if let Some(game) = Game::singleton() { + for player in &game.bind().players { + if let Some(transform) = player.get_transform() { + peer.send_reliable( + &conn.address, + Package::SpawnPlayer(player.id, transform), + ); + } + } + } + } _ => {} }, } @@ -424,15 +456,17 @@ impl NetworkManager { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Package { Hello, - Players(Vec, u16), + Players(Vec, u16), SetNick(u16, String), SetSelfNick(String), UpdatePings(HashMap), - NewPlayer(Player), - PlayerLeft(Player), + NewPlayer(NetPlayer), + PlayerLeft(NetPlayer), SetReady(u16, bool), SelectMap(u8), GameStarted, + SpawnPlayer(u16, NetPlayerTransform), + MapLoaded, } pub enum PeerKind { diff --git a/rust/src/net/util.rs b/rust/src/net/util.rs new file mode 100644 index 0000000..28f3536 --- /dev/null +++ b/rust/src/net/util.rs @@ -0,0 +1,25 @@ +use godot::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NetVector3 { + pub x: f32, + pub y: f32, + pub z: f32, +} + +impl From for NetVector3 { + fn from(value: Vector3) -> Self { + NetVector3 { + x: value.x, + y: value.y, + z: value.z, + } + } +} + +impl From for Vector3 { + fn from(value: NetVector3) -> Self { + Vector3::new(value.x, value.y, value.z) + } +} diff --git a/rust/src/player.rs b/rust/src/player.rs index 12c3945..8f8a48b 100644 --- a/rust/src/player.rs +++ b/rust/src/player.rs @@ -8,14 +8,24 @@ use godot::{ obj::WithBaseField, prelude::*, }; +use serde::{Deserialize, Serialize}; -use crate::ui::cli::CommandLinePanel; +use crate::{net::util::NetVector3, ui::cli::CommandLinePanel}; -pub trait Player: ICharacterBody3D + WithBaseField { +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NetPlayerTransform { + pub location: NetVector3, + pub rotation: NetVector3, +} + +pub trait IPlayer { + fn get_player_id(&self) -> Option; fn get_y_speed(&self) -> f32; fn set_y_speed(&mut self, speed: f32); fn get_movement_dir(&self) -> Vector3; fn get_move_speed(&self) -> f32; + fn apply_transform(&mut self, transform: NetPlayerTransform); + fn get_transform(&self) -> NetPlayerTransform; } pub trait PlayerMovement { @@ -23,7 +33,7 @@ pub trait PlayerMovement { fn jump(&mut self); } -impl PlayerMovement for T { +impl PlayerMovement for T { fn player_update(&mut self, delta: f64) { self.set_y_speed(self.get_y_speed() - 9.8 * delta as f32); if self.base().is_on_floor() && self.get_y_speed() < 0. { @@ -57,6 +67,8 @@ pub struct LocalPlayer { #[export] move_speed: f32, + pub player_id: Option, + movement_dir: Vector3, y_speed: f32, @@ -65,7 +77,12 @@ pub struct LocalPlayer { base: Base, } -impl Player for LocalPlayer { +#[godot_dyn] +impl IPlayer for LocalPlayer { + fn get_player_id(&self) -> Option { + self.player_id + } + fn get_y_speed(&self) -> f32 { self.y_speed } @@ -81,6 +98,20 @@ impl Player for LocalPlayer { fn get_move_speed(&self) -> f32 { self.move_speed } + + fn apply_transform(&mut self, transform: NetPlayerTransform) { + self.base_mut() + .set_global_position(transform.location.into()); + self.base_mut() + .set_global_rotation(transform.rotation.into()); + } + + fn get_transform(&self) -> NetPlayerTransform { + NetPlayerTransform { + location: self.base().get_global_position().into(), + rotation: self.base().get_global_rotation().into(), + } + } } #[godot_api] @@ -159,13 +190,16 @@ pub struct RemotePlayer { #[export] move_speed: f32, + pub player_id: Option, + movement_dir: Vector3, y_speed: f32, base: Base, } -impl Player for RemotePlayer { +#[godot_dyn] +impl IPlayer for RemotePlayer { fn get_y_speed(&self) -> f32 { self.y_speed } @@ -181,6 +215,24 @@ impl Player for RemotePlayer { fn get_move_speed(&self) -> f32 { self.move_speed } + + fn get_player_id(&self) -> Option { + self.player_id + } + + fn apply_transform(&mut self, transform: NetPlayerTransform) { + self.base_mut() + .set_global_position(transform.location.into()); + self.base_mut() + .set_global_rotation(transform.rotation.into()); + } + + fn get_transform(&self) -> NetPlayerTransform { + NetPlayerTransform { + location: self.base().get_global_position().into(), + rotation: self.base().get_global_rotation().into(), + } + } } #[godot_api] diff --git a/rust/src/ui/lobby.rs b/rust/src/ui/lobby.rs index 7e1f230..3e28380 100644 --- a/rust/src/ui/lobby.rs +++ b/rust/src/ui/lobby.rs @@ -5,7 +5,7 @@ use godot::{ use crate::{ game_manager::{Game, GameManager}, - map_resource::Map, + map_resource::MapResource, net::network_manager::{NetworkManager, Package, PeerKind}, ui::player_listing::PlayerListing, };