From 4074267844733949556af129550dfc42fc81da76 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Mon, 8 Feb 2021 19:10:21 +0100 Subject: [PATCH 01/16] Replaced Degrees and Radians with a single Angle type --- src/common/geometry.rs | 143 +++++++++++++++++++++++++++++++++++-------------- src/common/mod.rs | 4 +- src/core/controller.rs | 7 ++- src/core/game.rs | 6 +-- src/core/level/mod.rs | 13 ++--- 5 files changed, 117 insertions(+), 56 deletions(-) diff --git a/src/common/geometry.rs b/src/common/geometry.rs index 3fee2ad..d767d8e 100644 --- a/src/common/geometry.rs +++ b/src/common/geometry.rs @@ -28,12 +28,8 @@ impl Point { } } - pub fn to_radians(&self) -> Radians { - Radians(self.y.atan2(self.x)) - } - - pub fn to_degrees(&self) -> Degrees { - self.to_radians().to_degrees() + pub fn to_angle(&self) -> Angle { + self.y.atan2(self.x).radians() } pub fn to_i32(self) -> Point { @@ -147,46 +143,115 @@ impl From> for (T, T) { } } -impl From for Point { - fn from(item: Degrees) -> Self { - let r = item.0.to_radians(); - Point { - x: r.cos(), - y: r.sin(), - } +impl From for Point { + fn from(item: Angle) -> Self { + Point { + x: item.0.cos(), + y: item.0.sin(), + } } } -impl From for Point { - fn from(item: Radians) -> Self { - Point { - x: item.0.cos(), - y: item.0.sin(), - } - } -} +////////// ANGLE /////////////////////////////////////////////////////////////// #[derive(Debug, Default, PartialEq, Clone, Copy)] -pub struct Degrees(pub f64); -#[derive(Debug, Default, PartialEq, Clone, Copy)] -pub struct Radians(pub f64); +pub struct Angle(pub f64); -impl Degrees { - #[allow(dead_code)] - pub fn to_radians(&self) -> Radians { - Radians(self.0.to_radians()) +pub trait ToAngle { + fn radians(self) -> Angle; + fn degrees(self) -> Angle; +} + +macro_rules! impl_angle { + ($($type:ty),*) => { + $( + impl ToAngle for $type { + fn radians(self) -> Angle { + Angle(self as f64) + } + + fn degrees(self) -> Angle { + Angle((self as f64).to_radians()) + } + } + + impl Mul<$type> for Angle { + type Output = Self; + + fn mul(self, rhs: $type) -> Self { + Angle(self.0 * (rhs as f64)) + } + } + + impl MulAssign<$type> for Angle { + fn mul_assign(&mut self, rhs: $type) { + self.0 *= rhs as f64; + } + } + + impl Div<$type> for Angle { + type Output = Self; + + fn div(self, rhs: $type) -> Self { + Angle(self.0 / (rhs as f64)) + } + } + + impl DivAssign<$type> for Angle { + fn div_assign(&mut self, rhs: $type) { + self.0 /= rhs as f64; + } + } + )* } } -impl Radians { - #[allow(dead_code)] - pub fn to_degrees(&self) -> Degrees { - Degrees(self.0.to_degrees()) +impl_angle!(f32, f64, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize); + +impl Angle { + pub fn to_radians(self) -> f64 { + self.0 + } + + pub fn to_degrees(self) -> f64 { + self.0.to_degrees() } /// Returns the reflection of the incident when mirrored along this angle. - pub fn mirror(&self, incidence: Radians) -> Radians { - Radians((std::f64::consts::PI + self.0 * 2.0 - incidence.0) % std::f64::consts::TAU) + pub fn mirror(&self, incidence: Angle) -> Angle { + Angle((std::f64::consts::PI + self.0 * 2.0 - incidence.0) % std::f64::consts::TAU) + } +} + +// TODO override eq, 0==360 osv + +// addition and subtraction of angles + +impl Add for Angle { + type Output = Self; + + fn add(self, rhs: Angle) -> Self { + Angle(self.0 + rhs.0) + } +} + +impl AddAssign for Angle { + fn add_assign(&mut self, rhs: Angle) { + self.0 += rhs.0; + } +} + +impl Sub for Angle { + type Output = Self; + + fn sub(self, rhs: Angle) -> Self { + Angle(self.0 - rhs.0) + } +} + +impl SubAssign for Angle { + fn sub_assign(&mut self, rhs: Angle) { + self.0 -= rhs.0; } } @@ -402,11 +467,11 @@ mod tests { #[test] fn angles() { - assert_eq!(Radians(0.0).to_degrees(), Degrees(0.0)); - assert_eq!(Radians(std::f64::consts::PI).to_degrees(), Degrees(180.0)); - assert_eq!(Degrees(180.0).to_radians(), Radians(std::f64::consts::PI)); - assert!((Point::from(Degrees(90.0)) - point!(0.0, 1.0)).length() < 0.001); - assert!((Point::from(Radians(std::f64::consts::FRAC_PI_2)) - point!(0.0, 1.0)).length() < 0.001); + assert_eq!(0.radians(), 0.degrees()); + assert_eq!(180.degrees(), std::f64::consts::PI.radians()); + assert_eq!(std::f64::consts::PI.radians().to_degrees(), 180.0); + assert!((Point::from(90.degrees()) - point!(0.0, 1.0)).length() < 0.001); + assert!((Point::from(std::f64::consts::FRAC_PI_2.radians()) - point!(0.0, 1.0)).length() < 0.001); } #[test] diff --git a/src/common/mod.rs b/src/common/mod.rs index 675d973..592bdbb 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -2,8 +2,8 @@ mod geometry; pub use common::geometry::{ Point, Dimension, - Radians, - Degrees, + Angle, + ToAngle, Intersection, supercover_line, }; diff --git a/src/core/controller.rs b/src/core/controller.rs index 54dae94..3cbe391 100644 --- a/src/core/controller.rs +++ b/src/core/controller.rs @@ -1,6 +1,5 @@ -use common::Point; +use common::{Angle, Point}; use {hashmap, point}; -use common::Radians; use sdl2::HapticSubsystem; use sdl2::JoystickSubsystem; use sdl2::event::Event; @@ -67,7 +66,7 @@ pub struct Stick { idy: u8, pub x: f32, pub y: f32, - pub a: Radians, + pub a: Angle, } impl Stick { @@ -80,7 +79,7 @@ impl Stick { Ok(val) => val as f32 / 32768.0, Err(_) => panic!("invalid y axis {}", self.idy), }; - self.a = Radians(self.y.atan2(self.x) as f64); + self.a = point!(self.x as f64, self.y as f64).to_angle(); } #[inline(always)] #[allow(dead_code)] pub fn up(&self) -> bool { self.y < -0.99 } diff --git a/src/core/game.rs b/src/core/game.rs index 39af2e1..2fa7ddd 100644 --- a/src/core/game.rs +++ b/src/core/game.rs @@ -1,6 +1,6 @@ use teststate::TestState; use AppState; -use common::{Point, Radians}; +use common::{Point, ToAngle}; use core::app::StateChange; use core::controller::Controller; use core::controller::ControllerManager; @@ -276,7 +276,7 @@ impl Object for Boll { return Dead } self.bounces -= 1; - let mut a = wall.normal().mirror(self.vel.to_radians()); // TODO interpolera normalen mellan närliggande väggdelar? bollarna studsar väldigt "kantigt" nu + let mut a = wall.normal().mirror(self.vel.to_angle()); // TODO interpolera normalen mellan närliggande väggdelar? bollarna studsar väldigt "kantigt" nu self.pos = pos; self.vel = Point::from(a) * self.vel.length() * 0.35; self.pos += self.vel; // TODO det här kan få bollen att åka igenom en närliggande vägg utan att kollisionstestas, men behövs just nu för att inte kollidera med samma vägg bakifrån @@ -284,7 +284,7 @@ impl Object for Boll { // create another boll use rand::distributions::{Distribution, Normal}; let mut rng = rand::thread_rng(); - a.0 += Normal::new(0.0, 0.1).sample(&mut rng); // TODO slumpen kan ge en vinkel som är under tangenten. vinkel-metoder på väggen istället kanske? + a += Normal::new(0.0, 0.1).sample(&mut rng).radians(); // TODO slumpen kan ge en vinkel som är under tangenten. vinkel-metoder på väggen istället kanske? use rand::Rng; objects.push(Box::new(Boll { vel: Point::from(a) * Normal::new(1.0, 0.25).sample(&mut rng) * self.vel.length() * rng.gen_range(0.25, 1.0), diff --git a/src/core/level/mod.rs b/src/core/level/mod.rs index cccd253..00f3009 100644 --- a/src/core/level/mod.rs +++ b/src/core/level/mod.rs @@ -1,4 +1,4 @@ -use common::{Point, Dimension, Intersection, Radians, supercover_line}; +use common::{Point, Dimension, Intersection, Angle, ToAngle, supercover_line}; use core::render::Renderer; use sprites::SpriteManager; use std::rc::Rc; @@ -90,12 +90,11 @@ impl Level { for wall in &self.walls { for e in &wall.edges { let c = (e.p1 + e.p2) / 2.0; - let mut rad = (e.p2 - e.p1).to_radians(); - rad.0 += std::f64::consts::FRAC_PI_2; + let a = (e.p2 - e.p1).to_angle() + std::f64::consts::FRAC_PI_2.radians(); renderer.draw_line( <(i32, i32)>::from(c.to_i32()), - <(i32, i32)>::from((c + Point::from(rad) * 10.0).to_i32()), + <(i32, i32)>::from((c + Point::from(a) * 10.0).to_i32()), (255, 128, 0)); renderer.draw_line( @@ -248,9 +247,7 @@ impl<'a> Wall<'a> { } } - pub fn normal(&self) -> Radians { - let mut rad = (self.edge.p2 - self.edge.p1).to_radians(); - rad.0 += std::f64::consts::FRAC_PI_2; - rad + pub fn normal(&self) -> Angle { + (self.edge.p2 - self.edge.p1).to_angle() + std::f64::consts::FRAC_PI_2.radians() } } -- 2.11.0 From a9eacb2b9cd3d003b21ddc9c192ec414ac63afed Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Mon, 8 Feb 2021 19:53:46 +0100 Subject: [PATCH 02/16] Implemented PartialEq for Angle --- src/common/geometry.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/common/geometry.rs b/src/common/geometry.rs index d767d8e..50d1994 100644 --- a/src/common/geometry.rs +++ b/src/common/geometry.rs @@ -154,7 +154,7 @@ impl From for Point { ////////// ANGLE /////////////////////////////////////////////////////////////// -#[derive(Debug, Default, PartialEq, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy)] pub struct Angle(pub f64); pub trait ToAngle { @@ -223,7 +223,11 @@ impl Angle { } } -// TODO override eq, 0==360 osv +impl PartialEq for Angle { + fn eq(&self, rhs: &Angle) -> bool { + self.0 % std::f64::consts::TAU == rhs.0 % std::f64::consts::TAU + } +} // addition and subtraction of angles @@ -468,6 +472,7 @@ mod tests { #[test] fn angles() { assert_eq!(0.radians(), 0.degrees()); + assert_eq!(0.degrees(), 360.degrees()); assert_eq!(180.degrees(), std::f64::consts::PI.radians()); assert_eq!(std::f64::consts::PI.radians().to_degrees(), 180.0); assert!((Point::from(90.degrees()) - point!(0.0, 1.0)).length() < 0.001); -- 2.11.0 From 7b724ff393ea83b7a2d43fbfa93199d0c395af92 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Wed, 10 Feb 2021 19:50:42 +0100 Subject: [PATCH 03/16] Toggle debug mode with keypad enter --- src/core/game.rs | 11 ++++-- src/core/level/mod.rs | 92 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 67 insertions(+), 36 deletions(-) diff --git a/src/core/game.rs b/src/core/game.rs index 2fa7ddd..9d51e18 100644 --- a/src/core/game.rs +++ b/src/core/game.rs @@ -22,6 +22,7 @@ use time::Duration; pub struct GameState { world: World, lvlgen: LevelGenerator, + debug_mode: bool, } impl GameState { @@ -30,6 +31,7 @@ impl GameState { GameState { world: World::new(lvlgen.generate()), lvlgen, + ..Default::default() } } } @@ -49,7 +51,7 @@ impl AppState for GameState { } fn render(&mut self, renderer: &mut Renderer, sprites: &SpriteManager) { - self.world.render(renderer, sprites); + self.world.render(renderer, sprites, self.debug_mode); } fn handle_event(&mut self, event: Event) -> Option { @@ -60,6 +62,9 @@ impl AppState for GameState { Event::KeyDown { keycode: Some(Keycode::Return), .. } => { return Some(StateChange::Push(Box::new(TestState::new()))) } + Event::KeyDown { keycode: Some(Keycode::KpEnter), .. } => { + self.debug_mode = !self.debug_mode; + } Event::KeyDown { keycode: Some(Keycode::Space), .. } => { self.lvlgen.seed = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs() as u32; self.world.level = self.lvlgen.generate(); @@ -122,8 +127,8 @@ impl World { println!("\x1b[Kobject count: {}\x1b[1A", self.objects.len()); // clear line, print, move cursor up } - pub fn render(&mut self, renderer: &mut Renderer, sprites: &SpriteManager) { - self.level.render(renderer, sprites); + pub fn render(&mut self, renderer: &mut Renderer, sprites: &SpriteManager, debug_mode: bool) { + self.level.render(renderer, sprites, debug_mode); for o in &mut self.objects { o.render(renderer, sprites); } diff --git a/src/core/level/mod.rs b/src/core/level/mod.rs index 00f3009..578a94e 100644 --- a/src/core/level/mod.rs +++ b/src/core/level/mod.rs @@ -53,35 +53,50 @@ impl Level { grid } - pub fn render(&mut self, renderer: &mut Renderer, _sprites: &SpriteManager) { - // original grid - renderer.canvas().set_draw_color((64, 64, 64)); - let size = &self.grid.scale; - for x in 0..self.grid.size.width { - for y in 0..self.grid.size.height { - if self.grid.cells[x][y] { - renderer.canvas().fill_rect(sdl2::rect::Rect::new( - x as i32 * size.width as i32, - y as i32 * size.height as i32, - size.width as u32, - size.height as u32)).unwrap(); + pub fn render(&mut self, renderer: &mut Renderer, _sprites: &SpriteManager, debug_mode: bool) { + if debug_mode { + // original grid + renderer.canvas().set_draw_color((64, 64, 64)); + let size = &self.grid.scale; + for x in 0..self.grid.size.width { + for y in 0..self.grid.size.height { + if self.grid.cells[x][y] { + renderer.canvas().fill_rect(sdl2::rect::Rect::new( + x as i32 * size.width as i32, + y as i32 * size.height as i32, + size.width as u32, + size.height as u32)).unwrap(); + } + } + } + + // wall grid + renderer.canvas().set_draw_color((0, 32, 0)); + let size = &self.wall_grid.scale; + for x in 0..self.wall_grid.size.width { + for y in 0..self.wall_grid.size.height { + if !self.wall_grid.cells[x][y].is_empty() { + let num = self.wall_grid.cells[x][y].len(); + renderer.canvas().set_draw_color((0, 32*num as u8, 0)); + renderer.canvas().fill_rect(sdl2::rect::Rect::new( + x as i32 * size.width as i32, + y as i32 * size.height as i32, + size.width as u32, + size.height as u32)).unwrap(); + } } } - } - // wall grid - renderer.canvas().set_draw_color((0, 32, 0)); - let size = &self.wall_grid.scale; - for x in 0..self.wall_grid.size.width { - for y in 0..self.wall_grid.size.height { - if !self.wall_grid.cells[x][y].is_empty() { - let num = self.wall_grid.cells[x][y].len(); - renderer.canvas().set_draw_color((0, 32*num as u8, 0)); - renderer.canvas().fill_rect(sdl2::rect::Rect::new( - x as i32 * size.width as i32, - y as i32 * size.height as i32, - size.width as u32, - size.height as u32)).unwrap(); + // wall normals + for wall in &self.walls { + for e in &wall.edges { + let c = (e.p1 + e.p2) / 2.0; + let a = (e.p2 - e.p1).to_angle() + std::f64::consts::FRAC_PI_2.radians(); + + renderer.draw_line( + <(i32, i32)>::from(c.to_i32()), + <(i32, i32)>::from((c + Point::from(a) * 10.0).to_i32()), + (0, 128, 255)); } } } @@ -89,13 +104,24 @@ impl Level { // walls for wall in &self.walls { for e in &wall.edges { - let c = (e.p1 + e.p2) / 2.0; - let a = (e.p2 - e.p1).to_angle() + std::f64::consts::FRAC_PI_2.radians(); - - renderer.draw_line( - <(i32, i32)>::from(c.to_i32()), - <(i32, i32)>::from((c + Point::from(a) * 10.0).to_i32()), - (255, 128, 0)); + if !debug_mode { + let c = (e.p1 + e.p2) / 2.0; + let a = (e.p2 - e.p1).to_angle() - std::f64::consts::FRAC_PI_2.radians(); + + renderer.draw_line( + <(i32, i32)>::from(c.to_i32()), + <(i32, i32)>::from((c + Point::from(a) * 10.0).to_i32()), + (255, 128, 0)); + + renderer.draw_line( + <(i32, i32)>::from(e.p1.to_i32()), + <(i32, i32)>::from((c + Point::from(a) * 20.0).to_i32()), + (96, 48, 0)); + renderer.draw_line( + <(i32, i32)>::from(e.p2.to_i32()), + <(i32, i32)>::from((c + Point::from(a) * 20.0).to_i32()), + (96, 48, 0)); + } renderer.draw_line( <(i32, i32)>::from(e.p1.to_i32()), -- 2.11.0 From 0c56b1f7a5568d6bfbebd196cf63ccef503cf959 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Wed, 10 Feb 2021 20:40:07 +0100 Subject: [PATCH 04/16] Fixed a bunch of clippy suggestions --- src/common/geometry.rs | 4 ++-- src/common/time.rs | 2 +- src/core/app.rs | 4 ++-- src/core/level/lvlgen.rs | 10 +++++----- src/teststate.rs | 7 +++---- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/common/geometry.rs b/src/common/geometry.rs index 50d1994..540db53 100644 --- a/src/common/geometry.rs +++ b/src/common/geometry.rs @@ -278,7 +278,7 @@ impl Intersection { let s = (-s1.y * (p1.x - p3.x) + s1.x * (p1.y - p3.y)) / denomimator; let t = ( s2.x * (p1.y - p3.y) - s2.y * (p1.x - p3.x)) / denomimator; - if s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0 { + if (0.0..=1.0).contains(&s) && (0.0..=1.0).contains(&t) { return Intersection::Point(p1 + (s1 * t)) } } @@ -335,7 +335,7 @@ pub fn supercover_line_int(p1: Point, p2: Point) -> Vec 0 { 1 } else { -1 } ); - let mut p = p1.clone(); + let mut p = p1; let mut points = vec!(point!(p.x as isize, p.y as isize)); let mut i = point!(0, 0); while i.x < n.x || i.y < n.y { diff --git a/src/common/time.rs b/src/common/time.rs index 0468d5d..0902d94 100644 --- a/src/common/time.rs +++ b/src/common/time.rs @@ -9,7 +9,7 @@ pub struct ScopeTimer { impl ScopeTimer { pub fn new(name: &'static str) -> Self { - ScopeTimer { start: Instant::now(), name: name } + ScopeTimer { start: Instant::now(), name } } } diff --git a/src/core/app.rs b/src/core/app.rs index d440eb3..8bed04c 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -171,7 +171,7 @@ impl App { // if let Some(s) = self.states.last_mut() { // s.pause(); // } - state.enter(&mut self.ctrl_man); + state.enter(&self.ctrl_man); self.states.push(state); } StateChange::Pop => { @@ -262,7 +262,7 @@ impl App { fn render(&mut self) { self.renderer.clear(); - self.states.last_mut().unwrap().render(&mut self.renderer, &mut self.sprites); + self.states.last_mut().unwrap().render(&mut self.renderer, &self.sprites); self.renderer.present(); } } diff --git a/src/core/level/lvlgen.rs b/src/core/level/lvlgen.rs index daf27e8..8cdf6df 100644 --- a/src/core/level/lvlgen.rs +++ b/src/core/level/lvlgen.rs @@ -118,7 +118,7 @@ impl LevelGenerator { println!(" {} iterations needed", count); } - fn neighbours(&self, grid: &Vec>, px: usize, py: usize, distance: usize) -> u8 { + fn neighbours(&self, grid: &[Vec], px: usize, py: usize, distance: usize) -> u8 { let mut count = 0; for x in (px - distance)..=(px + distance) { for y in (py - distance)..=(py + distance) { @@ -292,7 +292,7 @@ impl Region { } #[allow(dead_code)] - fn print_grid(&self, grid: &Vec>) { + fn print_grid(&self, grid: &[Vec]) { let w = grid.len(); let h = grid[0].len(); let mut g = vec!(vec!(false; w); h); @@ -325,7 +325,7 @@ impl Region { grid } - fn find_first_point_of_outline(&self, rect: &(usize, usize, usize, usize), grid: &Vec>) -> Point { + fn find_first_point_of_outline(&self, rect: &(usize, usize, usize, usize), grid: &[Vec]) -> Point { let (ox, oy, w, h) = rect; let is_outer_wall = (ox, oy) == (&0, &0); // we know this is always the outer wall of the level for x in 0..*w { @@ -341,7 +341,7 @@ impl Region { panic!("no wall found!"); } - fn find_next_point_of_outline(&self, grid: &Vec>, p: &mut Point, directions: &mut Vec<(isize, isize)>) { + fn find_next_point_of_outline(&self, grid: &[Vec], p: &mut Point, directions: &mut Vec<(isize, isize)>) { directions.rotate_left(2); loop { let d = directions[0]; @@ -353,7 +353,7 @@ impl Region { } } - fn check(&self, p: Point, grid: &Vec>) -> bool { + fn check(&self, p: Point, grid: &[Vec]) -> bool { if p.x < 0 || p.x >= grid.len() as isize || p.y < 0 || p.y >= grid[0].len() as isize { false } else { diff --git a/src/teststate.rs b/src/teststate.rs index 5ea97bb..3437164 100644 --- a/src/teststate.rs +++ b/src/teststate.rs @@ -115,10 +115,9 @@ impl AppState for TestState { fn leave(&mut self) { } - fn handle_event(&mut self, _event: Event) -> Option { - match _event { - Event::MouseMotion { x, y, .. } => self.mouse = point!(x, y), - _ => {} + fn handle_event(&mut self, event: Event) -> Option { + if let Event::MouseMotion { x, y, .. } = event { + self.mouse = point!(x, y); } None } -- 2.11.0 From 953b4c960649b82f4e186c2a9afee5367270f0fc Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Sat, 13 Feb 2021 17:59:18 +0100 Subject: [PATCH 05/16] Moved geometry to root level --- src/boll.rs | 5 ++--- src/common/mod.rs | 10 ---------- src/core/app.rs | 2 +- src/core/controller.rs | 2 +- src/core/game.rs | 4 ++-- src/core/level/lvlgen.rs | 2 +- src/core/level/mod.rs | 2 +- src/{common => }/geometry.rs | 0 src/main.rs | 8 ++++---- src/teststate.rs | 4 ++-- 10 files changed, 14 insertions(+), 25 deletions(-) rename src/{common => }/geometry.rs (100%) diff --git a/src/boll.rs b/src/boll.rs index 5d09563..07c7b28 100644 --- a/src/boll.rs +++ b/src/boll.rs @@ -1,8 +1,7 @@ use core::render::Renderer; -use sdl2::rect::Rect; - -use common::Point; +use geometry::Point; use sdl2::gfx::primitives::DrawRenderer; +use sdl2::rect::Rect; use {SCREEN_HEIGHT, SCREEN_WIDTH}; pub trait Boll { diff --git a/src/common/mod.rs b/src/common/mod.rs index 592bdbb..36d3193 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,13 +1,3 @@ -mod geometry; -pub use common::geometry::{ - Point, - Dimension, - Angle, - ToAngle, - Intersection, - supercover_line, -}; - mod time; pub use common::time::ScopeTimer; diff --git a/src/core/app.rs b/src/core/app.rs index 8bed04c..f5452fd 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -1,7 +1,7 @@ use boll::*; -use common::{Point, Dimension}; use core::controller::ControllerManager; use core::render::Renderer; +use geometry::{Point, Dimension}; use point; // defined in common, but loaded from main... use rand::Rng; use sdl2::event::{Event, WindowEvent}; diff --git a/src/core/controller.rs b/src/core/controller.rs index 3cbe391..de91928 100644 --- a/src/core/controller.rs +++ b/src/core/controller.rs @@ -1,4 +1,4 @@ -use common::{Angle, Point}; +use geometry::{Angle, Point}; use {hashmap, point}; use sdl2::HapticSubsystem; use sdl2::JoystickSubsystem; diff --git a/src/core/game.rs b/src/core/game.rs index 9d51e18..3a8d3c9 100644 --- a/src/core/game.rs +++ b/src/core/game.rs @@ -1,11 +1,10 @@ -use teststate::TestState; use AppState; -use common::{Point, ToAngle}; use core::app::StateChange; use core::controller::Controller; use core::controller::ControllerManager; use core::level::{Level, LevelGenerator, Wall, IntersectResult::Intersection}; use core::render::Renderer; +use geometry::{Point, ToAngle}; use point; use sdl2::event::Event; use sdl2::joystick::PowerLevel; @@ -14,6 +13,7 @@ use sdl2::rect::Rect; use sprites::SpriteManager; use std::cell::RefCell; use std::rc::Rc; +use teststate::TestState; use time::Duration; ////////// GAMESTATE /////////////////////////////////////////////////////////// diff --git a/src/core/level/lvlgen.rs b/src/core/level/lvlgen.rs index 8cdf6df..af6ab76 100644 --- a/src/core/level/lvlgen.rs +++ b/src/core/level/lvlgen.rs @@ -1,4 +1,4 @@ -use common::{Point, Dimension}; +use geometry::{Point, Dimension}; use noise::{NoiseFn, OpenSimplex, Seedable}; use rand::Rng; use super::{Grid, Level, WallRegion}; diff --git a/src/core/level/mod.rs b/src/core/level/mod.rs index 578a94e..c2eb041 100644 --- a/src/core/level/mod.rs +++ b/src/core/level/mod.rs @@ -1,5 +1,5 @@ -use common::{Point, Dimension, Intersection, Angle, ToAngle, supercover_line}; use core::render::Renderer; +use geometry::{Point, Dimension, Intersection, Angle, ToAngle, supercover_line}; use sprites::SpriteManager; use std::rc::Rc; use {point, dimen}; diff --git a/src/common/geometry.rs b/src/geometry.rs similarity index 100% rename from src/common/geometry.rs rename to src/geometry.rs diff --git a/src/main.rs b/src/main.rs index a068a58..4a88954 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,12 @@ extern crate noise; use core::game::GameState; use core::app::*; -mod teststate; -mod core; -#[macro_use] -mod common; mod boll; +mod common; +mod core; +mod geometry; mod sprites; +mod teststate; const SCREEN_WIDTH: u16 = 1280; const SCREEN_HEIGHT: u16 = (SCREEN_WIDTH as f64 * (1440.0 / 2560.0)) as u16; diff --git a/src/teststate.rs b/src/teststate.rs index 3437164..82acb79 100644 --- a/src/teststate.rs +++ b/src/teststate.rs @@ -1,12 +1,12 @@ -use common::{Dimension, Point, Intersection}; use core::app::{AppState, StateChange}; use core::controller::ControllerManager; use core::level::Grid; use core::render::Renderer; -use {point, dimen}; +use geometry::{Dimension, Point, Intersection}; use sdl2::event::Event; use sprites::SpriteManager; use time::{Duration, Instant}; +use {point, dimen}; pub struct TestState { start: Instant, -- 2.11.0 From 5433a77fa53c337c07577dc02d769616945d58ea Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Sat, 13 Feb 2021 18:06:26 +0100 Subject: [PATCH 06/16] Renamed common to util --- src/core/app.rs | 2 +- src/main.rs | 2 +- src/{common => util}/mod.rs | 2 +- src/{common => util}/time.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) rename src/{common => util}/mod.rs (84%) rename src/{common => util}/time.rs (92%) diff --git a/src/core/app.rs b/src/core/app.rs index f5452fd..808277f 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -2,7 +2,7 @@ use boll::*; use core::controller::ControllerManager; use core::render::Renderer; use geometry::{Point, Dimension}; -use point; // defined in common, but loaded from main... +use point; use rand::Rng; use sdl2::event::{Event, WindowEvent}; use sdl2::keyboard::Keycode; diff --git a/src/main.rs b/src/main.rs index 4a88954..d60ad55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,11 +7,11 @@ use core::game::GameState; use core::app::*; mod boll; -mod common; mod core; mod geometry; mod sprites; mod teststate; +mod util; const SCREEN_WIDTH: u16 = 1280; const SCREEN_HEIGHT: u16 = (SCREEN_WIDTH as f64 * (1440.0 / 2560.0)) as u16; diff --git a/src/common/mod.rs b/src/util/mod.rs similarity index 84% rename from src/common/mod.rs rename to src/util/mod.rs index 36d3193..2fb29cf 100644 --- a/src/common/mod.rs +++ b/src/util/mod.rs @@ -1,5 +1,5 @@ mod time; -pub use common::time::ScopeTimer; +pub use util::time::ScopeTimer; #[macro_export] macro_rules! hashmap { diff --git a/src/common/time.rs b/src/util/time.rs similarity index 92% rename from src/common/time.rs rename to src/util/time.rs index 0902d94..b533bb6 100644 --- a/src/common/time.rs +++ b/src/util/time.rs @@ -23,11 +23,11 @@ impl Drop for ScopeTimer { #[macro_export] macro_rules! time_scope { () => { - use common::ScopeTimer; + use util::ScopeTimer; let _magical_scope_timer_ = ScopeTimer::new("scope"); }; ( $name:expr ) => { - use common::ScopeTimer; + use util::ScopeTimer; let _magical_scope_timer_ = ScopeTimer::new($name); }; } -- 2.11.0 From bb3eb700e040846ca793832da4adc133218f2954 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Sun, 14 Feb 2021 11:30:45 +0100 Subject: [PATCH 07/16] Use .signum() instead of if-else --- src/geometry.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/geometry.rs b/src/geometry.rs index 540db53..2d5a70c 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -330,10 +330,7 @@ impl From> for (T, T) { pub fn supercover_line_int(p1: Point, p2: Point) -> Vec> { let d = p2 - p1; let n = point!(d.x.abs(), d.y.abs()); - let step = point!( - if d.x > 0 { 1 } else { -1 }, - if d.y > 0 { 1 } else { -1 } - ); + let step = point!(d.x.signum(), d.y.signum()); let mut p = p1; let mut points = vec!(point!(p.x as isize, p.y as isize)); -- 2.11.0 From 2e679e6fb2c1a93d1cb9b6cd66d19f6239953394 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Sun, 14 Feb 2021 18:31:10 +0100 Subject: [PATCH 08/16] Replaced reference with smart pointer in Wall --- src/core/level/mod.rs | 76 ++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/src/core/level/mod.rs b/src/core/level/mod.rs index c2eb041..4d828de 100644 --- a/src/core/level/mod.rs +++ b/src/core/level/mod.rs @@ -14,7 +14,7 @@ pub use self::lvlgen::LevelGenerator; pub struct Level { pub gravity: Point, pub grid: Grid, - walls: Vec, + walls: Vec>, wall_grid: Grid>>, } @@ -26,7 +26,7 @@ impl Level { Level { gravity, grid, - walls, + walls: walls.into_iter().map(|i| Rc::new(i)).collect(), wall_grid, } } @@ -136,8 +136,8 @@ impl Level { for w in &self.wall_grid.cells[c.x][c.y] { if let Intersection::Point(p) = Intersection::lines(p1, p2, w.p1, w.p2) { let wall = Wall { - region: &self.walls[w.region], - edge: w, + region: Rc::clone(&self.walls[w.region]), + edge: Rc::clone(w), }; return IntersectResult::Intersection(wall, p) } @@ -147,8 +147,8 @@ impl Level { } } -pub enum IntersectResult<'a> { - Intersection(Wall<'a>, Point), +pub enum IntersectResult { + Intersection(Wall, Point), None } @@ -162,16 +162,16 @@ pub struct Grid { } impl Grid { - pub fn at(&self, c: C) -> Option<&T> - where C: Into<(isize, isize)> - { - let c = c.into(); - if c.0 >= 0 && c.0 < self.size.width as isize && c.1 >= 0 && c.1 < self.size.height as isize { - Some(&self.cells[c.0 as usize][c.1 as usize]) - } else { - None - } - } + // pub fn at(&self, c: C) -> Option<&T> + // where C: Into<(isize, isize)> + // { + // let c = c.into(); + // if c.0 >= 0 && c.0 < self.size.width as isize && c.1 >= 0 && c.1 < self.size.height as isize { + // Some(&self.cells[c.0 as usize][c.1 as usize]) + // } else { + // None + // } + // } pub fn to_grid_coordinate(&self, c: C) -> Option> where C: Into<(isize, isize)> @@ -196,7 +196,7 @@ impl Grid { ////////// WALL REGION ///////////////////////////////////////////////////////// -#[derive(Debug)] +#[derive(Debug, Default)] pub struct WallRegion { edges: Vec>, } @@ -219,17 +219,15 @@ impl WallRegion { WallRegion { edges } } - // #[allow(dead_code)] - // fn next(&self, index: EdgeIndex) -> Rc { - // let index = (index + 1) % self.edges.len(); - // Rc::clone(&self.edges[index]) - // } + fn next(&self, index: EdgeIndex) -> Rc { + let index = (index + 1) % self.edges.len(); + Rc::clone(&self.edges[index]) + } - // #[allow(dead_code)] - // fn previous(&self, index: EdgeIndex) -> Rc { - // let index = (index + self.edges.len() + 1) % self.edges.len(); - // Rc::clone(&self.edges[index]) - // } + fn previous(&self, index: EdgeIndex) -> Rc { + let index = (index + self.edges.len() + 1) % self.edges.len(); + Rc::clone(&self.edges[index]) + } } ////////// WALL EDGE /////////////////////////////////////////////////////////// @@ -247,29 +245,25 @@ struct WallEdge { ////////// WALL //////////////////////////////////////////////////////////////// -/// kommer det här att fungera ifall nåt objekt ska spara en referens till Wall? -/// kanske istället ska lägga Vec i en Rc och skicka med en klon av den, samt id:n till regionen och väggen? -pub struct Wall<'a> { - region: &'a WallRegion, - edge: &'a WallEdge, +pub struct Wall { + region: Rc, + edge: Rc, } -impl<'a> Wall<'a> { - pub fn next(&self) -> Wall<'a> { - let next = (self.edge.id + 1) % self.region.edges.len(); - let edge = &self.region.edges[next]; +impl Wall { + #[allow(dead_code)] + pub fn next(self) -> Wall { Wall { + edge: self.region.next(self.edge.id), region: self.region, - edge, } } - pub fn previous(&self) -> Wall<'a> { - let prev = (self.edge.id + self.region.edges.len() - 1) % self.region.edges.len(); - let edge = &self.region.edges[prev]; + #[allow(dead_code)] + pub fn previous(self) -> Wall { Wall { + edge: self.region.previous(self.edge.id), region: self.region, - edge, } } -- 2.11.0 From b1075e661c2ce2f107350b8d4f5f9a92e047b93e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Sun, 14 Feb 2021 20:55:16 +0100 Subject: [PATCH 09/16] Only collide with walls from the front --- src/core/level/mod.rs | 19 ++++++++++++++----- src/geometry.rs | 4 ++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/core/level/mod.rs b/src/core/level/mod.rs index 4d828de..bdc6cb0 100644 --- a/src/core/level/mod.rs +++ b/src/core/level/mod.rs @@ -135,11 +135,13 @@ impl Level { for c in self.wall_grid.grid_coordinates_on_line(p1, p2) { for w in &self.wall_grid.cells[c.x][c.y] { if let Intersection::Point(p) = Intersection::lines(p1, p2, w.p1, w.p2) { - let wall = Wall { - region: Rc::clone(&self.walls[w.region]), - edge: Rc::clone(w), - }; - return IntersectResult::Intersection(wall, p) + if w.point_is_in_front(p1) { + let wall = Wall { + region: Rc::clone(&self.walls[w.region]), + edge: Rc::clone(w), + }; + return IntersectResult::Intersection(wall, p) + } } } } @@ -243,6 +245,13 @@ struct WallEdge { pub p2: Point, } +impl WallEdge { + fn point_is_in_front(&self, p: Point) -> bool { + let cross = (self.p2 - self.p1).cross_product(p - self.p1); + cross > 0.0 + } +} + ////////// WALL //////////////////////////////////////////////////////////////// pub struct Wall { diff --git a/src/geometry.rs b/src/geometry.rs index 2d5a70c..7ddc3ec 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -38,6 +38,10 @@ impl Point { y: self.y as i32, } } + + pub fn cross_product(&self, p: Self) -> f64 { + return self.x * p.y - self.y * p.x; + } } macro_rules! impl_point_op { -- 2.11.0 From 92787d379ad61bc3ffc80d4944b5f19385deebff Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Sun, 14 Feb 2021 20:56:02 +0100 Subject: [PATCH 10/16] Collide with the walls instead of the grid --- src/core/game.rs | 51 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/core/game.rs b/src/core/game.rs index 3a8d3c9..5c1bda8 100644 --- a/src/core/game.rs +++ b/src/core/game.rs @@ -162,6 +162,7 @@ pub struct Character { ctrl: Rc>, pos: Point, vel: Point, + standing_on: Option, } impl Character { @@ -170,6 +171,7 @@ impl Character { ctrl, pos: point!(300.0, 300.0), vel: point!(0.0, 0.0), + standing_on: None, } } } @@ -178,23 +180,38 @@ impl Object for Character { fn update(&mut self, objects: &mut Objects, lvl: &Level, dt: Duration) -> ObjectState { let ctrl = self.ctrl.borrow(); - let x = (self.pos.x / lvl.grid.scale.width as f64).min(lvl.grid.size.width as f64 - 1.0).max(0.0) as usize; - let y = (self.pos.y / lvl.grid.scale.height as f64).min(lvl.grid.size.height as f64 - 1.0).max(0.0) as usize; - self.vel += lvl.gravity; - if lvl.grid.cells[x][y] { - if self.vel.y > 0.0 && !(ctrl.mov.down() && ctrl.jump.is_pressed) { - self.vel.y = 0.0; - self.vel.x *= 0.9; - self.pos.y -= 1.0; - } - - if !ctrl.mov.down() { + match &self.standing_on { + Some(wall) => { if ctrl.jump.is_pressed && !ctrl.jump.was_pressed { - self.vel.y = -5.0; + if ctrl.mov.to_point().length() < 0.1 { + self.vel = wall.normal().into(); + } else { + self.vel = ctrl.mov.to_point(); + } + self.vel *= 5.0; + self.pos += self.vel * 0.1; + self.standing_on = None; + } else { + self.vel *= 0.9; + } + }, + None => { + self.vel += lvl.gravity; + self.pos += self.vel; + + match ctrl.mov.x { + v if v < -0.9 && self.vel.x > -5.0 => { self.vel.x -= 0.5 } + v if v > 0.9 && self.vel.x < 5.0 => { self.vel.x += 0.5 } + _ => {} + } + + if let Intersection(wall, pos) = lvl.intersect_walls(self.pos - self.vel, self.pos) { + self.standing_on = Some(wall); + self.pos = pos; + self.vel = point!(0.0, 0.0); } } } - self.pos += self.vel; if ctrl.shoot.is_pressed { use rand::distributions::{Distribution, Normal}; @@ -202,7 +219,7 @@ impl Object for Character { let direction = if ctrl.aim.to_point().length() > 0.1 { ctrl.aim.to_point() } else { ctrl.mov.to_point() }; for _i in 0..100 { objects.push(Box::new(Boll { - pos: self.pos, + pos: self.pos + point!(0.0, -16.0), // half the height of mario vel: direction * (10.0 + rand::random::()) + point!(normal.sample(&mut rand::thread_rng()), normal.sample(&mut rand::thread_rng())) + self.vel, bounces: 2, })); @@ -223,12 +240,6 @@ impl Object for Character { }; } - match ctrl.mov.x { - v if v < -0.9 && self.vel.x > -5.0 => { self.vel.x -= 0.5 } - v if v > 0.9 && self.vel.x < 5.0 => { self.vel.x += 0.5 } - _ => {} - } - Alive } -- 2.11.0 From e8d0376f121580982bf615f179b001e2784d6029 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Wed, 17 Feb 2021 19:43:18 +0100 Subject: [PATCH 11/16] Stop bolls from passing through walls --- src/core/game.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/game.rs b/src/core/game.rs index 5c1bda8..8caf6ab 100644 --- a/src/core/game.rs +++ b/src/core/game.rs @@ -293,9 +293,8 @@ impl Object for Boll { } self.bounces -= 1; let mut a = wall.normal().mirror(self.vel.to_angle()); // TODO interpolera normalen mellan närliggande väggdelar? bollarna studsar väldigt "kantigt" nu - self.pos = pos; + self.pos = pos + Point::from(wall.normal()) * 0.1; // får bollen att inte åka igenom väggen av misstag p.g.a nedan slumpvinkel self.vel = Point::from(a) * self.vel.length() * 0.35; - self.pos += self.vel; // TODO det här kan få bollen att åka igenom en närliggande vägg utan att kollisionstestas, men behövs just nu för att inte kollidera med samma vägg bakifrån // create another boll use rand::distributions::{Distribution, Normal}; -- 2.11.0 From 09cd68feac9813b82f0c73e7c0429ddf0f719117 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Thu, 18 Feb 2021 18:27:22 +0100 Subject: [PATCH 12/16] Use GameControllers instead of Joysticks --- src/core/app.rs | 2 +- src/core/controller.rs | 206 ++++++++++++++++++++----------------------------- src/core/game.rs | 13 ---- 3 files changed, 83 insertions(+), 138 deletions(-) diff --git a/src/core/app.rs b/src/core/app.rs index 808277f..97679df 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -73,7 +73,7 @@ impl AppBuilder { event_pump, sprites, states: vec!(self.state.unwrap_or_else(|| Box::new(ActiveState::new(screen)))), - ctrl_man: ControllerManager::new(context.joystick()?, context.haptic()?), + ctrl_man: ControllerManager::new(context.game_controller()?, context.haptic()?), }) } diff --git a/src/core/controller.rs b/src/core/controller.rs index de91928..2373cb5 100644 --- a/src/core/controller.rs +++ b/src/core/controller.rs @@ -1,18 +1,18 @@ -use geometry::{Angle, Point}; -use {hashmap, point}; +use geometry::{Angle, ToAngle, Point}; +use sdl2::GameControllerSubsystem; use sdl2::HapticSubsystem; -use sdl2::JoystickSubsystem; +use sdl2::controller::{GameController, Axis as SDLAxis, Button as SDLButton}; use sdl2::event::Event; use sdl2::haptic::Haptic; -use sdl2::joystick::Joystick; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; use time::{Duration, prelude::*}; +use {hashmap, point}; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Button { - id: u8, + id: SDLButton, pub time_pressed: Duration, pub time_released: Duration, pub is_pressed: bool, @@ -21,10 +21,21 @@ pub struct Button { } impl Button { - fn update(&mut self, device: &Joystick, dt: Duration) { + pub fn new(id: SDLButton) -> Self { + Button { + id, + time_pressed: Duration::zero(), + time_released: Duration::zero(), + is_pressed: false, + was_pressed: false, + toggle: false, + } + } + + fn update(&mut self, device: &GameController, dt: Duration) { self.was_pressed = self.is_pressed; - self.is_pressed = match device.button(self.id as u32) { - Ok(true) => { + self.is_pressed = match device.button(self.id) { + true => { if !self.was_pressed { self.time_pressed = 0.seconds(); self.toggle = !self.toggle; @@ -32,53 +43,51 @@ impl Button { self.time_pressed += dt; true } - Ok(false) => { + false => { if self.was_pressed { self.time_released = 0.seconds(); } self.time_released += dt; false } - Err(_) => { panic!("invalid button {}", self.id) } } } } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Axis { - id: u8, + id: SDLAxis, pub val: f32, } impl Axis { #[allow(dead_code)] - fn update(&mut self, device: &Joystick, _dt: Duration) { - self.val = match device.axis(self.id as u32) { - Ok(val) => val as f32 / 32768.0, - Err(_) => panic!("invalid axis {}", self.id), - } + fn update(&mut self, device: &GameController, _dt: Duration) { + self.val = device.axis(self.id) as f32 / 32768.0; } } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Stick { - idx: u8, - idy: u8, + id: (SDLAxis, SDLAxis), pub x: f32, pub y: f32, pub a: Angle, } impl Stick { - fn update(&mut self, device: &Joystick, _dt: Duration) { - self.x = match device.axis(self.idx as u32) { - Ok(val) => val as f32 / 32768.0, - Err(_) => panic!("invalid x axis {}", self.idx), - }; - self.y = match device.axis(self.idy as u32) { - Ok(val) => val as f32 / 32768.0, - Err(_) => panic!("invalid y axis {}", self.idy), - }; + pub fn new(idx: SDLAxis, idy: SDLAxis) -> Self { + Stick { + id: (idx, idy), + x: 0.0, + y: 0.0, + a: 0.radians(), + } + } + + fn update(&mut self, device: &GameController, _dt: Duration) { + self.x = device.axis(self.id.0) as f32 / 32768.0; + self.y = device.axis(self.id.1) as f32 / 32768.0; self.a = point!(self.x as f64, self.y as f64).to_angle(); } @@ -117,34 +126,6 @@ impl From<&Stick> for (f64, f64) { } #[derive(Eq, PartialEq, Hash)] -enum DeviceControls { - AxisLX, - AxisLY, - AxisRX, - AxisRY, - AxisL2, - AxisR2, - ButtonA, - ButtonB, - ButtonY, - ButtonX, - ButtonSelect, - ButtonStart, - ButtonHome, - ButtonL3, - ButtonR3, - ButtonL1, - ButtonR1, - ButtonL2, - ButtonR2, - ButtonUp, - ButtonDown, - ButtonLeft, - ButtonRight, -} -use self::DeviceControls::*; - -#[derive(Eq, PartialEq, Hash)] enum ActionControls { MovementX, MovementY, @@ -158,7 +139,7 @@ use self::ActionControls::*; //#[derive(Debug)] pub struct Controller { - pub device: Joystick, + pub device: GameController, haptic: Option>>, pub mov: Stick, @@ -169,30 +150,29 @@ pub struct Controller { } impl Controller { - pub fn new(device: Joystick, haptic: Option>>) -> Self { - let action_map = get_action_mapping(); - let device_map = get_device_mapping(&device.name()); + pub fn new(device: GameController, haptic: Option>>) -> Self { + let map = get_action_mapping(); let mut ctrl = Controller { device, haptic, - mov: Default::default(), - aim: Default::default(), - jump: Default::default(), - start: Default::default(), - shoot: Default::default(), + mov: Stick::new(*map.axes.get(&MovementX).unwrap(), *map.axes.get(&MovementY).unwrap()), + aim: Stick::new(*map.axes.get(&AimX).unwrap(), *map.axes.get(&AimY).unwrap()), + jump: Button::new(*map.buttons.get(&Jump).unwrap()), + start: Button::new(*map.buttons.get(&Start).unwrap()), + shoot: Button::new(*map.buttons.get(&Shoot).unwrap()), }; - ctrl.set_mapping(&action_map, &device_map); + ctrl.set_mapping(&map); ctrl } - fn set_mapping(&mut self, action: &HashMap, device: &HashMap) { - self.mov.idx = *action.get(&MovementX).map(|i| device.get(i)).flatten().unwrap(); - self.mov.idy = *action.get(&MovementY).map(|i| device.get(i)).flatten().unwrap(); - self.aim.idx = *action.get(&AimX).map(|i| device.get(i)).flatten().unwrap(); - self.aim.idy = *action.get(&AimY).map(|i| device.get(i)).flatten().unwrap(); - self.jump.id = *action.get(&Jump).map(|i| device.get(i)).flatten().unwrap(); - self.shoot.id = *action.get(&Shoot).map(|i| device.get(i)).flatten().unwrap(); - self.start.id = *action.get(&Start).map(|i| device.get(i)).flatten().unwrap(); + fn set_mapping(&mut self, map: &ActionMapping) { + self.mov.id.0 = *map.axes.get(&MovementX).unwrap(); + self.mov.id.1 = *map.axes.get(&MovementY).unwrap(); + self.aim.id.0 = *map.axes.get(&AimX).unwrap(); + self.aim.id.1 = *map.axes.get(&AimY).unwrap(); + self.jump.id = *map.buttons.get(&Jump).unwrap(); + self.shoot.id = *map.buttons.get(&Shoot).unwrap(); + self.start.id = *map.buttons.get(&Start).unwrap(); } pub fn update(&mut self, dt: Duration) { @@ -211,61 +191,39 @@ impl Controller { } } -fn get_action_mapping() -> HashMap { - hashmap!( - MovementX => AxisLX, - MovementY => AxisLY, - AimX => AxisRX, - AimY => AxisRY, - Jump => ButtonA, - Shoot => ButtonR1, - Start => ButtonStart - ) +struct ActionMapping { + axes: HashMap, + buttons: HashMap, } -fn get_device_mapping(device_name: &str) -> HashMap { - match device_name { - "Sony PLAYSTATION(R)3 Controller" => hashmap!( - AxisLX => 0, - AxisLY => 1, - AxisRX => 3, - AxisRY => 4, - AxisL2 => 2, - AxisR2 => 5, - ButtonA => 0, - ButtonB => 1, - ButtonY => 3, - ButtonX => 2, - ButtonSelect => 8, - ButtonStart => 9, - ButtonHome => 10, - ButtonL3 => 11, - ButtonR3 => 12, - ButtonL1 => 4, - ButtonR1 => 5, - ButtonL2 => 6, - ButtonR2 => 7, - ButtonUp => 13, - ButtonDown => 14, - ButtonLeft => 15, - ButtonRight => 16 +fn get_action_mapping() -> ActionMapping { + ActionMapping { + axes: hashmap!( + MovementX => SDLAxis::LeftX, + MovementY => SDLAxis::LeftY, + AimX => SDLAxis::RightX, + AimY => SDLAxis::RightY ), - _ => panic!("No controller mapping for device '{}'", device_name) + buttons: hashmap!( + Jump => SDLButton::A, + Shoot => SDLButton::RightShoulder, + Start => SDLButton::Start + ) } } //#[derive(Debug)] pub struct ControllerManager { - pub joystick: JoystickSubsystem, + pub subsystem: GameControllerSubsystem, haptic: Rc, pub controllers: HashMap>>, } impl ControllerManager { - pub fn new(joystick: JoystickSubsystem, haptic: HapticSubsystem) -> Self { - joystick.set_event_state(true); + pub fn new(subsystem: GameControllerSubsystem, haptic: HapticSubsystem) -> Self { + subsystem.set_event_state(true); let mut c = ControllerManager { - joystick, + subsystem, haptic: Rc::new(haptic), controllers: HashMap::new(), }; @@ -274,7 +232,7 @@ impl ControllerManager { } fn init(&mut self) { - for i in 0..self.joystick.num_joysticks().unwrap() { + for i in 0..self.subsystem.num_joysticks().unwrap() { self.add_device(i); } } @@ -285,18 +243,18 @@ impl ControllerManager { pub fn handle_event(&mut self, event: &Event) { match event { - Event::JoyDeviceAdded { which, .. } => { self.add_device(*which) } - Event::JoyDeviceRemoved { which, .. } => { self.remove_device(*which) } - // Event::JoyButtonDown { which, button_idx, .. } => { println!("device {} button {} down!", which, button_idx) } - // Event::JoyButtonUp { which, button_idx, .. } => { println!("device {} button {} up!", which, button_idx) } - // Event::JoyAxisMotion { which, axis_idx, .. } => { println!("device {} axis motion {}!", which, axis_idx) } + Event::ControllerDeviceAdded { which, .. } => { self.add_device(*which) } + Event::ControllerDeviceRemoved { which, .. } => { self.remove_device(*which) } + // Event::ControllerButtonDown { which, button_idx, .. } => { println!("device {} button {} down!", which, button_idx) } + // Event::ControllerButtonUp { which, button_idx, .. } => { println!("device {} button {} up!", which, button_idx) } + // Event::ControllerAxisMotion { which, axis_idx, .. } => { println!("device {} axis motion {}!", which, axis_idx) } _ => {} } } fn add_device(&mut self, id: u32) { println!("device added ({})!", id); - let mut device = self.joystick.open(id).unwrap(); + let mut device = self.subsystem.open(id).unwrap(); println!("opened {}", device.name()); /* diff --git a/src/core/game.rs b/src/core/game.rs index 8caf6ab..d9df53a 100644 --- a/src/core/game.rs +++ b/src/core/game.rs @@ -7,7 +7,6 @@ use core::render::Renderer; use geometry::{Point, ToAngle}; use point; use sdl2::event::Event; -use sdl2::joystick::PowerLevel; use sdl2::keyboard::Keycode; use sdl2::rect::Rect; use sprites::SpriteManager; @@ -228,18 +227,6 @@ impl Object for Character { self.vel -= direction * 0.1; } - if ctrl.start.is_pressed && !ctrl.start.was_pressed { - match ctrl.device.power_level() { - Ok(PowerLevel::Unknown) => { println!("power level unknown"); } - Ok(PowerLevel::Empty) => { println!("power level empty"); } - Ok(PowerLevel::Low) => { println!("power level low"); } - Ok(PowerLevel::Medium) => { println!("power level medium"); } - Ok(PowerLevel::Full) => { println!("power level full"); } - Ok(PowerLevel::Wired) => { println!("power level wired"); } - Err(_) => {} - }; - } - Alive } -- 2.11.0 From 5d7eff9e9c0f585e9c4d57907f2e72003d39c757 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Wed, 24 Feb 2021 19:29:53 +0100 Subject: [PATCH 13/16] Moved out objects from game module --- src/core/game.rs | 180 +------------------------------------------ src/core/mod.rs | 3 +- src/core/object/boll.rs | 59 ++++++++++++++ src/core/object/character.rs | 118 ++++++++++++++++++++++++++++ src/core/object/mod.rs | 22 ++++++ 5 files changed, 205 insertions(+), 177 deletions(-) create mode 100644 src/core/object/boll.rs create mode 100644 src/core/object/character.rs create mode 100644 src/core/object/mod.rs diff --git a/src/core/game.rs b/src/core/game.rs index d9df53a..c356402 100644 --- a/src/core/game.rs +++ b/src/core/game.rs @@ -1,17 +1,13 @@ use AppState; use core::app::StateChange; -use core::controller::Controller; use core::controller::ControllerManager; -use core::level::{Level, LevelGenerator, Wall, IntersectResult::Intersection}; +use core::level::{Level, LevelGenerator}; +use core::object::{Object, Objects, ObjectState}; +use core::object::character::Character; use core::render::Renderer; -use geometry::{Point, ToAngle}; -use point; use sdl2::event::Event; use sdl2::keyboard::Keycode; -use sdl2::rect::Rect; use sprites::SpriteManager; -use std::cell::RefCell; -use std::rc::Rc; use teststate::TestState; use time::Duration; @@ -114,7 +110,7 @@ impl World { let mut breeding_ground = vec!(); for i in (0..self.objects.len()).rev() { - if self.objects[i].update(&mut breeding_ground, &self.level, dt) == Dead { + if self.objects[i].update(&mut breeding_ground, &self.level, dt) == ObjectState::Dead { self.objects.remove(i); // swap_remove is more efficient, but changes the order of the array } } @@ -137,171 +133,3 @@ impl World { self.objects.push(object); } } - -////////// OBJECT ////////////////////////////////////////////////////////////// - -type Objects = Vec>; - -pub trait Object { - fn update(&mut self, objects: &mut Objects, lvl: &Level, dt: Duration) -> ObjectState; - fn render(&self, _renderer: &mut Renderer, _sprites: &SpriteManager) {} -} - -#[derive(PartialEq)] -pub enum ObjectState { Alive, Dead } -use self::ObjectState::*; - - -pub trait Physical {} -pub trait Drawable {} - -////////// CHARACTER /////////////////////////////////////////////////////////// - -pub struct Character { - ctrl: Rc>, - pos: Point, - vel: Point, - standing_on: Option, -} - -impl Character { - pub fn new(ctrl: Rc>) -> Self { - Character { - ctrl, - pos: point!(300.0, 300.0), - vel: point!(0.0, 0.0), - standing_on: None, - } - } -} - -impl Object for Character { - fn update(&mut self, objects: &mut Objects, lvl: &Level, dt: Duration) -> ObjectState { - let ctrl = self.ctrl.borrow(); - - match &self.standing_on { - Some(wall) => { - if ctrl.jump.is_pressed && !ctrl.jump.was_pressed { - if ctrl.mov.to_point().length() < 0.1 { - self.vel = wall.normal().into(); - } else { - self.vel = ctrl.mov.to_point(); - } - self.vel *= 5.0; - self.pos += self.vel * 0.1; - self.standing_on = None; - } else { - self.vel *= 0.9; - } - }, - None => { - self.vel += lvl.gravity; - self.pos += self.vel; - - match ctrl.mov.x { - v if v < -0.9 && self.vel.x > -5.0 => { self.vel.x -= 0.5 } - v if v > 0.9 && self.vel.x < 5.0 => { self.vel.x += 0.5 } - _ => {} - } - - if let Intersection(wall, pos) = lvl.intersect_walls(self.pos - self.vel, self.pos) { - self.standing_on = Some(wall); - self.pos = pos; - self.vel = point!(0.0, 0.0); - } - } - } - - if ctrl.shoot.is_pressed { - use rand::distributions::{Distribution, Normal}; - let normal = Normal::new(0.0, 0.1); - let direction = if ctrl.aim.to_point().length() > 0.1 { ctrl.aim.to_point() } else { ctrl.mov.to_point() }; - for _i in 0..100 { - objects.push(Box::new(Boll { - pos: self.pos + point!(0.0, -16.0), // half the height of mario - vel: direction * (10.0 + rand::random::()) + point!(normal.sample(&mut rand::thread_rng()), normal.sample(&mut rand::thread_rng())) + self.vel, - bounces: 2, - })); - } - ctrl.rumble(1.0, dt); - self.vel -= direction * 0.1; - } - - Alive - } - - fn render(&self, renderer: &mut Renderer, sprites: &SpriteManager) { - let block = sprites.get("mario"); - let size = 32; - renderer.blit(block, None, Rect::new(self.pos.x as i32 - size as i32 / 2, self.pos.y as i32 - size as i32, size, size)); - - let ctrl = &self.ctrl.borrow(); - let l = 300.0; - let pos = (self.pos.x as i32, self.pos.y as i32); - // // axis values - // let p = (self.pos + ctrl.aim.to_axis_point() * l).to_i32().into(); - // renderer.draw_line(pos, p, (0, 255, 0)); - // draw_cross(renderer, p); - // values limited to unit vector - let p = (self.pos + ctrl.aim.to_point() * l).to_i32().into(); - renderer.draw_line(pos, p, (255, 0, 0)); - draw_cross(renderer, p); - let p = (self.pos + ctrl.mov.to_point() * l).to_i32().into(); - renderer.draw_line(pos, p, (0, 255, 0)); - draw_cross(renderer, p); - // // circle values - // let p = (self.pos + Point::from(ctrl.aim.a) * l).to_i32().into(); - // renderer.draw_line(pos, p, (0, 0, 255)); - // draw_cross(renderer, p); - } -} - -fn draw_cross(renderer: &mut Renderer, p: (i32, i32)) { - renderer.canvas().draw_line((p.0 - 5, p.1), (p.0 + 5, p.1)).unwrap(); - renderer.canvas().draw_line((p.0, p.1 - 5), (p.0, p.1 + 5)).unwrap(); -} - -////////// BOLL //////////////////////////////////////////////////////////////// - -pub struct Boll { - pos: Point, - vel: Point, - bounces: u8, -} - -impl Object for Boll { - fn update(&mut self, objects: &mut Objects, lvl: &Level, _dt: Duration) -> ObjectState { - self.vel += lvl.gravity; - self.pos += self.vel; - - if let Intersection(wall, pos) = lvl.intersect_walls(self.pos - self.vel, self.pos) { - if self.bounces == 0 { - return Dead - } - self.bounces -= 1; - let mut a = wall.normal().mirror(self.vel.to_angle()); // TODO interpolera normalen mellan närliggande väggdelar? bollarna studsar väldigt "kantigt" nu - self.pos = pos + Point::from(wall.normal()) * 0.1; // får bollen att inte åka igenom väggen av misstag p.g.a nedan slumpvinkel - self.vel = Point::from(a) * self.vel.length() * 0.35; - - // create another boll - use rand::distributions::{Distribution, Normal}; - let mut rng = rand::thread_rng(); - a += Normal::new(0.0, 0.1).sample(&mut rng).radians(); // TODO slumpen kan ge en vinkel som är under tangenten. vinkel-metoder på väggen istället kanske? - use rand::Rng; - objects.push(Box::new(Boll { - vel: Point::from(a) * Normal::new(1.0, 0.25).sample(&mut rng) * self.vel.length() * rng.gen_range(0.25, 1.0), - ..*self - })); - } - - Alive - } - - fn render(&self, renderer: &mut Renderer, _sprites: &SpriteManager) { - let block = _sprites.get("block"); - let size = 4 + self.bounces * 6; - renderer.blit(block, None, Rect::new(self.pos.x as i32 - size as i32 / 2, self.pos.y as i32 - size as i32 / 2, size as u32, size as u32)); - // renderer.canvas().set_draw_color((0, self.bounces * 100, 255)); - // renderer.canvas().draw_point((self.pos.x as i32, self.pos.y as i32)).unwrap(); - } -} diff --git a/src/core/mod.rs b/src/core/mod.rs index 9e25ba4..c37e19b 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,5 +1,6 @@ pub mod app; pub mod controller; pub mod game; -pub mod render; pub mod level; +pub mod object; +pub mod render; diff --git a/src/core/object/boll.rs b/src/core/object/boll.rs new file mode 100644 index 0000000..adae989 --- /dev/null +++ b/src/core/object/boll.rs @@ -0,0 +1,59 @@ +use core::level::{Level, IntersectResult::Intersection}; +use core::object::{Object, Objects, ObjectState}; +use core::render::Renderer; +use geometry::{Point, ToAngle}; +use sdl2::rect::Rect; +use sprites::SpriteManager; +use time::Duration; + +////////// BOLL //////////////////////////////////////////////////////////////// + +pub struct Boll { + pos: Point, + vel: Point, + bounces: u8, +} + +impl Boll { + pub fn new(pos: Point, vel: Point, bounces: u8) -> Self { + Boll { pos, vel, bounces } + } +} + +impl Object for Boll { + + fn update(&mut self, objects: &mut Objects, lvl: &Level, _dt: Duration) -> ObjectState { + self.vel += lvl.gravity; + self.pos += self.vel; + + if let Intersection(wall, pos) = lvl.intersect_walls(self.pos - self.vel, self.pos) { + if self.bounces == 0 { + return ObjectState::Dead + } + self.bounces -= 1; + let mut a = wall.normal().mirror(self.vel.to_angle()); // TODO interpolera normalen mellan närliggande väggdelar? bollarna studsar väldigt "kantigt" nu + self.pos = pos + Point::from(wall.normal()) * 0.1; // får bollen att inte åka igenom väggen av misstag p.g.a nedan slumpvinkel + self.vel = Point::from(a) * self.vel.length() * 0.35; + + // create another boll + use rand::distributions::{Distribution, Normal}; + let mut rng = rand::thread_rng(); + a += Normal::new(0.0, 0.1).sample(&mut rng).radians(); // TODO slumpen kan ge en vinkel som är under tangenten. vinkel-metoder på väggen istället kanske? + use rand::Rng; + objects.push(Box::new(Boll { + vel: Point::from(a) * Normal::new(1.0, 0.25).sample(&mut rng) * self.vel.length() * rng.gen_range(0.25, 1.0), + ..*self + })); + } + + ObjectState::Alive + } + + fn render(&self, renderer: &mut Renderer, _sprites: &SpriteManager) { + let block = _sprites.get("block"); + let size = 4 + self.bounces * 6; + renderer.blit(block, None, Rect::new(self.pos.x as i32 - size as i32 / 2, self.pos.y as i32 - size as i32 / 2, size as u32, size as u32)); + // renderer.canvas().set_draw_color((0, self.bounces * 100, 255)); + // renderer.canvas().draw_point((self.pos.x as i32, self.pos.y as i32)).unwrap(); + } +} diff --git a/src/core/object/character.rs b/src/core/object/character.rs new file mode 100644 index 0000000..8632ee2 --- /dev/null +++ b/src/core/object/character.rs @@ -0,0 +1,118 @@ +use core::controller::Controller; +use core::object::boll::Boll; +use core::level::{Level, Wall, IntersectResult::Intersection}; +use core::object::{Object, Objects, ObjectState}; +use core::render::Renderer; +use geometry::Point; +use point; +use sdl2::rect::Rect; +use sprites::SpriteManager; +use std::cell::RefCell; +use std::rc::Rc; +use time::Duration; + +////////// CHARACTER /////////////////////////////////////////////////////////// + +pub struct Character { + ctrl: Rc>, + pos: Point, + vel: Point, + standing_on: Option, +} + +impl Character { + pub fn new(ctrl: Rc>) -> Self { + Character { + ctrl, + pos: point!(300.0, 300.0), + vel: point!(0.0, 0.0), + standing_on: None, + } + } +} + +impl Object for Character { + fn update(&mut self, objects: &mut Objects, lvl: &Level, dt: Duration) -> ObjectState { + let ctrl = self.ctrl.borrow(); + + match &self.standing_on { + Some(wall) => { + if ctrl.jump.is_pressed && !ctrl.jump.was_pressed { + if ctrl.mov.to_point().length() < 0.1 { + self.vel = wall.normal().into(); + } else { + self.vel = ctrl.mov.to_point(); + } + self.vel *= 5.0; + self.pos += self.vel * 0.1; + self.standing_on = None; + } else { + self.vel *= 0.9; + } + }, + None => { + self.vel += lvl.gravity; + self.pos += self.vel; + + match ctrl.mov.x { + v if v < -0.9 && self.vel.x > -5.0 => { self.vel.x -= 0.5 } + v if v > 0.9 && self.vel.x < 5.0 => { self.vel.x += 0.5 } + _ => {} + } + + if let Intersection(wall, pos) = lvl.intersect_walls(self.pos - self.vel, self.pos) { + self.standing_on = Some(wall); + self.pos = pos; + self.vel = point!(0.0, 0.0); + } + } + } + + if ctrl.shoot.is_pressed { + use rand::distributions::{Distribution, Normal}; + let normal = Normal::new(0.0, 0.1); + let direction = if ctrl.aim.to_point().length() > 0.1 { ctrl.aim.to_point() } else { ctrl.mov.to_point() }; + for _i in 0..100 { + objects.push(Box::new(Boll::new( + self.pos + point!(0.0, -16.0), // half the height of mario + direction * (10.0 + rand::random::()) + point!(normal.sample(&mut rand::thread_rng()), normal.sample(&mut rand::thread_rng())) + self.vel, + 2, + ))); + } + ctrl.rumble(1.0, dt); + self.vel -= direction * 0.1; + } + + ObjectState::Alive + } + + fn render(&self, renderer: &mut Renderer, sprites: &SpriteManager) { + let block = sprites.get("mario"); + let size = 32; + renderer.blit(block, None, Rect::new(self.pos.x as i32 - size as i32 / 2, self.pos.y as i32 - size as i32, size, size)); + + let ctrl = &self.ctrl.borrow(); + let l = 300.0; + let pos = (self.pos.x as i32, self.pos.y as i32); + // // axis values + // let p = (self.pos + ctrl.aim.to_axis_point() * l).to_i32().into(); + // renderer.draw_line(pos, p, (0, 255, 0)); + // draw_cross(renderer, p); + // values limited to unit vector + let p = (self.pos + ctrl.aim.to_point() * l).to_i32().into(); + renderer.draw_line(pos, p, (255, 0, 0)); + draw_cross(renderer, p); + let p = (self.pos + ctrl.mov.to_point() * l).to_i32().into(); + renderer.draw_line(pos, p, (0, 255, 0)); + draw_cross(renderer, p); + // // circle values + // let p = (self.pos + Point::from(ctrl.aim.a) * l).to_i32().into(); + // renderer.draw_line(pos, p, (0, 0, 255)); + // draw_cross(renderer, p); + } +} + +fn draw_cross(renderer: &mut Renderer, p: (i32, i32)) { + renderer.canvas().draw_line((p.0 - 5, p.1), (p.0 + 5, p.1)).unwrap(); + renderer.canvas().draw_line((p.0, p.1 - 5), (p.0, p.1 + 5)).unwrap(); +} diff --git a/src/core/object/mod.rs b/src/core/object/mod.rs new file mode 100644 index 0000000..2ecf042 --- /dev/null +++ b/src/core/object/mod.rs @@ -0,0 +1,22 @@ +use core::level::Level; +use core::render::Renderer; +use sprites::SpriteManager; +use time::Duration; + +pub mod boll; +pub mod character; + +pub type Objects = Vec>; + +////////// OBJECT ////////////////////////////////////////////////////////////// + +pub trait Object { + fn update(&mut self, objects: &mut Objects, lvl: &Level, dt: Duration) -> ObjectState; + fn render(&self, _renderer: &mut Renderer, _sprites: &SpriteManager) {} +} + +#[derive(PartialEq)] +pub enum ObjectState { Alive, Dead } + +pub trait Physical {} +pub trait Drawable {} -- 2.11.0 From b1002380f9a0de4510043b09d15fec4590365ab0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Wed, 24 Feb 2021 19:37:19 +0100 Subject: [PATCH 14/16] Removed the unused ActiveState --- src/boll.rs | 82 ----------------------- src/core/app.rs | 193 +---------------------------------------------------- src/core/render.rs | 2 + src/main.rs | 1 - 4 files changed, 5 insertions(+), 273 deletions(-) delete mode 100644 src/boll.rs diff --git a/src/boll.rs b/src/boll.rs deleted file mode 100644 index 07c7b28..0000000 --- a/src/boll.rs +++ /dev/null @@ -1,82 +0,0 @@ -use core::render::Renderer; -use geometry::Point; -use sdl2::gfx::primitives::DrawRenderer; -use sdl2::rect::Rect; -use {SCREEN_HEIGHT, SCREEN_WIDTH}; - -pub trait Boll { - fn update(&mut self); - fn draw(&self, renderer: &mut Renderer, size: u32); -} - -pub struct SquareBoll { - pub pos: Point, - pub vel: Point, -} - -impl Boll for SquareBoll { - fn update(&mut self) { - self.vel.y += 0.1; - self.pos += self.vel; - - if self.pos.x < 0.0 { - self.pos.x = -self.pos.x; - self.vel.x = -self.vel.x; - } - if self.pos.x > SCREEN_WIDTH as f64 { - self.pos.x = SCREEN_WIDTH as f64 - (self.pos.x - SCREEN_WIDTH as f64); - self.vel.x = -self.vel.x; - } - if self.pos.y < 0.0 { - self.pos.y = -self.pos.y; - self.vel.y = -self.vel.y; - } - if self.pos.y > SCREEN_HEIGHT as f64 { - self.pos.y = SCREEN_HEIGHT as f64 - (self.pos.y - SCREEN_HEIGHT as f64); - self.vel.y = -self.vel.y; - } - } - - fn draw(&self, renderer: &mut Renderer, size: u32) { - renderer.canvas().set_draw_color(( - 255 - std::cmp::min(255, (self.vel.length() * 25.0) as u8), - (255.0 * (self.pos.x / SCREEN_WIDTH as f64)) as u8, - (255.0 * (self.pos.y / SCREEN_HEIGHT as f64)) as u8, - 128, - )); - let mut r = Rect::new(0, 0, size, size); - r.center_on((self.pos.x as i32, self.pos.y as i32)); - renderer.canvas().fill_rect(r).unwrap(); - } -} - -pub struct CircleBoll { - pub boll: SquareBoll, -} - -impl CircleBoll { - pub fn new(pos: Point, vel: Point) -> CircleBoll { - CircleBoll { - boll: SquareBoll { pos, vel }, - } - } -} - -impl Boll for CircleBoll { - fn update(&mut self) { - self.boll.update(); - } - - fn draw(&self, renderer: &mut Renderer, size: u32) { - let val = 255 - std::cmp::min(255, (self.boll.vel.length() * 20.0) as u8); - renderer - .canvas() - .filled_circle( - self.boll.pos.x as i16, - self.boll.pos.y as i16, - size as i16, - (val, val, val, 128), - ) - .unwrap(); - } -} diff --git a/src/core/app.rs b/src/core/app.rs index 97679df..6470b91 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -1,20 +1,12 @@ -use boll::*; use core::controller::ControllerManager; use core::render::Renderer; -use geometry::{Point, Dimension}; -use point; -use rand::Rng; +use geometry::{Dimension}; use sdl2::event::{Event, WindowEvent}; use sdl2::keyboard::Keycode; -use sdl2::rect::Rect as SDLRect; use sdl2::video::SwapInterval; use sdl2::{EventPump, VideoSubsystem}; use sprites::SpriteManager; -use std::f32::consts::PI; -use time::{Duration, Instant, prelude::*}; - -const FPS: u32 = 60; -const NS_PER_FRAME: u32 = 1_000_000_000 / FPS; +use time::{Duration, Instant}; #[derive(Default)] pub struct AppBuilder { @@ -61,7 +53,6 @@ impl AppBuilder { let canvas = window.into_canvas().build().unwrap(); let sprites = SpriteManager::new(canvas.texture_creator()); - let screen = canvas.output_size().unwrap(); let renderer = Renderer::new(canvas); video.gl_set_swap_interval(SwapInterval::VSync)?; @@ -72,7 +63,7 @@ impl AppBuilder { renderer, event_pump, sprites, - states: vec!(self.state.unwrap_or_else(|| Box::new(ActiveState::new(screen)))), + states: vec!(self.state.unwrap()), ctrl_man: ControllerManager::new(context.game_controller()?, context.haptic()?), }) } @@ -280,181 +271,3 @@ pub trait AppState { fn render(&mut self, renderer: &mut Renderer, sprites: &SpriteManager); fn handle_event(&mut self, event: Event) -> Option; } - -type Bollar = Vec>; - -#[derive(Default)] -pub struct ActiveState { - screen: Dimension, - bolls: Bollar, - boll_size: u32, - mario_angle: f64, -} - -impl ActiveState { - pub fn new(screen: (u32, u32)) -> ActiveState { - ActiveState { - bolls: Bollar::new(), - boll_size: 1, - screen: Dimension::from(screen), - ..Default::default() - } - } - - fn change_boll_count(&mut self, delta: i32) { - #[allow(clippy::comparison_chain)] - if delta > 0 { - for _i in 0..delta { - self.add_boll(); - } - } else if delta < 0 { - for _i in 0..(-delta) { - self.bolls.pop(); - } - } - } - - fn add_boll(&mut self) { - let mut rng = rand::thread_rng(); - self.bolls.push(Box::new(SquareBoll { - pos: point!( - rng.gen_range(0, self.screen.width) as f64, - rng.gen_range(0, self.screen.height) as f64 - ), - vel: point!(rng.gen_range(-2.0, 2.0), rng.gen_range(-2.0, 2.0)), - })); - } -} - -impl AppState for ActiveState { - fn enter(&mut self, _ctrl_man: &ControllerManager) {} - - fn update(&mut self, dt: Duration) -> Option { - for b in &mut self.bolls { - b.update(); - } - - match dt { - ns if ns < (NS_PER_FRAME - 90_0000).nanoseconds() => self.change_boll_count(100), - ns if ns > (NS_PER_FRAME + 90_0000).nanoseconds() => self.change_boll_count(-100), - _ => {} - } - - None - } - - fn render(&mut self, renderer: &mut Renderer, sprites: &SpriteManager) { - /* draw square of blocks */ { - let blocks = 20; - let size = 32; - let offset = point!( - (self.screen.width as i32 - (blocks + 1) * size) / 2, - (self.screen.height as i32 - (blocks + 1) * size) / 2 - ); - let block = sprites.get("block"); - for i in 0..blocks { - renderer - .blit( - block, - None, - SDLRect::new((i) * size + offset.x, offset.y, size as u32, size as u32), - ); - renderer - .blit( - block, - None, - SDLRect::new( - (blocks - i) * size + offset.x, - (blocks) * size + offset.y, - size as u32, - size as u32, - ), - ); - renderer - .blit( - block, - None, - SDLRect::new( - offset.x, - (blocks - i) * size + offset.y, - size as u32, - size as u32, - ), - ); - renderer - .blit( - block, - None, - SDLRect::new( - (blocks) * size + offset.x, - (i) * size + offset.y, - size as u32, - size as u32, - ), - ); - } - } - - /* draw mario */ { - let size = 64; - let offset = point!( - (self.screen.width as i32 - size) / 2, - (self.screen.height as i32 - size) / 2 - ); - let radius = 110.0 + size as f32 * 0.5; - let angle = (self.mario_angle as f32 - 90.0) * PI / 180.0; - let offset2 = point!((angle.cos() * radius) as i32, (angle.sin() * radius) as i32); - renderer - .blit_ex( - sprites.get("mario"), - None, - SDLRect::new( - offset.x + offset2.x, - offset.y + offset2.y, - size as u32, - size as u32, - ), - self.mario_angle, - sdl2::rect::Point::new(size / 2, size / 2), - false, - false, - ); - self.mario_angle = (self.mario_angle + 1.0) % 360.0; - } - - /* draw circles and ellipses*/ { - let p = point!((self.screen.width / 2) as i16, (self.screen.height / 2) as i16); - renderer.circle(p, 100, (255, 255, 255)); - renderer.circle(p, 110, (255, 255, 255)); - renderer.ellipse(p, (50, 100), (255, 255, 255)); - renderer.ellipse(p, (110, 55), (255, 255, 255)); - } - - for b in &self.bolls { - b.draw(renderer, self.boll_size); - } - } - - fn leave(&mut self) { - println!("number of bolls: {}", self.bolls.len()); - } - - fn handle_event(&mut self, event: Event) -> Option { - match event { - Event::KeyDown { - keycode: Some(Keycode::KpPlus), - .. - } => self.boll_size = std::cmp::min(self.boll_size + 1, 32), - Event::KeyDown { - keycode: Some(Keycode::KpMinus), - .. - } => self.boll_size = std::cmp::max(self.boll_size - 1, 1), - Event::MouseMotion { x, y, .. } => self.bolls.push(Box::new(CircleBoll::new( - point!(x as f64, y as f64), - point!(0.0, 0.0), - ))), - _ => {} - } - None - } -} diff --git a/src/core/render.rs b/src/core/render.rs index 4d375d6..5afa368 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -54,6 +54,7 @@ impl Renderer { self.canvas.copy(texture, src, dst).unwrap(); } + #[allow(dead_code)] pub fn blit_ex(&mut self, texture: &Texture, src: R1, @@ -77,6 +78,7 @@ impl Renderer { self.canvas.aa_circle(pos.0, pos.1, rad, col.into()).unwrap(); } + #[allow(dead_code)] pub fn ellipse(&self, pos: P, rad: R, col: C) where P: Into<(i16, i16)>, R: Into<(i16, i16)>, diff --git a/src/main.rs b/src/main.rs index d60ad55..b28fd5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,6 @@ extern crate noise; use core::game::GameState; use core::app::*; -mod boll; mod core; mod geometry; mod sprites; -- 2.11.0 From 8bfd1477ba05dfcfbe1dbb34d2c14d4e81ab2df7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Sun, 28 Feb 2021 17:20:51 +0100 Subject: [PATCH 15/16] Character states - first draft --- src/core/object/character.rs | 145 ++++++++++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 43 deletions(-) diff --git a/src/core/object/character.rs b/src/core/object/character.rs index 8632ee2..18cb0aa 100644 --- a/src/core/object/character.rs +++ b/src/core/object/character.rs @@ -11,22 +11,52 @@ use std::cell::RefCell; use std::rc::Rc; use time::Duration; +////////// STATE /////////////////////////////////////////////////////////////// + +trait State { + fn enter(&self) {} + fn exit(&self) {} + fn update(&mut self, body: &mut Body, ctrl: &Controller, objects: &mut Objects, lvl: &Level, dt: Duration) -> Option>; +} + ////////// CHARACTER /////////////////////////////////////////////////////////// +struct Body { + pub pos: Point, + pub vel: Point, + pub standing_on: Option, +} + +struct StateHolder(Box); + +impl StateHolder { + pub fn get(&mut self) -> &mut Box { + &mut self.0 + } + + pub fn set(&mut self, state: Box) { + self.0.exit(); + self.0 = state; + self.0.enter(); + } +} + pub struct Character { ctrl: Rc>, - pos: Point, - vel: Point, - standing_on: Option, + body: Body, + state: StateHolder, // Plays well with the borrow checker } impl Character { pub fn new(ctrl: Rc>) -> Self { Character { ctrl, - pos: point!(300.0, 300.0), - vel: point!(0.0, 0.0), - standing_on: None, + body: Body { + pos: point!(300.0, 300.0), + vel: point!(0.0, 0.0), + standing_on: None, + }, + state: StateHolder(Box::new(FallState)), } } } @@ -35,35 +65,19 @@ impl Object for Character { fn update(&mut self, objects: &mut Objects, lvl: &Level, dt: Duration) -> ObjectState { let ctrl = self.ctrl.borrow(); - match &self.standing_on { - Some(wall) => { - if ctrl.jump.is_pressed && !ctrl.jump.was_pressed { - if ctrl.mov.to_point().length() < 0.1 { - self.vel = wall.normal().into(); - } else { - self.vel = ctrl.mov.to_point(); - } - self.vel *= 5.0; - self.pos += self.vel * 0.1; - self.standing_on = None; - } else { - self.vel *= 0.9; - } - }, - None => { - self.vel += lvl.gravity; - self.pos += self.vel; - - match ctrl.mov.x { - v if v < -0.9 && self.vel.x > -5.0 => { self.vel.x -= 0.5 } - v if v > 0.9 && self.vel.x < 5.0 => { self.vel.x += 0.5 } - _ => {} - } + if let Some(state) = self.state.get().update(&mut self.body, &ctrl, objects, lvl, dt) { + self.state.set(state); + } - if let Intersection(wall, pos) = lvl.intersect_walls(self.pos - self.vel, self.pos) { - self.standing_on = Some(wall); - self.pos = pos; - self.vel = point!(0.0, 0.0); + match &self.body.standing_on { + Some(_wall) => { + }, + None => { // in air + if let Intersection(wall, pos) = lvl.intersect_walls(self.body.pos - self.body.vel, self.body.pos) { + self.body.standing_on = Some(wall); + self.body.pos = pos; + self.body.vel = point!(0.0, 0.0); + self.state.set(Box::new(StandState)); } } } @@ -74,13 +88,13 @@ impl Object for Character { let direction = if ctrl.aim.to_point().length() > 0.1 { ctrl.aim.to_point() } else { ctrl.mov.to_point() }; for _i in 0..100 { objects.push(Box::new(Boll::new( - self.pos + point!(0.0, -16.0), // half the height of mario - direction * (10.0 + rand::random::()) + point!(normal.sample(&mut rand::thread_rng()), normal.sample(&mut rand::thread_rng())) + self.vel, + self.body.pos + point!(0.0, -16.0), // half the height of mario + direction * (10.0 + rand::random::()) + point!(normal.sample(&mut rand::thread_rng()), normal.sample(&mut rand::thread_rng())) + self.body.vel, 2, ))); } ctrl.rumble(1.0, dt); - self.vel -= direction * 0.1; + self.body.vel -= direction * 0.1; } ObjectState::Alive @@ -89,24 +103,24 @@ impl Object for Character { fn render(&self, renderer: &mut Renderer, sprites: &SpriteManager) { let block = sprites.get("mario"); let size = 32; - renderer.blit(block, None, Rect::new(self.pos.x as i32 - size as i32 / 2, self.pos.y as i32 - size as i32, size, size)); + renderer.blit(block, None, Rect::new(self.body.pos.x as i32 - size as i32 / 2, self.body.pos.y as i32 - size as i32, size, size)); let ctrl = &self.ctrl.borrow(); let l = 300.0; - let pos = (self.pos.x as i32, self.pos.y as i32); + let pos = (self.body.pos.x as i32, self.body.pos.y as i32); // // axis values - // let p = (self.pos + ctrl.aim.to_axis_point() * l).to_i32().into(); + // let p = (self.body.pos + ctrl.aim.to_axis_point() * l).to_i32().into(); // renderer.draw_line(pos, p, (0, 255, 0)); // draw_cross(renderer, p); // values limited to unit vector - let p = (self.pos + ctrl.aim.to_point() * l).to_i32().into(); + let p = (self.body.pos + ctrl.aim.to_point() * l).to_i32().into(); renderer.draw_line(pos, p, (255, 0, 0)); draw_cross(renderer, p); - let p = (self.pos + ctrl.mov.to_point() * l).to_i32().into(); + let p = (self.body.pos + ctrl.mov.to_point() * l).to_i32().into(); renderer.draw_line(pos, p, (0, 255, 0)); draw_cross(renderer, p); // // circle values - // let p = (self.pos + Point::from(ctrl.aim.a) * l).to_i32().into(); + // let p = (self.body.pos + Point::from(ctrl.aim.a) * l).to_i32().into(); // renderer.draw_line(pos, p, (0, 0, 255)); // draw_cross(renderer, p); } @@ -116,3 +130,48 @@ fn draw_cross(renderer: &mut Renderer, p: (i32, i32)) { renderer.canvas().draw_line((p.0 - 5, p.1), (p.0 + 5, p.1)).unwrap(); renderer.canvas().draw_line((p.0, p.1 - 5), (p.0, p.1 + 5)).unwrap(); } + +////////// FALLING ///////////////////////////////////////////////////////////// + +struct FallState; + +impl State for FallState { + fn update(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects, lvl: &Level, _dt: Duration) -> Option> { + body.vel += lvl.gravity; + body.pos += body.vel; + + match ctrl.mov.x { + v if v < -0.9 && body.vel.x > -5.0 => { body.vel.x -= 0.5 } + v if v > 0.9 && body.vel.x < 5.0 => { body.vel.x += 0.5 } + _ => {} + } + + None + } +} + +////////// STANDING ///////////////////////////////////////////////////////////// + +struct StandState; + +impl State for StandState { + fn update(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects, _lvl: &Level, _dt: Duration) -> Option> { + if let Some(wall) = &body.standing_on { + if ctrl.jump.is_pressed && !ctrl.jump.was_pressed { + if ctrl.mov.to_point().length() < 0.1 { + body.vel = wall.normal().into(); + } else { + body.vel = ctrl.mov.to_point(); + } + body.vel *= 5.0; + body.pos += body.vel * 0.1; + body.standing_on = None; + return Some(Box::new(FallState)) + } else { + body.vel *= 0.9; + } + } + + None + } +} -- 2.11.0 From 856c3740c76feea6a82cc95b5e190b1ccd165621 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomas=20Wenstr=C3=B6m?= Date: Sun, 28 Feb 2021 20:58:13 +0100 Subject: [PATCH 16/16] Added a jumping trigger and state --- src/core/object/character.rs | 111 +++++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 36 deletions(-) diff --git a/src/core/object/character.rs b/src/core/object/character.rs index 18cb0aa..3d789f2 100644 --- a/src/core/object/character.rs +++ b/src/core/object/character.rs @@ -11,10 +11,16 @@ use std::cell::RefCell; use std::rc::Rc; use time::Duration; +////////// TRIGGER ///////////////////////////////////////////////////////////// + +trait Trigger { + fn update(&mut self, body: &Body, ctrl: &Controller, dt: Duration) -> Option>; +} + ////////// STATE /////////////////////////////////////////////////////////////// trait State { - fn enter(&self) {} + fn enter(&mut self, _body: &mut Body, _ctrl: &Controller, _objects: &mut Objects) {} fn exit(&self) {} fn update(&mut self, body: &mut Body, ctrl: &Controller, objects: &mut Objects, lvl: &Level, dt: Duration) -> Option>; } @@ -22,29 +28,16 @@ trait State { ////////// CHARACTER /////////////////////////////////////////////////////////// struct Body { - pub pos: Point, - pub vel: Point, - pub standing_on: Option, -} - -struct StateHolder(Box); - -impl StateHolder { - pub fn get(&mut self) -> &mut Box { - &mut self.0 - } - - pub fn set(&mut self, state: Box) { - self.0.exit(); - self.0 = state; - self.0.enter(); - } + pos: Point, + vel: Point, + standing_on: Option, } pub struct Character { ctrl: Rc>, body: Body, - state: StateHolder, // Plays well with the borrow checker + triggers: Vec>, + state: Box, } impl Character { @@ -56,7 +49,8 @@ impl Character { vel: point!(0.0, 0.0), standing_on: None, }, - state: StateHolder(Box::new(FallState)), + triggers: vec!(Box::new(JumpTrigger)), + state: Box::new(FallState), } } } @@ -65,8 +59,10 @@ impl Object for Character { fn update(&mut self, objects: &mut Objects, lvl: &Level, dt: Duration) -> ObjectState { let ctrl = self.ctrl.borrow(); - if let Some(state) = self.state.get().update(&mut self.body, &ctrl, objects, lvl, dt) { - self.state.set(state); + if let Some(state) = self.state.update(&mut self.body, &ctrl, objects, lvl, dt) { + self.state.exit(); + self.state = state; + self.state.enter(&mut self.body, &ctrl, objects); } match &self.body.standing_on { @@ -77,11 +73,22 @@ impl Object for Character { self.body.standing_on = Some(wall); self.body.pos = pos; self.body.vel = point!(0.0, 0.0); - self.state.set(Box::new(StandState)); + + self.state.exit(); + self.state = Box::new(StandState); + self.state.enter(&mut self.body, &ctrl, objects); } } } + for trigger in &mut self.triggers { + if let Some(state) = trigger.update(&self.body, &ctrl, dt) { + self.state.exit(); + self.state = state; + self.state.enter(&mut self.body, &ctrl, objects); + } + } + if ctrl.shoot.is_pressed { use rand::distributions::{Distribution, Normal}; let normal = Normal::new(0.0, 0.1); @@ -150,26 +157,58 @@ impl State for FallState { } } -////////// STANDING ///////////////////////////////////////////////////////////// +////////// STANDING //////////////////////////////////////////////////////////// struct StandState; impl State for StandState { - fn update(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects, _lvl: &Level, _dt: Duration) -> Option> { + fn update(&mut self, body: &mut Body, _ctrl: &Controller, _objects: &mut Objects, _lvl: &Level, _dt: Duration) -> Option> { + if let Some(_wall) = &body.standing_on { + body.vel *= 0.9; + } + + None + } +} + +////////// JUMPING ///////////////////////////////////////////////////////////// + +struct JumpTrigger; + +impl Trigger for JumpTrigger { + fn update(&mut self, body: &Body, ctrl: &Controller, _dt: Duration) -> Option> { + if body.standing_on.is_some() && ctrl.jump.is_pressed && !ctrl.jump.was_pressed { // this is redundant now because JumpState needs a wall to get starting velocity, but that will probably change + Some(Box::new(JumpState)) + } else { + None + } + } +} + +struct JumpState; + +impl State for JumpState { + fn enter(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects) { if let Some(wall) = &body.standing_on { - if ctrl.jump.is_pressed && !ctrl.jump.was_pressed { - if ctrl.mov.to_point().length() < 0.1 { - body.vel = wall.normal().into(); - } else { - body.vel = ctrl.mov.to_point(); - } - body.vel *= 5.0; - body.pos += body.vel * 0.1; - body.standing_on = None; - return Some(Box::new(FallState)) + if ctrl.mov.to_point().length() < 0.1 { + body.vel = wall.normal().into(); } else { - body.vel *= 0.9; + body.vel = ctrl.mov.to_point(); } + body.vel *= 5.0; + body.pos += body.vel * 0.1; + body.standing_on = None; + } + } + + fn update(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects, lvl: &Level, _dt: Duration) -> Option> { + body.vel += lvl.gravity; + body.pos += body.vel; + + match ctrl.mov.x { + v if v < -0.9 && body.vel.x > -5.0 => { body.vel.x -= 0.5 } + v if v > 0.9 && body.vel.x < 5.0 => { body.vel.x += 0.5 } + _ => {} } None -- 2.11.0