Add collision detection methods

This commit is contained in:
Yash Karandikar 2022-04-12 13:09:28 -05:00
parent 3eb772cbd5
commit 49619bd141
3 changed files with 109 additions and 38 deletions

View file

@ -6,22 +6,22 @@
//! //!
//! fn main() { //! fn main() {
//! let game = Game::new("catbox demo", 1000, 800); //! let game = Game::new("catbox demo", 1000, 800);
//! //!
//! let mut i = 0u8; //! let mut i = 0u8;
//! let mut s = Sprite::new("duck.png", 500, 400).unwrap(); //! let mut s = Sprite::new("duck.png", 500, 400).unwrap();
//! let mut s2 = Sprite::new("duck.png", 400, 500).unwrap(); //! let mut s2 = Sprite::new("duck.png", 400, 500).unwrap();
//! //!
//! let mut coll = SpriteCollection::new(); //! let mut coll = SpriteCollection::new();
//! for n in 0..10 { //! for n in 0..10 {
//! for o in 0..8 { //! for o in 0..8 {
//! let x = Sprite::new("duck.png", n * 100, o * 100).unwrap(); //! let x = Sprite::new("duck.png", n * 100, o * 100).unwrap();
//! coll.push(x); //! coll.push(x);
//! } //! }
//! } //! }
//! game.run(|ctx| { //! game.run(|ctx| {
//! i = (i + 1) % 255; //! i = (i + 1) % 255;
//! ctx.set_background_colour(i as u8, 64, 255); //! ctx.set_background_colour(i as u8, 64, 255);
//! //!
//! draw_text( //! draw_text(
//! ctx, //! ctx,
//! format!("i is {}", i), //! format!("i is {}", i),
@ -34,27 +34,27 @@
//! }, //! },
//! ) //! )
//! .unwrap(); //! .unwrap();
//! //!
//! let (start_x, start_y) = s.position(); //! let (start_x, start_y) = s.position();
//! let m = get_mouse_state(ctx); //! let m = get_mouse_state(ctx);
//! let x_diff = m.x - start_x; //! let x_diff = m.x - start_x;
//! let y_diff = m.y - start_y; //! let y_diff = m.y - start_y;
//! //!
//! let angle = (y_diff as f64).atan2(x_diff as f64); //! let angle = (y_diff as f64).atan2(x_diff as f64);
//! s.set_angle(angle.to_degrees()); //! s.set_angle(angle.to_degrees());
//! //!
//! for spr in coll.iter() { //! for spr in coll.iter() {
//! let (start_x, start_y) = spr.position(); //! let (start_x, start_y) = spr.position();
//! let m = get_mouse_state(ctx); //! let m = get_mouse_state(ctx);
//! let x_diff = m.x - start_x; //! let x_diff = m.x - start_x;
//! let y_diff = m.y - start_y; //! let y_diff = m.y - start_y;
//! //!
//! let angle = (y_diff as f64).atan2(x_diff as f64); //! let angle = (y_diff as f64).atan2(x_diff as f64);
//! spr.set_angle(angle.to_degrees()); //! spr.set_angle(angle.to_degrees());
//! } //! }
//! //!
//! let keys = get_keyboard_state(ctx).keys; //! let keys = get_keyboard_state(ctx).keys;
//! //!
//! for key in keys { //! for key in keys {
//! let offset = match key { //! let offset = match key {
//! Scancode::Escape => { //! Scancode::Escape => {
@ -67,14 +67,14 @@
//! Scancode::D | Scancode::Right => (5, 0), //! Scancode::D | Scancode::Right => (5, 0),
//! _ => (0, 0), //! _ => (0, 0),
//! }; //! };
//! //!
//! s.translate(offset); //! s.translate(offset);
//! //!
//! for spr in coll.iter() { //! for spr in coll.iter() {
//! spr.translate(offset); //! spr.translate(offset);
//! } //! }
//! } //! }
//! //!
//! s2.draw(ctx).unwrap(); //! s2.draw(ctx).unwrap();
//! s.draw(ctx).unwrap(); //! s.draw(ctx).unwrap();
//! coll.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::{ use sdl2::{
image::ImageRWops, image::ImageRWops,
keyboard::Scancode,
mouse::MouseButton,
rect::Rect, rect::Rect,
render::{Canvas, TextureCreator, TextureValueError}, render::{Canvas, TextureCreator, TextureValueError},
rwops::RWops, rwops::RWops,
surface::Surface, surface::Surface,
ttf::{FontError, Sdl2TtfContext}, ttf::{FontError, Sdl2TtfContext},
video::{Window, WindowBuildError, WindowContext}, video::{Window, WindowBuildError, WindowContext},
EventPump, IntegerOrSdlError, mouse::MouseButton, keyboard::Scancode, EventPump, IntegerOrSdlError,
}; };
#[doc(no_inline)] #[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, /// Technically, this is a thin wrapper around a simple [`Vec`] of sprites,
/// although with some convenience methods. /// although with some convenience methods.
pub struct SpriteCollection { pub struct SpriteCollection {
@ -285,20 +294,18 @@ pub struct SpriteCollection {
impl SpriteCollection { impl SpriteCollection {
/// Creates a new [`SpriteCollection`]. /// Creates a new [`SpriteCollection`].
/// ///
/// See [`Vec::new()`] for more information. /// See [`Vec::new()`] for more information.
/// ``` /// ```
/// # use cat_box::*; /// # use cat_box::*;
/// let sprites = SpriteCollection::new(); /// let sprites = SpriteCollection::new();
/// ``` /// ```
pub fn new() -> Self { pub fn new() -> Self {
Self { Self { v: Vec::new() }
v: Vec::new()
}
} }
/// Creates a new [`SpriteCollection`] with the specified capacity. /// Creates a new [`SpriteCollection`] with the specified capacity.
/// ///
/// The collection will be able to hold exactly `capacity` items without reallocating. /// The collection will be able to hold exactly `capacity` items without reallocating.
/// ``` /// ```
/// # use cat_box::*; /// # use cat_box::*;
@ -306,7 +313,7 @@ impl SpriteCollection {
/// ``` /// ```
pub fn with_capacity(cap: usize) -> Self { pub fn with_capacity(cap: usize) -> Self {
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); /// # let mut game = Game::new("asjdfhalksjdf", 1, 1);
/// # game.run(|ctx| { /// # game.run(|ctx| {
/// sprites.draw(ctx); /// sprites.draw(ctx);
/// # }); /// # });
/// ``` /// ```
pub fn draw(&mut self, ctx: &mut Context) -> Result<()> { pub fn draw(&mut self, ctx: &mut Context) -> Result<()> {
for s in self.v.iter_mut() { for s in self.v.iter_mut() {
@ -327,7 +334,7 @@ impl SpriteCollection {
Ok(()) Ok(())
} }
/// Add a new [`Sprite`] to the end of this collection. /// Add a new [`Sprite`] to the end of this collection.
/// ``` /// ```
/// # use cat_box::*; /// # use cat_box::*;
@ -336,7 +343,7 @@ impl SpriteCollection {
/// sprites.push(s); /// sprites.push(s);
/// ``` /// ```
pub fn push(&mut self, s: Sprite) { pub fn push(&mut self, s: Sprite) {
self.v.push(s); self.v.push(s);
} }
/// Inserts an element at position `index` within the collection. /// Inserts an element at position `index` within the collection.
@ -350,7 +357,7 @@ impl SpriteCollection {
pub fn insert(&mut self, s: Sprite, index: usize) { pub fn insert(&mut self, s: Sprite, index: usize) {
self.v.insert(index, s); self.v.insert(index, s);
} }
/// Removes and returns the last element, or `None` if the collection is empty. /// Removes and returns the last element, or `None` if the collection is empty.
/// ``` /// ```
/// # use cat_box::*; /// # use cat_box::*;
@ -424,6 +431,11 @@ impl SpriteCollection {
pub fn get(&self, index: usize) -> Option<&Sprite> { pub fn get(&self, index: usize) -> Option<&Sprite> {
self.v.get(index) self.v.get(index)
} }
/// Return the inner Vec. Only use this method if you know what you're doing.
pub fn inner(&self) -> &Vec<Sprite> {
&self.v
}
} }
impl Deref for SpriteCollection { impl Deref for SpriteCollection {
@ -464,8 +476,18 @@ impl Context {
/// Get the inner [`Canvas`](sdl2::render::Canvas) and [`TextureCreator`](sdl2::render::TextureCreator). /// Get the inner [`Canvas`](sdl2::render::Canvas) and [`TextureCreator`](sdl2::render::TextureCreator).
/// ///
/// Only use this method if you know what you're doing. /// Only use this method if you know what you're doing.
pub fn inner(&mut self) -> (&TextureCreator<WindowContext>, &mut Canvas<Window>, &mut EventPump) { pub fn inner(
(&self.texture_creator, &mut self.canvas, &mut self.event_pump) &mut self,
) -> (
&TextureCreator<WindowContext>,
&mut Canvas<Window>,
&mut EventPump,
) {
(
&self.texture_creator,
&mut self.canvas,
&mut self.event_pump,
)
} }
fn update(&mut self) { fn update(&mut self) {
@ -483,7 +505,7 @@ impl Context {
if let Event::Quit { .. } = event { if let Event::Quit { .. } = event {
return true; return true;
} }
} }
false false
} }
@ -560,12 +582,12 @@ pub fn draw_text<S: AsRef<str>>(
pub struct MouseRepr { pub struct MouseRepr {
pub buttons: Vec<MouseButton>, pub buttons: Vec<MouseButton>,
pub x: i32, pub x: i32,
pub y: i32 pub y: i32,
} }
/// Representation of the keyboard state. /// Representation of the keyboard state.
pub struct KeyboardRepr { pub struct KeyboardRepr {
pub keys: Vec<Scancode> pub keys: Vec<Scancode>,
} }
/// Get the mouse state. /// Get the mouse state.
@ -602,9 +624,9 @@ pub fn get_keyboard_state(ctx: &mut Context) -> KeyboardRepr {
let (_, _, pump) = ctx.inner(); let (_, _, pump) = ctx.inner();
let keyboard = pump.keyboard_state(); let keyboard = pump.keyboard_state();
KeyboardRepr { KeyboardRepr {
keys: keyboard.pressed_scancodes().collect() keys: keyboard.pressed_scancodes().collect(),
} }
} }
@ -662,7 +684,7 @@ impl Game {
let s = sdl2::ttf::init().unwrap(); let s = sdl2::ttf::init().unwrap();
let event_pump = sdl_context.event_pump()?; let event_pump = sdl_context.event_pump()?;
let mut ctx = Context::new(canvas, event_pump, s); let mut ctx = Context::new(canvas, event_pump, s);
loop { loop {

View file

@ -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; use sdl2::keyboard::Scancode;
fn main() { fn main() {
@ -14,7 +14,7 @@ fn main() {
let x = Sprite::new("duck.png", n * 100, o * 100).unwrap(); let x = Sprite::new("duck.png", n * 100, o * 100).unwrap();
coll.push(x); coll.push(x);
} }
} }
game.run(|ctx| { game.run(|ctx| {
i = (i + 1) % 255; i = (i + 1) % 255;
ctx.set_background_colour(i as u8, 64, 255); ctx.set_background_colour(i as u8, 64, 255);
@ -57,7 +57,7 @@ fn main() {
Scancode::Escape => { Scancode::Escape => {
game.terminate(); game.terminate();
(0, 0) (0, 0)
}, }
Scancode::W | Scancode::Up => (0, 5), Scancode::W | Scancode::Up => (0, 5),
Scancode::S | Scancode::Down => (0, -5), Scancode::S | Scancode::Down => (0, -5),
Scancode::A | Scancode::Left => (-5, 0), 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(); s2.draw(ctx).unwrap();
s.draw(ctx).unwrap(); s.draw(ctx).unwrap();
coll.draw(ctx).unwrap(); coll.draw(ctx).unwrap();

45
src/physics.rs Normal file
View file

@ -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()
}