game/src/engine/mod.rs
2022-08-02 14:35:44 +02:00

261 lines
7.0 KiB
Rust

//! This module contains the whole engine that manages everything.
pub mod bbox;
pub mod character;
pub mod controls;
pub mod input;
pub mod map;
pub mod math;
pub mod physics;
pub mod scene;
pub mod texture;
pub mod vector;
use std::cell::RefCell;
use std::rc::Rc;
use std::time::{Duration, SystemTime};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use crate::engine::bbox::Bbox;
use crate::engine::character::Character;
use crate::engine::controls::Controls;
use crate::engine::input::InputManager;
use crate::engine::map::Map;
use crate::engine::math::now;
use crate::engine::scene::{Scene, State};
use crate::engine::texture::{Texture, TextureManager};
use crate::engine::vector::Vector;
use crate::{error_js, log, Result};
macro_rules! unwrap {
($t: expr) => {{
match $t {
None => return Err(JsValue::from_str("cannot unwrap on none")),
Some(x) => x,
}
}};
}
/// Our game engine.
#[derive(Clone)]
pub struct Game(Rc<RefCell<Engine>>);
impl Game {
/// Creates a new game.
pub fn new() -> Result<Game> {
Ok(Game(Rc::new(RefCell::new(Engine::new()?))))
// let clone = inner.clone();
// let cb = Closure::<dyn FnMut(_)>::new(move |event: web_sys::GamepadEvent| {
// let mut inner = clone.borrow_mut();
// inner.add_gamepad(&event);
// });
// (*window)
// .add_event_listener_with_callback("gamepadconnected", cb.as_ref().unchecked_ref())?;
// cb.forget();
}
/// Starts the game.
pub fn start(&self) -> Result<()> {
let clone = self.clone();
let cb = Closure::<dyn FnMut(_)>::new(move |_event: web_sys::Event| {
if let Err(e) = clone.run() {
error_js!(e);
}
});
let inner = self.0.borrow_mut();
inner
.window
.request_animation_frame(cb.as_ref().unchecked_ref())?;
cb.forget();
Ok(())
}
/// Launches a loop of the engine, and schedules the next one.
pub fn run(&self) -> Result<()> {
let mut inner = self.0.borrow_mut();
// Perform update
inner.update()?;
// Perform render
inner.render()?;
// Schedule next render
let clone = self.clone();
let cb = Closure::<dyn FnMut(_)>::new(move |_event: web_sys::Event| {
if let Err(e) = clone.run() {
error_js!(e);
}
});
inner
.window
.request_animation_frame(cb.as_ref().unchecked_ref())?;
cb.forget();
Ok(())
}
}
/// The data contained in our engine.
pub struct Engine {
/// The scene of the engine.
pub scene: Scene,
/// The time when the loop is done.
pub after_loop: SystemTime,
/// The texture manager.
pub textures: TextureManager,
/// The input manager.
pub inputs: InputManager,
/// The web page window.
pub window: web_sys::Window,
/// The web page document.
pub document: web_sys::Document,
/// The canvas rendering context.
///
/// We keep a reference so that we can easily render things.
pub context: web_sys::CanvasRenderingContext2d,
/// The performance object.
pub performance: web_sys::Performance,
}
impl Engine {
/// Initializes the engine.
pub fn new() -> Result<Engine> {
let window = unwrap!(web_sys::window());
let document = unwrap!(window.document());
let performance = unwrap!(window.performance());
let canvas = unwrap!(document.get_element_by_id("canvas"));
let canvas: web_sys::HtmlCanvasElement = canvas.dyn_into::<web_sys::HtmlCanvasElement>()?;
canvas.set_width(1920);
canvas.set_height(1080);
let context =
unwrap!(canvas.get_context("2d")?).dyn_into::<web_sys::CanvasRenderingContext2d>()?;
let map = Map::from_str(include_str!("../../static/levels/level1.lvl")).unwrap();
let mut scene = Scene::from_map(map);
let character = Character::new();
scene.add(character);
Ok(Engine {
scene,
after_loop: now(&performance),
textures: TextureManager::new()?,
inputs: InputManager::new(),
window,
document,
context,
performance,
})
}
/// Triggers the update of the model of the engine.
pub fn update(&mut self) -> Result<()> {
// Manage the physics
let now = now(&self.performance);
let duration = unwrap!(now.duration_since(self.after_loop).ok());
self.after_loop = now;
if self.document.has_focus()? {
// Manage events
// while let Some(event) = inner.keyboard.pop() {
// inner.scene.manage_action(&action);
// }
// let keyboard = inner.keyboard.clone();
if self.scene.update(now, duration) == State::Finished {
let map = Map::from_str(include_str!("../../static/levels/level1.lvl")).unwrap();
let mut scene = Scene::from_map(map);
let character = Character::new();
scene.add(character);
self.scene = scene;
}
}
Ok(())
}
/// Performs the rendering of the engine.
pub fn render(&self) -> Result<()> {
let view = self.scene.view().unwrap(); // TODO remove this unwrap
// Clear render
self.context.clear_rect(0.0, 0.0, 1920.0, 1080.0);
self.context
.set_fill_style(&JsValue::from_str("rgb(135, 206, 235)"));
self.context.fill_rect(0.0, 0.0, 1920.0, 1080.0);
// Draw the scene
let map = self.scene.map();
let rows = map.rows();
let cols = map.cols();
for i in 0..rows {
for j in 0..cols {
let tile = map.at(i, j);
if tile.graphic.is_visible() {
self.draw(&tile, view)?;
}
}
}
// Draw characters
for c in self.scene.characters() {
if true {
self.draw(c, view)?;
}
}
Ok(())
}
/// Draw a drawable.
pub fn draw<D: Drawable>(&self, drawable: &D, view: Bbox) -> Result<()> {
let image = self.textures.get(drawable.texture());
let source = drawable.texture_rect(self.after_loop);
let mut dest = source.clone();
dest.position = drawable.position() - view.position;
dest.position.x *= 1920.0 / view.size.x;
dest.position.y *= 1080.0 / view.size.y;
dest.size.x *= 1920.0 / view.size.x;
dest.size.y *= 1080.0 / view.size.y;
image.render(source, dest, &self.context)?;
Ok(())
}
}
/// Our custom drawable trait.
pub trait Drawable {
/// Returns the texture of the drawable.
fn texture(&self) -> Texture;
/// Returns the coordinates to use on the texture.
fn texture_rect(&self, now: SystemTime) -> Bbox;
/// Returns the position on which the drawable should be drawn.
fn position(&self) -> Vector;
}