diff --git a/src/lib.rs b/src/lib.rs index 9002ed2..1fd3e29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,22 +6,22 @@ //! //! fn main() { //! let game = Game::new("catbox demo", 1000, 800); -//! +//! //! let mut i = 0u8; //! let mut s = Sprite::new("duck.png", 500, 400).unwrap(); //! let mut s2 = Sprite::new("duck.png", 400, 500).unwrap(); -//! +//! //! let mut coll = SpriteCollection::new(); //! for n in 0..10 { //! for o in 0..8 { //! let x = Sprite::new("duck.png", n * 100, o * 100).unwrap(); //! coll.push(x); //! } -//! } +//! } //! game.run(|ctx| { //! i = (i + 1) % 255; //! ctx.set_background_colour(i as u8, 64, 255); -//! +//! //! draw_text( //! ctx, //! format!("i is {}", i), @@ -34,27 +34,27 @@ //! }, //! ) //! .unwrap(); -//! +//! //! let (start_x, start_y) = s.position(); //! let m = get_mouse_state(ctx); //! let x_diff = m.x - start_x; //! let y_diff = m.y - start_y; -//! +//! //! let angle = (y_diff as f64).atan2(x_diff as f64); //! s.set_angle(angle.to_degrees()); -//! +//! //! for spr in coll.iter() { //! let (start_x, start_y) = spr.position(); //! let m = get_mouse_state(ctx); //! let x_diff = m.x - start_x; //! let y_diff = m.y - start_y; -//! +//! //! let angle = (y_diff as f64).atan2(x_diff as f64); //! spr.set_angle(angle.to_degrees()); //! } -//! +//! //! let keys = get_keyboard_state(ctx).keys; -//! +//! //! for key in keys { //! let offset = match key { //! Scancode::Escape => { @@ -67,14 +67,14 @@ //! Scancode::D | Scancode::Right => (5, 0), //! _ => (0, 0), //! }; -//! +//! //! s.translate(offset); -//! +//! //! for spr in coll.iter() { //! spr.translate(offset); //! } //! } -//! +//! //! s2.draw(ctx).unwrap(); //! s.draw(ctx).unwrap(); //! coll.draw(ctx).unwrap(); @@ -83,17 +83,26 @@ //! } //! ``` -use std::{cell::Cell, path::Path, ops::{DerefMut, Deref}, slice::IterMut}; +pub mod physics; + +use std::{ + cell::Cell, + ops::{Deref, DerefMut}, + path::Path, + slice::IterMut, +}; use sdl2::{ image::ImageRWops, + keyboard::Scancode, + mouse::MouseButton, rect::Rect, render::{Canvas, TextureCreator, TextureValueError}, rwops::RWops, surface::Surface, ttf::{FontError, Sdl2TtfContext}, video::{Window, WindowBuildError, WindowContext}, - EventPump, IntegerOrSdlError, mouse::MouseButton, keyboard::Scancode, + EventPump, IntegerOrSdlError, }; #[doc(no_inline)] @@ -275,8 +284,8 @@ impl Sprite { } } -/// Manages a collection of [`Sprite`]s. -/// +/// Manages a collection of [`Sprite`]s. +/// /// Technically, this is a thin wrapper around a simple [`Vec`] of sprites, /// although with some convenience methods. pub struct SpriteCollection { @@ -285,20 +294,18 @@ pub struct SpriteCollection { impl SpriteCollection { /// Creates a new [`SpriteCollection`]. - /// + /// /// See [`Vec::new()`] for more information. /// ``` /// # use cat_box::*; /// let sprites = SpriteCollection::new(); /// ``` pub fn new() -> Self { - Self { - v: Vec::new() - } + Self { v: Vec::new() } } /// Creates a new [`SpriteCollection`] with the specified capacity. - /// + /// /// The collection will be able to hold exactly `capacity` items without reallocating. /// ``` /// # use cat_box::*; @@ -306,7 +313,7 @@ impl SpriteCollection { /// ``` pub fn with_capacity(cap: usize) -> Self { Self { - v: Vec::with_capacity(cap) + v: Vec::with_capacity(cap), } } @@ -318,7 +325,7 @@ impl SpriteCollection { /// # let mut game = Game::new("asjdfhalksjdf", 1, 1); /// # game.run(|ctx| { /// sprites.draw(ctx); - /// # }); + /// # }); /// ``` pub fn draw(&mut self, ctx: &mut Context) -> Result<()> { for s in self.v.iter_mut() { @@ -327,7 +334,7 @@ impl SpriteCollection { Ok(()) } - + /// Add a new [`Sprite`] to the end of this collection. /// ``` /// # use cat_box::*; @@ -336,7 +343,7 @@ impl SpriteCollection { /// sprites.push(s); /// ``` pub fn push(&mut self, s: Sprite) { - self.v.push(s); + self.v.push(s); } /// Inserts an element at position `index` within the collection. @@ -350,7 +357,7 @@ impl SpriteCollection { pub fn insert(&mut self, s: Sprite, index: usize) { self.v.insert(index, s); } - + /// Removes and returns the last element, or `None` if the collection is empty. /// ``` /// # use cat_box::*; @@ -424,6 +431,11 @@ impl SpriteCollection { pub fn get(&self, index: usize) -> Option<&Sprite> { self.v.get(index) } + + /// Return the inner Vec. Only use this method if you know what you're doing. + pub fn inner(&self) -> &Vec { + &self.v + } } impl Deref for SpriteCollection { @@ -464,8 +476,18 @@ impl Context { /// Get the inner [`Canvas`](sdl2::render::Canvas) and [`TextureCreator`](sdl2::render::TextureCreator). /// /// Only use this method if you know what you're doing. - pub fn inner(&mut self) -> (&TextureCreator, &mut Canvas, &mut EventPump) { - (&self.texture_creator, &mut self.canvas, &mut self.event_pump) + pub fn inner( + &mut self, + ) -> ( + &TextureCreator, + &mut Canvas, + &mut EventPump, + ) { + ( + &self.texture_creator, + &mut self.canvas, + &mut self.event_pump, + ) } fn update(&mut self) { @@ -483,7 +505,7 @@ impl Context { if let Event::Quit { .. } = event { return true; } - } + } false } @@ -560,12 +582,12 @@ pub fn draw_text>( pub struct MouseRepr { pub buttons: Vec, pub x: i32, - pub y: i32 + pub y: i32, } /// Representation of the keyboard state. pub struct KeyboardRepr { - pub keys: Vec + pub keys: Vec, } /// Get the mouse state. @@ -602,9 +624,9 @@ pub fn get_keyboard_state(ctx: &mut Context) -> KeyboardRepr { let (_, _, pump) = ctx.inner(); let keyboard = pump.keyboard_state(); - + KeyboardRepr { - keys: keyboard.pressed_scancodes().collect() + keys: keyboard.pressed_scancodes().collect(), } } @@ -662,7 +684,7 @@ impl Game { let s = sdl2::ttf::init().unwrap(); let event_pump = sdl_context.event_pump()?; - + let mut ctx = Context::new(canvas, event_pump, s); loop { diff --git a/src/main.rs b/src/main.rs index fc3bf2d..dbc0ca2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use cat_box::{draw_text, Game, Sprite, SpriteCollection, get_mouse_state, get_keyboard_state}; +use cat_box::{draw_text, get_keyboard_state, get_mouse_state, Game, Sprite, SpriteCollection}; use sdl2::keyboard::Scancode; fn main() { @@ -14,7 +14,7 @@ fn main() { let x = Sprite::new("duck.png", n * 100, o * 100).unwrap(); coll.push(x); } - } + } game.run(|ctx| { i = (i + 1) % 255; ctx.set_background_colour(i as u8, 64, 255); @@ -57,7 +57,7 @@ fn main() { Scancode::Escape => { game.terminate(); (0, 0) - }, + } Scancode::W | Scancode::Up => (0, 5), Scancode::S | Scancode::Down => (0, -5), Scancode::A | Scancode::Left => (-5, 0), @@ -72,6 +72,10 @@ fn main() { } } + if !cat_box::physics::check_for_collision_with_collection(&s2, &coll).is_empty() { + println!("Sprites collided! {}", i); + } + s2.draw(ctx).unwrap(); s.draw(ctx).unwrap(); coll.draw(ctx).unwrap(); diff --git a/src/physics.rs b/src/physics.rs new file mode 100644 index 0000000..4509850 --- /dev/null +++ b/src/physics.rs @@ -0,0 +1,45 @@ +use crate::{Sprite, SpriteCollection}; +use std::cmp::max; + +// https://github.com/pythonarcade/arcade/blob/d2ce45a9b965020cde57a2a88536311e04504e6e/arcade/sprite_list/spatial_hash.py#L356 + +fn collided(sprite1: &Sprite, sprite2: &Sprite) -> bool { + let coll_rad1 = max(sprite1.rect.width(), sprite1.rect.height()) as i32; + let coll_rad2 = max(sprite2.rect.width(), sprite2.rect.height()) as i32; + + let collision_radius = coll_rad1 + coll_rad2; + let collision_diameter = collision_radius * collision_radius; + + let diff_x = sprite1.position().0 - sprite2.position().0; + let diff_x2 = diff_x * diff_x; + + if diff_x2 > collision_diameter { + return false; + } + + let diff_y = sprite1.position().1 - sprite2.position().1; + let diff_y2 = diff_y * diff_y; + + if diff_y2 > collision_diameter { + return false; + } + + return sprite1.rect.has_intersection(sprite2.rect); +} + +/// Check if two sprites are touching or overlapping. +pub fn check_for_collision(sprite1: &Sprite, sprite2: &Sprite) -> bool { + collided(sprite1, sprite2) +} + +/// Check if the sprite is colliding with any sprite in the collection, and return a list of +/// references to the sprites which are colliding +pub fn check_for_collision_with_collection<'a>( + sprite: &Sprite, + list: &'a SpriteCollection, +) -> Vec<&'a Sprite> { + list.inner() + .iter() + .filter(|s| check_for_collision(sprite, s)) + .collect() +}