Initial commit
This commit is contained in:
@@ -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<RefCell<InnerKeyboard>>,
|
||||
}
|
||||
|
||||
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<Keyboard> {
|
||||
let inner = Rc::new(RefCell::new(InnerKeyboard::new()));
|
||||
|
||||
let clone = Keyboard {
|
||||
inner: inner.clone(),
|
||||
};
|
||||
|
||||
let down_cb = Closure::<dyn FnMut(_)>::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::<dyn FnMut(_)>::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<Key, bool>,
|
||||
|
||||
/// The list of events to be processed.
|
||||
events: Vec<Event>,
|
||||
}
|
||||
|
||||
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<Key> {
|
||||
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<Event> {
|
||||
let key = Key::from_str(&event.code())?;
|
||||
|
||||
let event = if down {
|
||||
Some(Event::KeyPressed(key))
|
||||
} else {
|
||||
Some(Event::KeyReleased(key))
|
||||
};
|
||||
|
||||
event
|
||||
}
|
||||
}
|
||||
@@ -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<RefCell<InnerImage>>,
|
||||
}
|
||||
|
||||
/// 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<Image> {
|
||||
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::<dyn FnMut(_)>::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(())
|
||||
}
|
||||
}
|
||||
@@ -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<RefCell> in order to deal with events.
|
||||
pub inner: Rc<RefCell<InnerEngine>>,
|
||||
|
||||
/// The web page document.
|
||||
pub document: Rc<web_sys::Document>,
|
||||
|
||||
/// The canvas rendering context.
|
||||
///
|
||||
/// We keep a reference so that we can easily render things.
|
||||
pub context: Rc<web_sys::CanvasRenderingContext2d>,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Creates a new engine.
|
||||
pub fn new() -> Result<Engine> {
|
||||
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::<web_sys::HtmlCanvasElement>()?;
|
||||
|
||||
canvas.set_width(1920);
|
||||
canvas.set_height(1080);
|
||||
|
||||
let context = canvas
|
||||
.get_context("2d")?
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
|
||||
|
||||
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::<dyn FnMut(_)>::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::<dyn FnMut(_)>::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<InnerEngine> {
|
||||
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<TextureManager> {
|
||||
let test = Image::new("static/image.png")?;
|
||||
Ok(TextureManager { test })
|
||||
}
|
||||
}
|
||||
+40
@@ -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<T> = StdResult<T, JsValue>;
|
||||
|
||||
#[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(())
|
||||
}
|
||||
Reference in New Issue
Block a user