micro_games_kit/
game.rs

1use crate::{
2    assets::{
3        font::FontAssetSubsystem, shader::ShaderAssetSubsystem, sound::SoundAssetSubsystem,
4        texture::TextureAssetSubsystem,
5    },
6    audio::Audio,
7    context::GameContext,
8};
9#[cfg(not(target_arch = "wasm32"))]
10use glutin::{event::Event, window::Window};
11#[cfg(target_arch = "wasm32")]
12use instant::Instant;
13use keket::database::AssetDatabase;
14use spitfire_draw::{
15    context::DrawContext,
16    utils::{ShaderRef, Vertex},
17};
18use spitfire_glow::{app::AppState, graphics::Graphics, renderer::GlowBlending};
19use spitfire_gui::context::GuiContext;
20use spitfire_input::InputContext;
21#[cfg(not(target_arch = "wasm32"))]
22use std::time::Instant;
23#[cfg(target_arch = "wasm32")]
24use winit::{event::Event, window::Window};
25
26pub trait GameObject {
27    #[allow(unused_variables)]
28    fn activate(&mut self, context: &mut GameContext) {}
29
30    #[allow(unused_variables)]
31    fn deactivate(&mut self, context: &mut GameContext) {}
32
33    #[allow(unused_variables)]
34    fn process(&mut self, context: &mut GameContext, delta_time: f32) {}
35
36    #[allow(unused_variables)]
37    fn draw(&mut self, context: &mut GameContext) {}
38}
39
40#[derive(Default)]
41pub enum GameStateChange {
42    #[default]
43    Continue,
44    Swap(Box<dyn GameState>),
45    Push(Box<dyn GameState>),
46    Pop,
47}
48
49#[allow(unused_variables)]
50pub trait GameState {
51    fn enter(&mut self, context: GameContext) {}
52
53    fn exit(&mut self, context: GameContext) {}
54
55    fn update(&mut self, context: GameContext, delta_time: f32) {}
56
57    fn fixed_update(&mut self, context: GameContext, delta_time: f32) {}
58
59    fn draw(&mut self, context: GameContext) {}
60
61    fn draw_gui(&mut self, context: GameContext) {}
62}
63
64pub trait GameSubsystem {
65    fn run(&mut self, context: GameContext, delta_time: f32);
66}
67
68pub struct GameInstance {
69    pub fixed_delta_time: f32,
70    pub color_shader: &'static str,
71    pub image_shader: &'static str,
72    pub text_shader: &'static str,
73    pub input_maintain_on_fixed_step: bool,
74    draw: DrawContext,
75    gui: GuiContext,
76    input: InputContext,
77    assets: AssetDatabase,
78    audio: Audio,
79    timer: Instant,
80    fixed_timer: Instant,
81    states: Vec<Box<dyn GameState>>,
82    state_change: GameStateChange,
83    subsystems: Vec<Box<dyn GameSubsystem>>,
84}
85
86impl Default for GameInstance {
87    fn default() -> Self {
88        Self {
89            fixed_delta_time: 1.0 / 60.0,
90            color_shader: "color",
91            image_shader: "image",
92            text_shader: "text",
93            input_maintain_on_fixed_step: true,
94            draw: Default::default(),
95            gui: Default::default(),
96            input: Default::default(),
97            assets: Default::default(),
98            audio: Default::default(),
99            timer: Instant::now(),
100            fixed_timer: Instant::now(),
101            states: Default::default(),
102            state_change: Default::default(),
103            subsystems: vec![
104                Box::new(ShaderAssetSubsystem),
105                Box::new(TextureAssetSubsystem),
106                Box::new(FontAssetSubsystem),
107                Box::new(SoundAssetSubsystem),
108            ],
109        }
110    }
111}
112
113impl GameInstance {
114    pub fn new(state: impl GameState + 'static) -> Self {
115        Self {
116            state_change: GameStateChange::Push(Box::new(state)),
117            ..Default::default()
118        }
119    }
120
121    pub fn with_fixed_time_step(mut self, value: f32) -> Self {
122        self.fixed_delta_time = value;
123        self
124    }
125
126    pub fn with_fps(mut self, frames_per_second: usize) -> Self {
127        self.set_fps(frames_per_second);
128        self
129    }
130
131    pub fn with_color_shader(mut self, name: &'static str) -> Self {
132        self.color_shader = name;
133        self
134    }
135
136    pub fn with_image_shader(mut self, name: &'static str) -> Self {
137        self.image_shader = name;
138        self
139    }
140
141    pub fn with_text_shader(mut self, name: &'static str) -> Self {
142        self.text_shader = name;
143        self
144    }
145
146    pub fn with_input_maintain_on_fixed_step(mut self, value: bool) -> Self {
147        self.input_maintain_on_fixed_step = value;
148        self
149    }
150
151    pub fn with_subsystem(mut self, subsystem: impl GameSubsystem + 'static) -> Self {
152        self.subsystems.push(Box::new(subsystem));
153        self
154    }
155
156    pub fn setup_assets(mut self, f: impl FnOnce(&mut AssetDatabase)) -> Self {
157        f(&mut self.assets);
158        self
159    }
160
161    pub fn fps(&self) -> usize {
162        (1.0 / self.fixed_delta_time).ceil() as usize
163    }
164
165    pub fn set_fps(&mut self, frames_per_second: usize) {
166        self.fixed_delta_time = 1.0 / frames_per_second as f32;
167    }
168
169    pub fn process_frame(&mut self, graphics: &mut Graphics<Vertex>) {
170        let delta_time = self.timer.elapsed().as_secs_f32();
171
172        for subsystem in &mut self.subsystems {
173            subsystem.run(
174                GameContext {
175                    graphics,
176                    draw: &mut self.draw,
177                    gui: &mut self.gui,
178                    input: &mut self.input,
179                    state_change: &mut self.state_change,
180                    assets: &mut self.assets,
181                    audio: &mut self.audio,
182                },
183                delta_time,
184            );
185        }
186        self.assets.maintain().unwrap();
187
188        if let Some(state) = self.states.last_mut() {
189            self.timer = Instant::now();
190            state.update(
191                GameContext {
192                    graphics,
193                    draw: &mut self.draw,
194                    gui: &mut self.gui,
195                    input: &mut self.input,
196                    state_change: &mut self.state_change,
197                    assets: &mut self.assets,
198                    audio: &mut self.audio,
199                },
200                delta_time,
201            );
202        }
203
204        let fixed_delta_time = self.fixed_timer.elapsed().as_secs_f32();
205        let fixed_step = if fixed_delta_time > self.fixed_delta_time {
206            self.fixed_timer = Instant::now();
207            if let Some(state) = self.states.last_mut() {
208                state.fixed_update(
209                    GameContext {
210                        graphics,
211                        draw: &mut self.draw,
212                        gui: &mut self.gui,
213                        input: &mut self.input,
214                        state_change: &mut self.state_change,
215                        assets: &mut self.assets,
216                        audio: &mut self.audio,
217                    },
218                    fixed_delta_time,
219                );
220            }
221            true
222        } else {
223            false
224        };
225
226        self.draw.begin_frame(graphics);
227        self.draw.push_shader(&ShaderRef::name(self.image_shader));
228        self.draw.push_blending(GlowBlending::Alpha);
229        if let Some(state) = self.states.last_mut() {
230            state.draw(GameContext {
231                graphics,
232                draw: &mut self.draw,
233                gui: &mut self.gui,
234                input: &mut self.input,
235                state_change: &mut self.state_change,
236                assets: &mut self.assets,
237                audio: &mut self.audio,
238            });
239        }
240        self.gui.begin_frame();
241        if let Some(state) = self.states.last_mut() {
242            state.draw_gui(GameContext {
243                graphics,
244                draw: &mut self.draw,
245                gui: &mut self.gui,
246                input: &mut self.input,
247                state_change: &mut self.state_change,
248                assets: &mut self.assets,
249                audio: &mut self.audio,
250            });
251        }
252        self.gui.end_frame(
253            &mut self.draw,
254            graphics,
255            &ShaderRef::name(self.color_shader),
256            &ShaderRef::name(self.image_shader),
257            &ShaderRef::name(self.text_shader),
258        );
259        self.draw.end_frame();
260        if !self.input_maintain_on_fixed_step || fixed_step {
261            self.input.maintain();
262        }
263
264        match std::mem::take(&mut self.state_change) {
265            GameStateChange::Continue => {}
266            GameStateChange::Swap(mut state) => {
267                if let Some(mut state) = self.states.pop() {
268                    state.exit(GameContext {
269                        graphics,
270                        draw: &mut self.draw,
271                        gui: &mut self.gui,
272                        input: &mut self.input,
273                        state_change: &mut self.state_change,
274                        assets: &mut self.assets,
275                        audio: &mut self.audio,
276                    });
277                }
278                state.enter(GameContext {
279                    graphics,
280                    draw: &mut self.draw,
281                    gui: &mut self.gui,
282                    input: &mut self.input,
283                    state_change: &mut self.state_change,
284                    assets: &mut self.assets,
285                    audio: &mut self.audio,
286                });
287                self.states.push(state);
288                self.timer = Instant::now();
289            }
290            GameStateChange::Push(mut state) => {
291                state.enter(GameContext {
292                    graphics,
293                    draw: &mut self.draw,
294                    gui: &mut self.gui,
295                    input: &mut self.input,
296                    state_change: &mut self.state_change,
297                    assets: &mut self.assets,
298                    audio: &mut self.audio,
299                });
300                self.states.push(state);
301                self.timer = Instant::now();
302            }
303            GameStateChange::Pop => {
304                if let Some(mut state) = self.states.pop() {
305                    state.exit(GameContext {
306                        graphics,
307                        draw: &mut self.draw,
308                        gui: &mut self.gui,
309                        input: &mut self.input,
310                        state_change: &mut self.state_change,
311                        assets: &mut self.assets,
312                        audio: &mut self.audio,
313                    });
314                }
315                self.timer = Instant::now();
316            }
317        }
318    }
319
320    pub fn process_event(&mut self, event: &Event<()>) -> bool {
321        if let Event::WindowEvent { event, .. } = event {
322            self.input.on_event(event);
323        }
324        !self.states.is_empty() || !matches!(self.state_change, GameStateChange::Continue)
325    }
326}
327
328impl AppState<Vertex> for GameInstance {
329    fn on_redraw(&mut self, graphics: &mut Graphics<Vertex>) {
330        self.process_frame(graphics);
331    }
332
333    fn on_event(&mut self, event: Event<()>, _: &mut Window) -> bool {
334        self.process_event(&event)
335    }
336}