commit 313b18887339e7dd3ff253faded85bf60c404694 Author: Thomas Forgione Date: Fri Jul 29 16:21:08 2022 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5bcaadc --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +pkg +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4a9b248 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,147 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "game" +version = "0.1.0" +dependencies = [ + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "web-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..43e849b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "game" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.51" +wasm-bindgen = "0.2.74" + +[dependencies.web-sys] +version = "0.3.4" +features = [ + 'CanvasRenderingContext2d', + 'Document', + 'Element', + 'Event', + 'KeyboardEvent', + 'HtmlCanvasElement', + 'HtmlImageElement', + 'Window', +] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..25e6a0c --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +all: + wasm-pack build --target web + +deploy: all + ssh pi -C "rm -rf /mnt/www/storage.tforgione.fr/game" + ssh pi -C "mkdir /mnt/www/storage.tforgione.fr/game" + scp index.html pi:/mnt/www/storage.tforgione.fr/game + scp -r static pkg pi:/mnt/www/storage.tforgione.fr/game diff --git a/index.html b/index.html new file mode 100644 index 0000000..f6fa7db --- /dev/null +++ b/index.html @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/src/engine/event.rs b/src/engine/event.rs new file mode 100644 index 0000000..d6a1f52 --- /dev/null +++ b/src/engine/event.rs @@ -0,0 +1,141 @@ +//! This module helps us deal with controls and events. + +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +use crate::Result; + +/// The state of the keyboard. +pub struct Keyboard { + /// The inner keyboard. + inner: Rc>, +} + +impl Keyboard { + /// Checks wether a key is pressed or not. + pub fn is_key_pressed(&self, key: Key) -> bool { + let mut b = self.inner.borrow_mut(); + *b.keys.entry(key).or_insert(false) + } + + /// Receives and treats an event. + pub fn manage_event(&self, event: Event) { + let mut b = self.inner.borrow_mut(); + match event { + Event::KeyPressed(x) => *b.keys.entry(x).or_insert(true) = true, + Event::KeyReleased(x) => *b.keys.entry(x).or_insert(false) = false, + } + } +} + +impl Keyboard { + /// Initializes the keyboard. + pub fn new(document: &web_sys::Document) -> Result { + let inner = Rc::new(RefCell::new(InnerKeyboard::new())); + + let clone = Keyboard { + inner: inner.clone(), + }; + + let down_cb = Closure::::new(move |event: web_sys::KeyboardEvent| { + if let Some(event) = Event::from_js(true, event) { + clone.manage_event(event); + } + }); + + document.set_onkeydown(Some(down_cb.as_ref().unchecked_ref())); + down_cb.forget(); + + let clone = Keyboard { + inner: inner.clone(), + }; + + let up_cb = Closure::::new(move |event: web_sys::KeyboardEvent| { + if let Some(event) = Event::from_js(false, event) { + clone.manage_event(event); + } + }); + + document.set_onkeyup(Some(up_cb.as_ref().unchecked_ref())); + up_cb.forget(); + + Ok(Keyboard { inner }) + } +} + +/// The state of the keyboard. +pub struct InnerKeyboard { + /// Holds the state of the keys. + keys: HashMap, + + /// The list of events to be processed. + events: Vec, +} + +impl InnerKeyboard { + /// Creates a new inner arrowboard. + pub fn new() -> InnerKeyboard { + InnerKeyboard { + keys: HashMap::new(), + events: vec![], + } + } +} + +/// The different events that can occur. +#[derive(Copy, Clone)] +pub enum Event { + /// A key was pressed down. + KeyPressed(Key), + + /// A key was released. + KeyReleased(Key), +} + +/// The different key we cant to support. +#[derive(Hash, PartialEq, Eq, Copy, Clone)] +pub enum Key { + /// The left arrow key. + ArrowLeft, + + /// The right arrow key. + ArrowRight, + + /// The up arrow key. + ArrowUp, + + /// The bottom arrow key. + ArrowDown, +} + +impl Key { + /// Tries and converts a javascript key to our key type. + pub fn from_str(string: &str) -> Option { + match string { + "ArrowLeft" => Some(Key::ArrowLeft), + "ArrowRight" => Some(Key::ArrowRight), + "ArrowUp" => Some(Key::ArrowUp), + "ArrowDown" => Some(Key::ArrowDown), + _ => None, + } + } +} + +impl Event { + /// Tries and converts a javacript event to our event type. + pub fn from_js(down: bool, event: web_sys::KeyboardEvent) -> Option { + let key = Key::from_str(&event.code())?; + + let event = if down { + Some(Event::KeyPressed(key)) + } else { + Some(Event::KeyReleased(key)) + }; + + event + } +} diff --git a/src/engine/image.rs b/src/engine/image.rs new file mode 100644 index 0000000..9763cd2 --- /dev/null +++ b/src/engine/image.rs @@ -0,0 +1,77 @@ +//! A module that makes it easier to deal with images. + +use std::borrow::BorrowMut; +use std::cell::{Ref, RefCell}; +use std::rc::Rc; + +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +use web_sys::HtmlImageElement; + +use crate::{log, Result}; + +/// A wrapper to make it easier to manage HTML images. +pub struct Image { + /// The inner image. + pub inner: Rc>, +} + +/// The content of the image. +pub struct InnerImage { + /// The inner HtmlImageElement. + pub inner: HtmlImageElement, + + /// Whether the image has been loaded or not. + pub loaded: bool, +} + +impl Image { + /// Loads a new image from its src. + pub fn new(path: &str) -> Result { + let image = HtmlImageElement::new()?; + image.set_src(path); + + let image = Image { + inner: Rc::new(RefCell::new(InnerImage { + inner: image, + loaded: false, + })), + }; + + let clone = image.inner.clone(); + let path_clone = path.to_string(); + + let cb = Closure::::new(move |_event: web_sys::Event| { + log!("{} loaded", path_clone); + (*clone).borrow_mut().loaded = true; + }); + + image + .inner + .borrow() + .inner + .set_onload(Some(cb.as_ref().unchecked_ref())); + + cb.forget(); + + Ok(image) + } + + /// Returns whether the image is loaded. + pub fn is_loaded(&self) -> bool { + self.inner.borrow().loaded + } + + /// Renders the image on a context. + pub fn render( + &self, + x: f64, + y: f64, + context: &web_sys::CanvasRenderingContext2d, + ) -> Result<()> { + context.draw_image_with_html_image_element(&self.inner.borrow().inner, x, y)?; + + Ok(()) + } +} diff --git a/src/engine/mod.rs b/src/engine/mod.rs new file mode 100644 index 0000000..545d4e9 --- /dev/null +++ b/src/engine/mod.rs @@ -0,0 +1,196 @@ +//! This module contains the whole engine that manages everything. + +pub mod event; +pub mod image; + +use std::cell::RefCell; +use std::rc::Rc; + +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +use crate::engine::event::{Key, Keyboard}; +use crate::engine::image::Image; +use crate::Result; + +/// Our game engine. +pub struct Engine { + /// The inner engine. + /// + /// We need Rc in order to deal with events. + pub inner: Rc>, + + /// The web page document. + pub document: Rc, + + /// The canvas rendering context. + /// + /// We keep a reference so that we can easily render things. + pub context: Rc, +} + +impl Engine { + /// Creates a new engine. + pub fn new() -> Result { + let document = web_sys::window().unwrap().document().unwrap(); + let canvas = document.get_element_by_id("canvas").unwrap(); + let canvas: web_sys::HtmlCanvasElement = canvas.dyn_into::()?; + + canvas.set_width(1920); + canvas.set_height(1080); + + let context = canvas + .get_context("2d")? + .unwrap() + .dyn_into::()?; + + let inner = InnerEngine::new(&document)?; + + let document = Rc::new(document); + let context = Rc::new(context); + + Ok(Engine { + inner: Rc::new(RefCell::new(inner)), + document, + context, + }) + } + + /// Clones the engine. + /// + /// It is made to be efficient, cloning only Rcs. + pub fn clone(&self) -> Engine { + Engine { + inner: self.inner.clone(), + document: self.document.clone(), + context: self.context.clone(), + } + } + + /// Starts the engine. + pub fn start(&self) -> Result<()> { + let clone = self.clone(); + let cb = Closure::::new(move |event: web_sys::Event| { + clone.run(); + }); + + web_sys::window() + .unwrap() + .request_animation_frame(cb.as_ref().unchecked_ref()) + .unwrap(); + + cb.forget(); + + Ok(()) + } + + /// Launches a loop of the engine, and schedules the next one. + pub fn run(&self) -> Result<()> { + // Perform update + self.update()?; + + // Perform render + self.render()?; + + // Schedule next render + let clone = self.clone(); + let cb = Closure::::new(move |event: web_sys::Event| { + clone.run(); + }); + + web_sys::window() + .unwrap() + .request_animation_frame(cb.as_ref().unchecked_ref()) + .unwrap(); + + cb.forget(); + + Ok(()) + } + + /// Triggers the update of the model of the engine. + pub fn update(&self) -> Result<()> { + let mut inner = self.inner.borrow_mut(); + + // Perform update + let mut dx = 0.0; + let mut dy = 0.0; + + if inner.keyboard.is_key_pressed(Key::ArrowDown) { + dy += 5.0; + } + + if inner.keyboard.is_key_pressed(Key::ArrowUp) { + dy -= 5.0; + } + + if inner.keyboard.is_key_pressed(Key::ArrowLeft) { + dx -= 5.0; + } + + if inner.keyboard.is_key_pressed(Key::ArrowRight) { + dx += 5.0; + } + + inner.x += dx; + inner.y += dy; + + Ok(()) + } + + /// Performs the rendering of the engine. + pub fn render(&self) -> Result<()> { + let inner = self.inner.borrow(); + + // Perform render + self.context.clear_rect(0.0, 0.0, 1920.0, 1080.0); + inner + .textures + .test + .render(inner.x, inner.y, &self.context)?; + + Ok(()) + } +} + +/// The data contained in our engine. +pub struct InnerEngine { + /// The x position of the drawing. + pub x: f64, + + /// The y position of the drawing. + pub y: f64, + + /// The keyboard. + pub keyboard: Keyboard, + + /// The texture manager. + pub textures: TextureManager, +} + +impl InnerEngine { + /// Initializes the engine. + pub fn new(document: &web_sys::Document) -> Result { + Ok(InnerEngine { + x: 0.0, + y: 0.0, + keyboard: Keyboard::new(document)?, + textures: TextureManager::new()?, + }) + } +} + +/// Our texture manager. +/// +/// It holds all our resources. +pub struct TextureManager { + test: Image, +} + +impl TextureManager { + /// Creates and start the loading of all our textures. + fn new() -> Result { + let test = Image::new("static/image.png")?; + Ok(TextureManager { test }) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2dc0859 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,40 @@ +use std::rc::Rc; +use std::result::Result as StdResult; + +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +pub mod engine; + +/// A type that makes dealing with results easier. +pub type Result = StdResult; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console, js_name = log)] + pub fn console_log(s: &str); + + #[wasm_bindgen(js_namespace = console, js_name = log)] + pub fn console_log_js(s: JsValue); +} + +macro_rules! log { + ($($t:tt)*) => (crate::console_log(&format_args!($($t)*).to_string())) +} + +macro_rules! log_js { + ($t:tt) => {{ + let e: JsValue = $t.into(); + crate::console_log_js(e) + }}; +} + +pub(crate) use log; +pub(crate) use log_js; + +#[wasm_bindgen(start)] +pub fn start() -> Result<()> { + let mut engine = engine::Engine::new()?; + engine.start()?; + Ok(()) +} diff --git a/static/image.png b/static/image.png new file mode 100644 index 0000000..a775f9f Binary files /dev/null and b/static/image.png differ