tilemap_chunk/
tilemap_chunk.rs1use bevy::{
4 color::palettes::tailwind::RED_400,
5 image::{ImageArrayLayout, ImageLoaderSettings},
6 prelude::*,
7 sprite_render::{TileData, TilemapChunk, TilemapChunkTileData},
8};
9use chacha20::ChaCha8Rng;
10use rand::{RngExt, SeedableRng};
11
12fn 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)]
21struct UpdateTimer(Timer);
22
23#[derive(Resource, Deref, DerefMut)]
24struct SeededRng(ChaCha8Rng);
25
26fn setup(mut commands: Commands, assets: Res<AssetServer>) {
27 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 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)]
68struct MovePlayer;
69
70fn 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 commands.spawn((
91 Mesh2d(meshes.add(Rectangle::new(8., 8.))),
92 MeshMaterial2d(materials.add(Color::from(RED_400))),
93 transform,
94 ));
95}
96
97fn 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
116fn 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
133fn 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 if tile_data.tileset_index != *local {
141 info!(?tile_data, "tile_data changed");
142 *local = tile_data.tileset_index;
143 }
144}