rustcollision-detectiontiledbevy

How to do polygon collisions in Bevy with Tiled editor and Rapier?


I'm very new to game dev and to Rust, so I might be way on the wrong track. I'm using bevy 0.10.1, bevy_ecs_tilemap, bevy_rapier2d, and tiled to make a tilemap with collisions specified in the .tsx file. Some of my tiles use polygons to define the collision objects. I'm able to get the rectangular collisions to work, but the polygon collisions are in the wrong location and sometimes the wrong shape. Game showing collision shapes

if let Some(object_layer) = layer_tile.collision.clone() {
                            let shapes = object_layer
                                .object_data()
                                .iter()
                                .filter_map(|object_data| {
                                    let pos = Vect::new(object_data.x / 2., -object_data.y / 2.);
                                    let rot = object_data.rotation;
                                    let shape = match &object_data.shape {
                                        tiled::ObjectShape::Rect { width, height } => {
                                            Collider::cuboid(width / 2., height / 2.)
                                        }
                                        tiled::ObjectShape::Ellipse { width, height } => {
                                            Collider::capsule_x(width / 2., height / 2.)
                                        }
                                        tiled::ObjectShape::Polyline { points } => {
                                            Collider::polyline(
                                                points
                                                    .iter()
                                                    .map(|(x, y)| Vect::new(*x, *y))
                                                    .collect(),
                                                None,
                                            )
                                        }
                                        tiled::ObjectShape::Polygon { points } => {
                                            Collider::convex_hull(
                                                &points
                                                    .iter()
                                                    .map(|(x, y)| Vect::new(*x, *y))
                                                    .collect::<Vec<_>>(),
                                            )?
                                        }
                                        _ => {
                                            return None;
                                        }
                                    };
                                    Some((pos, rot, shape))
                                })
                                .collect::<Vec<_>>();
                            let world_pos = tile_pos.center_in_world(&grid_size, &map_type);
                            let transform =
                                get_tilemap_center_transform(&map_size, &grid_size, &map_type, 0.)
                                    * Transform::from_xyz(
                                        world_pos.x + offset_x,
                                        world_pos.y - offset_y,
                                        0.,
                                    );
                            if shapes.len() == 1 {
                                let (pos, rot, collider) = shapes[0].clone();
                                let transform = transform
                                    * Transform {
                                        translation: Vec3::new(pos.x, pos.y, 0.),
                                        rotation: Quat::from_rotation_x(rot),
                                        ..default()
                                    };
                                commands
                                    .entity(tile_entity)
                                    .insert((collider, TransformBundle::from_transform(transform)));
                            } else if shapes.len() > 1 {
                                commands.entity(tile_entity).insert((
                                    Collider::compound(shapes),
                                    TransformBundle::from_transform(transform),
                                ));
                            }
                        }

How do I convert tiled polygon collision data into Rapier colliders? Or am I combining tools that don't belong together?


Solution

  • After much trial and error, I figured out how to do it. The polygons were upside-down and I needed to treat the rectangles different from the polygons. (My tilemap currently doesn't have ellipses or polylines, so I'm assuming they work the same as the other shapes.)

    if let Some(object_layer) = layer_tile.collision.clone() {
                                let shapes = object_layer
                                    .object_data()
                                    .iter()
                                    .filter_map(|object_data| {
                                        let pos = Vect::new(object_data.x, -object_data.y);
                                        let rot = object_data.rotation;
                                        match &object_data.shape {
                                            tiled::ObjectShape::Rect { width, height } => {
                                                let shape = Collider::cuboid(width / 2., height / 2.);
                                                Some((
                                                    pos + Vec2::new(
                                                        (-grid_size.x + width) / 2.,
                                                        (grid_size.y - height) / 2.,
                                                    ),
                                                    rot,
                                                    shape,
                                                ))
                                            }
                                            tiled::ObjectShape::Ellipse { width, height } => {
                                                let shape =
                                                    Collider::capsule_x(width / 2., height / 2.);
                                                Some((
                                                    pos + Vec2::new(
                                                        (-grid_size.x + width) / 2.,
                                                        (grid_size.y - height) / 2.,
                                                    ),
                                                    rot,
                                                    shape,
                                                ))
                                            }
                                            tiled::ObjectShape::Polyline { points } => {
                                                let shape = Collider::polyline(
                                                    points
                                                        .iter()
                                                        .map(|(x, y)| Vect::new(*x, -*y))
                                                        .collect(),
                                                    None,
                                                );
                                                Some((
                                                    pos + Vec2::new(
                                                        -grid_size.x / 2.,
                                                        grid_size.y / 2.,
                                                    ),
                                                    rot,
                                                    shape,
                                                ))
                                            }
                                            tiled::ObjectShape::Polygon { points } => {
                                                let shape = Collider::convex_hull(
                                                    &points
                                                        .iter()
                                                        .map(|(x, y)| Vect::new(*x, -*y))
                                                        .collect::<Vec<_>>(),
                                                )?;
                                                Some((
                                                    pos + Vec2::new(
                                                        -grid_size.x / 2.,
                                                        grid_size.y / 2.,
                                                    ),
                                                    rot,
                                                    shape,
                                                ))
                                            }
                                            _ => {
                                                return None;
                                            }
                                        }
                                    })
                                    .collect::<Vec<_>>();
                                let world_pos = tile_pos.center_in_world(&grid_size, &map_type);
                                let transform =
                                    get_tilemap_center_transform(&map_size, &grid_size, &map_type, 0.)
                                        * Transform::from_xyz(
                                            world_pos.x + offset_x,
                                            world_pos.y - offset_y,
                                            0.,
                                        );
                                if shapes.len() == 1 {
                                    let (pos, rot, collider) = shapes[0].clone();
                                    let transform = transform
                                        * Transform {
                                            translation: Vec3::new(pos.x, pos.y, 0.),
                                            rotation: Quat::from_rotation_x(rot),
                                            ..default()
                                        };
                                    commands
                                        .entity(tile_entity)
                                        .insert((collider, TransformBundle::from_transform(transform)));
                                } else if shapes.len() > 1 {
                                    commands.entity(tile_entity).insert((
                                        Collider::compound(shapes),
                                        TransformBundle::from_transform(transform),
                                    ));
                                }
                            }