diff --git a/examples/large_scenes/bevy_city/Cargo.toml b/examples/large_scenes/bevy_city/Cargo.toml index deb64ecaa04ea..046596ea4bfde 100644 --- a/examples/large_scenes/bevy_city/Cargo.toml +++ b/examples/large_scenes/bevy_city/Cargo.toml @@ -5,6 +5,9 @@ edition = "2024" publish = false license = "MIT OR Apache-2.0" +[features] +traffic = [] + [dependencies] bevy = { path = "../../../", features = [ "https", diff --git a/examples/large_scenes/bevy_city/src/assets.rs b/examples/large_scenes/bevy_city/src/assets.rs index f1baa9e13f417..dc2703acc272f 100644 --- a/examples/large_scenes/bevy_city/src/assets.rs +++ b/examples/large_scenes/bevy_city/src/assets.rs @@ -27,6 +27,7 @@ pub struct CityAssets { pub tree_large: Handle, pub path_stones_long: Handle, pub fence: Handle, + pub traffic_light: Handle, } impl CityAssets { @@ -219,6 +220,9 @@ pub fn load_assets( GltfAssetLabel::Scene(0).from_asset(format!("{base_url}/city-kit-suburban/fence.glb")) ); + let traffic_light: Handle = + load_asset!(GltfAssetLabel::Scene(0).from_asset("traffic_assets/traffic_light.glb")); + commands.insert_resource(CityAssets { untyped_assets, cars, @@ -232,5 +236,6 @@ pub fn load_assets( tree_large, path_stones_long, fence, + traffic_light, }); } diff --git a/examples/large_scenes/bevy_city/src/generate_city.rs b/examples/large_scenes/bevy_city/src/generate_city.rs index 46aa5594e4cc8..73c9df09d5b79 100644 --- a/examples/large_scenes/bevy_city/src/generate_city.rs +++ b/examples/large_scenes/bevy_city/src/generate_city.rs @@ -2,8 +2,7 @@ use bevy::prelude::*; use noise::{NoiseFn, OpenSimplex}; use rand::{rngs::SmallRng, RngExt, SeedableRng}; -use crate::{assets::CityAssets, Car, Road}; - +use crate::{assets::CityAssets, traffic, Car, CarAtStopState, CarState, Road}; #[derive(Component)] pub struct CityRoot; @@ -87,10 +86,26 @@ fn spawn_roads_and_cars( let x = offset.x; let z = offset.z; - commands.spawn(( - SceneRoot(assets.crossroad.clone()), - Transform::from_xyz(x, 0.0, z), - )); + let crossroad = commands + .spawn(( + SceneRoot(assets.crossroad.clone()), + Transform::from_xyz(x, 0.0, z), + traffic::TrafficLight::new((x * 7.3 + z * 3.7).rem_euclid(traffic::CYCLE_DURATION)), + )) + .id(); + + // 2 traffic lights at diagonal corners + for (dx, dz, y_rot) in [ + (-0.5_f32, -0.5_f32, 0.0_f32), + (0.5_f32, -0.5_f32, std::f32::consts::FRAC_PI_2), + ] { + commands.spawn(( + SceneRoot(assets.traffic_light.clone()), + Transform::from_translation(Vec3::new(x + dx, 0.0, z + dz)) + .with_rotation(Quat::from_rotation_y(y_rot)) + .with_scale(Vec3::splat(0.3)), + )); + } let max_car_density = 0.4; @@ -108,6 +123,7 @@ fn spawn_roads_and_cars( Road { start: Vec3::new(0.75, 0.0, 0.0), end: Vec3::new(0.75 + (0.5 * car_count as f32), 0.0, 0.0), + intersection: crossroad, }, )) .with_children(|commands| { @@ -133,6 +149,9 @@ fn spawn_roads_and_cars( distance_traveled: i as f32 * 0.5, dir: -1.0, offset: Vec3::new(4.25, 0.0, -0.15), + car_state: CarState::Driving, + at_stop_state: CarAtStopState::Default, + next_lane: None, }, )); } @@ -150,6 +169,9 @@ fn spawn_roads_and_cars( distance_traveled: i as f32 * 0.5, dir: 1.0, offset: Vec3::new(-0.25, 0.0, 0.15), + car_state: CarState::Driving, + at_stop_state: CarAtStopState::Default, + next_lane: None, }, )); } @@ -165,6 +187,7 @@ fn spawn_roads_and_cars( Road { start: Vec3::new(0.0, 0.0, 0.75), end: Vec3::new(0.0, 0.0, 0.75 + (0.5 * car_count as f32)), + intersection: crossroad, }, )) .with_children(|commands| { @@ -187,6 +210,9 @@ fn spawn_roads_and_cars( distance_traveled: i as f32 * 0.5, dir: 1.0, offset: Vec3::new(-0.15, 0.0, -0.25), + car_state: CarState::Driving, + at_stop_state: CarAtStopState::Default, + next_lane: None, }, )); } @@ -201,6 +227,9 @@ fn spawn_roads_and_cars( distance_traveled: i as f32 * 0.5, dir: -1.0, offset: Vec3::new(0.15, 0.0, 2.75), + car_state: CarState::Driving, + at_stop_state: CarAtStopState::Default, + next_lane: None, }, )); } diff --git a/examples/large_scenes/bevy_city/src/main.rs b/examples/large_scenes/bevy_city/src/main.rs index 24d53893ecc31..de86e98e326ed 100644 --- a/examples/large_scenes/bevy_city/src/main.rs +++ b/examples/large_scenes/bevy_city/src/main.rs @@ -27,6 +27,8 @@ mod assets; mod generate_city; mod settings; +mod traffic; + #[derive(FromArgs, Resource, Clone)] /// Config pub struct Args { @@ -41,14 +43,23 @@ pub struct Args { /// adds NoCpuCulling to all meshes #[argh(switch)] no_cpu_culling: bool, + // forces Vulkan backend and clears instance flags for RenderDoc capture + //#[argh(switch)] + //renderdoc: bool, } fn main() { let args: Args = argh::from_env(); - App::new() - .add_plugins(( - DefaultPlugins.set(WindowPlugin { + let mut app = App::new(); + + app.add_plugins(( + DefaultPlugins + .set(AssetPlugin { + file_path: "./".to_string(), + ..default() + }) + .set(WindowPlugin { primary_window: Some(Window { title: "bevy_city".into(), resolution: WindowResolution::new(1920, 1080).with_scale_factor_override(1.0), @@ -58,30 +69,34 @@ fn main() { }), ..default() }), - FreeCameraPlugin, - FeathersPlugins, - WireframePlugin::default(), - )) - .insert_resource(args.clone()) - .insert_resource(ClearColor(Color::BLACK)) - .insert_resource(WinitSettings::continuous()) - .init_resource::() - .insert_resource(UiTheme(create_dark_theme())) - .insert_resource(WireframeConfig { - global: false, - default_color: WHITE.into(), - ..default() - }) - // Like in many realistic large scenes, many of the objects don't move - // We can accelerate transform propagation by optimizing for this case - .insert_resource(StaticTransformOptimizations::Enabled) - .add_systems(Startup, (setup, load_assets)) - .add_systems(Update, (simulate_cars, loading_screen)) - .add_observer(add_no_cpu_culling) - .add_observer(add_no_cpu_culling_on_scene_ready) - .add_observer(on_city_assets_ready) - .add_observer(setup_settings_ui) - .run(); + traffic::TrafficPlugin, + FreeCameraPlugin, + FeathersPlugins, + WireframePlugin::default(), + )) + .insert_resource(args.clone()) + .insert_resource(ClearColor(Color::BLACK)) + .insert_resource(WinitSettings::continuous()) + .init_resource::() + .insert_resource(UiTheme(create_dark_theme())) + .insert_resource(WireframeConfig { + global: false, + default_color: WHITE.into(), + ..default() + }) + // Like in many realistic large scenes, many of the objects don't move + // We can accelerate transform propagation by optimizing for this case + .insert_resource(StaticTransformOptimizations::Enabled) + .add_systems(Startup, (setup, load_assets)) + .add_systems(Update, (simulate_cars, loading_screen)) + .add_observer(add_no_cpu_culling) + .add_observer(add_no_cpu_culling_on_scene_ready) + .add_observer(on_city_assets_ready) + .add_observer(setup_settings_ui); + + app.add_systems(Update, apply_traffic_rules.before(simulate_cars)); + + app.run(); } fn setup(mut commands: Commands, mut scattering_mediums: ResMut>) { @@ -225,6 +240,7 @@ fn on_city_assets_ready( struct Road { start: Vec3, end: Vec3, + intersection: Entity, } #[derive(Component)] @@ -232,11 +248,34 @@ struct Car { offset: Vec3, distance_traveled: f32, dir: f32, + car_state: CarState, + at_stop_state: CarAtStopState, + next_lane: Option, +} + +#[derive(PartialEq, Eq, Default)] +pub enum CarState { + Accelerating, + #[default] + Driving, + Breaking, + Stopped, +} + +#[derive(PartialEq, Eq, Default)] +pub enum CarAtStopState { + #[default] + Default, + WaitForIntersection, + MoveOnIntersection, + WaitProtectedIntersection, + MoveOnProtectedIntersection, } fn simulate_cars( settings: Res, - roads: Query<(&Road, &Transform, &Children), Without>, + roads: Query<(&Road, &Children), Without>, + car_positions: Query<&Car, Without>, mut cars: Query<(&mut Car, &mut Transform), Without>, time: Res