Skip to main content

messages_demo/
messages_demo.rs

1use bevy::prelude::*;
2use bevy_networker_multiplayer::{netmsg, sync, NetResource, Replicated, ReplicatedPlugin};
3
4const ADDRESS: &str = "127.0.0.1:5002";
5
6#[netmsg]
7#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
8struct ChatSend {
9    text: String,
10}
11
12#[netmsg]
13#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
14struct ChatBroadcast {
15    text: String,
16}
17
18#[netmsg]
19#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
20struct ShootProjectile {
21    origin: Vec2,
22    direction: Vec2,
23}
24
25#[sync]
26#[derive(Component)]
27struct Position(Vec2);
28
29#[sync]
30#[derive(Component)]
31struct Velocity(Vec2);
32
33#[sync]
34#[derive(Component)]
35struct Projectile;
36
37#[derive(Clone, Copy, PartialEq, Eq)]
38enum Mode {
39    Server,
40    Client,
41}
42
43#[derive(Resource, Clone, Copy)]
44struct DemoMode(Mode);
45
46fn main() {
47    let mode = parse_mode();
48
49    let mut app = App::new();
50    if mode == Mode::Client {
51        app.add_plugins(
52            DefaultPlugins.set(WindowPlugin {
53                primary_window: Some(Window {
54                    title: "Bevy Netvent Multiplayer".into(),
55                    resolution: (960, 540).into(),
56                    ..default()
57                }),
58                ..default()
59            }),
60        );
61    } else {
62        app.add_plugins(MinimalPlugins);
63    }
64    app.add_plugins(ReplicatedPlugin);
65    app.insert_resource(DemoMode(mode));
66    app.add_systems(Startup, setup);
67
68    match mode {
69        Mode::Server => {
70            app.add_systems(Update, (server_handle_messages, server_move_projectiles));
71        }
72        Mode::Client => {
73            app.add_systems(
74                Startup,
75                setup_client_window,
76            )
77            .add_systems(
78                Update,
79                (
80                    client_send_messages,
81                    client_print_broadcasts,
82                    client_spawn_projectile_visuals,
83                    client_sync_projectile_visuals,
84                ),
85            );
86        }
87    }
88
89    app.run();
90}
91
92fn parse_mode() -> Mode {
93    match std::env::args().nth(1).as_deref() {
94        Some("server") => Mode::Server,
95        Some("client") => Mode::Client,
96        _ => {
97            eprintln!("usage: cargo run --example messages_demo -- [server|client]");
98            std::process::exit(1);
99        }
100    }
101}
102
103fn setup(mut net: ResMut<NetResource>, mode: Res<DemoMode>) {
104    match mode.0 {
105        Mode::Server => {
106            net.start_server(5002);
107            println!("server listening on {ADDRESS}");
108        }
109        Mode::Client => {
110            net.join_server(ADDRESS.to_string());
111            println!("client connected to {ADDRESS}");
112        }
113    }
114}
115
116fn setup_client_window(mut commands: Commands) {
117    commands.spawn(Camera2d);
118}
119
120fn server_handle_messages(
121    mut commands: Commands,
122    mut net: ResMut<NetResource>,
123) {
124    for chat in net.drain_messages::<ChatSend>() {
125        println!("chat: {}", chat.text);
126        net.queue_message(ChatBroadcast {
127            text: format!("server heard: {}", chat.text),
128        });
129    }
130
131    for shot in net.drain_messages::<ShootProjectile>() {
132        println!("shoot request from client: {:?} -> {:?}", shot.origin, shot.direction);
133        commands.spawn((
134            Replicated,
135            Projectile,
136            Position(shot.origin),
137            Velocity(shot.direction.normalize_or_zero() * 150.0),
138        ));
139    }
140}
141
142fn server_move_projectiles(
143    time: Res<Time>,
144    mut query: Query<(&mut Position, &Velocity), With<Projectile>>,
145) {
146    for (mut position, velocity) in &mut query {
147        position.0 += velocity.0 * time.delta_secs();
148    }
149}
150
151fn client_send_messages(
152    time: Res<Time>,
153    mut tick: Local<f32>,
154    mut net: ResMut<NetResource>,
155) {
156    *tick += time.delta_secs();
157    if *tick < 1.0 {
158        return;
159    }
160    *tick = 0.0;
161
162    net.queue_message(ChatSend {
163        text: "hello from client".to_string(),
164    });
165    net.queue_message(ShootProjectile {
166        origin: Vec2::new(0.0, 0.0),
167        direction: Vec2::new(1.0, 0.2),
168    });
169}
170
171fn client_print_broadcasts(mut net: ResMut<NetResource>) {
172    for chat in net.drain_messages::<ChatBroadcast>() {
173        println!("broadcast: {}", chat.text);
174    }
175}
176
177fn client_spawn_projectile_visuals(
178    mut commands: Commands,
179    query: Query<(Entity, &Position), Added<Projectile>>,
180) {
181    for (entity, position) in &query {
182        commands.entity(entity).insert((
183            Sprite::from_color(Color::srgb(1.0, 0.25, 0.25), Vec2::splat(20.0)),
184            Transform::from_xyz(position.0.x, position.0.y, 0.0),
185        ));
186    }
187}
188
189fn client_sync_projectile_visuals(mut query: Query<(&Position, &mut Transform), With<Projectile>>) {
190    for (position, mut transform) in &mut query {
191        transform.translation.x = position.0.x;
192        transform.translation.y = position.0.y;
193    }
194}