Skip to main content

multiplayer/
multiplayer.rs

1use sge::prelude::*;
2use ui::*;
3
4#[derive(Clone)]
5#[persistent(diff, lerp)]
6struct State {
7    position: Vec2,
8}
9
10const NAMES: &[&str] = &["Alice", "Bob", "Charlie", "Derek", "Emily", "Fred", "Gurt"];
11const UPDATE_RATE: f32 = 0.1;
12const INTERPOLATION_DELAY: f32 = UPDATE_RATE * 2.0;
13
14#[main("Multiplayer")]
15fn main() {
16    set_min_log_level(LevelFilter::Debug);
17
18    let mut state = MultiplayerState::new(
19        State {
20            position: Vec2::ZERO,
21        },
22        rand_choice(NAMES).to_string(),
23        "example_room".to_string(),
24    );
25
26    loop {
27        clear_screen(Color::NEUTRAL_900);
28        let now = time();
29
30        if once_per_n_seconds(UPDATE_RATE) {
31            state.update().unwrap();
32        }
33
34        let movement =
35            pressed_movement_vector(KeyCode::KeyW, KeyCode::KeyS, KeyCode::KeyA, KeyCode::KeyD);
36        state.your_state_mut().position += movement * delta_time() * 300.0;
37
38        draw_user(
39            state.your_username().to_string(),
40            state.your_state().position,
41        );
42
43        let render_target_time = now - INTERPOLATION_DELAY;
44        for (_, user) in state.other_users().iter() {
45            if let Some(interpolated_state) = user.current_lerped(render_target_time) {
46                draw_user(user.username.clone(), interpolated_state.position);
47            }
48        }
49
50        chat(&mut state);
51
52        draw_logs();
53
54        if should_quit() {
55            break;
56        }
57        next_frame().await;
58    }
59
60    state.disconnect();
61    // so it has time to send the disconnect message before being killed
62    std::thread::sleep(Duration::from_millis(50));
63}
64
65struct ChatState {
66    messages: Vec<RichText>,
67}
68
69fn chat(state: &mut MultiplayerState<State>) {
70    if !storage_exists::<ChatState>() {
71        storage_store_state(ChatState { messages: vec![] });
72    }
73
74    let messages = &mut storage_get_state_mut::<ChatState>().messages;
75
76    let other_state = unsafe { &*{ state as *const MultiplayerState<State> } };
77    for notification in state.drain_notifications() {
78        let Some(user) = other_state.get_user(notification.user_id) else {
79            break;
80        };
81        let username = &user.username;
82        let text = String::from_utf8(notification.data).unwrap();
83
84        messages.push(rich_text(format!("<b>{username}</b>: {text}")).unwrap());
85    }
86
87    let input_id = id!();
88    let button_id = id!();
89
90    if button_clicked_last_frame(button_id) {
91        let input_state = text_input_state(input_id);
92        messages.push(rich_text(format!("<b>You</b>: {}", input_state.value)).unwrap());
93        state.send_notification(input_state.value.as_bytes().to_vec());
94        input_state.value = "".to_string();
95    }
96
97    let ui = Align::bottom_left(
98        BoxFill::new(
99            flat::BG0,
100            Padding::all(
101                20.0,
102                Col::new([
103                    FlexRow::with_gap(
104                        10.0,
105                        [
106                            FlexBox::Flex(flat::TextInput::new(flat::BG1, input_id)),
107                            FlexBox::Fixed(flat::Button::text(
108                                flat::BG1,
109                                flat::BG2,
110                                button_id,
111                                "Send",
112                            )),
113                        ],
114                    ),
115                    Scroll::new(
116                        id!(),
117                        Col::with_gap(
118                            10.0,
119                            messages
120                                .iter()
121                                .map(|r| RichTextNode::new(r.clone()))
122                                .collect::<Vec<_>>(),
123                        ),
124                    )
125                    .padding_vertical(20.0),
126                ]),
127            ),
128        )
129        .sized_wh(600.0, 400.0),
130    )
131    .sized(window_size());
132
133    draw_ui(ui, Vec2::ZERO);
134}
135
136fn draw_user(username: String, pos: Vec2) {
137    draw_circle_world(pos, 50.0, Color::BLUE_500);
138    draw_text_world(&username, pos);
139}