Compare commits

...

4 Commits
dev ... main

Author SHA1 Message Date
0d841135a5 Work 2022-08-06 14:14:00 +02:00
6edc140a19 Adds sprite 2022-08-05 10:59:49 +02:00
28ff6f28fa Few fixes 2022-08-03 10:38:00 +02:00
94f0cdffca Better management of window size 2022-08-02 17:44:34 +02:00
7 changed files with 247 additions and 95 deletions

View File

@ -28,4 +28,12 @@ impl Bbox {
size, size,
} }
} }
/// Returns true is the two bboxes overlap.
pub fn overlap(self, rhs: Bbox) -> bool {
rhs.position.x < self.position.x + self.size.x
&& rhs.position.x + rhs.size.x > self.position.x
&& rhs.position.y < self.position.y + self.size.y
&& rhs.position.y + rhs.size.y > self.position.y
}
} }

View File

@ -302,4 +302,10 @@ impl Inputs {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
inner.events.pop_front() inner.events.pop_front()
} }
/// Deletes all the events and resets all the values.
pub fn clear(&mut self) {
let mut inner = self.0.borrow_mut();
inner.events.clear();
}
} }

View File

@ -94,6 +94,32 @@ impl CollisionAxis {
_ => true, _ => true,
} }
} }
/// Adds another collision axis.
pub fn and(self, other: CollisionAxis) -> CollisionAxis {
match (self, other) {
(CollisionAxis::X, CollisionAxis::X) => CollisionAxis::X,
(CollisionAxis::X, CollisionAxis::Y) => CollisionAxis::Both,
(CollisionAxis::Y, CollisionAxis::X) => CollisionAxis::Both,
(CollisionAxis::Y, CollisionAxis::Y) => CollisionAxis::Y,
(CollisionAxis::Both, _) => CollisionAxis::Both,
(_, CollisionAxis::Both) => CollisionAxis::Both,
}
}
/// Adds another collision axis to options.
pub fn and_option(first: Option<CollisionAxis>, other: CollisionAxis) -> Option<CollisionAxis> {
Some(match (first, other) {
(None, CollisionAxis::X) => CollisionAxis::X,
(None, CollisionAxis::Y) => CollisionAxis::Y,
(Some(CollisionAxis::X), CollisionAxis::X) => CollisionAxis::X,
(Some(CollisionAxis::X), CollisionAxis::Y) => CollisionAxis::Both,
(Some(CollisionAxis::Y), CollisionAxis::X) => CollisionAxis::Both,
(Some(CollisionAxis::Y), CollisionAxis::Y) => CollisionAxis::Y,
(Some(CollisionAxis::Both), _) => CollisionAxis::Both,
(_, CollisionAxis::Both) => CollisionAxis::Both,
})
}
} }
/// This struct represents the different sides from which a collision can occur. /// This struct represents the different sides from which a collision can occur.
@ -146,7 +172,7 @@ impl CollisionTile {
/// This struct represents a renderable tile linking to its part in the tileset texture. /// This struct represents a renderable tile linking to its part in the tileset texture.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct GraphicTile(Option<i32>); pub struct GraphicTile(pub Option<i32>);
impl GraphicTile { impl GraphicTile {
/// Creates the correct graphic tile depending on the neighbours. /// Creates the correct graphic tile depending on the neighbours.
@ -230,6 +256,82 @@ pub struct PositionedTile {
pub position: Vector, pub position: Vector,
} }
impl PositionedTile {
/// Returns the collision information between a collision tile and a moving object.
pub fn collides(self, old: Bbox, new: Bbox) -> Option<(CollisionAxis, Vector)> {
let mut collision_x = false;
let mut collision_y = false;
let mut new = new;
let tile = Bbox::new(self.position.x, self.position.y, SPRITE_SIZE, SPRITE_SIZE);
loop {
// I know, this loop is ugly, but I don't care
if !new.overlap(tile) {
break;
}
// Collisions between feet and ground
if self.collision.from_top
&& old.position.y + old.size.y <= tile.position.y
&& new.position.y + new.size.y >= tile.position.y
{
collision_y = true;
new.position.y = tile.position.y - new.size.y;
}
if !new.overlap(tile) {
break;
}
// Collisions between right and right wall
if self.collision.from_left
&& old.position.x + old.size.x <= tile.position.x
&& new.position.x + new.size.x >= tile.position.x
{
collision_x = true;
new.position.x = tile.position.x - new.size.x;
}
if !new.overlap(tile) {
break;
}
// Collisions between left and left wall
if self.collision.from_right
&& old.position.x >= tile.position.x + SPRITE_SIZE
&& new.position.x <= tile.position.x + SPRITE_SIZE
{
collision_x = true;
new.position.x = tile.position.x + SPRITE_SIZE;
}
if !new.overlap(tile) {
break;
}
// Collisions between head and roof
if self.collision.from_bottom
&& old.position.y >= tile.position.y + SPRITE_SIZE
&& new.position.y <= tile.position.y + SPRITE_SIZE
{
collision_y = true;
new.position.y = tile.position.y + SPRITE_SIZE;
}
break;
}
match (collision_x, collision_y) {
(true, true) => Some((CollisionAxis::Both, new.position)),
(true, false) => Some((CollisionAxis::X, new.position)),
(false, true) => Some((CollisionAxis::Y, new.position)),
(false, false) => None,
}
}
}
impl Drawable for PositionedTile { impl Drawable for PositionedTile {
fn texture(&self) -> Texture { fn texture(&self) -> Texture {
Texture::Overworld Texture::Overworld
@ -472,67 +574,15 @@ impl Map {
rows as f64, rows as f64,
) as usize; ) as usize;
let mut collision_x = false; let mut collision_axis: Option<CollisionAxis> = None;
let mut collision_y = false;
let mut new = new; let mut new = new;
for col in min_col..=max_col { for col in min_col..=max_col {
for row in min_row..=max_row { for row in min_row..=max_row {
let tile_left = col as f64 * SPRITE_SIZE; if let Some((axis, new_pos)) = self.at(row, col).collides(old, new) {
let tile_top = row as f64 * SPRITE_SIZE; new.position = new_pos;
collision_axis = CollisionAxis::and_option(collision_axis, axis);
let tile = Bbox::new(tile_left, tile_top, SPRITE_SIZE, SPRITE_SIZE);
if !overlap(new, tile) {
continue;
}
// Collisions between feet and ground
if self.collision_tiles[(row, col)].from_top
&& old.position.y + old.size.y <= tile_top
&& new.position.y + new.size.y >= tile_top
{
collision_y = true;
new.position.y = tile_top - new.size.y;
}
if !overlap(new, tile) {
continue;
}
// Collisions between right and right wall
if self.collision_tiles[(row, col)].from_left
&& old.position.x + old.size.x <= tile_left
&& new.position.x + new.size.x >= tile_left
{
collision_x = true;
new.position.x = tile_left - new.size.x;
}
if !overlap(new, tile) {
continue;
}
// Collisions between left and left wall
if self.collision_tiles[(row, col)].from_right
&& old.position.x >= tile_left + SPRITE_SIZE
&& new.position.x <= tile_left + SPRITE_SIZE
{
collision_x = true;
new.position.x = tile_left + SPRITE_SIZE;
}
if !overlap(new, tile) {
continue;
}
// Collisions between head and roof
if self.collision_tiles[(row, col)].from_bottom
&& old.position.y >= tile_top + SPRITE_SIZE
&& new.position.y <= tile_top + SPRITE_SIZE
{
collision_y = true;
new.position.y = tile_top + SPRITE_SIZE;
} }
} }
} }
@ -540,35 +590,25 @@ impl Map {
// Collision between the player and left border of the level // Collision between the player and left border of the level
if new.position.x < 0.0 { if new.position.x < 0.0 {
new.position.x = 0.0; new.position.x = 0.0;
collision_x = true; collision_axis = CollisionAxis::and_option(collision_axis, CollisionAxis::X);
} }
// Collision between the player and right border of the level // Collision between the player and right border of the level
if new.position.x > cols as f64 * SPRITE_SIZE { if new.position.x > cols as f64 * SPRITE_SIZE {
new.position.x = cols as f64 * SPRITE_SIZE; new.position.x = cols as f64 * SPRITE_SIZE;
collision_x = true; collision_axis = CollisionAxis::and_option(collision_axis, CollisionAxis::Y);
} }
// Collision between the player and the void // Collision between the player and the void
if new.position.y > self.collision_tiles.rows() as f64 * SPRITE_SIZE { if new.position.y > self.collision_tiles.rows() as f64 * SPRITE_SIZE {
new.position.y = self.collision_tiles.rows() as f64 * SPRITE_SIZE; new.position.y = self.collision_tiles.rows() as f64 * SPRITE_SIZE;
collision_y = true;
death = true; death = true;
} }
match (collision_x, collision_y) { if let Some(collision_axis) = collision_axis {
(true, true) => Some((CollisionAxis::Both, new.position, death)), Some((collision_axis, new.position, death))
(true, false) => Some((CollisionAxis::X, new.position, death)), } else {
(false, true) => Some((CollisionAxis::Y, new.position, death)), None
(false, false) => None,
} }
} }
} }
/// Checks if two boxes overlap.
pub fn overlap(box1: Bbox, box2: Bbox) -> bool {
box2.position.x < box1.position.x + box1.size.x
&& box2.position.x + box2.size.x > box1.position.x
&& box2.position.y < box1.position.y + box1.size.y
&& box2.position.y + box2.size.y > box1.position.y
}

View File

@ -6,6 +6,7 @@ pub mod controls;
pub mod input; pub mod input;
pub mod map; pub mod map;
pub mod math; pub mod math;
pub mod object;
pub mod physics; pub mod physics;
pub mod scene; pub mod scene;
pub mod texture; pub mod texture;
@ -55,17 +56,6 @@ impl Game {
/// Creates a new game. /// Creates a new game.
pub fn new() -> Result<Game> { pub fn new() -> Result<Game> {
Ok(Game(Rc::new(RefCell::new(Engine::new()?)))) 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. /// Starts the game.
@ -135,6 +125,11 @@ pub struct Engine {
/// The web page document. /// The web page document.
pub document: web_sys::Document, pub document: web_sys::Document,
/// The canvas.
///
/// We keep it to easily know the size.
pub canvas: web_sys::HtmlCanvasElement,
/// The canvas rendering context. /// The canvas rendering context.
/// ///
/// We keep a reference so that we can easily render things. /// We keep a reference so that we can easily render things.
@ -172,6 +167,7 @@ impl Engine {
inputs: Inputs::new(&window)?, inputs: Inputs::new(&window)?,
window, window,
document, document,
canvas,
context, context,
performance, performance,
}) })
@ -224,6 +220,9 @@ impl Engine {
self.scene = scene; self.scene = scene;
} }
} else {
// Clear the events received while document was out of focus
self.inputs.clear();
} }
Ok(()) Ok(())
@ -231,13 +230,30 @@ impl Engine {
/// Performs the rendering of the engine. /// Performs the rendering of the engine.
pub fn render(&self) -> Result<()> { pub fn render(&self) -> Result<()> {
let window_width = unwrap!(self.window.inner_width()?.as_f64());
let window_height = unwrap!(self.window.inner_height()?.as_f64());
let window_width_u32 = window_width as u32;
let window_height_u32 = window_height as u32;
if window_width_u32 != self.canvas.width() {
self.canvas.set_width(window_width_u32);
}
if window_height_u32 != self.canvas.height() {
self.canvas.set_height(window_height_u32);
}
let window_size = Vector::new(window_width, window_height);
let view = self.scene.view().unwrap(); // TODO remove this unwrap let view = self.scene.view().unwrap(); // TODO remove this unwrap
// Clear render // Clear render
self.context.clear_rect(0.0, 0.0, 1920.0, 1080.0); self.context
.clear_rect(0.0, 0.0, window_width, window_height);
self.context self.context
.set_fill_style(&JsValue::from_str("rgb(135, 206, 235)")); .set_fill_style(&JsValue::from_str("rgb(135, 206, 235)"));
self.context.fill_rect(0.0, 0.0, 1920.0, 1080.0); self.context
.fill_rect(0.0, 0.0, window_width, window_height);
// Draw the scene // Draw the scene
let map = self.scene.map(); let map = self.scene.map();
@ -248,15 +264,20 @@ impl Engine {
for j in 0..cols { for j in 0..cols {
let tile = map.at(i, j); let tile = map.at(i, j);
if tile.graphic.is_visible() { if tile.graphic.is_visible() {
self.draw(&tile, view)?; self.draw(&tile, view, window_size)?;
} }
} }
} }
// Draw the objects
for o in self.scene.objects() {
self.draw(o, view, window_size)?;
}
// Draw characters // Draw characters
for c in self.scene.characters() { for c in self.scene.characters() {
if true { if true {
self.draw(c, view)?; self.draw(c, view, window_size)?;
} }
} }
@ -264,16 +285,20 @@ impl Engine {
} }
/// Draw a drawable. /// Draw a drawable.
pub fn draw<D: Drawable>(&self, drawable: &D, view: Bbox) -> Result<()> { pub fn draw<D: Drawable>(&self, drawable: &D, view: Bbox, window: Vector) -> Result<()> {
let image = self.textures.get(drawable.texture()); let image = self.textures.get(drawable.texture());
let source = drawable.texture_rect(self.after_loop); let source = drawable.texture_rect(self.after_loop);
let mut dest = source.clone(); let mut dest = source.clone();
// Without view
// dest.position = drawable.position();
// With view
dest.position = drawable.position() - view.position; dest.position = drawable.position() - view.position;
dest.position.x *= 1920.0 / view.size.x; dest.position.x *= window.x / view.size.x;
dest.position.y *= 1080.0 / view.size.y; dest.position.y *= window.y / view.size.y;
dest.size.x *= 1920.0 / view.size.x; dest.size.x *= window.x / view.size.x;
dest.size.y *= 1080.0 / view.size.y; dest.size.y *= window.y / view.size.y;
image.render(source, dest, &self.context)?; image.render(source, dest, &self.context)?;
Ok(()) Ok(())

35
src/engine/object.rs Normal file
View File

@ -0,0 +1,35 @@
//! This module contains objects that are not characters.
use std::time::SystemTime;
use crate::engine::bbox::Bbox;
use crate::engine::texture::{Texture, SPRITE_SIZE};
use crate::engine::vector::Vector;
use crate::engine::Drawable;
/// An object that can collide with the character.
pub struct Object {
/// The initial position of the object.
pub position: Vector,
}
impl Object {
/// Creates a new object.
pub fn new(position: Vector) -> Object {
Object { position }
}
}
impl Drawable for Object {
fn texture(&self) -> Texture {
Texture::Overworld
}
fn texture_rect(&self, _now: SystemTime) -> Bbox {
Bbox::new(46.0 * SPRITE_SIZE, 0.0, SPRITE_SIZE, SPRITE_SIZE)
}
fn position(&self) -> Vector {
self.position
}
}

View File

@ -6,14 +6,19 @@ use crate::engine::bbox::Bbox;
use crate::engine::character::Character; use crate::engine::character::Character;
use crate::engine::input::Action; use crate::engine::input::Action;
use crate::engine::input::InputManager; use crate::engine::input::InputManager;
use crate::engine::map::Map; use crate::engine::map::{CollisionTile, GraphicTile, Map, PositionedTile};
use crate::engine::object::Object;
use crate::engine::texture::SPRITE_SIZE; use crate::engine::texture::SPRITE_SIZE;
use crate::engine::vector::Vector;
/// Contains everything needed to play. /// Contains everything needed to play.
pub struct Scene { pub struct Scene {
/// The characters contained in the scene. /// The characters contained in the scene.
characters: Vec<Character>, characters: Vec<Character>,
/// The objects contained in the scene.
objects: Vec<Object>,
/// The map of the scene. /// The map of the scene.
map: Map, map: Map,
} }
@ -31,8 +36,10 @@ pub enum State {
impl Scene { impl Scene {
/// Creates a scene from a map level. /// Creates a scene from a map level.
pub fn from_map(map: Map) -> Scene { pub fn from_map(map: Map) -> Scene {
let object = Object::new(Vector::new(100.0, 300.0));
Scene { Scene {
characters: vec![], characters: vec![],
objects: vec![object],
map, map,
} }
} }
@ -46,6 +53,11 @@ impl Scene {
self.characters.push(character); self.characters.push(character);
} }
/// Adds an object to the scene.
pub fn add_object(&mut self, object: Object) {
self.objects.push(object);
}
/// Returns the controlable. /// Returns the controlable.
pub fn controlable(&self) -> Option<&Character> { pub fn controlable(&self) -> Option<&Character> {
for character in &self.characters { for character in &self.characters {
@ -115,6 +127,7 @@ impl Scene {
if axis.is_x() { if axis.is_x() {
c.speed.x = 0.0; c.speed.x = 0.0;
} }
if axis.is_y() { if axis.is_y() {
c.speed.y = 0.0; c.speed.y = 0.0;
c.ground_collision(); c.ground_collision();
@ -127,6 +140,26 @@ impl Scene {
} else { } else {
c.fall_off(); c.fall_off();
} }
for object in &self.objects {
let positioned = PositionedTile {
graphic: GraphicTile(None),
collision: CollisionTile::full(),
position: object.position,
};
if let Some((axis, position)) = positioned.collides(old, c.bbox()) {
c.position = position - offset;
if axis.is_x() {
c.speed.x = 0.0;
}
if axis.is_y() {
c.speed.y = 0.0;
c.ground_collision();
}
}
}
} }
} }
@ -149,6 +182,11 @@ impl Scene {
pub fn map(&self) -> &Map { pub fn map(&self) -> &Map {
&self.map &self.map
} }
/// Returns a reference to the objects of the scene.
pub fn objects(&self) -> &Vec<Object> {
&self.objects
}
} }
/// Trait that needs to be implemented for everything that can be updatable. /// Trait that needs to be implemented for everything that can be updatable.

BIN
static/sprites/ice.aseprite Normal file

Binary file not shown.