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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
//! An object oriented approach to the gameloop, separate from [`fps_gameloop!`](crate::fps_gameloop). Read the [`MainLoopRoot`] documentation for more info
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 where everything related to the game is stored inside a single struct which implements this trait.
///
/// Check out the [example](https://docs.rs/crate/gemini-engine/latest/source/examples/game-loop-root.rs), a version of `quick-start.rs` (the example from the [elements](crate::elements) doc page) rewritten using `MainLoopRoot`. While in that particular case it might appear to have a lot of boilerplate code, it can make your game much easier to manage as you add more things and scale it
///
/// This trait's separate functions have more documentation to explain further details
pub trait MainLoopRoot {
/// This type should be generated by [`MainLoopRoot::sleep_and_get_input_data()`] and will be passed to [`MainLoopRoot::frame()`]
type InputDataType;
/// This is where the main logic of your game - handling input, moving objects, handling collisions, etc.
///
/// # Example
/// ```
/// # use gemini_engine::elements::view::{Wrapping, ViewElement, View, Point, Vec2D };
/// # use gemini_engine::gameloop::MainLoopRoot;
/// # struct Dummy { pos: Vec2D }
/// # impl ViewElement for Dummy {
/// # fn active_pixels(&self) -> Vec<Point> { vec![] }
/// # }
/// # struct Game {
/// # view: View,
/// # player: Dummy,
/// # enemies: Vec<Dummy>,
/// #
/// # }
/// # impl MainLoopRoot for Game {
/// # type InputDataType = u32;
/// // --inside impl MainLoopRoot for Game--
/// fn frame(&mut self, input_data: Option<Self::InputDataType>) {
/// self.player.pos.x += 1; // player has a pos: Vec2D field
/// }
/// # fn render_frame(&mut self) {}
/// # }
/// ```
fn frame(&mut self, input_data: Option<Self::InputDataType>);
/// All rendering code (blitting, 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::elements::view::{Wrapping, ViewElement, View, Point};
/// # struct Dummy {}
/// # impl ViewElement for Dummy {
/// # fn active_pixels(&self) -> Vec<Point> { vec![] }
/// # }
/// # struct Game {
/// # view: View,
/// # player: Dummy,
/// # enemies: Vec<Dummy>,
/// #
/// # }
/// # impl Game {
/// // --inside impl MainLoopRoot for Game--
/// fn render_frame(&mut self) {
/// self.view.clear();
///
/// for enemy in &self.enemies { // Go through a vector of Enemy structs (all of which would implement ViewElement)
/// self.view.blit(enemy, Wrapping::Ignore);
/// }
/// self.view.blit(&self.player, Wrapping::Ignore);
///
/// self.view.display_render().unwrap();
/// }
/// # }
/// ```
fn render_frame(&mut self);
/// The function used to sleep for the appropriate amount based on the FPS. Uses [`gameloop::sleep_fps`](super::sleep_fps()) by default and will return None for the `InputDataType`. The returned bool value should represent whether or not to skip rendering on the next frame
fn sleep_and_get_input_data(
&self,
fps: f32,
elapsed: Duration,
) -> (bool, Option<Self::InputDataType>) {
(super::sleep_fps(fps, Some(elapsed)), None)
}
/// The main loop function of the main loop root. This shouldnt be overriden. The `fps` parameter will be passed straight to [`sleep_and_get_input()`](MainLoopRoot::sleep_and_get_input_data()). See the [`MainLoopRoot`] documentation for more info
/// ```rust, no_run
/// # use gemini_engine::gameloop::MainLoopRoot;
/// # struct Game {}
/// # impl Game {
/// # fn new() -> Game { Game {} }
/// # }
/// # impl MainLoopRoot for Game {
/// # type InputDataType = u32;
/// # fn frame(&mut self, input_data: Option<Self::InputDataType>) {}
/// # fn render_frame(&mut self) {}
/// # }
/// const FPS: f32 = 30.0;
/// let mut game = Game::new(); // Implements MainLoopRoot and has a function that sets up all the game objects
///
/// game.main_loop(FPS);
/// ```
fn main_loop(&mut self, fps: f32) {
let mut elapsed = Duration::ZERO;
loop {
let (frame_skip, input_data) = self.sleep_and_get_input_data(fps, elapsed);
let now = Instant::now();
self.frame(input_data);
if !frame_skip {
self.render_frame();
}
elapsed = now.elapsed();
}
}
}