Add GameManager and Game

This commit is contained in:
Sofia 2026-07-16 07:17:07 +03:00
parent 82d3a22193
commit 154c5fa743
11 changed files with 272 additions and 14 deletions

View File

@ -19,6 +19,7 @@ config/icon="res://icon.svg"
NetworkManagerGlob="*uid://cxuy526twid8s"
CommandLinePanelGlobal="*uid://bsb6asp5o35w3"
GameManagerGlobal="*uid://bii6018k3ip3t"
[display]

View File

@ -0,0 +1,3 @@
[gd_scene format=3 uid="uid://bii6018k3ip3t"]
[node name="game_manager" type="GameManager" unique_id=1442044769]

52
godot/scenes/lobby.tscn Normal file
View File

@ -0,0 +1,52 @@
[gd_scene format=3 uid="uid://bwd6bj21x310s"]
[sub_resource type="LabelSettings" id="LabelSettings_1cgct"]
font_size = 32
[node name="lobby" type="LobbyPanel" unique_id=915192272 node_paths=PackedStringArray("players_list")]
players_list = NodePath("v_box_container/players_list")
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="label" type="Label" parent="." unique_id=1445541926]
layout_mode = 1
anchors_preset = 10
anchor_right = 1.0
offset_top = 69.0
offset_bottom = 92.0
grow_horizontal = 2
text = "Lobby"
label_settings = SubResource("LabelSettings_1cgct")
horizontal_alignment = 1
[node name="v_box_container" type="VBoxContainer" parent="." unique_id=336946640]
layout_mode = 0
offset_left = 178.0
offset_top = 189.0
offset_right = 239.0
offset_bottom = 229.0
[node name="players_label" type="Label" parent="v_box_container" unique_id=1537542117]
layout_mode = 2
text = "Players:"
[node name="players_list" type="VBoxContainer" parent="v_box_container" unique_id=1834737379]
layout_mode = 2
[node name="h_box_container" type="HBoxContainer" parent="." unique_id=1789302658]
layout_mode = 0
offset_left = 221.0
offset_top = 512.0
offset_right = 261.0
offset_bottom = 552.0
[node name="label" type="Label" parent="h_box_container" unique_id=626633696]
layout_mode = 2
text = "Name:"
[node name="text_edit" type="TextEdit" parent="h_box_container" unique_id=683714912]
custom_minimum_size = Vector2(150, 0)
layout_mode = 2

View File

@ -1,3 +1,6 @@
[gd_scene format=3 uid="uid://cxuy526twid8s"]
[ext_resource type="PackedScene" uid="uid://bwd6bj21x310s" path="res://scenes/lobby.tscn" id="1_cemok"]
[node name="network_manager" type="NetworkManager" unique_id=1177599919]
lobby_scene = ExtResource("1_cemok")

113
rust/src/game_manager.rs Normal file
View File

@ -0,0 +1,113 @@
use std::net::SocketAddr;
use godot::{prelude::*, tools::get_autoload_by_name};
use serde::{Deserialize, Serialize};
pub const GAME_MANAGER_GLOBAL: &str = "GameManagerGlobal";
#[derive(GodotClass)]
#[class(base=Node, init)]
pub struct GameManager {
pub game: Option<Gd<Game>>,
base: Base<Node>,
}
#[godot_api]
impl GameManager {
#[signal]
pub fn on_players_updated();
#[signal]
pub fn on_new_player(id: u16);
#[signal]
pub fn on_player_disconnected(id: u16);
pub fn singleton() -> Gd<GameManager> {
get_autoload_by_name::<GameManager>(GAME_MANAGER_GLOBAL)
}
pub fn init_game(&mut self, is_host: bool) {
if let Some(mut game) = self.game.take() {
game.queue_free();
}
let mut game = Game::new_alloc();
game.bind_mut().is_host = is_host;
self.base_mut().add_child(&game);
game.signals()
.on_new_player()
.connect_other(self, |s, id| s.signals().on_new_player().emit(id));
game.signals()
.on_player_disconnected()
.connect_other(self, |s, id| s.signals().on_player_disconnected().emit(id));
game.signals()
.on_players_updated()
.connect_other(self, |s| s.signals().on_players_updated().emit());
self.game = Some(game);
}
}
#[derive(GodotClass)]
#[class(base=Node, init)]
pub struct Game {
is_host: bool,
player_counter: u16,
self_id: Option<u16>,
pub players: Vec<Player>,
base: Base<Node>,
}
#[godot_api]
impl Game {
#[signal]
pub fn on_players_updated();
#[signal]
pub fn on_new_player(id: u16);
#[signal]
pub fn on_player_disconnected(id: u16);
pub fn set_self(&mut self, id: u16) {
self.self_id = Some(id);
}
pub fn new_player(&mut self, addr: SocketAddr, name: String) -> u16 {
let id = self.player_counter;
self.player_counter += 1;
self.players.push(Player {
id,
connection_addr: addr,
name,
});
self.signals().on_new_player().emit(id);
id
}
pub fn set_players(&mut self, players: Vec<Player>) {
self.players = players;
self.signals().on_players_updated().emit();
}
pub fn find_player_by_addr(&self, addr: &SocketAddr) -> Option<&Player> {
self.players.iter().find(|p| p.connection_addr == *addr)
}
pub fn remove_player(&mut self, id: u16) {
self.signals().on_new_player().emit(id);
self.players.retain(|p| p.id != id);
}
pub fn update_player_nick(&mut self, player_id: u16, nick: String) {
if let Some(player) = self.players.iter_mut().find(|p| p.id == player_id) {
player.name = nick;
}
self.signals().on_players_updated().emit();
}
}
#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct Player {
pub id: u16,
pub connection_addr: SocketAddr,
pub name: String,
}

View File

@ -2,6 +2,7 @@ use std::net::SocketAddr;
use godot::prelude::*;
pub mod game_manager;
pub mod net;
pub mod ui;

View File

@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
use teanet::{Peer, PeerConfig};
use crate::{
game_manager::{Game, GameManager, Player},
net::net_stats::Stats,
ui::cli::{CLI_GLOBAL_NAME, CliColor, CommandLinePanel},
};
@ -15,7 +16,10 @@ use crate::{
#[derive(GodotClass)]
#[class(base=Node)]
pub struct NetworkManager {
peer: Option<PeerKind>,
#[export]
lobby_scene: Option<Gd<PackedScene>>,
pub peer: Option<PeerKind>,
last_hello: Instant,
last_netstat_update: Instant,
@ -29,6 +33,8 @@ pub const NETWORK_SINGLETON_NAME: &str = "NetworkManagerGlob";
impl INode for NetworkManager {
fn init(base: Base<Node>) -> Self {
NetworkManager {
lobby_scene: None,
peer: None,
last_hello: Instant::now(),
@ -55,9 +61,9 @@ impl INode for NetworkManager {
.connect_other(self, |s| s.disconnect());
}
fn process(&mut self, delta: f64) {
fn process(&mut self, _delta: f64) {
if let Some(peer) = &mut self.peer {
let mut cli = get_autoload_by_name::<CommandLinePanel>(CLI_GLOBAL_NAME);
let mut cli = CommandLinePanel::singleton();
let now = Instant::now();
if now - self.last_netstat_update > Duration::from_millis(1000) {
@ -88,6 +94,7 @@ impl INode for NetworkManager {
format!("Connected to: {}", connection.address),
CliColor::Info,
);
self.swap_to_lobby(false);
}
teanet::PeerMessage::Disconnected(connection, connection_error) => {
cli.bind_mut().publish_message(
@ -104,6 +111,7 @@ impl INode for NetworkManager {
}
teanet::PeerMessage::Closed => {
self.peer = None;
GameManager::singleton().bind_mut().game = None;
}
teanet::PeerMessage::Message(msg) => match msg {
Package::Hello => {
@ -112,6 +120,21 @@ impl INode for NetworkManager {
CliColor::Info,
);
}
Package::Players(players, self_id) => {
if let Some(game) =
&mut GameManager::singleton().bind_mut().game
{
game.bind_mut().set_players(players);
game.bind_mut().set_self(self_id);
}
}
Package::SetNick(id, nick) => {
if let Some(game) =
&mut GameManager::singleton().bind_mut().game
{
game.bind_mut().update_player_nick(id, nick);
}
}
},
}
}
@ -130,6 +153,17 @@ impl INode for NetworkManager {
format!("Client connected from: {}", connection.address),
CliColor::Info,
);
if let Some(game) =
&mut GameManager::singleton().bind_mut().game
{
let self_id = game
.bind_mut()
.new_player(connection.address, String::new());
peer.send_reliable(
&connection.address,
Package::Players(game.bind().players.clone(), self_id),
);
}
}
teanet::PeerMessage::Disconnected(connection, connection_error) => {
cli.bind_mut().publish_message(
@ -142,19 +176,26 @@ impl INode for NetworkManager {
CliColor::Error,
);
}
if let Some(game) =
&mut GameManager::singleton().bind_mut().game
{
let player_id = game
.bind()
.find_player_by_addr(&connection.address)
.map(|p| p.id);
if let Some(player_id) = player_id {
game.bind_mut().remove_player(player_id);
}
}
}
teanet::PeerMessage::Closed => {
cli.bind_mut()
.publish_message(format!("Server closed"), CliColor::Info);
self.peer = None;
GameManager::singleton().bind_mut().game = None;
}
teanet::PeerMessage::Message(msg) => match msg {
Package::Hello => {
cli.bind_mut().publish_message(
format!("Got \"Hello\"!"),
CliColor::Info,
);
}
_ => {}
},
}
}
@ -170,9 +211,13 @@ impl INode for NetworkManager {
}
impl NetworkManager {
pub fn singleton() -> Gd<NetworkManager> {
get_autoload_by_name::<NetworkManager>(NETWORK_SINGLETON_NAME)
}
pub fn host(&mut self, port: u16) {
if self.peer.is_none() {
let mut cli = get_autoload_by_name::<CommandLinePanel>(CLI_GLOBAL_NAME);
let mut cli = CommandLinePanel::singleton();
cli.bind_mut()
.publish_message(format!("Hosting server at {}", port), CliColor::Info);
let peer = Peer::listen(Some(port), PeerConfig::default());
@ -182,6 +227,15 @@ impl NetworkManager {
cli.bind_mut()
.publish_message(format!("Server hosted"), CliColor::Info);
self.swap_to_lobby(true);
if let Some(game) = &mut GameManager::singleton().bind_mut().game {
let id = game
.bind_mut()
.new_player(SocketAddr::from(([0, 0, 0, 0], 0)), "Host".to_owned());
game.bind_mut().set_self(id);
}
}
Err(err) => {
cli.bind_mut()
@ -193,7 +247,7 @@ impl NetworkManager {
pub fn join(&mut self, addr: SocketAddr) {
if self.peer.is_none() {
let mut cli = get_autoload_by_name::<CommandLinePanel>(CLI_GLOBAL_NAME);
let mut cli = CommandLinePanel::singleton();
cli.bind_mut()
.publish_message(format!("Joining server at {}", addr), CliColor::Info);
let peer = Peer::listen(None, PeerConfig::default());
@ -218,11 +272,20 @@ impl NetworkManager {
}
}
}
pub fn swap_to_lobby(&self, is_host: bool) {
if let Some(scene) = &self.lobby_scene {
self.base().get_tree().change_scene_to_packed(scene);
GameManager::singleton().bind_mut().init_game(is_host);
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Package {
Hello,
Players(Vec<Player>, u16),
SetNick(u16, String),
}
pub enum PeerKind {

View File

@ -7,8 +7,6 @@ use godot::classes::{Engine, IPanel, Panel, RichTextLabel, ScrollContainer, Text
use godot::prelude::*;
use godot::tools::get_autoload_by_name;
use crate::net::network_manager::{NETWORK_SINGLETON_NAME, NetworkManager};
pub enum CliColor {
Command,
Info,
@ -95,6 +93,10 @@ impl CommandLinePanel {
#[signal]
pub fn on_disconnect();
pub fn singleton() -> Gd<CommandLinePanel> {
get_autoload_by_name::<CommandLinePanel>(CLI_GLOBAL_NAME)
}
pub fn input_command(&mut self, command: String) {
self.publish_message(format!("> {}", command), CliColor::Command);
if let Some(input) = &mut self.input_field {

19
rust/src/ui/lobby.rs Normal file
View File

@ -0,0 +1,19 @@
use godot::{
classes::{IPanel, Panel, VBoxContainer},
prelude::*,
};
#[derive(GodotClass)]
#[class(base=Panel, init)]
pub struct LobbyPanel {
#[export]
players_list: Option<Gd<VBoxContainer>>,
base: Base<Panel>,
}
#[godot_api]
impl IPanel for LobbyPanel {
fn ready(&mut self) {}
fn process(&mut self, delta: f64) {}
}

View File

@ -1,2 +1,3 @@
pub mod cli;
pub mod lobby;
pub mod net_stats;

@ -1 +1 @@
Subproject commit 3551d0e50b598e96fb3ffbb5cce5ad5792f891ae
Subproject commit a348dde42e9973488f89d1d89e4f0f24138654a6