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;
23use std::{
24    any::{Any, TypeId},
25    cell::{Ref, RefCell, RefMut},
26    collections::HashMap,
27};
28#[cfg(target_arch = "wasm32")]
29use winit::{event::Event, window::Window};
30
31pub trait GameObject {
32    #[allow(unused_variables)]
33    fn activate(&mut self, context: &mut GameContext) {}
34
35    #[allow(unused_variables)]
36    fn deactivate(&mut self, context: &mut GameContext) {}
37
38    #[allow(unused_variables)]
39    fn process(&mut self, context: &mut GameContext, delta_time: f32) {}
40
41    #[allow(unused_variables)]
42    fn draw(&mut self, context: &mut GameContext) {}
43}
44
45#[derive(Default)]
46pub enum GameStateChange {
47    #[default]
48    Continue,
49    Swap(Box<dyn GameState>),
50    Push(Box<dyn GameState>),
51    Pop,
52}
53
54#[allow(unused_variables)]
55pub trait GameState {
56    fn enter(&mut self, context: GameContext) {}
57
58    fn exit(&mut self, context: GameContext) {}
59
60    fn update(&mut self, context: GameContext, delta_time: f32) {}
61
62    fn fixed_update(&mut self, context: GameContext, delta_time: f32) {}
63
64    fn draw(&mut self, context: GameContext) {}
65
66    fn draw_gui(&mut self, context: GameContext) {}
67}
68
69pub trait GameSubsystem {
70    fn run(&mut self, context: GameContext, delta_time: f32);
71}
72
73#[derive(Default)]
74pub struct GameGlobals {
75    globals: HashMap<TypeId, RefCell<Box<dyn Any>>>,
76}
77
78impl GameGlobals {
79    pub fn set<T: 'static>(&mut self, value: T) {
80        self.globals
81            .insert(TypeId::of::<T>(), RefCell::new(Box::new(value)));
82    }
83
84    pub fn unset<T: 'static>(&mut self) {
85        self.globals.remove(&TypeId::of::<T>());
86    }
87
88    pub fn read<T: 'static>(&self) -> Option<Ref<T>> {
89        self.globals
90            .get(&TypeId::of::<T>())
91            .and_then(|v| v.try_borrow().ok())
92            .map(|v| Ref::map(v, |v| v.downcast_ref::<T>().unwrap()))
93    }
94
95    pub fn write<T: 'static>(&self) -> Option<RefMut<T>> {
96        self.globals
97            .get(&TypeId::of::<T>())
98            .and_then(|v| v.try_borrow_mut().ok())
99            .map(|v| RefMut::map(v, |v| v.downcast_mut::<T>().unwrap()))
100    }
101}
102
103pub struct GameInstance {
104    pub fixed_delta_time: f32,
105    pub color_shader: &'static str,
106    pub image_shader: &'static str,
107    pub text_shader: &'static str,
108    pub input_maintain_on_fixed_step: bool,
109    draw: DrawContext,
110    gui: GuiContext,
111    input: InputContext,
112    assets: AssetDatabase,
113    audio: Audio,
114    timer: Instant,
115    fixed_timer: Instant,
116    states: Vec<Box<dyn GameState>>,
117    state_change: GameStateChange,
118    subsystems: Vec<Box<dyn GameSubsystem>>,
119    globals: GameGlobals,
120}
121
122impl Default for GameInstance {
123    fn default() -> Self {
124        Self {
125            fixed_delta_time: 1.0 / 60.0,
126            color_shader: "color",
127            image_shader: "image",
128            text_shader: "text",
129            input_maintain_on_fixed_step: true,
130            draw: Default::default(),
131            gui: Default::default(),
132            input: Default::default(),
133            assets: Default::default(),
134            audio: Default::default(),
135            timer: Instant::now(),
136            fixed_timer: Instant::now(),
137            states: Default::default(),
138            state_change: Default::default(),
139            subsystems: vec![
140                Box::new(ShaderAssetSubsystem),
141                Box::new(TextureAssetSubsystem),
142                Box::new(FontAssetSubsystem),
143                Box::new(SoundAssetSubsystem),
144            ],
145            globals: Default::default(),
146        }
147    }
148}
149
150impl GameInstance {
151    pub fn new(state: impl GameState + 'static) -> Self {
152        Self {
153            state_change: GameStateChange::Push(Box::new(state)),
154            ..Default::default()
155        }
156    }
157
158    pub fn with_fixed_time_step(mut self, value: f32) -> Self {
159        self.fixed_delta_time = value;
160        self
161    }
162
163    pub fn with_fps(mut self, frames_per_second: usize) -> Self {
164        self.set_fps(frames_per_second);
165        self
166    }
167
168    pub fn with_color_shader(mut self, name: &'static str) -> Self {
169        self.color_shader = name;
170        self
171    }
172
173    pub fn with_image_shader(mut self, name: &'static str) -> Self {
174        self.image_shader = name;
175        self
176    }
177
178    pub fn with_text_shader(mut self, name: &'static str) -> Self {
179        self.text_shader = name;
180        self
181    }
182
183    pub fn with_input_maintain_on_fixed_step(mut self, value: bool) -> Self {
184        self.input_maintain_on_fixed_step = value;
185        self
186    }
187
188    pub fn with_subsystem(mut self, subsystem: impl GameSubsystem + 'static) -> Self {
189        self.subsystems.push(Box::new(subsystem));
190        self
191    }
192
193    pub fn with_globals<T: 'static>(mut self, value: T) -> Self {
194        self.globals.set(value);
195        self
196    }
197
198    pub fn setup_assets(mut self, f: impl FnOnce(&mut AssetDatabase)) -> Self {
199        f(&mut self.assets);
200        self
201    }
202
203    pub fn fps(&self) -> usize {
204        (1.0 / self.fixed_delta_time).ceil() as usize
205    }
206
207    pub fn set_fps(&mut self, frames_per_second: usize) {
208        self.fixed_delta_time = 1.0 / frames_per_second as f32;
209    }
210
211    pub fn process_frame(&mut self, graphics: &mut Graphics<Vertex>) {
212        let delta_time = self.timer.elapsed().as_secs_f32();
213
214        for subsystem in &mut self.subsystems {
215            subsystem.run(
216                GameContext {
217                    graphics,
218                    draw: &mut self.draw,
219                    gui: &mut self.gui,
220                    input: &mut self.input,
221                    state_change: &mut self.state_change,
222                    assets: &mut self.assets,
223                    audio: &mut self.audio,
224                    globals: &mut self.globals,
225                },
226                delta_time,
227            );
228        }
229        self.assets.maintain().unwrap();
230
231        if let Some(state) = self.states.last_mut() {
232            self.timer = Instant::now();
233            state.update(
234                GameContext {
235                    graphics,
236                    draw: &mut self.draw,
237                    gui: &mut self.gui,
238                    input: &mut self.input,
239                    state_change: &mut self.state_change,
240                    assets: &mut self.assets,
241                    audio: &mut self.audio,
242                    globals: &mut self.globals,
243                },
244                delta_time,
245            );
246        }
247
248        let fixed_delta_time = self.fixed_timer.elapsed().as_secs_f32();
249        let fixed_step = if fixed_delta_time > self.fixed_delta_time {
250            self.fixed_timer = Instant::now();
251            if let Some(state) = self.states.last_mut() {
252                state.fixed_update(
253                    GameContext {
254                        graphics,
255                        draw: &mut self.draw,
256                        gui: &mut self.gui,
257                        input: &mut self.input,
258                        state_change: &mut self.state_change,
259                        assets: &mut self.assets,
260                        audio: &mut self.audio,
261                        globals: &mut self.globals,
262                    },
263                    fixed_delta_time,
264                );
265            }
266            true
267        } else {
268            false
269        };
270
271        self.draw.begin_frame(graphics);
272        self.draw.push_shader(&ShaderRef::name(self.image_shader));
273        self.draw.push_blending(GlowBlending::Alpha);
274        if let Some(state) = self.states.last_mut() {
275            state.draw(GameContext {
276                graphics,
277                draw: &mut self.draw,
278                gui: &mut self.gui,
279                input: &mut self.input,
280                state_change: &mut self.state_change,
281                assets: &mut self.assets,
282                audio: &mut self.audio,
283                globals: &mut self.globals,
284            });
285        }
286        self.gui.begin_frame();
287        if let Some(state) = self.states.last_mut() {
288            state.draw_gui(GameContext {
289                graphics,
290                draw: &mut self.draw,
291                gui: &mut self.gui,
292                input: &mut self.input,
293                state_change: &mut self.state_change,
294                assets: &mut self.assets,
295                audio: &mut self.audio,
296                globals: &mut self.globals,
297            });
298        }
299        self.gui.end_frame(
300            &mut self.draw,
301            graphics,
302            &ShaderRef::name(self.color_shader),
303            &ShaderRef::name(self.image_shader),
304            &ShaderRef::name(self.text_shader),
305        );
306        self.draw.end_frame();
307        if !self.input_maintain_on_fixed_step || fixed_step {
308            self.input.maintain();
309        }
310
311        match std::mem::take(&mut self.state_change) {
312            GameStateChange::Continue => {}
313            GameStateChange::Swap(mut state) => {
314                if let Some(mut state) = self.states.pop() {
315                    state.exit(GameContext {
316                        graphics,
317                        draw: &mut self.draw,
318                        gui: &mut self.gui,
319                        input: &mut self.input,
320                        state_change: &mut self.state_change,
321                        assets: &mut self.assets,
322                        audio: &mut self.audio,
323                        globals: &mut self.globals,
324                    });
325                }
326                state.enter(GameContext {
327                    graphics,
328                    draw: &mut self.draw,
329                    gui: &mut self.gui,
330                    input: &mut self.input,
331                    state_change: &mut self.state_change,
332                    assets: &mut self.assets,
333                    audio: &mut self.audio,
334                    globals: &mut self.globals,
335                });
336                self.states.push(state);
337                self.timer = Instant::now();
338            }
339            GameStateChange::Push(mut state) => {
340                state.enter(GameContext {
341                    graphics,
342                    draw: &mut self.draw,
343                    gui: &mut self.gui,
344                    input: &mut self.input,
345                    state_change: &mut self.state_change,
346                    assets: &mut self.assets,
347                    audio: &mut self.audio,
348                    globals: &mut self.globals,
349                });
350                self.states.push(state);
351                self.timer = Instant::now();
352            }
353            GameStateChange::Pop => {
354                if let Some(mut state) = self.states.pop() {
355                    state.exit(GameContext {
356                        graphics,
357                        draw: &mut self.draw,
358                        gui: &mut self.gui,
359                        input: &mut self.input,
360                        state_change: &mut self.state_change,
361                        assets: &mut self.assets,
362                        audio: &mut self.audio,
363                        globals: &mut self.globals,
364                    });
365                }
366                self.timer = Instant::now();
367            }
368        }
369    }
370
371    pub fn process_event(&mut self, event: &Event<()>) -> bool {
372        if let Event::WindowEvent { event, .. } = event {
373            self.input.on_event(event);
374        }
375        !self.states.is_empty() || !matches!(self.state_change, GameStateChange::Continue)
376    }
377}
378
379impl AppState<Vertex> for GameInstance {
380    fn on_redraw(&mut self, graphics: &mut Graphics<Vertex>) {
381        self.process_frame(graphics);
382    }
383
384    fn on_event(&mut self, event: Event<()>, _: &mut Window) -> bool {
385        self.process_event(&event)
386    }
387}