//! 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>); impl Game { /// Creates a new game. pub fn new() -> Result { Ok(Game(Rc::new(RefCell::new(Engine::new()?)))) // let clone = inner.clone(); // let cb = Closure::::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::::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::::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 { 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::()?; canvas.set_width(1920); canvas.set_height(1080); let context = unwrap!(canvas.get_context("2d")?).dyn_into::()?; 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(&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; }