swamp_game/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5extern crate core;
6
7pub mod prelude;
8
9use int_math::{URect, UVec2, Vec2};
10
11use fixed32::Fp;
12use limnus_app::prelude::{App, AppReturnValue, ApplicationExit, Plugin};
13use limnus_audio_mixer::{AudioMixer, StereoSample};
14use limnus_basic_input::InputMessage;
15use limnus_basic_input::prelude::{ButtonState, KeyCode, MouseButton, MouseScrollDelta};
16use limnus_default_stages::{FixedUpdate, RenderUpdate, Update};
17use limnus_gamepad::{Axis, Button, GamePadId, Gamepad, GamepadMessage, Gamepads};
18use limnus_local_resource::prelude::LocalResource;
19use limnus_message::MessagesIterator;
20use limnus_resource::ResourceStorage;
21use limnus_resource::prelude::Resource;
22use limnus_screen::WindowMessage;
23use limnus_system_params::{LoReM, Msg, Re, ReAll, ReM};
24use monotonic_time_rs::{InstantMonotonicClock, Millis, MonotonicClock};
25use std::cmp::{max, min};
26use std::fmt::{Debug, Formatter};
27use std::marker::PhantomData;
28use swamp_game_assets::{Assets, GameAssets};
29use swamp_game_audio::{Audio, GameAudio};
30use swamp_render_wgpu::{Gfx, Render};
31use tracing::debug;
32
33pub trait Application: Sized + 'static {
34    fn new(assets: &mut impl Assets) -> Self;
35    fn tick(&mut self, assets: &mut impl Assets);
36    fn render(&mut self, gfx: &mut impl Gfx);
37    fn audio(&mut self, _audio: &mut impl Audio) {}
38
39    fn wants_to_quit(&self) -> bool {
40        false
41    }
42
43    fn wants_cursor_visible(&self) -> bool {
44        true
45    }
46
47    fn keyboard_input(&mut self, _state: ButtonState, _key_code: KeyCode) {}
48
49    fn cursor_entered(&mut self) {}
50
51    fn cursor_left(&mut self) {}
52
53    fn cursor_moved(&mut self, _position: UVec2) {}
54
55    fn mouse_input(&mut self, _state: ButtonState, _button: MouseButton) {}
56
57    fn mouse_wheel(&mut self, _delta_y: i16) {}
58
59    fn mouse_motion(&mut self, _delta: Vec2) {}
60
61    fn gamepad_activated(&mut self, _gamepad_id: GamePadId, _name: String) {}
62    fn gamepad_button_changed(&mut self, _gamepad: &Gamepad, _button: Button, _value: Fp) {}
63    fn gamepad_axis_changed(&mut self, _gamepad: &Gamepad, _axis: Axis, _value: Fp) {}
64    fn gamepad_disconnected(&mut self, _gamepad_id: GamePadId) {}
65
66    fn scale_factor_changed(&mut self, _scale_factor: f64) -> Option<UVec2> {
67        None
68    }
69}
70
71#[derive(Debug,  Resource)]
72pub struct GameSettings {
73    pub virtual_size: UVec2,
74}
75
76#[derive(LocalResource)]
77pub struct Game<G: Application> {
78    game: G,
79    clock: InstantMonotonicClock,
80}
81
82impl<G: Application> Debug for Game<G> {
83    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
84        write!(f, "WgpuGame")
85    }
86}
87
88impl<G: Application> Game<G> {
89    #[must_use]
90    pub fn new(all_resources: &mut ResourceStorage) -> Self {
91        let clock = InstantMonotonicClock::new();
92        let mut assets = GameAssets::new(all_resources, clock.now());
93        let game = G::new(&mut assets);
94
95        Self { game, clock }
96    }
97
98    pub fn inputs(&mut self, iter: MessagesIterator<InputMessage>) {
99        for message in iter {
100            match message {
101                InputMessage::KeyboardInput(button_state, key_code) => {
102                    self.game.keyboard_input(*button_state, *key_code);
103                }
104                InputMessage::MouseInput(button_state, button) => {
105                    self.game.mouse_input(*button_state, *button);
106                }
107                InputMessage::MouseWheel(scroll_delta, _touch_phase) => {
108                    if let MouseScrollDelta::LineDelta(delta) = scroll_delta {
109                        let game_scroll_y = (-delta.y as f32 * 120.0) as i16;
110                        self.game.mouse_wheel(game_scroll_y);
111                    }
112                }
113            }
114        }
115    }
116
117    pub fn cursor_moved(
118        &mut self,
119        physical_position: UVec2,
120        viewport: URect,
121        virtual_surface_size: UVec2,
122    ) {
123        let relative_x = max(
124            0,
125            min(
126                physical_position.x as i64 - viewport.position.x as i64,
127                (viewport.size.x - 1) as i64,
128            ),
129        );
130
131        let relative_y = max(
132            0,
133            min(
134                physical_position.y as i64 - viewport.position.y as i64,
135                (viewport.size.y - 1) as i64,
136            ),
137        );
138
139        let clamped_to_viewport: UVec2 = UVec2::new(relative_x as u16, relative_y as u16);
140
141        let virtual_position_x =
142            (clamped_to_viewport.x as u64 * virtual_surface_size.x as u64) / viewport.size.x as u64;
143
144        let virtual_position_y =
145            (clamped_to_viewport.y as u64 * virtual_surface_size.y as u64) / viewport.size.y as u64;
146
147        let virtual_position = UVec2::new(virtual_position_x as u16, virtual_position_y as u16);
148        self.game.cursor_moved(virtual_position);
149    }
150
151    pub fn mouse_move(&mut self, iter: MessagesIterator<WindowMessage>, wgpu_render: &Render) {
152        for message in iter {
153            match message {
154                WindowMessage::CursorMoved(position) => self.cursor_moved(
155                    *position,
156                    wgpu_render.viewport(),
157                    wgpu_render.virtual_surface_size(),
158                ),
159                WindowMessage::WindowCreated() => {}
160                WindowMessage::Resized(_) => {}
161            }
162        }
163    }
164
165    pub fn tick(&mut self, storage: &mut ResourceStorage, now: Millis) {
166        // This is a quick operation, we basically wrap storage
167        let mut assets = GameAssets::new(storage, now);
168
169        self.game.tick(&mut assets);
170    }
171
172    pub fn render(&mut self, wgpu_render: &mut Render, now: Millis) {
173        wgpu_render.set_now(now);
174        self.game.render(wgpu_render);
175    }
176}
177
178pub struct GamePlugin<G: Application> {
179    pub phantom_data: PhantomData<G>,
180}
181impl<G: Application> Default for GamePlugin<G> {
182    fn default() -> Self {
183        Self::new()
184    }
185}
186
187impl<G: Application> GamePlugin<G> {
188    #[must_use]
189    pub const fn new() -> Self {
190        Self {
191            phantom_data: PhantomData,
192        }
193    }
194}
195
196pub fn mouse_input_tick<G: Application>(
197    mut internal_game: LoReM<Game<G>>,
198    window_messages: Msg<WindowMessage>,
199    wgpu_render: Re<Render>,
200) {
201    internal_game.mouse_move(window_messages.iter_previous(), &wgpu_render);
202}
203
204pub fn keyboard_input_tick<G: Application>(
205    mut internal_game: LoReM<Game<G>>,
206    input_messages: Msg<InputMessage>,
207) {
208    internal_game.inputs(input_messages.iter_previous());
209}
210
211pub fn audio_tick<G: Application>(
212    mut internal_game: LoReM<Game<G>>,
213    stereo_samples: Re<limnus_assets::Assets<StereoSample>>,
214    mut audio_mixer: LoReM<AudioMixer>,
215) {
216    let mut game_audio = GameAudio::new(&mut audio_mixer, &stereo_samples);
217    internal_game.game.audio(&mut game_audio);
218}
219
220pub fn logic_tick<G: Application>(mut internal_game: LoReM<Game<G>>, mut all_resources: ReAll) {
221    let now = internal_game.clock.now();
222
223    internal_game.tick(&mut all_resources, now);
224    if internal_game.game.wants_to_quit() {
225        all_resources.insert(ApplicationExit {
226            value: AppReturnValue::Value(0),
227        });
228    }
229}
230
231pub fn render_tick<G: Application>(
232    mut internal_game: LoReM<Game<G>>,
233    mut wgpu_render: ReM<Render>,
234) {
235    let now = internal_game.clock.now();
236
237    internal_game.render(&mut wgpu_render, now);
238}
239
240pub fn gamepad_input_tick<G: Application>(
241    mut internal_game: LoReM<Game<G>>,
242    gamepads: Re<Gamepads>,
243    gamepad_messages: Msg<GamepadMessage>,
244) {
245    for gamepad_message in gamepad_messages.iter_current() {
246        match gamepad_message {
247            GamepadMessage::Connected(_gamepad_id, _gamepad_name) => {}
248            GamepadMessage::Disconnected(gamepad_id) => {
249                if let Some(gamepad) = gamepads.gamepad(*gamepad_id) {
250                    if gamepad.is_active {
251                        internal_game.game.gamepad_disconnected(*gamepad_id);
252                    }
253                }
254            }
255            GamepadMessage::Activated(gamepad_id) => {
256                if let Some(gamepad) = gamepads.gamepad(*gamepad_id) {
257                    internal_game
258                        .game
259                        .gamepad_activated(*gamepad_id, gamepad.name.as_str().to_string());
260                }
261            }
262            GamepadMessage::ButtonChanged(gamepad_id, button, value) => {
263                if let Some(gamepad) = gamepads.gamepad(*gamepad_id) {
264                    if gamepad.is_active {
265                        internal_game.game.gamepad_button_changed(
266                            gamepad,
267                            *button,
268                            Fp::from(*value),
269                        );
270                    }
271                }
272            }
273            GamepadMessage::AxisChanged(gamepad_id, axis, value) => {
274                if let Some(gamepad) = gamepads.gamepad(*gamepad_id) {
275                    if gamepad.is_active {
276                        internal_game
277                            .game
278                            .gamepad_axis_changed(gamepad, *axis, Fp::from(*value));
279                    }
280                }
281            }
282        }
283    }
284}
285
286impl<G: Application> Plugin for GamePlugin<G> {
287    fn post_initialization(&self, app: &mut App) {
288        debug!("calling WgpuGame::new()");
289
290        let all_resources = app.resources_mut();
291        let internal_game = Game::<G>::new(all_resources);
292        app.insert_local_resource(internal_game);
293
294        app.add_system(Update, gamepad_input_tick::<G>);
295        app.add_system(Update, keyboard_input_tick::<G>);
296        app.add_system(Update, mouse_input_tick::<G>);
297        app.add_system(Update, audio_tick::<G>);
298        app.add_system(FixedUpdate, logic_tick::<G>);
299        app.add_system(RenderUpdate, render_tick::<G>);
300    }
301}