Add TilemapChunk rendering (#18866)
# Objective An attempt to start building a base for first-party tilemaps (#13782). The objective is to create a very simple tilemap chunk rendering plugin that can be used as a building block for 3rd-party tilemap crates, and eventually a first-party tilemap implementation. ## Solution - Introduces two user-facing components, `TilemapChunk` and `TilemapChunkIndices`, and a new material `TilemapChunkMaterial`. - `TilemapChunk` holds the chunk and tile sizes, and the tileset image - The tileset image is expected to be a layered image for use with `texture_2d_array`, with the assumption that atlases or multiple images would go through an asset loader/processor. Not sure if that should be part of this PR or not.. - `TilemapChunkIndices` holds a 1d representation of all of the tile's Option<u32> index into the tileset image. - Indices are fixed to the size of tiles in a chunk (though maybe this should just be an assertion instead?) - Indices are cloned and sent to the shader through a u32 texture. ## Testing - Initial testing done with the `tilemap_chunk` example, though I need to include some way to update indices as part of it. - Tested wasm with webgl2 and webgpu - I'm thinking it would probably be good to do some basic perf testing. --- ## Showcase ```rust let chunk_size = UVec2::splat(64); let tile_size = UVec2::splat(16); let indices: Vec<Option<u32>> = (0..chunk_size.x * chunk_size.y) .map(|_| rng.gen_range(0..5)) .map(|i| if i == 0 { None } else { Some(i - 1) }) .collect(); commands.spawn(( TilemapChunk { chunk_size, tile_size, tileset, }, TilemapChunkIndices(indices), )); ``` 
This commit is contained in:
parent
d3ad66f033
commit
3f187cf752
11
Cargo.toml
11
Cargo.toml
@ -845,6 +845,17 @@ description = "Generates a texture atlas (sprite sheet) from individual sprites"
|
||||
category = "2D Rendering"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "tilemap_chunk"
|
||||
path = "examples/2d/tilemap_chunk.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.tilemap_chunk]
|
||||
name = "Tilemap Chunk"
|
||||
description = "Renders a tilemap chunk"
|
||||
category = "2D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "transparency_2d"
|
||||
path = "examples/2d/transparency_2d.rs"
|
||||
|
@ -16,6 +16,7 @@ mod picking_backend;
|
||||
mod render;
|
||||
mod sprite;
|
||||
mod texture_slice;
|
||||
mod tilemap_chunk;
|
||||
|
||||
/// The sprite prelude.
|
||||
///
|
||||
@ -40,6 +41,7 @@ pub use picking_backend::*;
|
||||
pub use render::*;
|
||||
pub use sprite::*;
|
||||
pub use texture_slice::*;
|
||||
pub use tilemap_chunk::*;
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{embedded_asset, AssetEventSystems, Assets};
|
||||
@ -87,7 +89,12 @@ impl Plugin for SpritePlugin {
|
||||
.register_type::<TextureSlicer>()
|
||||
.register_type::<Anchor>()
|
||||
.register_type::<Mesh2d>()
|
||||
.add_plugins((Mesh2dRenderPlugin, ColorMaterialPlugin))
|
||||
.add_plugins((
|
||||
Mesh2dRenderPlugin,
|
||||
ColorMaterialPlugin,
|
||||
TilemapChunkPlugin,
|
||||
TilemapChunkMaterialPlugin,
|
||||
))
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
|
263
crates/bevy_sprite/src/tilemap_chunk/mod.rs
Normal file
263
crates/bevy_sprite/src/tilemap_chunk/mod.rs
Normal file
@ -0,0 +1,263 @@
|
||||
use crate::{AlphaMode2d, Anchor, MeshMaterial2d};
|
||||
use bevy_app::{App, Plugin, Update};
|
||||
use bevy_asset::{Assets, Handle, RenderAssetUsages};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
lifecycle::Add,
|
||||
observer::On,
|
||||
query::Changed,
|
||||
resource::Resource,
|
||||
system::{Commands, Query, ResMut},
|
||||
};
|
||||
use bevy_image::{Image, ImageSampler};
|
||||
use bevy_math::{FloatOrd, UVec2, Vec2, Vec3};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_render::{
|
||||
mesh::{Indices, Mesh, Mesh2d, PrimitiveTopology},
|
||||
render_resource::{
|
||||
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
|
||||
},
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
mod tilemap_chunk_material;
|
||||
|
||||
pub use tilemap_chunk_material::*;
|
||||
|
||||
/// Plugin that handles the initialization and updating of tilemap chunks.
|
||||
/// Adds systems for processing newly added tilemap chunks and updating their indices.
|
||||
pub struct TilemapChunkPlugin;
|
||||
|
||||
impl Plugin for TilemapChunkPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<TilemapChunkMeshCache>()
|
||||
.add_observer(on_add_tilemap_chunk)
|
||||
.add_systems(Update, update_tilemap_chunk_indices);
|
||||
}
|
||||
}
|
||||
|
||||
type TilemapChunkMeshCacheKey = (UVec2, FloatOrd, FloatOrd, FloatOrd, FloatOrd);
|
||||
|
||||
/// A resource storing the meshes for each tilemap chunk size.
|
||||
#[derive(Resource, Default, Deref, DerefMut)]
|
||||
pub struct TilemapChunkMeshCache(HashMap<TilemapChunkMeshCacheKey, Handle<Mesh>>);
|
||||
|
||||
/// A component representing a chunk of a tilemap.
|
||||
/// Each chunk is a rectangular section of tiles that is rendered as a single mesh.
|
||||
#[derive(Component, Clone, Debug, Default)]
|
||||
#[require(Mesh2d, MeshMaterial2d<TilemapChunkMaterial>, TilemapChunkIndices, Anchor)]
|
||||
pub struct TilemapChunk {
|
||||
/// The size of the chunk in tiles
|
||||
pub chunk_size: UVec2,
|
||||
/// The size to use for each tile, not to be confused with the size of a tile in the tileset image.
|
||||
/// The size of the tile in the tileset image is determined by the tileset image's dimensions.
|
||||
pub tile_display_size: UVec2,
|
||||
/// Handle to the tileset image containing all tile textures
|
||||
pub tileset: Handle<Image>,
|
||||
/// The alpha mode to use for the tilemap chunk
|
||||
pub alpha_mode: AlphaMode2d,
|
||||
}
|
||||
|
||||
/// Component storing the indices of tiles within a chunk.
|
||||
/// Each index corresponds to a specific tile in the tileset.
|
||||
#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
|
||||
pub struct TilemapChunkIndices(pub Vec<Option<u16>>);
|
||||
|
||||
fn on_add_tilemap_chunk(
|
||||
trigger: On<Add, TilemapChunk>,
|
||||
tilemap_chunk_query: Query<(&TilemapChunk, &TilemapChunkIndices, &Anchor)>,
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<TilemapChunkMaterial>>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
mut tilemap_chunk_mesh_cache: ResMut<TilemapChunkMeshCache>,
|
||||
) {
|
||||
let chunk_entity = trigger.target();
|
||||
let Ok((
|
||||
TilemapChunk {
|
||||
chunk_size,
|
||||
tile_display_size,
|
||||
tileset,
|
||||
alpha_mode,
|
||||
},
|
||||
indices,
|
||||
anchor,
|
||||
)) = tilemap_chunk_query.get(chunk_entity)
|
||||
else {
|
||||
warn!("Tilemap chunk {} not found", chunk_entity);
|
||||
return;
|
||||
};
|
||||
|
||||
let expected_indices_length = chunk_size.element_product() as usize;
|
||||
if indices.len() != expected_indices_length {
|
||||
warn!(
|
||||
"Invalid indices length for tilemap chunk {} of size {}. Expected {}, got {}",
|
||||
chunk_entity,
|
||||
chunk_size,
|
||||
indices.len(),
|
||||
expected_indices_length
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let indices_image = make_chunk_image(chunk_size, &indices.0);
|
||||
|
||||
let display_size = (chunk_size * tile_display_size).as_vec2();
|
||||
|
||||
let mesh_key: TilemapChunkMeshCacheKey = (
|
||||
*chunk_size,
|
||||
FloatOrd(display_size.x),
|
||||
FloatOrd(display_size.y),
|
||||
FloatOrd(anchor.as_vec().x),
|
||||
FloatOrd(anchor.as_vec().y),
|
||||
);
|
||||
|
||||
let mesh = tilemap_chunk_mesh_cache
|
||||
.entry(mesh_key)
|
||||
.or_insert_with(|| meshes.add(make_chunk_mesh(chunk_size, &display_size, anchor)));
|
||||
|
||||
commands.entity(chunk_entity).insert((
|
||||
Mesh2d(mesh.clone()),
|
||||
MeshMaterial2d(materials.add(TilemapChunkMaterial {
|
||||
tileset: tileset.clone(),
|
||||
indices: images.add(indices_image),
|
||||
alpha_mode: *alpha_mode,
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
fn update_tilemap_chunk_indices(
|
||||
query: Query<
|
||||
(
|
||||
Entity,
|
||||
&TilemapChunk,
|
||||
&TilemapChunkIndices,
|
||||
&MeshMaterial2d<TilemapChunkMaterial>,
|
||||
),
|
||||
Changed<TilemapChunkIndices>,
|
||||
>,
|
||||
mut materials: ResMut<Assets<TilemapChunkMaterial>>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
) {
|
||||
for (chunk_entity, TilemapChunk { chunk_size, .. }, indices, material) in query {
|
||||
let expected_indices_length = chunk_size.element_product() as usize;
|
||||
if indices.len() != expected_indices_length {
|
||||
warn!(
|
||||
"Invalid TilemapChunkIndices length for tilemap chunk {} of size {}. Expected {}, got {}",
|
||||
chunk_entity,
|
||||
chunk_size,
|
||||
indices.len(),
|
||||
expected_indices_length
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(material) = materials.get_mut(material.id()) else {
|
||||
warn!(
|
||||
"TilemapChunkMaterial not found for tilemap chunk {}",
|
||||
chunk_entity
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Some(indices_image) = images.get_mut(&material.indices) else {
|
||||
warn!(
|
||||
"TilemapChunkMaterial indices image not found for tilemap chunk {}",
|
||||
chunk_entity
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Some(data) = indices_image.data.as_mut() else {
|
||||
warn!(
|
||||
"TilemapChunkMaterial indices image data not found for tilemap chunk {}",
|
||||
chunk_entity
|
||||
);
|
||||
continue;
|
||||
};
|
||||
data.clear();
|
||||
data.extend(
|
||||
indices
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(|i| u16::to_ne_bytes(i.unwrap_or(u16::MAX))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn make_chunk_image(size: &UVec2, indices: &[Option<u16>]) -> Image {
|
||||
Image {
|
||||
data: Some(
|
||||
indices
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(|i| u16::to_ne_bytes(i.unwrap_or(u16::MAX)))
|
||||
.collect(),
|
||||
),
|
||||
texture_descriptor: TextureDescriptor {
|
||||
size: Extent3d {
|
||||
width: size.x,
|
||||
height: size.y,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::R16Uint,
|
||||
label: None,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
},
|
||||
sampler: ImageSampler::nearest(),
|
||||
texture_view_descriptor: None,
|
||||
asset_usage: RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_chunk_mesh(size: &UVec2, display_size: &Vec2, anchor: &Anchor) -> Mesh {
|
||||
let mut mesh = Mesh::new(
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD,
|
||||
);
|
||||
|
||||
let offset = display_size * (Vec2::splat(-0.5) - anchor.as_vec());
|
||||
|
||||
let num_quads = size.element_product() as usize;
|
||||
let quad_size = display_size / size.as_vec2();
|
||||
|
||||
let mut positions = Vec::with_capacity(4 * num_quads);
|
||||
let mut uvs = Vec::with_capacity(4 * num_quads);
|
||||
let mut indices = Vec::with_capacity(6 * num_quads);
|
||||
|
||||
for y in 0..size.y {
|
||||
for x in 0..size.x {
|
||||
let i = positions.len() as u32;
|
||||
|
||||
let p0 = offset + quad_size * UVec2::new(x, y).as_vec2();
|
||||
let p1 = p0 + quad_size;
|
||||
|
||||
positions.extend([
|
||||
Vec3::new(p0.x, p0.y, 0.0),
|
||||
Vec3::new(p1.x, p0.y, 0.0),
|
||||
Vec3::new(p0.x, p1.y, 0.0),
|
||||
Vec3::new(p1.x, p1.y, 0.0),
|
||||
]);
|
||||
|
||||
uvs.extend([
|
||||
Vec2::new(0.0, 1.0),
|
||||
Vec2::new(1.0, 1.0),
|
||||
Vec2::new(0.0, 0.0),
|
||||
Vec2::new(1.0, 0.0),
|
||||
]);
|
||||
|
||||
indices.extend([i, i + 2, i + 1]);
|
||||
indices.extend([i + 3, i + 1, i + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
|
||||
mesh.insert_indices(Indices::U32(indices));
|
||||
|
||||
mesh
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
use crate::{AlphaMode2d, Material2d, Material2dKey, Material2dPlugin};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{embedded_asset, embedded_path, Asset, AssetPath, Handle};
|
||||
use bevy_image::Image;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_render::{
|
||||
mesh::{Mesh, MeshVertexBufferLayoutRef},
|
||||
render_resource::*,
|
||||
};
|
||||
|
||||
/// Plugin that adds support for tilemap chunk materials.
|
||||
#[derive(Default)]
|
||||
pub struct TilemapChunkMaterialPlugin;
|
||||
|
||||
impl Plugin for TilemapChunkMaterialPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
embedded_asset!(app, "tilemap_chunk_material.wgsl");
|
||||
|
||||
app.add_plugins(Material2dPlugin::<TilemapChunkMaterial>::default());
|
||||
}
|
||||
}
|
||||
|
||||
/// Material used for rendering tilemap chunks.
|
||||
///
|
||||
/// This material is used internally by the tilemap system to render chunks of tiles
|
||||
/// efficiently using a single draw call per chunk.
|
||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
||||
pub struct TilemapChunkMaterial {
|
||||
pub alpha_mode: AlphaMode2d,
|
||||
|
||||
#[texture(0, dimension = "2d_array")]
|
||||
#[sampler(1)]
|
||||
pub tileset: Handle<Image>,
|
||||
|
||||
#[texture(2, sample_type = "u_int")]
|
||||
pub indices: Handle<Image>,
|
||||
}
|
||||
|
||||
impl Material2d for TilemapChunkMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
ShaderRef::Path(
|
||||
AssetPath::from_path_buf(embedded_path!("tilemap_chunk_material.wgsl"))
|
||||
.with_source("embedded"),
|
||||
)
|
||||
}
|
||||
|
||||
fn vertex_shader() -> ShaderRef {
|
||||
ShaderRef::Path(
|
||||
AssetPath::from_path_buf(embedded_path!("tilemap_chunk_material.wgsl"))
|
||||
.with_source("embedded"),
|
||||
)
|
||||
}
|
||||
|
||||
fn alpha_mode(&self) -> AlphaMode2d {
|
||||
self.alpha_mode
|
||||
}
|
||||
|
||||
fn specialize(
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
layout: &MeshVertexBufferLayoutRef,
|
||||
_key: Material2dKey<Self>,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
let vertex_layout = layout.0.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
Mesh::ATTRIBUTE_UV_0.at_shader_location(1),
|
||||
])?;
|
||||
descriptor.vertex.buffers = vec![vertex_layout];
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
#import bevy_sprite::{
|
||||
mesh2d_functions as mesh_functions,
|
||||
mesh2d_view_bindings::view,
|
||||
}
|
||||
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) uv: vec2<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) tile_index: u32,
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var tileset: texture_2d_array<f32>;
|
||||
@group(2) @binding(1) var tileset_sampler: sampler;
|
||||
@group(2) @binding(2) var tile_indices: texture_2d<u32>;
|
||||
|
||||
@vertex
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
|
||||
let world_position = mesh_functions::mesh2d_position_local_to_world(
|
||||
world_from_local,
|
||||
vec4<f32>(vertex.position, 1.0)
|
||||
);
|
||||
|
||||
out.position = mesh_functions::mesh2d_position_world_to_clip(world_position);
|
||||
out.uv = vertex.uv;
|
||||
out.tile_index = vertex.vertex_index / 4u;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let chunk_size = textureDimensions(tile_indices, 0);
|
||||
let tile_xy = vec2<u32>(
|
||||
in.tile_index % chunk_size.x,
|
||||
in.tile_index / chunk_size.x
|
||||
);
|
||||
let tile_id = textureLoad(tile_indices, tile_xy, 0).r;
|
||||
|
||||
if tile_id == 0xffffu {
|
||||
discard;
|
||||
}
|
||||
|
||||
let color = textureSample(tileset, tileset_sampler, in.uv, tile_id);
|
||||
if color.a < 0.001 {
|
||||
discard;
|
||||
}
|
||||
return color;
|
||||
}
|
70
examples/2d/tilemap_chunk.rs
Normal file
70
examples/2d/tilemap_chunk.rs
Normal file
@ -0,0 +1,70 @@
|
||||
//! Shows a tilemap chunk rendered with a single draw call.
|
||||
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
sprite::{TilemapChunk, TilemapChunkIndices},
|
||||
};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((DefaultPlugins.set(ImagePlugin::default_nearest()),))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (update_tileset_image, update_tilemap))
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Component, Deref, DerefMut)]
|
||||
struct UpdateTimer(Timer);
|
||||
|
||||
fn setup(mut commands: Commands, assets: Res<AssetServer>) {
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(42);
|
||||
let chunk_size = UVec2::splat(64);
|
||||
let tile_display_size = UVec2::splat(8);
|
||||
let indices: Vec<Option<u16>> = (0..chunk_size.element_product())
|
||||
.map(|_| rng.gen_range(0..5))
|
||||
.map(|i| if i == 0 { None } else { Some(i - 1) })
|
||||
.collect();
|
||||
|
||||
commands.spawn((
|
||||
TilemapChunk {
|
||||
chunk_size,
|
||||
tile_display_size,
|
||||
tileset: assets.load("textures/array_texture.png"),
|
||||
..default()
|
||||
},
|
||||
TilemapChunkIndices(indices),
|
||||
UpdateTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
|
||||
));
|
||||
|
||||
commands.spawn(Camera2d);
|
||||
}
|
||||
|
||||
fn update_tileset_image(
|
||||
chunk_query: Single<&TilemapChunk>,
|
||||
mut events: EventReader<AssetEvent<Image>>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
) {
|
||||
let chunk = *chunk_query;
|
||||
for event in events.read() {
|
||||
if event.is_loaded_with_dependencies(chunk.tileset.id()) {
|
||||
let image = images.get_mut(&chunk.tileset).unwrap();
|
||||
image.reinterpret_stacked_2d_as_array(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_tilemap(time: Res<Time>, mut query: Query<(&mut TilemapChunkIndices, &mut UpdateTimer)>) {
|
||||
for (mut indices, mut timer) in query.iter_mut() {
|
||||
timer.tick(time.delta());
|
||||
|
||||
if timer.just_finished() {
|
||||
let mut rng = ChaCha8Rng::from_entropy();
|
||||
for _ in 0..50 {
|
||||
let index = rng.gen_range(0..indices.len());
|
||||
indices[index] = Some(rng.gen_range(0..5));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -129,6 +129,7 @@ Example | Description
|
||||
[Sprite Tile](../examples/2d/sprite_tile.rs) | Renders a sprite tiled in a grid
|
||||
[Text 2D](../examples/2d/text2d.rs) | Generates text in 2D
|
||||
[Texture Atlas](../examples/2d/texture_atlas.rs) | Generates a texture atlas (sprite sheet) from individual sprites
|
||||
[Tilemap Chunk](../examples/2d/tilemap_chunk.rs) | Renders a tilemap chunk
|
||||
[Transparency in 2D](../examples/2d/transparency_2d.rs) | Demonstrates transparency in 2d
|
||||
|
||||
## 3D Rendering
|
||||
|
25
release-content/release-notes/tilemap-chunk-rendering.md
Normal file
25
release-content/release-notes/tilemap-chunk-rendering.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Tilemap Chunk Rendering
|
||||
authors: ["@ConnerPetzold", "@grind086", "@IceSentry"]
|
||||
pull_requests: [18866]
|
||||
---
|
||||
|
||||
A performant way to render tilemap chunks has been added as the first building block to Bevy's tilemap support. You can render a chunk by supplying a tileset texture to the `TilemapChunk` component and the indices into that tileset for each tile to `TilemapChunkIndices`.
|
||||
|
||||
```rust
|
||||
let chunk_size = UVec2::splat(64);
|
||||
let tile_size = UVec2::splat(16);
|
||||
let indices: Vec<Option<u32>> = (0..chunk_size.x * chunk_size.y)
|
||||
.map(|_| rng.gen_range(0..5))
|
||||
.map(|i| if i == 0 { None } else { Some(i - 1) })
|
||||
.collect();
|
||||
|
||||
commands.spawn((
|
||||
TilemapChunk {
|
||||
chunk_size,
|
||||
tile_size,
|
||||
tileset,
|
||||
},
|
||||
TilemapChunkIndices(indices),
|
||||
));
|
||||
```
|
Loading…
Reference in New Issue
Block a user