/bevy-starfighter

Primary LanguageRustApache License 2.0Apache-2.0

Bevy Starfighter

A simple 2D space shooter game with deep neural network opponents built with Bevy and EntityGym.

starfighter-4B-2023-01-22.mp4

Usage

Play the web version here. WASD to move, space to shoot.

Run locally (requries Rust toolchain):

git clone https://github.com/cswinter/bevy-starfighter.git
cd bevy-starfighter
cargo run --bin native-launcher -- --agent-asset=230111-134322-versus-reldir-1024m --ccd --players=2 --ai-act-interval=12 --human-player

Run AI against itself:

cargo run --bin native-launcher -- --agent-asset=230111-134322-versus-reldir-4096m --ccd --players=2 --ai-act-interval=12

Train new AI:

poetry install
poetry run pip install torch==1.12.0+cu113 -f https://download.pytorch.org/whl/cu113/torch_stable.html
poetry run pip install torch-scatter -f https://data.pyg.org/whl/torch-1.12.0+cu113.html
poetry run maturin develop --features=python --release
poetry run python -u train.py --config=train.ron --checkpoint-dir=out

Technical Details

This sections goes into some of the specifics of how to apply EntityGym Rust to real-time Bevy games that use Rapier as a physics engine.

Faster than realtime headless mode

To run the game in headless mode faster than realtime during training in a way that keeps the physics identical, we configure Rapier with TimestepMode::Fixed (src/lib.rs#L136):

    let timestep_mode = if settings.frameskip > 1 || settings.headless {
        TimestepMode::Fixed {
            dt: 1.0 * settings.frameskip as f32 / settings.frame_rate as f32,
            substeps: 1,
        }
    } else {
        TimestepMode::Variable {
            max_dt: 1.0 / settings.frame_rate,
            time_scale: 1.0,
            substeps: 1,
        }
    };
    app.add_plugin(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(1.0))
        .insert_resource(RapierConfiguration {
            gravity: Vect::new(0.0, 0.0),
            timestep_mode,
            ..default()
        })

Additionally, we configure the Bevy scheduler to run without any delay during frames (src/lib.rs#L198):

    app.insert_resource(ScheduleRunnerSettings::run_loop(
        Duration::from_secs_f64(0.0),
    ))

Faster physics during training

To speed up the AI and bring its abilities closer to those of a human player, we only allow it to take an action every ai_act_interval frames (by default, every 12 frames = 133ms). While training, we don't really care about the intermediate physics steps, so we can speed up the simulation by not calculating intermediary frames. For some reason, the fidelity of the simulation degrades when skipping too many frames. Empirically, combining up to 4 physics steps into a single frame (--frameskip=4) still gives fairly accurate physics.

Command to observe game with 4x accelerated physics:

cargo run --bin native-launcher -- --agent-asset=230111-134322-versus-reldir-4096m --ccd --players=2 --ai-act-interval=12 --frameskip=4