player bound collision tests

This commit is contained in:
Thilo Behnke
2022-05-01 18:08:50 +02:00
parent 5a27fbd3fe
commit 967e13545d
11 changed files with 249 additions and 71 deletions

View File

@@ -93,10 +93,20 @@ pub mod collision {
mapping: (String, String),
callback: fn(&mut Box<dyn GameObject>, &mut Box<dyn GameObject>),
) {
if self.handlers.contains_key(&mapping) {
panic!(
"Collision handler for mapping {:?} is already registered.",
mapping
)
}
self.handlers.insert(mapping, callback);
}
pub fn handle(&self, obj_a: &mut Box<dyn GameObject>, obj_b: &mut Box<dyn GameObject>) -> bool {
pub fn handle(
&self,
obj_a: &mut Box<dyn GameObject>,
obj_b: &mut Box<dyn GameObject>,
) -> bool {
let key = (obj_a.obj_type().to_string(), obj_b.obj_type().to_string());
if !self.handlers.contains_key(&key) {
return false;

View File

@@ -1,4 +1,3 @@
use std::borrow::{Borrow, BorrowMut};
use crate::collision::collision::{
Collision, CollisionDetector, CollisionHandler, CollisionRegistry, Collisions,
};
@@ -6,7 +5,9 @@ use crate::game_object::components::{DefaultGeomComp, DefaultPhysicsComp};
use crate::game_object::game_object::{DefaultGameObject, GameObject};
use crate::geom::geom::Vector;
use crate::geom::shape::{Shape, ShapeType};
use crate::pong::pong_collisions::{handle_ball_bounds_collision, handle_player_ball_collision};
use crate::utils::utils::{Logger, NoopLogger};
use std::borrow::{Borrow, BorrowMut};
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::collections::HashMap;
use std::ops::Deref;
@@ -42,7 +43,10 @@ impl Field {
logger,
width,
height,
objs: DefaultGameObject::bounds(width, height),
objs: DefaultGameObject::bounds(width, height)
.into_iter()
.map(|b| Rc::new(RefCell::new(b.inner())))
.collect(),
collisions: Box::new(Collisions::new(vec![])),
collision_handler: CollisionHandler::new(),
};
@@ -53,28 +57,17 @@ impl Field {
field.collision_handler.register(
(String::from("ball"), String::from("player")),
|ball, player| {
// reflect
ball.vel_mut().reflect(&player.orientation());
// use vel of player obj
if *player.vel() != Vector::zero() {
let mut adjusted = player.vel().clone();
adjusted.normalize();
ball.vel_mut().add(&adjusted);
}
// move out of collision
let mut b_to_a = ball.pos().clone();
b_to_a.sub(&player.pos());
b_to_a.normalize();
ball.pos_mut().add(&b_to_a);
},
handle_player_ball_collision,
);
field.collision_handler.register(
(String::from("ball"), String::from("bound")),
|ball, bound| {
ball.vel_mut().reflect(&bound.orientation());
},
handle_ball_bounds_collision,
);
field.collision_handler.register(
(String::from("player"), String::from("bound")),
handle_ball_bounds_collision,
);
return field;
@@ -85,7 +78,10 @@ impl Field {
logger: Box::new(NoopLogger {}),
width,
height,
objs: DefaultGameObject::bounds(width, height),
objs: DefaultGameObject::bounds(width, height)
.into_iter()
.map(|b| Rc::new(RefCell::new(b.inner())))
.collect(),
collisions: Box::new(Collisions::new(vec![])),
collision_handler: CollisionHandler::new(),
}
@@ -193,18 +189,18 @@ impl Field {
impl DefaultGameObject {
pub fn player(id: u16, x: u16, y: u16, field: &Field) -> Box<dyn GameObject> {
Box::new(DefaultGameObject::new(
id,
"player".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
Vector {
x: x as f64,
y: y as f64,
},
Vector::new(0., 1.),
(field.width as f64) / 25.,
(field.height as f64) / 5.,
))),
Box::new(DefaultPhysicsComp::new(Vector::zero(), true)),
id,
"player".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
Vector {
x: x as f64,
y: y as f64,
},
Vector::new(0., 1.),
(field.width as f64) / 25.,
(field.height as f64) / 5.,
))),
Box::new(DefaultPhysicsComp::new(Vector::zero(), true)),
))
}
}
@@ -212,26 +208,25 @@ impl DefaultGameObject {
impl DefaultGameObject {
pub fn ball(id: u16, x: u16, y: u16, field: &Field) -> Box<dyn GameObject> {
Box::new(DefaultGameObject::new(
id,
"ball".to_string(),
Box::new(DefaultGeomComp::new(Shape::circle(
Vector {
x: x as f64,
y: y as f64,
},
Vector::zero(),
(field.width as f64) / 80.,
))),
Box::new(DefaultPhysicsComp::new(Vector::zero(), false)),
id,
"ball".to_string(),
Box::new(DefaultGeomComp::new(Shape::circle(
Vector {
x: x as f64,
y: y as f64,
},
Vector::zero(),
(field.width as f64) / 80.,
))),
Box::new(DefaultPhysicsComp::new(Vector::zero(), false)),
))
}
}
impl DefaultGameObject {
pub fn bounds(width: u16, height: u16) -> Vec<Rc<RefCell<Box<dyn GameObject>>>> {
let bounds: Vec<Box<dyn GameObject>> = vec![
// top
Box::new(DefaultGameObject::new(
pub fn bounds(width: u16, height: u16) -> Vec<Bounds> {
let bounds = vec![
Bounds(Bound::BOTTOM, Box::new(DefaultGameObject::new(
90,
"bound".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
@@ -244,9 +239,8 @@ impl DefaultGameObject {
2.,
))),
Box::new(DefaultPhysicsComp::new_static()),
)),
// bottom
Box::new(DefaultGameObject::new(
))),
Bounds(Bound::TOP, Box::new(DefaultGameObject::new(
91,
"bound".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
@@ -259,9 +253,8 @@ impl DefaultGameObject {
2.,
))),
Box::new(DefaultPhysicsComp::new_static()),
)),
// left
Box::new(DefaultGameObject::new(
))),
Bounds(Bound::LEFT, Box::new(DefaultGameObject::new(
92,
"bound".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
@@ -274,9 +267,8 @@ impl DefaultGameObject {
height as f64,
))),
Box::new(DefaultPhysicsComp::new_static()),
)),
// right
Box::new(DefaultGameObject::new(
))),
Bounds(Bound::RIGHT, Box::new(DefaultGameObject::new(
93,
"bound".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
@@ -289,8 +281,24 @@ impl DefaultGameObject {
height as f64,
))),
Box::new(DefaultPhysicsComp::new_static()),
)),
))),
];
bounds.into_iter().map(|o| Rc::new(RefCell::new(o))).collect()
bounds
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Bound {
TOP,
RIGHT,
BOTTOM,
LEFT,
}
pub struct Bounds(pub Bound, pub Box<dyn GameObject>);
impl Bounds {
pub fn inner(self) -> Box<dyn GameObject> {
self.1
}
}

View File

@@ -11,6 +11,7 @@ pub mod game_object {
fn pos(&self) -> &Vector;
fn pos_mut(&mut self) -> &mut Vector;
fn orientation(&self) -> &Vector;
fn orientation_mut(&mut self) -> &mut Vector;
fn update_pos(&mut self);
fn bounding_box(&self) -> BoundingBox;
fn vel(&self) -> &Vector;
@@ -68,6 +69,10 @@ pub mod game_object {
self.geom.orientation()
}
fn orientation_mut(&mut self) -> &mut Vector {
self.geom.orientation_mut()
}
fn update_pos(&mut self) {
let vel = self.vel().clone();
let center = self.geom.center_mut();

View File

@@ -108,10 +108,12 @@ pub mod geom {
let mut orthogonal1 = onto.clone();
orthogonal1.orthogonal_clockwise();
if self.dot(&orthogonal1) < 0. {
// orthogonal1.normalize();
return orthogonal1;
}
let mut orthogonal2 = onto.clone();
orthogonal2.orthogonal_counter_clockwise();
// orthogonal2.normalize();
return orthogonal2;
}

View File

@@ -2,4 +2,5 @@ pub mod collision;
pub mod game_field;
pub mod game_object;
pub mod geom;
pub mod pong;
pub mod utils;

53
pong/src/pong.rs Normal file
View File

@@ -0,0 +1,53 @@
pub mod pong_collisions {
use std::ops::Add;
use crate::game_object::game_object::GameObject;
use crate::geom::geom::Vector;
use crate::geom::shape::ShapeType;
pub fn handle_player_ball_collision(
ball: &mut Box<dyn GameObject>,
player: &mut Box<dyn GameObject>,
) {
// reflect
ball.vel_mut().reflect(&player.orientation());
// use vel of player obj
if *player.vel() != Vector::zero() {
let mut adjusted = player.vel().clone();
adjusted.normalize();
ball.vel_mut().add(&adjusted);
}
// move out of collision
let mut b_to_a = ball.pos().clone();
b_to_a.sub(&player.pos());
b_to_a.normalize();
ball.pos_mut().add(&b_to_a);
}
pub fn handle_ball_bounds_collision(
ball: &mut Box<dyn GameObject>,
bound: &mut Box<dyn GameObject>,
) {
ball.vel_mut().reflect(&bound.orientation());
}
pub fn handle_player_bound_collision(
player: &mut Box<dyn GameObject>,
bound: &mut Box<dyn GameObject>,
) {
let shape = player.shape().clone();
let player_orientation = player.orientation().clone();
let height = match shape {
ShapeType::Rect(_, _, height) => height.clone(),
ShapeType::Circle(_, radius) => radius * 2.,
};
let mut perpendicular = player_orientation.get_opposing_orthogonal(bound.orientation());
println!("{:?}", bound);
println!("{:?}", perpendicular);
perpendicular.x = 0.;
perpendicular.y *= height;
let mut new_pos = bound.pos().clone();
new_pos.add(&perpendicular);
let player_pos = player.pos_mut();
player_pos.y = new_pos.y;
}
}

View File

@@ -1,10 +1,10 @@
use std::borrow::{Borrow, BorrowMut};
use pong::collision::collision::CollisionHandler;
use pong::game_object::components::{DefaultGeomComp, DefaultPhysicsComp};
use pong::game_object::game_object::{DefaultGameObject, GameObject};
use pong::geom::geom::Vector;
use pong::geom::shape::Shape;
use rstest::rstest;
use std::borrow::{Borrow, BorrowMut};
#[rstest]
#[case(
@@ -70,7 +70,7 @@ pub fn should_handle_collision(
#[case] expected_b: Box<dyn GameObject>,
) {
let mut handler = CollisionHandler::new();
handler.register((String::from("obj"), String::from("obj")), |a,b| {});
handler.register((String::from("obj"), String::from("obj")), |a, b| {});
let res = handler.handle(&mut obj_a, &mut obj_b);
assert_eq!(true, res)
// assert_eq!(obj_a.pos(), expected_a.pos());

View File

@@ -1,10 +1,10 @@
use std::cell::{Ref, RefCell};
use std::rc::Rc;
use pong::collision::collision::{Collision, CollisionDetector};
use pong::game_object::game_object::GameObject;
use pong::geom::geom::{BoundingBox, Vector};
use pong::geom::shape::ShapeType;
use rstest::rstest;
use std::cell::{Ref, RefCell};
use std::rc::Rc;
#[rstest]
#[case(vec![], vec![])]

View File

@@ -1,8 +1,8 @@
#[cfg(test)]
mod game_field_tests {
use pong::game_field::{Field, Input, InputType};
use std::borrow::Borrow;
use std::cell::RefCell;
use pong::game_field::{Field, Input, InputType};
#[test]
fn player_input_update_pos__up() {
@@ -14,7 +14,13 @@ mod game_field_tests {
obj_id: 1,
}];
field.tick(inputs);
let player = RefCell::borrow(field.objs().iter().find(|o| RefCell::borrow(o).obj_type() == "player").unwrap());
let player = RefCell::borrow(
field
.objs()
.iter()
.find(|o| RefCell::borrow(o).obj_type() == "player")
.unwrap(),
);
assert_eq!(player.pos().y, height as f64 / 2. + 1.);
}
@@ -29,7 +35,10 @@ mod game_field_tests {
}];
field.tick(inputs);
let objs = field.objs();
let player = objs.iter().find(|o| RefCell::borrow(o).obj_type() == "player").unwrap();
let player = objs
.iter()
.find(|o| RefCell::borrow(o).obj_type() == "player")
.unwrap();
assert_eq!(RefCell::borrow(player).pos().y, height as f64 / 2. - 1.);
}
@@ -44,8 +53,14 @@ mod game_field_tests {
}];
field.tick(inputs);
let objs = field.objs();
let player = objs.iter().find(|o| RefCell::borrow(o).obj_type() == "player").unwrap();
assert_eq!(RefCell::borrow(player).pos().y, height as f64 - height as f64 / 5. / 2.);
let player = objs
.iter()
.find(|o| RefCell::borrow(o).obj_type() == "player")
.unwrap();
assert_eq!(
RefCell::borrow(player).pos().y,
height as f64 - height as f64 / 5. / 2.
);
}
#[test]
@@ -59,7 +74,10 @@ mod game_field_tests {
}];
field.tick(inputs);
let objs = field.objs();
let player = objs.iter().find(|o| RefCell::borrow(o).obj_type() == "player").unwrap();
let player = objs
.iter()
.find(|o| RefCell::borrow(o).obj_type() == "player")
.unwrap();
assert_eq!(RefCell::borrow(player).pos().y, height as f64 / 5. / 2.);
}
}

75
pong/tests/pong_tests.rs Normal file
View File

@@ -0,0 +1,75 @@
use rstest::rstest;
use pong::game_field::{Bound, Field};
use pong::game_object::game_object::{DefaultGameObject, GameObject};
use pong::geom::geom::Vector;
use pong::pong::pong_collisions::handle_player_bound_collision;
use pong::utils::utils::NoopLogger;
#[rstest]
#[case(
// given
create_player(1, 10, 0, Vector::new(0., -1.)),
get_bound(Bound::BOTTOM),
// expected
create_player(1, 10, 120, Vector::new(0., -1.)),
get_bound(Bound::BOTTOM)
)]
#[case(
// given
create_player(1, 10, 119, Vector::new(0., -1.)),
get_bound(Bound::BOTTOM),
// expected
create_player(1, 10, 120, Vector::new(0., -1.)),
get_bound(Bound::BOTTOM)
)]
#[case(
// given
create_player(1, 10, 121, Vector::new(0., -1.)),
get_bound(Bound::BOTTOM),
// expected
create_player(1, 10, 120, Vector::new(0., -1.)),
get_bound(Bound::BOTTOM)
)]
#[case(
// given
create_player(1, 10, 600, Vector::new(0., 1.)),
get_bound(Bound::TOP),
// expected
create_player(1, 10, 480, Vector::new(0., 1.)),
get_bound(Bound::TOP)
)]
#[case(
// given
create_player(1, 10, 599, Vector::new(0., 1.)),
get_bound(Bound::TOP),
// expected
create_player(1, 10, 480, Vector::new(0., 1.)),
get_bound(Bound::TOP)
)]
pub fn should_correctly_handle_player_bounds_collision(
#[case] mut player: Box<dyn GameObject>,
#[case] mut bounds: Box<dyn GameObject>,
#[case] mut player_expected: Box<dyn GameObject>,
#[case] mut bounds_expected: Box<dyn GameObject>
) {
handle_player_bound_collision(&mut player, &mut bounds);
assert_eq!(player_expected.pos(), player.pos());
assert_eq!(bounds_expected.pos(), bounds.pos());
}
fn create_player(id: u16, x: u16, y: u16, orientation: Vector) -> Box<dyn GameObject> {
let field = Field::new(Box::new(NoopLogger{}));
let mut player = DefaultGameObject::player(id, x, y, &field);
let player_orientation = player.orientation_mut();
player_orientation.x = orientation.x;
player_orientation.y = orientation.y;
player
}
fn get_bound(bound: Bound) -> Box<dyn GameObject> {
let field = Field::new(Box::new(NoopLogger{}));
let bounds = DefaultGameObject::bounds(field.width, field.height);
return bounds.into_iter().find(|b| {
b.0 == bound
}).unwrap().inner();
}

View File

@@ -121,7 +121,13 @@ impl FieldWrapper {
}
pub fn objects(&self) -> *const GameObjectDTO {
let mut objs = self.field.objs.borrow().iter().map(|o| GameObjectDTO::from(o)).collect::<Vec<GameObjectDTO>>();
let mut objs = self
.field
.objs
.borrow()
.iter()
.map(|o| GameObjectDTO::from(o))
.collect::<Vec<GameObjectDTO>>();
objs.as_ptr()
}