Compare commits

...

3 Commits

Author SHA1 Message Date
7837ab49b0 Start button triggers connection & state change
When the start button is pressed, switch to a "connecting" state. This
triggers the spawning of the "connecting" UI message and the connection
startup.

When the connection task finishes, `fn handle_tasks()` collects it and
pushes the CommandQueue into the main world just as before. In addition,
it will change to the "playing" state, which triggers the despawning of
the UI notice.

There is no meaningful connection-error handling path. A failed
connection will print a warning to stdout, and that is all.

There is still no transmitter at all, nor is the receiver hooked up to
one of the paddles.
2025-10-21 13:55:58 -05:00
62c84aceaf Remove stale TODO note 2025-10-21 12:52:53 -05:00
7ac3882e9e Marker components for the ball & paddles 2025-10-21 12:36:47 -05:00
2 changed files with 68 additions and 10 deletions

View File

@@ -8,7 +8,10 @@ use bevy::{
use thiserror::Error; use thiserror::Error;
use tungstenite::{WebSocket, http::Response, stream::MaybeTlsStream}; use tungstenite::{WebSocket, http::Response, stream::MaybeTlsStream};
use crate::ui::{despawn_main_menu, spawn_main_menu}; use crate::ui::{
despawn_connection_wait_screen, despawn_main_menu, spawn_connection_wait_screen,
spawn_main_menu,
};
mod ui; mod ui;
@@ -23,8 +26,24 @@ fn main() {
.add_systems(OnExit(GameState::MainMenu), despawn_main_menu) .add_systems(OnExit(GameState::MainMenu), despawn_main_menu)
.add_observer(ui::button_hover_start) .add_observer(ui::button_hover_start)
.add_observer(ui::button_hover_stop) .add_observer(ui::button_hover_stop)
// TODO: System to operate buttons & other UI widgets .add_systems(
// .add_systems(Update, ) OnEnter(GameState::Connecting),
(
spawn_connection_wait_screen,
// Closure to immediately dispatch a setup-connection request.
|mut messages: MessageWriter<WebSocketConnectionMessage>| {
messages.write(WebSocketConnectionMessage::SetupConnection);
},
),
)
.add_systems(
OnExit(GameState::Connecting),
despawn_connection_wait_screen,
)
.add_systems(
Update,
(setup_connection, handle_tasks).run_if(in_state(GameState::Connecting)),
)
.add_systems(OnEnter(GameState::Playing), setup_game) .add_systems(OnEnter(GameState::Playing), setup_game)
.add_systems( .add_systems(
Update, Update,
@@ -48,6 +67,7 @@ fn main() {
#[derive(Clone, Debug, Eq, Hash, PartialEq, States)] #[derive(Clone, Debug, Eq, Hash, PartialEq, States)]
enum GameState { enum GameState {
MainMenu, MainMenu,
Connecting,
Playing, Playing,
ConnectionDemo, // TODO: Remove this state. ConnectionDemo, // TODO: Remove this state.
} }
@@ -76,6 +96,7 @@ fn setup_game(
) { ) {
// ball // ball
commands.spawn(( commands.spawn((
Ball,
Mesh2d(meshes.add(Circle::new(10.0))), Mesh2d(meshes.add(Circle::new(10.0))),
MeshMaterial2d(materials.add(Color::srgb(1.0, 0.0, 0.0))), MeshMaterial2d(materials.add(Color::srgb(1.0, 0.0, 0.0))),
Transform::default(), Transform::default(),
@@ -85,23 +106,32 @@ fn setup_game(
let paddle_material = materials.add(Color::WHITE); let paddle_material = materials.add(Color::WHITE);
// Player 1 // Player 1
commands.spawn(( commands.spawn((
// TODO: Marker component for player Paddle,
// Maybe one for each player?
// Maybe it can hold the WebSocket, too.
// *Maybe* I can have one struct with an Option<Ws> to know which
// player is the local one (the one with a socket).
Mesh2d(paddle_mesh.clone()), Mesh2d(paddle_mesh.clone()),
MeshMaterial2d(paddle_material.clone()), MeshMaterial2d(paddle_material.clone()),
Transform::from_xyz(-window.width() / 2.0 + PADDLE_GAP, 0.0, 1.0), Transform::from_xyz(-window.width() / 2.0 + PADDLE_GAP, 0.0, 1.0),
)); ));
// Player 2 // Player 2
commands.spawn(( commands.spawn((
Paddle,
Mesh2d(paddle_mesh), Mesh2d(paddle_mesh),
MeshMaterial2d(paddle_material), MeshMaterial2d(paddle_material),
Transform::from_xyz(window.width() / 2.0 - PADDLE_GAP, 0.0, 1.0), Transform::from_xyz(window.width() / 2.0 - PADDLE_GAP, 0.0, 1.0),
)); ));
} }
#[derive(Component)]
struct Ball;
/// Marker component for player paddles
///
/// Maybe one for each player?
/// Maybe it can hold the WebSocket, too.
/// *Maybe* I can have one struct with an Option<Ws> to know which
/// player is the local one (the one with a socket).
#[derive(Component)]
struct Paddle;
/// ECS Component to hold the WebSocket. I guess there's going to be a magic /// ECS Component to hold the WebSocket. I guess there's going to be a magic
/// entity that controls the networking. /// entity that controls the networking.
#[derive(Component)] #[derive(Component)]
@@ -208,12 +238,17 @@ fn setup_connection(
/// ///
/// The task is self-removing, so we don't need to delete the [`WsSetupTask`] /// The task is self-removing, so we don't need to delete the [`WsSetupTask`]
/// component here. /// component here.
fn handle_tasks(mut commands: Commands, mut transform_tasks: Query<&mut WsSetupTask>) { fn handle_tasks(
mut commands: Commands,
mut transform_tasks: Query<&mut WsSetupTask>,
mut states: ResMut<NextState<GameState>>,
) {
for mut task in &mut transform_tasks { for mut task in &mut transform_tasks {
if let Some(result) = block_on(future::poll_once(&mut task.0)) { if let Some(result) = block_on(future::poll_once(&mut task.0)) {
match result { match result {
Ok(mut commands_queue) => { Ok(mut commands_queue) => {
commands.append(&mut commands_queue); commands.append(&mut commands_queue);
states.set(GameState::Playing);
} }
Err(e) => info!("Connection failed. Err: {e:?}"), Err(e) => info!("Connection failed. Err: {e:?}"),
} }

View File

@@ -33,7 +33,7 @@ pub fn spawn_main_menu(mut commands: Commands) {
let mut start_button = cmds.spawn(button_bundle("Start game")); let mut start_button = cmds.spawn(button_bundle("Start game"));
start_button.observe( start_button.observe(
|_trigger: On<Pointer<Click>>, mut game_state: ResMut<NextState<GameState>>| { |_trigger: On<Pointer<Click>>, mut game_state: ResMut<NextState<GameState>>| {
game_state.set(GameState::Playing); game_state.set(GameState::Connecting);
}, },
); );
let mut quit_button = cmds.spawn(button_bundle("Quit Game")); let mut quit_button = cmds.spawn(button_bundle("Quit Game"));
@@ -58,6 +58,29 @@ pub fn despawn_main_menu(
commands.entity(top_node.into_inner()).despawn(); commands.entity(top_node.into_inner()).despawn();
} }
pub fn spawn_connection_wait_screen(mut commands: Commands) {
info!("Spawning connecting notice.");
commands.spawn((
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
flex_direction: FlexDirection::Column,
..Default::default()
},
children![Text::new("Connecting...")],
));
}
pub fn despawn_connection_wait_screen(
mut commands: Commands,
text_nodes: Single<Entity, (With<Node>, With<Text>)>,
) {
info!("Despawning connecting notice.");
commands.entity(text_nodes.into_inner()).despawn();
}
/// The basic bundle for generic buttons. /// The basic bundle for generic buttons.
/// ///
/// It's mostly so I don't have to copy & paste this everywhere I want to use it. /// It's mostly so I don't have to copy & paste this everywhere I want to use it.