TilemapChunk single quad; TileData (color/visibility) (#19924)
# Objective - Use a single quad to render a TilemapChunk - Add support for tile color and visibility ## Testing - Tested using example -- there doesn't appear to be any visual tile bleeding or rounding issues. Open to ideas on further testing
This commit is contained in:
parent
25cb339a12
commit
ebf6bf6ea9
@ -1,25 +1,24 @@
|
|||||||
use crate::{AlphaMode2d, Anchor, MeshMaterial2d};
|
use crate::{AlphaMode2d, MeshMaterial2d};
|
||||||
use bevy_app::{App, Plugin, Update};
|
use bevy_app::{App, Plugin, Update};
|
||||||
use bevy_asset::{Assets, Handle, RenderAssetUsages};
|
use bevy_asset::{Assets, Handle};
|
||||||
|
use bevy_color::Color;
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
lifecycle::HookContext,
|
lifecycle::HookContext,
|
||||||
query::Changed,
|
query::Changed,
|
||||||
|
reflect::{ReflectComponent, ReflectResource},
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
system::{Query, ResMut},
|
system::{Query, ResMut},
|
||||||
world::DeferredWorld,
|
world::DeferredWorld,
|
||||||
};
|
};
|
||||||
use bevy_image::{Image, ImageSampler, ToExtents};
|
use bevy_image::Image;
|
||||||
use bevy_math::{FloatOrd, UVec2, Vec2, Vec3};
|
use bevy_math::{primitives::Rectangle, UVec2};
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_render::{
|
use bevy_reflect::{prelude::*, Reflect};
|
||||||
mesh::{Indices, Mesh, Mesh2d, PrimitiveTopology},
|
use bevy_render::mesh::{Mesh, Mesh2d};
|
||||||
render_resource::{
|
use bevy_utils::default;
|
||||||
TextureDataOrder, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
mod tilemap_chunk_material;
|
mod tilemap_chunk_material;
|
||||||
@ -32,38 +31,73 @@ pub struct TilemapChunkPlugin;
|
|||||||
|
|
||||||
impl Plugin for TilemapChunkPlugin {
|
impl Plugin for TilemapChunkPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_resource::<TilemapChunkMeshCache>()
|
app.register_type::<TilemapChunkMeshCache>()
|
||||||
|
.register_type::<TilemapChunk>()
|
||||||
|
.register_type::<TilemapChunkTileData>()
|
||||||
|
.init_resource::<TilemapChunkMeshCache>()
|
||||||
.add_systems(Update, update_tilemap_chunk_indices);
|
.add_systems(Update, update_tilemap_chunk_indices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TilemapChunkMeshCacheKey = (UVec2, FloatOrd, FloatOrd, FloatOrd, FloatOrd);
|
|
||||||
|
|
||||||
/// A resource storing the meshes for each tilemap chunk size.
|
/// A resource storing the meshes for each tilemap chunk size.
|
||||||
#[derive(Resource, Default, Deref, DerefMut)]
|
#[derive(Resource, Default, Deref, DerefMut, Reflect)]
|
||||||
pub struct TilemapChunkMeshCache(HashMap<TilemapChunkMeshCacheKey, Handle<Mesh>>);
|
#[reflect(Resource, Default)]
|
||||||
|
pub struct TilemapChunkMeshCache(HashMap<UVec2, Handle<Mesh>>);
|
||||||
|
|
||||||
/// A component representing a chunk of a tilemap.
|
/// A component representing a chunk of a tilemap.
|
||||||
/// Each chunk is a rectangular section of tiles that is rendered as a single mesh.
|
/// Each chunk is a rectangular section of tiles that is rendered as a single mesh.
|
||||||
#[derive(Component, Clone, Debug, Default)]
|
#[derive(Component, Clone, Debug, Default, Reflect)]
|
||||||
#[require(Anchor)]
|
#[reflect(Component, Clone, Debug, Default)]
|
||||||
#[component(immutable, on_insert = on_insert_tilemap_chunk)]
|
#[component(immutable, on_insert = on_insert_tilemap_chunk)]
|
||||||
pub struct TilemapChunk {
|
pub struct TilemapChunk {
|
||||||
/// The size of the chunk in tiles
|
/// The size of the chunk in tiles.
|
||||||
pub chunk_size: UVec2,
|
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 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.
|
/// The size of the tile in the tileset image is determined by the tileset image's dimensions.
|
||||||
pub tile_display_size: UVec2,
|
pub tile_display_size: UVec2,
|
||||||
/// Handle to the tileset image containing all tile textures
|
/// Handle to the tileset image containing all tile textures.
|
||||||
pub tileset: Handle<Image>,
|
pub tileset: Handle<Image>,
|
||||||
/// The alpha mode to use for the tilemap chunk
|
/// The alpha mode to use for the tilemap chunk.
|
||||||
pub alpha_mode: AlphaMode2d,
|
pub alpha_mode: AlphaMode2d,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Component storing the indices of tiles within a chunk.
|
/// Data for a single tile in the tilemap chunk.
|
||||||
/// Each index corresponds to a specific tile in the tileset.
|
#[derive(Clone, Copy, Debug, Reflect)]
|
||||||
#[derive(Component, Clone, Debug, Deref, DerefMut)]
|
#[reflect(Clone, Debug, Default)]
|
||||||
pub struct TilemapChunkIndices(pub Vec<Option<u16>>);
|
pub struct TileData {
|
||||||
|
/// The index of the tile in the corresponding tileset array texture.
|
||||||
|
pub tileset_index: u16,
|
||||||
|
/// The color tint of the tile. White leaves the sampled texture color unchanged.
|
||||||
|
pub color: Color,
|
||||||
|
/// The visibility of the tile.
|
||||||
|
pub visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileData {
|
||||||
|
/// Creates a new `TileData` with the given tileset index and default values.
|
||||||
|
pub fn from_tileset_index(tileset_index: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
tileset_index,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TileData {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
tileset_index: 0,
|
||||||
|
color: Color::WHITE,
|
||||||
|
visible: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Component storing the data of tiles within a chunk.
|
||||||
|
/// Each index corresponds to a specific tile in the tileset. `None` indicates an empty tile.
|
||||||
|
#[derive(Component, Clone, Debug, Deref, DerefMut, Reflect)]
|
||||||
|
#[reflect(Component, Clone, Debug)]
|
||||||
|
pub struct TilemapChunkTileData(pub Vec<Option<TileData>>);
|
||||||
|
|
||||||
fn on_insert_tilemap_chunk(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
|
fn on_insert_tilemap_chunk(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
|
||||||
let Some(tilemap_chunk) = world.get::<TilemapChunk>(entity) else {
|
let Some(tilemap_chunk) = world.get::<TilemapChunk>(entity) else {
|
||||||
@ -75,55 +109,46 @@ fn on_insert_tilemap_chunk(mut world: DeferredWorld, HookContext { entity, .. }:
|
|||||||
let alpha_mode = tilemap_chunk.alpha_mode;
|
let alpha_mode = tilemap_chunk.alpha_mode;
|
||||||
let tileset = tilemap_chunk.tileset.clone();
|
let tileset = tilemap_chunk.tileset.clone();
|
||||||
|
|
||||||
let Some(indices) = world.get::<TilemapChunkIndices>(entity) else {
|
let Some(tile_data) = world.get::<TilemapChunkTileData>(entity) else {
|
||||||
warn!("TilemapChunkIndices not found for tilemap chunk {}", entity);
|
warn!("TilemapChunkIndices not found for tilemap chunk {}", entity);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(&anchor) = world.get::<Anchor>(entity) else {
|
let expected_tile_data_length = chunk_size.element_product() as usize;
|
||||||
warn!("Anchor not found for tilemap chunk {}", entity);
|
if tile_data.len() != expected_tile_data_length {
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let expected_indices_length = chunk_size.element_product() as usize;
|
|
||||||
if indices.len() != expected_indices_length {
|
|
||||||
warn!(
|
warn!(
|
||||||
"Invalid indices length for tilemap chunk {} of size {}. Expected {}, got {}",
|
"Invalid tile data length for tilemap chunk {} of size {}. Expected {}, got {}",
|
||||||
entity,
|
entity,
|
||||||
chunk_size,
|
chunk_size,
|
||||||
indices.len(),
|
expected_tile_data_length,
|
||||||
expected_indices_length
|
tile_data.len(),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let indices_image = make_chunk_image(&chunk_size, &indices.0);
|
let packed_tile_data: Vec<PackedTileData> =
|
||||||
|
tile_data.0.iter().map(|&tile| tile.into()).collect();
|
||||||
|
|
||||||
let display_size = (chunk_size * tilemap_chunk.tile_display_size).as_vec2();
|
let tile_data_image = make_chunk_tile_data_image(&chunk_size, &packed_tile_data);
|
||||||
|
|
||||||
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 tilemap_chunk_mesh_cache = world.resource::<TilemapChunkMeshCache>();
|
let tilemap_chunk_mesh_cache = world.resource::<TilemapChunkMeshCache>();
|
||||||
let mesh = if let Some(mesh) = tilemap_chunk_mesh_cache.get(&mesh_key) {
|
|
||||||
|
let mesh_size = chunk_size * tilemap_chunk.tile_display_size;
|
||||||
|
|
||||||
|
let mesh = if let Some(mesh) = tilemap_chunk_mesh_cache.get(&mesh_size) {
|
||||||
mesh.clone()
|
mesh.clone()
|
||||||
} else {
|
} else {
|
||||||
let mut meshes = world.resource_mut::<Assets<Mesh>>();
|
let mut meshes = world.resource_mut::<Assets<Mesh>>();
|
||||||
meshes.add(make_chunk_mesh(&chunk_size, &display_size, &anchor))
|
meshes.add(Rectangle::from_size(mesh_size.as_vec2()))
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut images = world.resource_mut::<Assets<Image>>();
|
let mut images = world.resource_mut::<Assets<Image>>();
|
||||||
let indices = images.add(indices_image);
|
let tile_data = images.add(tile_data_image);
|
||||||
|
|
||||||
let mut materials = world.resource_mut::<Assets<TilemapChunkMaterial>>();
|
let mut materials = world.resource_mut::<Assets<TilemapChunkMaterial>>();
|
||||||
let material = materials.add(TilemapChunkMaterial {
|
let material = materials.add(TilemapChunkMaterial {
|
||||||
tileset,
|
tileset,
|
||||||
indices,
|
tile_data,
|
||||||
alpha_mode,
|
alpha_mode,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -138,27 +163,30 @@ fn update_tilemap_chunk_indices(
|
|||||||
(
|
(
|
||||||
Entity,
|
Entity,
|
||||||
&TilemapChunk,
|
&TilemapChunk,
|
||||||
&TilemapChunkIndices,
|
&TilemapChunkTileData,
|
||||||
&MeshMaterial2d<TilemapChunkMaterial>,
|
&MeshMaterial2d<TilemapChunkMaterial>,
|
||||||
),
|
),
|
||||||
Changed<TilemapChunkIndices>,
|
Changed<TilemapChunkTileData>,
|
||||||
>,
|
>,
|
||||||
mut materials: ResMut<Assets<TilemapChunkMaterial>>,
|
mut materials: ResMut<Assets<TilemapChunkMaterial>>,
|
||||||
mut images: ResMut<Assets<Image>>,
|
mut images: ResMut<Assets<Image>>,
|
||||||
) {
|
) {
|
||||||
for (chunk_entity, TilemapChunk { chunk_size, .. }, indices, material) in query {
|
for (chunk_entity, TilemapChunk { chunk_size, .. }, tile_data, material) in query {
|
||||||
let expected_indices_length = chunk_size.element_product() as usize;
|
let expected_tile_data_length = chunk_size.element_product() as usize;
|
||||||
if indices.len() != expected_indices_length {
|
if tile_data.len() != expected_tile_data_length {
|
||||||
warn!(
|
warn!(
|
||||||
"Invalid TilemapChunkIndices length for tilemap chunk {} of size {}. Expected {}, got {}",
|
"Invalid TilemapChunkTileData length for tilemap chunk {} of size {}. Expected {}, got {}",
|
||||||
chunk_entity,
|
chunk_entity,
|
||||||
chunk_size,
|
chunk_size,
|
||||||
indices.len(),
|
tile_data.len(),
|
||||||
expected_indices_length
|
expected_tile_data_length
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let packed_tile_data: Vec<PackedTileData> =
|
||||||
|
tile_data.0.iter().map(|&tile| tile.into()).collect();
|
||||||
|
|
||||||
// Getting the material mutably to trigger change detection
|
// Getting the material mutably to trigger change detection
|
||||||
let Some(material) = materials.get_mut(material.id()) else {
|
let Some(material) = materials.get_mut(material.id()) else {
|
||||||
warn!(
|
warn!(
|
||||||
@ -167,101 +195,21 @@ fn update_tilemap_chunk_indices(
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(indices_image) = images.get_mut(&material.indices) else {
|
let Some(tile_data_image) = images.get_mut(&material.tile_data) else {
|
||||||
warn!(
|
warn!(
|
||||||
"TilemapChunkMaterial indices image not found for tilemap chunk {}",
|
"TilemapChunkMaterial tile data image not found for tilemap chunk {}",
|
||||||
chunk_entity
|
chunk_entity
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(data) = indices_image.data.as_mut() else {
|
let Some(data) = tile_data_image.data.as_mut() else {
|
||||||
warn!(
|
warn!(
|
||||||
"TilemapChunkMaterial indices image data not found for tilemap chunk {}",
|
"TilemapChunkMaterial tile data image data not found for tilemap chunk {}",
|
||||||
chunk_entity
|
chunk_entity
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
data.clear();
|
data.clear();
|
||||||
data.extend(
|
data.extend_from_slice(bytemuck::cast_slice(&packed_tile_data));
|
||||||
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(),
|
|
||||||
),
|
|
||||||
data_order: TextureDataOrder::default(),
|
|
||||||
texture_descriptor: TextureDescriptor {
|
|
||||||
size: size.to_extents(),
|
|
||||||
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,
|
|
||||||
copy_on_resize: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use crate::{AlphaMode2d, Material2d, Material2dKey, Material2dPlugin};
|
use crate::{AlphaMode2d, Material2d, Material2dPlugin, TileData};
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
use bevy_asset::{embedded_asset, embedded_path, Asset, AssetPath, Handle};
|
use bevy_asset::{embedded_asset, embedded_path, Asset, AssetPath, Handle, RenderAssetUsages};
|
||||||
use bevy_image::Image;
|
use bevy_color::ColorToPacked;
|
||||||
|
use bevy_image::{Image, ImageSampler, ToExtents};
|
||||||
|
use bevy_math::UVec2;
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use bevy_render::{
|
use bevy_render::render_resource::*;
|
||||||
mesh::{Mesh, MeshVertexBufferLayoutRef},
|
use bytemuck::{Pod, Zeroable};
|
||||||
render_resource::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Plugin that adds support for tilemap chunk materials.
|
/// Plugin that adds support for tilemap chunk materials.
|
||||||
pub struct TilemapChunkMaterialPlugin;
|
pub struct TilemapChunkMaterialPlugin;
|
||||||
@ -32,7 +32,7 @@ pub struct TilemapChunkMaterial {
|
|||||||
pub tileset: Handle<Image>,
|
pub tileset: Handle<Image>,
|
||||||
|
|
||||||
#[texture(2, sample_type = "u_int")]
|
#[texture(2, sample_type = "u_int")]
|
||||||
pub indices: Handle<Image>,
|
pub tile_data: Handle<Image>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Material2d for TilemapChunkMaterial {
|
impl Material2d for TilemapChunkMaterial {
|
||||||
@ -43,27 +43,71 @@ impl Material2d for TilemapChunkMaterial {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vertex_shader() -> ShaderRef {
|
|
||||||
ShaderRef::Path(
|
|
||||||
AssetPath::from_path_buf(embedded_path!("tilemap_chunk_material.wgsl"))
|
|
||||||
.with_source("embedded"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alpha_mode(&self) -> AlphaMode2d {
|
fn alpha_mode(&self) -> AlphaMode2d {
|
||||||
self.alpha_mode
|
self.alpha_mode
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn specialize(
|
/// Packed per-tile data for use in the `Rgba16Uint` tile data texture in `TilemapChunkMaterial`.
|
||||||
descriptor: &mut RenderPipelineDescriptor,
|
#[repr(C)]
|
||||||
layout: &MeshVertexBufferLayoutRef,
|
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||||
_key: Material2dKey<Self>,
|
pub struct PackedTileData {
|
||||||
) -> Result<(), SpecializedMeshPipelineError> {
|
tileset_index: u16, // red channel
|
||||||
let vertex_layout = layout.0.get_layout(&[
|
color: [u8; 4], // green and blue channels
|
||||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
flags: u16, // alpha channel
|
||||||
Mesh::ATTRIBUTE_UV_0.at_shader_location(1),
|
}
|
||||||
])?;
|
|
||||||
descriptor.vertex.buffers = vec![vertex_layout];
|
impl PackedTileData {
|
||||||
Ok(())
|
fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
tileset_index: u16::MAX,
|
||||||
|
color: [0, 0, 0, 0],
|
||||||
|
flags: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TileData> for PackedTileData {
|
||||||
|
fn from(
|
||||||
|
TileData {
|
||||||
|
tileset_index,
|
||||||
|
color,
|
||||||
|
visible,
|
||||||
|
}: TileData,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
tileset_index,
|
||||||
|
color: color.to_srgba().to_u8_array(),
|
||||||
|
flags: visible as u16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Option<TileData>> for PackedTileData {
|
||||||
|
fn from(maybe_tile_data: Option<TileData>) -> Self {
|
||||||
|
maybe_tile_data
|
||||||
|
.map(Into::into)
|
||||||
|
.unwrap_or(PackedTileData::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_chunk_tile_data_image(size: &UVec2, data: &[PackedTileData]) -> Image {
|
||||||
|
Image {
|
||||||
|
data: Some(bytemuck::cast_slice(data).to_vec()),
|
||||||
|
data_order: TextureDataOrder::default(),
|
||||||
|
texture_descriptor: TextureDescriptor {
|
||||||
|
size: size.to_extents(),
|
||||||
|
dimension: TextureDimension::D2,
|
||||||
|
format: TextureFormat::Rgba16Uint,
|
||||||
|
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,
|
||||||
|
copy_on_resize: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,58 +1,55 @@
|
|||||||
#import bevy_sprite::{
|
#import bevy_sprite::{
|
||||||
mesh2d_functions as mesh_functions,
|
mesh2d_functions as mesh_functions,
|
||||||
mesh2d_view_bindings::view,
|
mesh2d_view_bindings::view,
|
||||||
}
|
mesh2d_vertex_output::VertexOutput,
|
||||||
|
|
||||||
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(0) var tileset: texture_2d_array<f32>;
|
||||||
@group(2) @binding(1) var tileset_sampler: sampler;
|
@group(2) @binding(1) var tileset_sampler: sampler;
|
||||||
@group(2) @binding(2) var tile_indices: texture_2d<u32>;
|
@group(2) @binding(2) var tile_data: texture_2d<u32>;
|
||||||
|
|
||||||
@vertex
|
struct TileData {
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
tileset_index: u32,
|
||||||
var out: VertexOutput;
|
color: vec4<f32>,
|
||||||
|
visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
let world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
|
fn get_tile_data(coord: vec2<u32>) -> TileData {
|
||||||
let world_position = mesh_functions::mesh2d_position_local_to_world(
|
let data = textureLoad(tile_data, coord, 0);
|
||||||
world_from_local,
|
|
||||||
vec4<f32>(vertex.position, 1.0)
|
|
||||||
);
|
|
||||||
|
|
||||||
out.position = mesh_functions::mesh2d_position_world_to_clip(world_position);
|
let tileset_index = data.r;
|
||||||
out.uv = vertex.uv;
|
|
||||||
out.tile_index = vertex.vertex_index / 4u;
|
|
||||||
|
|
||||||
return out;
|
let color_r = f32(data.g & 0xFFu) / 255.0;
|
||||||
|
let color_g = f32((data.g >> 8u) & 0xFFu) / 255.0;
|
||||||
|
let color_b = f32(data.b & 0xFFu) / 255.0;
|
||||||
|
let color_a = f32((data.b >> 8u) & 0xFFu) / 255.0;
|
||||||
|
|
||||||
|
let color = vec4<f32>(color_r, color_g, color_b, color_a);
|
||||||
|
|
||||||
|
let visible = data.a != 0u;
|
||||||
|
|
||||||
|
return TileData(tileset_index, color, visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
let chunk_size = textureDimensions(tile_indices, 0);
|
let chunk_size = textureDimensions(tile_data, 0);
|
||||||
let tile_xy = vec2<u32>(
|
let tile_uv = in.uv * vec2<f32>(chunk_size);
|
||||||
in.tile_index % chunk_size.x,
|
let tile_coord = clamp(vec2<u32>(floor(tile_uv)), vec2<u32>(0), chunk_size - 1);
|
||||||
in.tile_index / chunk_size.x
|
|
||||||
);
|
|
||||||
let tile_id = textureLoad(tile_indices, tile_xy, 0).r;
|
|
||||||
|
|
||||||
if tile_id == 0xffffu {
|
let tile = get_tile_data(tile_coord);
|
||||||
|
|
||||||
|
if (tile.tileset_index == 0xffffu || !tile.visible) {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = textureSample(tileset, tileset_sampler, in.uv, tile_id);
|
let local_uv = fract(tile_uv);
|
||||||
if color.a < 0.001 {
|
let tex_color = textureSample(tileset, tileset_sampler, local_uv, tile.tileset_index);
|
||||||
|
let final_color = tex_color * tile.color;
|
||||||
|
|
||||||
|
if (final_color.a < 0.001) {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
return color;
|
|
||||||
|
return final_color;
|
||||||
}
|
}
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
sprite::{TilemapChunk, TilemapChunkIndices},
|
sprite::{TileData, TilemapChunk, TilemapChunkTileData},
|
||||||
};
|
};
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use rand_chacha::ChaCha8Rng;
|
use rand_chacha::ChaCha8Rng;
|
||||||
@ -28,9 +28,15 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
|
|||||||
|
|
||||||
let chunk_size = UVec2::splat(64);
|
let chunk_size = UVec2::splat(64);
|
||||||
let tile_display_size = UVec2::splat(8);
|
let tile_display_size = UVec2::splat(8);
|
||||||
let indices: Vec<Option<u16>> = (0..chunk_size.element_product())
|
let tile_data: Vec<Option<TileData>> = (0..chunk_size.element_product())
|
||||||
.map(|_| rng.gen_range(0..5))
|
.map(|_| rng.gen_range(0..5))
|
||||||
.map(|i| if i == 0 { None } else { Some(i - 1) })
|
.map(|i| {
|
||||||
|
if i == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(TileData::from_tileset_index(i - 1))
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
@ -40,7 +46,7 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
|
|||||||
tileset: assets.load("textures/array_texture.png"),
|
tileset: assets.load("textures/array_texture.png"),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
TilemapChunkIndices(indices),
|
TilemapChunkTileData(tile_data),
|
||||||
UpdateTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
|
UpdateTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -65,16 +71,16 @@ fn update_tileset_image(
|
|||||||
|
|
||||||
fn update_tilemap(
|
fn update_tilemap(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut query: Query<(&mut TilemapChunkIndices, &mut UpdateTimer)>,
|
mut query: Query<(&mut TilemapChunkTileData, &mut UpdateTimer)>,
|
||||||
mut rng: ResMut<SeededRng>,
|
mut rng: ResMut<SeededRng>,
|
||||||
) {
|
) {
|
||||||
for (mut indices, mut timer) in query.iter_mut() {
|
for (mut tile_data, mut timer) in query.iter_mut() {
|
||||||
timer.tick(time.delta());
|
timer.tick(time.delta());
|
||||||
|
|
||||||
if timer.just_finished() {
|
if timer.just_finished() {
|
||||||
for _ in 0..50 {
|
for _ in 0..50 {
|
||||||
let index = rng.gen_range(0..indices.len());
|
let index = rng.gen_range(0..tile_data.len());
|
||||||
indices[index] = Some(rng.gen_range(0..5));
|
tile_data[index] = Some(TileData::from_tileset_index(rng.gen_range(0..5)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,22 +4,28 @@ authors: ["@ConnerPetzold", "@grind086", "@IceSentry"]
|
|||||||
pull_requests: [18866]
|
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`.
|
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 tile data to `TilemapChunkTileData`. For each tile, `TileData` allows you to specify the index into the tileset, the visibility, and the color tint.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let chunk_size = UVec2::splat(64);
|
let chunk_size = UVec2::splat(64);
|
||||||
let tile_size = UVec2::splat(16);
|
let tile_display_size = UVec2::splat(16);
|
||||||
let indices: Vec<Option<u32>> = (0..chunk_size.x * chunk_size.y)
|
let tile_data: Vec<Option<TileData>> = (0..chunk_size.element_product())
|
||||||
.map(|_| rng.gen_range(0..5))
|
.map(|_| rng.gen_range(0..5))
|
||||||
.map(|i| if i == 0 { None } else { Some(i - 1) })
|
.map(|i| {
|
||||||
|
if i == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(TileData::from_index(i - 1))
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
TilemapChunk {
|
TilemapChunk {
|
||||||
chunk_size,
|
chunk_size,
|
||||||
tile_size,
|
tile_display_size,
|
||||||
tileset,
|
tileset,
|
||||||
},
|
},
|
||||||
TilemapChunkIndices(indices),
|
TilemapChunkTileData(tile_data),
|
||||||
));
|
));
|
||||||
```
|
```
|
||||||
|
Loading…
Reference in New Issue
Block a user