diff --git a/Cargo.toml b/Cargo.toml index 29ab8f22..80360cd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,6 +114,9 @@ required-features = ["render"] [[example]] name = "custom_shader" path = "examples/custom_shader.rs" +[[example]] +name = "textured_tile_shader" +path = "examples/textured_tile_shader.rs" required-features = ["render"] [[example]] name = "frustum_cull_test" diff --git a/README.md b/README.md index 434aad07..e88d6a5f 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ fn update_damage( - [`chunking`](examples/chunking.rs) - A simple example showing how to implement an infinite tilemap by spawning multiple chunks. - [`colors`](examples/colors.rs) - Showcases how each tile can have an individual color. - [`custom_shader`](examples/custom_shader.rs) - An example showing how to use a custom shader. +- [`textured_tile_shader`](examples/textured_tile_shader.rs) - An example showing how to use a custom shader to apply a texture across multiple tiles while preserving the edges. - [`frustum_cull_test`](examples/frustum_cull_test.rs) - An environment for testing frustum culling. - [`game_of_life`](examples/game_of_life.rs) - A game of life simulator. - [`hex_neighbors`](examples/hex_neighbors.rs) - An example showing how to get neighbors on hexagonal maps. diff --git a/assets/textured_tile.wgsl b/assets/textured_tile.wgsl new file mode 100644 index 00000000..6e1ec3a2 --- /dev/null +++ b/assets/textured_tile.wgsl @@ -0,0 +1,13 @@ +#import bevy_ecs_tilemap::common::process_fragment +#import bevy_ecs_tilemap::vertex_output::MeshVertexOutput +#import bevy_sprite::mesh2d_view_bindings::globals + +@group(3) @binding(2) var overlay_texture: texture_2d; +@group(3) @binding(3) var overlay_sampler: sampler; + +@fragment +fn fragment(in: MeshVertexOutput) -> @location(0) vec4 { + let color = process_fragment(in); + let overlay_color = textureSample(overlay_texture, overlay_sampler, fract(in.world_position.xy * 0.0069444444)); + return mix(color, overlay_color, color.g); +} \ No newline at end of file diff --git a/assets/tile_texture.png b/assets/tile_texture.png new file mode 100644 index 00000000..6ac2b0c3 Binary files /dev/null and b/assets/tile_texture.png differ diff --git a/examples/textured_tile_shader.rs b/examples/textured_tile_shader.rs new file mode 100644 index 00000000..726237a9 --- /dev/null +++ b/examples/textured_tile_shader.rs @@ -0,0 +1,83 @@ +use bevy::{prelude::*, reflect::TypePath, render::render_resource::AsBindGroup}; +use bevy_ecs_tilemap::prelude::*; +mod helpers; + +#[derive(AsBindGroup, TypePath, Debug, Clone, Default, Asset)] +pub struct MyMaterial { + #[texture(2)] + #[sampler(3)] + pub texture_overlay: Handle, +} + +impl MaterialTilemap for MyMaterial { + fn fragment_shader() -> bevy::render::render_resource::ShaderRef { + "textured_tile.wgsl".into() + } +} + +fn startup( + mut commands: Commands, + asset_server: Res, + mut materials: ResMut>, +) { + commands.spawn(Camera2d); + let texture_overlay: Handle = asset_server.load("tile_texture.png"); + + let my_material_handle = MaterialTilemapHandle::from(materials.add(MyMaterial { + texture_overlay + })); + + let texture_handle: Handle = asset_server.load("tiles.png"); + + let map_size = TilemapSize { x: 32, y: 32 }; + + // Layer 1 + let mut tile_storage = TileStorage::empty(map_size); + let tilemap_entity = commands.spawn_empty().id(); + + fill_tilemap( + TileTextureIndex(0), + map_size, + TilemapId(tilemap_entity), + &mut commands, + &mut tile_storage, + ); + + let tile_size = TilemapTileSize { x: 16.0, y: 16.0 }; + let grid_size = tile_size.into(); + let map_type = TilemapType::default(); + + commands + .entity(tilemap_entity) + .insert(MaterialTilemapBundle { + grid_size, + map_type, + size: map_size, + storage: tile_storage, + texture: TilemapTexture::Single(texture_handle.clone()), + tile_size, + anchor: TilemapAnchor::Center, + material: my_material_handle.clone(), + ..Default::default() + }); +} + +fn main() { + App::new() + .add_plugins( + DefaultPlugins + .set(WindowPlugin { + primary_window: Some(Window { + title: String::from("Textured Tiles"), + ..Default::default() + }), + ..default() + }) + .set(ImagePlugin::default_nearest()), + ) + .add_plugins(TilemapPlugin) + .add_plugins(MaterialTilemapPlugin::::default()) + .add_systems(Startup, startup) + .add_systems(Update, helpers::camera::movement) + .run(); +} diff --git a/src/render/shaders/tilemap_vertex.wgsl b/src/render/shaders/tilemap_vertex.wgsl index 60b88bd3..427a3a43 100644 --- a/src/render/shaders/tilemap_vertex.wgsl +++ b/src/render/shaders/tilemap_vertex.wgsl @@ -133,5 +133,6 @@ fn vertex(vertex_input: VertexInput) -> MeshVertexOutput { out.position = view.clip_from_world * mesh_data.world_position; out.color = vertex_input.color; out.storage_position = vec2(vertex_input.position.xy); + out.world_position = mesh_data.world_position; return out; } diff --git a/src/render/shaders/tilemap_vertex_output.wgsl b/src/render/shaders/tilemap_vertex_output.wgsl index 549b55d9..5f6b9dfe 100644 --- a/src/render/shaders/tilemap_vertex_output.wgsl +++ b/src/render/shaders/tilemap_vertex_output.wgsl @@ -6,4 +6,5 @@ struct MeshVertexOutput { @location(1) color: vec4, @location(2) @interpolate(flat) tile_id: i32, @location(3) storage_position: vec2, + @location(4) world_position: vec4, }