docs/bevy/examples/2d/tilemap_chunk.rs 4.4 K · 144 lines · raw · history

1 //! Shows a tilemap chunk rendered with a single draw call.
2
3 use bevy::{
4 color::palettes::tailwind::RED_400,
5 image::{ImageArrayLayout, ImageLoaderSettings},
6 prelude::*,
7 sprite_render::{TileData, TilemapChunk, TilemapChunkTileData},
8 };
9 use chacha20::ChaCha8Rng;
10 use rand::{RngExt, SeedableRng};
11
12 fn main() {
13 App::new()
14 .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
15 .add_systems(Startup, (setup, spawn_fake_player).chain())
16 .add_systems(Update, (update_tilemap, move_player, log_tile))
17 .run();
18 }
19
20 #[derive(Component, Deref, DerefMut)]
21 struct UpdateTimer(Timer);
22
23 #[derive(Resource, Deref, DerefMut)]
24 struct SeededRng(ChaCha8Rng);
25
26 fn setup(mut commands: Commands, assets: Res<AssetServer>) {
27 // We're seeding the PRNG here to make this example deterministic for testing purposes.
28 // This isn't strictly required in practical use unless you need your app to be deterministic.
29 let mut rng = ChaCha8Rng::seed_from_u64(42);
30
31 let chunk_size = UVec2::splat(64);
32 let tile_display_size = UVec2::splat(8);
33 let tile_data: Vec<Option<TileData>> = (0..chunk_size.element_product())
34 .map(|_| rng.random_range(0..5))
35 .map(|i| {
36 if i == 0 {
37 None
38 } else {
39 Some(TileData::from_tileset_index(i - 1))
40 }
41 })
42 .collect();
43
44 commands.spawn((
45 TilemapChunk {
46 chunk_size,
47 tile_display_size,
48 tileset: assets
49 .load_builder()
50 .with_settings(|settings: &mut ImageLoaderSettings| {
51 // The tileset texture is expected to be an array of tile textures, so we tell the
52 // `ImageLoader` that our texture is composed of 4 stacked tile images.
53 settings.array_layout = Some(ImageArrayLayout::RowCount { rows: 4 });
54 })
55 .load("textures/array_texture.png"),
56 ..default()
57 },
58 TilemapChunkTileData(tile_data),
59 UpdateTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
60 ));
61
62 commands.spawn(Camera2d);
63
64 commands.insert_resource(SeededRng(rng));
65 }
66
67 #[derive(Component)]
68 struct MovePlayer;
69
70 fn spawn_fake_player(
71 mut commands: Commands,
72 mut meshes: ResMut<Assets<Mesh>>,
73 mut materials: ResMut<Assets<ColorMaterial>>,
74 chunk: Single<&TilemapChunk>,
75 ) {
76 let mut transform = chunk.calculate_tile_transform(UVec2::new(0, 0));
77 transform.translation.z = 1.;
78
79 commands.spawn((
80 Mesh2d(meshes.add(Rectangle::new(8., 8.))),
81 MeshMaterial2d(materials.add(Color::from(RED_400))),
82 transform,
83 MovePlayer,
84 ));
85
86 let mut transform = chunk.calculate_tile_transform(UVec2::new(5, 6));
87 transform.translation.z = 1.;
88
89 // second "player" to visually test a non-zero position
90 commands.spawn((
91 Mesh2d(meshes.add(Rectangle::new(8., 8.))),
92 MeshMaterial2d(materials.add(Color::from(RED_400))),
93 transform,
94 ));
95 }
96
97 fn move_player(
98 mut player: Single<&mut Transform, With<MovePlayer>>,
99 time: Res<Time>,
100 chunk: Single<&TilemapChunk>,
101 ) {
102 let t = (ops::sin(time.elapsed_secs()) + 1.) / 2.;
103
104 let origin = chunk
105 .calculate_tile_transform(UVec2::new(0, 0))
106 .translation
107 .x;
108 let destination = chunk
109 .calculate_tile_transform(UVec2::new(63, 0))
110 .translation
111 .x;
112
113 player.translation.x = origin.lerp(destination, t);
114 }
115
116 fn update_tilemap(
117 time: Res<Time>,
118 mut query: Query<(&mut TilemapChunkTileData, &mut UpdateTimer)>,
119 mut rng: ResMut<SeededRng>,
120 ) {
121 for (mut tile_data, mut timer) in query.iter_mut() {
122 timer.tick(time.delta());
123
124 if timer.just_finished() {
125 for _ in 0..50 {
126 let index = rng.random_range(0..tile_data.len());
127 tile_data[index] = Some(TileData::from_tileset_index(rng.random_range(0..5)));
128 }
129 }
130 }
131 }
132
133 // find the data for an arbitrary tile in the chunk and log its data
134 fn log_tile(tilemap: Single<(&TilemapChunk, &TilemapChunkTileData)>, mut local: Local<u16>) {
135 let (chunk, data) = tilemap.into_inner();
136 let Some(tile_data) = data.tile_data_from_tile_pos(chunk.chunk_size, UVec2::new(3, 4)) else {
137 return;
138 };
139 // log when the tile changes
140 if tile_data.tileset_index != *local {
141 info!(?tile_data, "tile_data changed");
142 *local = tile_data.tileset_index;
143 }
144 }