Path: blob/main/examples/large_scenes/bevy_city/src/generate_city.rs
9730 views
use bevy::prelude::*;1use noise::{NoiseFn, OpenSimplex};2use rand::{rngs::SmallRng, RngExt, SeedableRng};34use crate::{assets::CityAssets, Car, Road};56#[derive(Component)]7pub struct CityRoot;89/// Spawns a grid of city blocks10///11/// For simplicity we spawn the roads and buildings in this pattern12///13/// X-------14/// | B B B15/// | B B B16///17/// X = crossroad, B = buildings18///19/// This way we can easily tile each city block20/// Each city block is 5.5 units x 4.0 units.21///22/// Every asset gets spawned relative to the crossroad position23pub fn spawn_city(commands: &mut Commands, assets: &CityAssets, seed: u64, size: u32) {24let mut rng = SmallRng::seed_from_u64(seed);25let noise = OpenSimplex::new(rng.random());26let noise_scale = 0.025;2728commands29.spawn((CityRoot, Transform::default(), Visibility::default()))30.with_children(|commands| {31let half_size = size as i32 / 2;32for x in -half_size..half_size {33for z in -half_size..half_size {34// scale the position to match the city block size35let x = x as f32 * 5.5;36let z = z as f32 * 4.0;37let offset = Vec3::new(x, 0.0, z);3839spawn_roads_and_cars(commands, assets, &mut rng, offset);4041let density = noise.get([42offset.x as f64 * noise_scale,43offset.z as f64 * noise_scale,440.0,45]) * 0.546+ 0.5;4748let forest = 0.45;49let low_density = 0.6;50let medium_density = 0.7;5152let ground_tile_scale = Vec3::new(4.5, 1.0, 3.0);53commands.spawn((54Mesh3d(assets.ground_tile.0.clone()),55if density < low_density {56MeshMaterial3d(assets.ground_tile.2.clone())57} else {58MeshMaterial3d(assets.ground_tile.1.clone())59},60Transform::from_translation(61Vec3::new(0.5, -0.5005, 0.5) + ground_tile_scale / 2.0 + offset,62)63.with_scale(ground_tile_scale),64));6566if density < forest {67spawn_forest(commands, assets, &mut rng, offset);68} else if density < low_density {69spawn_low_density(commands, assets, &mut rng, offset);70} else if density < medium_density {71spawn_medium_density(commands, assets, &mut rng, offset);72} else {73spawn_high_density(commands, assets, &mut rng, offset);74}75}76}77});78}7980fn spawn_roads_and_cars<R: RngExt>(81commands: &mut ChildSpawnerCommands,82assets: &CityAssets,83rng: &mut R,84offset: Vec3,85) {86let x = offset.x;87let z = offset.z;8889commands.spawn((90SceneRoot(assets.crossroad.clone()),91Transform::from_xyz(x, 0.0, z),92));9394let max_car_density = 0.4;9596// When spawning roads we rotate and stretch a single road asset instead of spawning multiple97// road segments9899// NOTE most of the magic numbers were hand tweaked for something that looks visually nice100101// horizontal road102let car_count = 9;103commands104.spawn((105Transform::from_translation(offset),106Visibility::default(),107Road {108start: Vec3::new(0.75, 0.0, 0.0),109end: Vec3::new(0.75 + (0.5 * car_count as f32), 0.0, 0.0),110},111))112.with_children(|commands| {113commands.spawn((114SceneRoot(assets.road_straight.clone()),115Transform::from_translation(Vec3::new(2.75, 0.0, 0.0))116.with_scale(Vec3::new(4.5, 1.0, 1.0)),117));118119for i in 0..car_count {120let car_pos = Vec3::new(0.0, 0.0, 0.75 + i as f32 * 0.5);121122if rng.random::<f32>() < max_car_density {123commands.spawn((124SceneRoot(assets.get_random_car(rng)),125Transform::from_translation(car_pos + Vec3::new(0.0, 0.0, -0.15))126.with_scale(Vec3::splat(0.15))127.with_rotation(Quat::from_axis_angle(128Vec3::Y,1293.0 * std::f32::consts::FRAC_PI_2,130)),131Car {132distance_traveled: i as f32 * 0.5,133dir: -1.0,134offset: Vec3::new(4.25, 0.0, -0.15),135},136));137}138139if rng.random::<f32>() < max_car_density {140commands.spawn((141SceneRoot(assets.get_random_car(rng)),142Transform::from_translation(car_pos + Vec3::new(0.0, 0.0, 0.15))143.with_scale(Vec3::splat(0.15))144.with_rotation(Quat::from_axis_angle(145Vec3::Y,146std::f32::consts::FRAC_PI_2,147)),148Car {149distance_traveled: i as f32 * 0.5,150dir: 1.0,151offset: Vec3::new(-0.25, 0.0, 0.15),152},153));154}155}156});157158// vertical road159let car_count = 6;160commands161.spawn((162Transform::from_translation(offset),163Visibility::default(),164Road {165start: Vec3::new(0.0, 0.0, 0.75),166end: Vec3::new(0.0, 0.0, 0.75 + (0.5 * car_count as f32)),167},168))169.with_children(|commands| {170commands.spawn((171SceneRoot(assets.road_straight.clone()),172Transform::from_translation(Vec3::new(0.0, 0.0, 2.0))173.with_scale(Vec3::new(3.0, 1.0, 1.0))174.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::FRAC_PI_2)),175));176177for i in 0..car_count {178let car_pos = Vec3::new(0.0, 0.0, 0.75 + i as f32 * 0.5);179180if rng.random::<f32>() < max_car_density {181commands.spawn((182SceneRoot(assets.get_random_car(rng)),183Transform::from_translation(car_pos + Vec3::new(0.15, 0.0, 0.0))184.with_scale(Vec3::splat(0.15)),185Car {186distance_traveled: i as f32 * 0.5,187dir: 1.0,188offset: Vec3::new(-0.15, 0.0, -0.25),189},190));191}192193if rng.random::<f32>() < max_car_density {194commands.spawn((195SceneRoot(assets.get_random_car(rng)),196Transform::from_translation(car_pos + Vec3::new(-0.15, 0.0, 0.0))197.with_scale(Vec3::splat(0.15))198.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::PI)),199Car {200distance_traveled: i as f32 * 0.5,201dir: -1.0,202offset: Vec3::new(0.15, 0.0, 2.75),203},204));205}206}207});208}209210fn spawn_low_density<R: RngExt>(211commands: &mut ChildSpawnerCommands,212assets: &CityAssets,213rng: &mut R,214offset: Vec3,215) {216for x in 1..=2 {217let x_factor = 1.8;218commands.spawn((219assets.low_density.get_random_building(rng),220Transform::from_translation(Vec3::new(x as f32 * x_factor, 0.0, 1.25) + offset),221));222commands.spawn((223assets.low_density.get_random_building(rng),224Transform::from_translation(Vec3::new(x as f32 * x_factor, 0.0, 2.75) + offset)225.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::PI)),226));227}228for i in 0..=6 {229commands.spawn((230SceneRoot(assets.fence.clone()),231Transform::from_translation(Vec3::new(2.75, 0.0, 0.75 + i as f32 * 0.4) + offset)232.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::FRAC_PI_2)),233));234}235for z in 0..=8 {236commands.spawn((237SceneRoot(assets.tree_small.clone()),238Transform::from_translation(Vec3::new(0.75, 0.0, 0.75 + z as f32 * 0.3) + offset),239));240commands.spawn((241SceneRoot(assets.tree_small.clone()),242Transform::from_translation(Vec3::new(4.75, 0.0, 0.75 + z as f32 * 0.3) + offset),243));244}245}246247fn spawn_medium_density<R: RngExt>(248commands: &mut ChildSpawnerCommands,249assets: &CityAssets,250rng: &mut R,251offset: Vec3,252) {253let x_factor = 0.9;254for x in 1..=5 {255commands.spawn((256assets.medium_density.get_random_building(rng),257Transform::from_translation(Vec3::new(x as f32 * x_factor, 0.0, 1.0) + offset),258));259260for tree_x in 0..=1 {261let tree_x = tree_x as f32 * 0.5;262if x == 5 && tree_x == 0.5 {263break;264}265commands.spawn((266SceneRoot(assets.tree_large.clone()),267Transform::from_translation(268Vec3::new(tree_x + x as f32 * x_factor, 0.0, 1.75) + offset,269),270));271commands.spawn((272SceneRoot(assets.tree_large.clone()),273Transform::from_translation(274Vec3::new(tree_x + x as f32 * x_factor, 0.0, 2.25) + offset,275),276));277}278279commands.spawn((280assets.medium_density.get_random_building(rng),281Transform::from_translation(Vec3::new(x as f32 * x_factor, 0.0, 3.0) + offset)282.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::PI)),283));284}285286for x in 0..=10 {287commands.spawn((288SceneRoot(assets.path_stones_long.clone()),289Transform::from_translation(Vec3::new(0.75 + (x as f32 * 0.4), 0.02, 2.0) + offset)290.with_scale(Vec3::new(1.0, 2.0, 1.0))291.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::FRAC_PI_2)),292));293commands.spawn((294SceneRoot(assets.fence.clone()),295Transform::from_translation(Vec3::new(0.75 + (x as f32 * 0.4), 0.02, 1.85) + offset),296));297commands.spawn((298SceneRoot(assets.fence.clone()),299Transform::from_translation(Vec3::new(0.75 + (x as f32 * 0.4), 0.02, 2.15) + offset),300));301}302}303304fn spawn_high_density<R: RngExt>(305commands: &mut ChildSpawnerCommands,306assets: &CityAssets,307rng: &mut R,308offset: Vec3,309) {310for x in 0..3 {311let x = x as f32;312commands.spawn((313assets.high_density.get_random_building(rng),314Transform::from_translation(Vec3::new(1.25 + x * 1.5, 0.0, 1.25) + offset),315));316commands.spawn((317assets.high_density.get_random_building(rng),318Transform::from_translation(Vec3::new(1.25 + x * 1.5, 0.0, 2.75) + offset)319.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::PI)),320));321}322}323324fn spawn_forest<R: RngExt>(325commands: &mut ChildSpawnerCommands,326assets: &CityAssets,327rng: &mut R,328offset: Vec3,329) {330for x in 0..=12 {331for z in 0..=8 {332let transform = Transform::from_translation(333Vec3::new(x as f32, 0.0, z as f32) * Vec3::new(0.325, 0.0, 0.3)334+ Vec3::new(0.75, 0.0, 0.85)335+ offset,336);337338match rng.random_range(0..3) {3390 => {}3401 => {341commands.spawn((SceneRoot(assets.tree_small.clone()), transform));342}3432 => {344commands.spawn((SceneRoot(assets.tree_large.clone()), transform));345}346_ => {}347}348}349}350}351352353