-
Hey all, Im tinkering, building a 2d game like blasterball, the player controls a paddle at the bottom of the screen and bounces balls back to break the blocks above. I want to induce some randomness to help ensure the balls don't bounce at angles too repetitive. My two ideas for this are to just induce some random +/- 3 degrees to the angle of reflection. Or some +/- effect based on how far from the center of the player the ball impacted. (if modifying the reflection angle proves too hard my other thought was to apply an Impulse to ball the frame after collision) I basically have no idea how to go about implementing either approach. As a secondary question, I'm curious about different ways of "querying" the different collisions. For example, to remove blocks after the ball has impacted them, I have: fn despawn_blocks(
mut commands: Commands,
query: Query<(Entity, &CollidingEntities), With<Block>>,
) {
for (block_entity, colliding_entities) in query.iter() {
if colliding_entities.len() > 0 {
commands.entity(block_entity).despawn_recursive();
}
}
} This also naively assumes the only thing colliding is a Let me know if there is any more context I should share for some help. Thanks in advance 🙏 ! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
You can either modify the contact normal or apply an impulse after the collision, like you mentioned. To modify the contact normal, you need to access the fn main() {
App::new()
.add_plugins((DefaultPlugins, PhysicsPlugins::default()))
.add_systems(PostProcessCollisions, modify_reflection_angles)
.run();
}
#[derive(Component)]
struct Paddle;
#[derive(Component)]
struct Ball;
fn modify_reflection_angles(
paddle: Query<Entity, With<Paddle>>,
balls: Query<Entity, With<Ball>>,
mut collisions: ResMut<Collisions>,
) {
// I'm assuming that there's a single paddle here, but this could be turned into a loop
let paddle_entity = paddle.single();
for ball_entity in &balls {
// Get the collision between the paddle and this ball if they are colliding
let Some(mut collision) = collisions.get_mut(paddle_entity, ball_entity) else {
continue;
};
// One collision can have several contacts for more complex colliders.
// For a rectangle and a ball, there should only be one, but we'll still loop just in case.
for contact in collision.manifolds.iter().map(|m| m.contacts).flatten() {
// Compute the random change in rotation however you want.
// Here I'm generating a +/- 3 degree angle and converting that to radians.
let angle_change = (rand::random() * 6.0 - 3.0).to_radians();
// Side note: The contact normals are in local space.
contact.normal1 = rotate_vec2(contact.normal1, angle_change);
contact.normal2 = rotate_vec2(contact.normal2, -angle_change);
}
}
}
/// A helper to rotate a 2D vector by an angle in radians.
fn rotate_vec2(vec: Vec2, rotation: f32) -> Vec2 {
let (sin, cos) = rotation.sin_cos();
Vec2::new(vec.x * cos - vec.y * sin, vec.x * sin + vec.y * cos)
} Instead of iterating over all balls and calling If you want to determine the angle change based on the distance from the center of the paddle, you could use e.g. the length of
You can always use normal Bevy queries to get data associated to colliding entities as needed. For your block despawning case, you could do something like this: (I'm also including the point multiplier example for demonstration purposes) fn despawn_blocks(
mut commands: Commands,
mut balls: Query<&mut PointMultiplier, With<Ball>>,
mut blocks: Query<(Entity, &mut HitCount, &CollidingEntities), With<Block>>,
mut score: ResMut<Score>,
) {
for (block_entity, mut hit_count, colliding_entities) in blocks.iter_mut() {
// Iterate over balls included in `colliding_entities` (entities that don't match the query are skipped)
let mut ball_iter = balls.iter_many_mut(colliding_entities.iter());
while let Some(mut multiplier) = ball_iter.fetch_next() {
// Increase number of hits
hit_count.0 += 1;
// Increase score by 10 times the multiplier (or whatever you want)
score.0 += multiplier.0 * 10;
// Increase multiplier (however you want)
multiplier.0 += 1;
// Max hit count reached, despawn block
if hit_count.0 >= 5 {
commands.entity(block_entity).despawn_recursive();
}
}
}
} Let me know if you have more questions or face issues, either here or on Discord! |
Beta Was this translation helpful? Give feedback.
You can either modify the contact normal or apply an impulse after the collision, like you mentioned.
To modify the contact normal, you need to access the
Collisions
resource in thePostProcessCollisions
schedule. In your case, it might look something like this: