gemini_engine/gameloop/
with_root.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use std::time::{Duration, Instant};

/// This is an alternative way to handle the gameloop, separate from [`fps_gameloop!`](crate::fps_gameloop). It takes on a more object-oriented approach - here, everything related to the game is stored inside a single struct which implements [`MainLoopRoot`].
///
/// Check out the `game-loop-root.rs` example, a version of `quick-start.rs` rewritten using `MainLoopRoot`
pub trait MainLoopRoot {
    /// Return the FPS at which the main loop should run. A constant like `60.0` or `30.0` is sufficient
    fn get_fps(&self) -> f32;

    /// This is where the main logic of your game should go - handling input, moving objects, handling collisions, etc.
    fn frame(&mut self);

    /// All rendering code (drawing, printing to the screen, etc.) should be called in here. If the bool value returned by [`MainLoopRoot::sleep_and_get_input_data()`] is true, this won't run and nothing should be printed to the screen
    /// ## Example
    /// Here's an example of what a `render_frame` trait implementation might look like, assuming your root struct has a `view: View` property for your main view
    /// ```
    /// # use gemini_engine::{core::{CanDraw, Canvas}, view::View};
    /// # struct Dummy {}
    /// # impl CanDraw for Dummy {
    /// #   fn draw_to(&self, canvas: &mut impl Canvas) {}
    /// # }
    /// # struct Game {
    /// #   view: View,
    /// #   player: Dummy,
    /// #   enemies: Vec<Dummy>,
    /// # }
    /// # impl Game {
    /// // --inside impl MainLoopRoot for Game--
    /// fn render_frame(&mut self) {
    ///     self.view.clear();
    ///
    ///     // Draw every enemy in a vector of `Enemies` (all of which would implement `CanDraw`)
    ///     for enemy in &self.enemies {
    ///         self.view.draw(enemy);
    ///     }
    ///     self.view.draw(&self.player);
    ///
    ///     self.view.display_render().unwrap();
    /// }
    /// # }
    /// ```
    fn render_frame(&mut self);

    /// The function used to sleep for the appropriate amount based on the value returned by `get_fps`. Uses [`gameloop::sleep_fps`](super::sleep_fps()) by default and will return None for the `InputDataType`. If the return value is `true`, `render_frame` will not be called on the next frame
    fn sleep_and_get_input_data(
        &self,
        fps: f32,
        elapsed: Duration,
    ) -> bool {
        super::sleep_fps(fps, Some(elapsed))
    }

    /// The main loop function of the main loop root. This shouldn't be overriden. See the [`MainLoopRoot`] documentation for more info
    /// ```no_run
    /// # use gemini_engine::gameloop::MainLoopRoot;
    /// # struct Game {}
    /// # impl Game {
    /// #   fn new() -> Game { Game {} }
    /// # }
    /// impl MainLoopRoot for Game {
    ///     fn get_fps(&self) -> f32 { 30.0 }
    ///     fn frame(&mut self) {
    ///         // --snip--
    ///     }
    ///     fn render_frame(&mut self) {
    ///         // --snip--
    ///     }
    /// }
    /// let mut game = Game::new(); // `Game` implements `MainLoopRoot`. Its `new` method sets up all the game objects
    ///
    /// game.main_loop();
    /// ```
    fn main_loop(&mut self) {
        let mut elapsed = Duration::ZERO;

        loop {
            let frame_skip = self.sleep_and_get_input_data(self.get_fps(), elapsed);
            let now = Instant::now();

            self.frame();

            if !frame_skip {
                self.render_frame();
            }

            elapsed = now.elapsed();
        }
    }
}