From 0712265d55e63edb4aadb3c64a9fa2798b1bf218 Mon Sep 17 00:00:00 2001 From: Sofia Date: Thu, 23 Jul 2026 18:47:21 +0300 Subject: [PATCH] Add simple main menu --- godot/scenes/main.tscn | 189 ++++++++++++++++++++++++++--- godot/scenes/misc/menu_player.tscn | 9 ++ rust/src/ui/main_menu.rs | 152 +++++++++++++++++++++++ rust/src/ui/menu_player.rs | 21 ++++ rust/src/ui/mod.rs | 2 + 5 files changed, 359 insertions(+), 14 deletions(-) create mode 100644 godot/scenes/misc/menu_player.tscn create mode 100644 rust/src/ui/main_menu.rs create mode 100644 rust/src/ui/menu_player.rs diff --git a/godot/scenes/main.tscn b/godot/scenes/main.tscn index 8aec83c..133bb04 100644 --- a/godot/scenes/main.tscn +++ b/godot/scenes/main.tscn @@ -1,24 +1,185 @@ [gd_scene format=3 uid="uid://lahirbqfppvw"] -[sub_resource type="BoxMesh" id="BoxMesh_m77kh"] +[ext_resource type="PackedScene" uid="uid://bbmc2gmy7g8ag" path="res://scenes/misc/menu_player.tscn" id="1_o5qli"] -[node name="node_3d" type="Node3D" unique_id=591926555] +[node name="menu" type="MainMenu" unique_id=2036763876 node_paths=PackedStringArray("camera", "main_position", "host_position", "main_panel", "host_panel", "join_panel", "host_port", "join_addr")] +camera = NodePath("camera_3d") +main_position = NodePath("main_pos") +host_position = NodePath("host_pos") +main_panel = NodePath("main_panel") +host_panel = NodePath("host_panel") +join_panel = NodePath("join_panel") +host_port = NodePath("host_panel/v_box_container/host_port") +join_addr = NodePath("join_panel/v_box_container/address_text") [node name="camera_3d" type="Camera3D" parent="." unique_id=1513321931] -transform = Transform3D(0.85754585, -0.09722054, 0.50513697, 0, 0.98197806, 0.18899518, -0.5144076, -0.16207202, 0.8420912, 1.0974057, 0.86883736, 1.7596759) - -[node name="mesh_instance_3d" type="MeshInstance3D" parent="." unique_id=1466304853] -mesh = SubResource("BoxMesh_m77kh") +transform = Transform3D(0.98629165, -0.031186381, 0.16203775, -1.4054481e-08, 0.981978, 0.1889952, -0.16501158, -0.18640438, 0.9685167, 1.0974052, 1.7166122, 1.4909106) +current = true [node name="directional_light_3d" type="DirectionalLight3D" parent="." unique_id=75522463] transform = Transform3D(0.61237246, -0.5, 0.61237246, 0.3535534, 0.8660254, 0.3535534, -0.7071068, 0, 0.7071068, 0, 1, 2) -[node name="netstats" type="NetStatsLabel" parent="." unique_id=1366169487] -anchors_preset = 12 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_top = -144.0 +[node name="menu_player" parent="." unique_id=928718358 instance=ExtResource("1_o5qli")] +transform = Transform3D(0.66809446, 0, -0.74407655, 0, 1, 0, 0.74407655, 0, 0.66809446, 2.270261, -2.3841858e-07, -1.3596804) + +[node name="menu_player2" parent="." unique_id=1735741962 instance=ExtResource("1_o5qli")] +transform = Transform3D(0.9823497, 0, 0.18705386, 0, 1, 0, -0.18705386, 0, 0.9823497, -3.3954296, 1.1920929e-06, 0.47203088) + +[node name="menu_player3" parent="." unique_id=1638403481 instance=ExtResource("1_o5qli")] +transform = Transform3D(-0.26696783, 0, 0.9637054, 0, 1, 0, -0.9637054, 0, -0.26696783, -3.5550833, -2.3841858e-07, 2.7302406) + +[node name="main_panel" type="Panel" parent="." unique_id=2078435804] +offset_left = 139.0 +offset_top = 167.0 +offset_right = 336.0 +offset_bottom = 474.0 + +[node name="v_box_container" type="VBoxContainer" parent="main_panel" unique_id=287461844] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -47.0 +offset_top = -95.0 +offset_right = 47.0 +offset_bottom = 95.0 grow_horizontal = 2 -grow_vertical = 0 -vertical_alignment = 2 +grow_vertical = 2 +alignment = 1 + +[node name="label" type="Label" parent="main_panel/v_box_container" unique_id=140542377] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "QuakeBall" +horizontal_alignment = 1 +metadata/_edit_lock_ = true + +[node name="host" type="Button" parent="main_panel/v_box_container" unique_id=1962091117] +layout_mode = 2 +text = "Host Game" +metadata/_edit_lock_ = true + +[node name="join" type="Button" parent="main_panel/v_box_container" unique_id=1364459397] +layout_mode = 2 +text = "Join Game" +metadata/_edit_lock_ = true + +[node name="settings" type="Button" parent="main_panel/v_box_container" unique_id=1153899756] +layout_mode = 2 +text = "Settings" +metadata/_edit_lock_ = true + +[node name="quit" type="Button" parent="main_panel/v_box_container" unique_id=1508139096] +layout_mode = 2 +text = "Quit" +metadata/_edit_lock_ = true + +[node name="host_panel" type="Panel" parent="." unique_id=1633876079] +visible = false +offset_left = 139.0 +offset_top = 167.0 +offset_right = 336.0 +offset_bottom = 474.0 + +[node name="v_box_container" type="VBoxContainer" parent="host_panel" unique_id=1651169053] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -47.0 +offset_top = -95.0 +offset_right = 47.0 +offset_bottom = 95.0 +grow_horizontal = 2 +grow_vertical = 2 +alignment = 1 + +[node name="title" type="Label" parent="host_panel/v_box_container" unique_id=1539421918] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Host Game" +horizontal_alignment = 1 +metadata/_edit_lock_ = true + +[node name="port_label" type="Label" parent="host_panel/v_box_container" unique_id=1618746525] +layout_mode = 2 +text = "Port" +horizontal_alignment = 1 + +[node name="host_port" type="SpinBox" parent="host_panel/v_box_container" unique_id=1116593197] +layout_mode = 2 +max_value = 16000.0 +value = 8080.0 +alignment = 1 + +[node name="host" type="Button" parent="host_panel/v_box_container" unique_id=312403378] +layout_mode = 2 +text = "Host" + +[node name="back" type="Button" parent="host_panel/v_box_container" unique_id=1658922684] +layout_mode = 2 +text = "Back" + +[node name="join_panel" type="Panel" parent="." unique_id=1630672771] +visible = false +offset_left = 139.0 +offset_top = 167.0 +offset_right = 336.0 +offset_bottom = 474.0 + +[node name="v_box_container" type="VBoxContainer" parent="join_panel" unique_id=2029384993] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -47.0 +offset_top = -95.0 +offset_right = 47.0 +offset_bottom = 95.0 +grow_horizontal = 2 +grow_vertical = 2 +alignment = 1 + +[node name="title" type="Label" parent="join_panel/v_box_container" unique_id=268455606] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Join Game" +horizontal_alignment = 1 +metadata/_edit_lock_ = true + +[node name="address_label" type="Label" parent="join_panel/v_box_container" unique_id=425811384] +layout_mode = 2 +text = "Address" +horizontal_alignment = 1 + +[node name="address_text" type="TextEdit" parent="join_panel/v_box_container" unique_id=584304528] +custom_minimum_size = Vector2(150, 30) +layout_mode = 2 + +[node name="join" type="Button" parent="join_panel/v_box_container" unique_id=1953386108] +layout_mode = 2 +text = "Join" + +[node name="back" type="Button" parent="join_panel/v_box_container" unique_id=759190425] +layout_mode = 2 +text = "Back" + +[node name="main_pos" type="Node3D" parent="." unique_id=98241074] +transform = Transform3D(0.98629165, -0.031186387, 0.16203767, -1.4054484e-08, 0.981978, 0.1889952, -0.16501155, -0.18640435, 0.9685167, 1.0974054, 1.7166122, 1.4909104) + +[node name="host_pos" type="Node3D" parent="." unique_id=663794107] +transform = Transform3D(0.3960592, -0.17354009, 0.90167665, -1.9851193e-08, 0.981978, 0.18899521, -0.9182249, -0.0748533, 0.38892144, -1.0602233, 1.7166122, 3.759781) + +[connection signal="pressed" from="main_panel/v_box_container/host" to="." method="go_to_host_game"] +[connection signal="pressed" from="main_panel/v_box_container/join" to="." method="go_to_join_game"] +[connection signal="pressed" from="main_panel/v_box_container/quit" to="." method="quit_game"] +[connection signal="pressed" from="host_panel/v_box_container/host" to="." method="host_game"] +[connection signal="pressed" from="host_panel/v_box_container/back" to="." method="go_to_main_menu"] +[connection signal="pressed" from="join_panel/v_box_container/join" to="." method="join_game"] +[connection signal="pressed" from="join_panel/v_box_container/back" to="." method="go_to_main_menu"] diff --git a/godot/scenes/misc/menu_player.tscn b/godot/scenes/misc/menu_player.tscn new file mode 100644 index 0000000..38e1168 --- /dev/null +++ b/godot/scenes/misc/menu_player.tscn @@ -0,0 +1,9 @@ +[gd_scene format=3 uid="uid://bbmc2gmy7g8ag"] + +[ext_resource type="PackedScene" uid="uid://bod6b3befmyt5" path="res://scenes/player/soldier_m.tscn" id="1_pgypt"] + +[node name="menu_player" type="MenuPlayer" unique_id=928718358 node_paths=PackedStringArray("mesh")] +mesh = NodePath("soldier_m") + +[node name="soldier_m" parent="." unique_id=281995630 instance=ExtResource("1_pgypt")] +transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0) diff --git a/rust/src/ui/main_menu.rs b/rust/src/ui/main_menu.rs new file mode 100644 index 0000000..e43245e --- /dev/null +++ b/rust/src/ui/main_menu.rs @@ -0,0 +1,152 @@ +use godot::{ + classes::{Camera3D, Panel, SpinBox, TextEdit}, + prelude::*, + register::property::SimpleVar, +}; + +use crate::net::network_manager::NetworkManager; + +#[derive(Debug, Clone, GodotConvert, Default, Export)] +#[godot(via = u8)] +pub enum Menu { + #[default] + MainMenu, + HostMenu, + JoinMenu, +} + +impl SimpleVar for Menu {} + +#[derive(GodotClass)] +#[class(base=Node3D, init, tool)] +pub struct MainMenu { + #[export] + camera: Option>, + + #[export] + main_position: Option>, + #[export] + host_position: Option>, + + #[export] + main_panel: Option>, + #[export] + host_panel: Option>, + #[export] + join_panel: Option>, + + #[export] + host_port: Option>, + #[export] + join_addr: Option>, + + #[export] + current_menu: Menu, + + prev_panel: Option>, + + base: Base, +} + +#[godot_api] +impl INode3D for MainMenu { + fn process(&mut self, _delta: f64) { + if let Some(camera) = &mut self.camera { + let target = match self.current_menu { + Menu::MainMenu => { + if let Some(pos) = &self.main_position { + Some(( + pos.get_global_position(), + pos.get_global_rotation(), + self.main_panel.clone(), + )) + } else { + None + } + } + Menu::HostMenu => { + if let Some(pos) = &self.host_position { + Some(( + pos.get_global_position(), + pos.get_global_rotation(), + self.host_panel.clone(), + )) + } else { + None + } + } + Menu::JoinMenu => { + if let Some(pos) = &self.host_position { + Some(( + pos.get_global_position(), + pos.get_global_rotation(), + self.join_panel.clone(), + )) + } else { + None + } + } + }; + + if let Some((target_pos, target_rot, target_panel)) = target { + let prev_pos = camera.get_global_position(); + let prev_rot = camera.get_global_rotation(); + let new_pos = prev_pos.lerp(target_pos, 0.2); + let new_rot = prev_rot.lerp(target_rot, 0.2); + camera.set_global_position(new_pos); + camera.set_global_rotation(new_rot); + + if let Some(mut panel) = target_panel { + if let Some(prev_panel) = &mut self.prev_panel { + if prev_panel.instance_id() != panel.instance_id() { + prev_panel.set_visible(false); + } + } + self.prev_panel = Some(panel.clone()); + panel.set_visible(true); + } + } + } + } +} + +#[godot_api] +impl MainMenu { + #[func] + pub fn host_game(&mut self) { + if let Some(host_port) = &self.host_port { + NetworkManager::singleton() + .bind_mut() + .host(host_port.get_value() as u16); + } + } + + #[func] + pub fn join_game(&mut self) { + if let Some(addr) = &self.join_addr { + if let Ok(addr) = addr.get_text().to_string().parse() { + NetworkManager::singleton().bind_mut().join(addr); + } + } + } + + #[func] + pub fn go_to_main_menu(&mut self) { + self.current_menu = Menu::MainMenu; + } + + #[func] + pub fn go_to_host_game(&mut self) { + self.current_menu = Menu::HostMenu; + } + + #[func] + pub fn go_to_join_game(&mut self) { + self.current_menu = Menu::JoinMenu; + } + + #[func] + pub fn quit_game(&mut self) { + self.base().get_tree().quit(); + } +} diff --git a/rust/src/ui/menu_player.rs b/rust/src/ui/menu_player.rs new file mode 100644 index 0000000..6f9d129 --- /dev/null +++ b/rust/src/ui/menu_player.rs @@ -0,0 +1,21 @@ +use godot::prelude::*; + +use crate::soldier_mesh::SoldierMesh; + +#[derive(GodotClass)] +#[class(base=Node3D, init)] +pub struct MenuPlayer { + #[export] + mesh: Option>, + + base: Base, +} + +#[godot_api] +impl INode3D for MenuPlayer { + fn ready(&mut self) { + if let Some(mesh) = &mut self.mesh { + mesh.bind_mut().spawn_raygun(); + } + } +} diff --git a/rust/src/ui/mod.rs b/rust/src/ui/mod.rs index 52c2c76..cad9fe1 100644 --- a/rust/src/ui/mod.rs +++ b/rust/src/ui/mod.rs @@ -2,6 +2,8 @@ pub mod cli; pub mod game_finished_screen; pub mod lobby; pub mod lobby_player_listing; +pub mod main_menu; +pub mod menu_player; pub mod net_stats; pub mod player_listing; pub mod simple_score_view;