Basic Physics

Let's add more asteroids, and handle collisions!

More Asteroids

We'll spawn 4 asteroids, at fixed positions for now.

#![allow(unused)]
fn main() {
extern crate bevy;
use bevy::prelude::*;
#[derive(Component)]
struct Player;
#[derive(Component)]
struct Asteroid;
#[derive(Resource)]
struct GameAssets {
    asteroid: Handle<Image>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, States, Default)]
enum GameState {
    #[default]
    Game,
}
fn display_level(mut commands: Commands, game_assets: Res<GameAssets>) {
    // Same player spawning

    // Asteroids spawning
    for (x, y) in [(1., 1.), (-1., 1.), (-1., -1.), (1., -1.)] {
        commands.spawn((
            Sprite::from_image(game_assets.asteroid.clone()),
            Transform::from_xyz(300.0 * x, 200.0 * y, 0.0),
            Asteroid,
            StateScoped(GameState::Game),
        ));
    }
}
}

Collisions

One of the easiest way to test collisions is to consider everything is round, and then check that the distance between two objects is less than the sum of their radii. This is a close enough approximation that works well in our case. Another basic shape that is often used for collision detection is AABB (for Axis-Aligned Bounding Box, so a rectangle).

Let's get the position of the player, and check the distance with every asteroid.

#![allow(unused)]
fn main() {
extern crate bevy;
use bevy::prelude::*;
#[derive(Component)]
struct Player;
#[derive(Component)]
struct Asteroid;
fn collision(
    asteroids: Query<&Transform, With<Asteroid>>,
    player: Query<&Transform, With<Player>>,
) -> Result {
    let player_radius = 40.0;
    let asteroid_radius = 50.0;
    let player_transform = player.single()?;
    for asteroid_transform in &asteroids {
        let distance = asteroid_transform
            .translation
            .distance(player_transform.translation);
        if distance < (asteroid_radius + player_radius) {
            info!("Collision detected!");
        }
    }

    Ok(())
}
}

Don't forget to add the new collision system to the game_plugin, on Update in the GameState::Game state.

Gizmos

An easy way to debug what is happening on screen are gizmos. They make it possible to draw simple shapes on screen, like circles or rectangles.

We'll draw circles around the different objects, with their radius.

#![allow(unused)]
fn main() {
extern crate bevy;
use bevy::prelude::*;
#[derive(Component)]
struct Player;
#[derive(Component)]
struct Asteroid;
fn collision(
    asteroids: Query<&Transform, With<Asteroid>>,
    player: Query<&Transform, With<Player>>,
    mut gizmos: Gizmos,
) -> Result {
    let player_radius = 40.0;
    let asteroid_radius = 50.0;
    let player_transform = player.single()?;
    gizmos.circle_2d(
        player_transform.translation.xy(),
        player_radius,
        Color::linear_rgb(1.0, 0.0, 0.0),
    );
    for asteroid_transform in &asteroids {
        gizmos.circle_2d(
            asteroid_transform.translation.xy(),
            asteroid_radius,
            Color::linear_rgb(0.0, 0.0, 1.0),
        );
        let distance = asteroid_transform
            .translation
            .distance(player_transform.translation);
        if distance < (asteroid_radius + player_radius) {
            info!("Collision detected!");
        }
    }

    Ok(())
}
}

It's often useful to add a debug feature to your game, and put things like debug drawing with gizmos behind it!