messages_demo/
messages_demo.rs1use 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}