From 0d841135a51eca5f1b6df0871dc2abc589b5d6e5 Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Sat, 6 Aug 2022 14:14:00 +0200 Subject: [PATCH] Work --- src/engine/bbox.rs | 8 ++ src/engine/map.rs | 188 ++++++++++++++++++++++-------------- src/engine/mod.rs | 10 ++ src/engine/object.rs | 35 +++++++ src/engine/scene.rs | 40 +++++++- static/sprites/ice.aseprite | Bin 1036 -> 1064 bytes static/sprites/ice.gif | Bin 15325 -> 0 bytes 7 files changed, 206 insertions(+), 75 deletions(-) create mode 100644 src/engine/object.rs delete mode 100644 static/sprites/ice.gif diff --git a/src/engine/bbox.rs b/src/engine/bbox.rs index 6230de7..b4c65ab 100644 --- a/src/engine/bbox.rs +++ b/src/engine/bbox.rs @@ -28,4 +28,12 @@ impl Bbox { 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 + } } diff --git a/src/engine/map.rs b/src/engine/map.rs index 46f0b89..6f4b20f 100644 --- a/src/engine/map.rs +++ b/src/engine/map.rs @@ -94,6 +94,32 @@ impl CollisionAxis { _ => 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, other: CollisionAxis) -> Option { + 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. @@ -146,7 +172,7 @@ impl CollisionTile { /// This struct represents a renderable tile linking to its part in the tileset texture. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct GraphicTile(Option); +pub struct GraphicTile(pub Option); impl GraphicTile { /// Creates the correct graphic tile depending on the neighbours. @@ -230,6 +256,82 @@ pub struct PositionedTile { 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 { fn texture(&self) -> Texture { Texture::Overworld @@ -472,67 +574,15 @@ impl Map { rows as f64, ) as usize; - let mut collision_x = false; - let mut collision_y = false; + let mut collision_axis: Option = None; + let mut new = new; for col in min_col..=max_col { for row in min_row..=max_row { - let tile_left = col as f64 * SPRITE_SIZE; - let tile_top = row as f64 * SPRITE_SIZE; - - 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; + if let Some((axis, new_pos)) = self.at(row, col).collides(old, new) { + new.position = new_pos; + collision_axis = CollisionAxis::and_option(collision_axis, axis); } } } @@ -540,35 +590,25 @@ impl Map { // Collision between the player and left border of the level if 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 if 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 if 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; } - match (collision_x, collision_y) { - (true, true) => Some((CollisionAxis::Both, new.position, death)), - (true, false) => Some((CollisionAxis::X, new.position, death)), - (false, true) => Some((CollisionAxis::Y, new.position, death)), - (false, false) => None, + if let Some(collision_axis) = collision_axis { + Some((collision_axis, new.position, death)) + } else { + 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 -} diff --git a/src/engine/mod.rs b/src/engine/mod.rs index f1c773f..1ed0b8a 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -6,6 +6,7 @@ pub mod controls; pub mod input; pub mod map; pub mod math; +pub mod object; pub mod physics; pub mod scene; pub mod texture; @@ -268,6 +269,11 @@ impl Engine { } } + // Draw the objects + for o in self.scene.objects() { + self.draw(o, view, window_size)?; + } + // Draw characters for c in self.scene.characters() { if true { @@ -284,6 +290,10 @@ impl Engine { let source = drawable.texture_rect(self.after_loop); let mut dest = source.clone(); + // Without view + // dest.position = drawable.position(); + + // With view dest.position = drawable.position() - view.position; dest.position.x *= window.x / view.size.x; dest.position.y *= window.y / view.size.y; diff --git a/src/engine/object.rs b/src/engine/object.rs new file mode 100644 index 0000000..664c3e6 --- /dev/null +++ b/src/engine/object.rs @@ -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 + } +} diff --git a/src/engine/scene.rs b/src/engine/scene.rs index d84184b..7679d14 100644 --- a/src/engine/scene.rs +++ b/src/engine/scene.rs @@ -6,14 +6,19 @@ use crate::engine::bbox::Bbox; use crate::engine::character::Character; use crate::engine::input::Action; 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::vector::Vector; /// Contains everything needed to play. pub struct Scene { /// The characters contained in the scene. characters: Vec, + /// The objects contained in the scene. + objects: Vec, + /// The map of the scene. map: Map, } @@ -31,8 +36,10 @@ pub enum State { impl Scene { /// Creates a scene from a map level. pub fn from_map(map: Map) -> Scene { + let object = Object::new(Vector::new(100.0, 300.0)); Scene { characters: vec![], + objects: vec![object], map, } } @@ -46,6 +53,11 @@ impl Scene { 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. pub fn controlable(&self) -> Option<&Character> { for character in &self.characters { @@ -115,6 +127,7 @@ impl Scene { if axis.is_x() { c.speed.x = 0.0; } + if axis.is_y() { c.speed.y = 0.0; c.ground_collision(); @@ -127,6 +140,26 @@ impl Scene { } else { 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 { &self.map } + + /// Returns a reference to the objects of the scene. + pub fn objects(&self) -> &Vec { + &self.objects + } } /// Trait that needs to be implemented for everything that can be updatable. diff --git a/static/sprites/ice.aseprite b/static/sprites/ice.aseprite index eee853aa22dd7a0c88709a6546ba2daa031cdfbb..705016fa874508df0085805c12abc21e417158db 100644 GIT binary patch delta 67 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