Created
November 14, 2020 00:36
-
-
Save alec-deason/3d33c4520525a42cf8a3a77736032bb7 to your computer and use it in GitHub Desktop.
tile map experiment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use bevy::{ | |
prelude::*, | |
asset::LoadState, | |
sprite::{TextureAtlasBuilder, node}, | |
render::{ | |
mesh::{shape, VertexAttributeValues, Indices}, | |
pipeline::{PipelineDescriptor, RenderPipeline, PrimitiveTopology}, | |
render_graph::{base, AssetRenderResourcesNode, RenderGraph}, | |
renderer::RenderResources, | |
shader::{ShaderStage, ShaderStages}, | |
}, | |
type_registry::TypeUuid, | |
}; | |
use rand::prelude::*; | |
/// This example illustrates how to add a custom attribute to a mesh and use it in a custom shader. | |
fn main() { | |
App::build() | |
.add_plugins(DefaultPlugins) | |
.add_asset::<TileMapSupport>() | |
.add_startup_system(setup.system()) | |
.add_system(load_atlas.system()) | |
.run(); | |
} | |
#[derive(RenderResources, Default, TypeUuid)] | |
#[uuid = "0320b9b8-b3a3-4baa-8bfa-c94008177b17"] | |
struct TileMapSupport { | |
chunk_width: u32, | |
chunk_height: u32, | |
} | |
const VERTEX_SHADER: &str = r#" | |
#version 450 | |
layout(location = 0) in vec3 Vertex_Position; | |
layout(location = 1) in float Vertex_Tile_Index; | |
layout(location = 0) out vec2 v_Uv; | |
layout(location = 1) out vec4 v_Color; | |
layout(set = 0, binding = 0) uniform Camera { | |
mat4 ViewProj; | |
}; | |
// TODO: merge dimensions into "sprites" buffer when that is supported in the Uniforms derive abstraction | |
layout(set = 1, binding = 0) uniform TextureAtlas_size { | |
vec2 AtlasSize; | |
}; | |
struct Rect { | |
vec2 begin; | |
vec2 end; | |
}; | |
layout(set = 1, binding = 1) buffer TextureAtlas_textures { | |
Rect[] Textures; | |
}; | |
layout(set = 2, binding = 0) uniform Transform { | |
mat4 SpriteTransform; | |
}; | |
layout(set = 3, binding = 1) uniform TileMapSupport_chunk_width { | |
uint chunk_width; | |
}; | |
layout(set = 3, binding = 2) uniform TileMapSupport_chunk_height { | |
uint chunk_height; | |
}; | |
void main() { | |
Rect sprite_rect = Textures[int(Vertex_Tile_Index)]; | |
vec2 sprite_dimensions = sprite_rect.end - sprite_rect.begin; | |
vec3 vertex_position = vec3(Vertex_Position.xy * sprite_dimensions * vec2(chunk_width, chunk_height), 0.0); | |
vec2 atlas_positions[4] = vec2[]( | |
vec2(sprite_rect.begin.x, sprite_rect.end.y), | |
sprite_rect.begin, | |
vec2(sprite_rect.end.x, sprite_rect.begin.y), | |
sprite_rect.end | |
); | |
v_Uv = (atlas_positions[gl_VertexIndex % 4] + vec2(0.01, 0.01)) / AtlasSize; | |
v_Color = vec4(1.0); | |
gl_Position = ViewProj * SpriteTransform * vec4(vertex_position, 1.0); | |
} | |
"#; | |
const FRAGMENT_SHADER: &str = r#" | |
#version 450 | |
layout(location = 0) in vec2 v_Uv; | |
layout(location = 1) in vec4 v_Color; | |
layout(location = 0) out vec4 o_Target; | |
layout(set = 1, binding = 2) uniform texture2D TextureAtlas_texture; | |
layout(set = 1, binding = 3) uniform sampler TextureAtlas_texture_sampler; | |
void main() { | |
o_Target = v_Color * texture( | |
sampler2D(TextureAtlas_texture, TextureAtlas_texture_sampler), | |
v_Uv); | |
} | |
"#; | |
fn setup( | |
commands: &mut Commands, | |
mut pipelines: ResMut<Assets<PipelineDescriptor>>, | |
mut shaders: ResMut<Assets<Shader>>, | |
mut render_graph: ResMut<RenderGraph>, | |
mut asset_server: ResMut<AssetServer>, | |
) { | |
// Create a new shader pipeline | |
let pipeline_handle = pipelines.add(PipelineDescriptor::default_config(ShaderStages { | |
vertex: shaders.add(Shader::from_glsl(ShaderStage::Vertex, VERTEX_SHADER)), | |
fragment: Some(shaders.add(Shader::from_glsl(ShaderStage::Fragment, FRAGMENT_SHADER))), | |
})); | |
commands.insert_resource(TileMapPipeline(pipeline_handle.clone())); | |
render_graph.add_system_node( | |
"tile_map", | |
AssetRenderResourcesNode::<TileMapSupport>::new(true), | |
); | |
render_graph.add_system_node( | |
node::SPRITE_SHEET, | |
AssetRenderResourcesNode::<TextureAtlas>::new(false), | |
); | |
render_graph | |
.add_node_edge( | |
"tile_map", | |
base::node::MAIN_PASS, | |
) | |
.unwrap(); | |
commands.insert_resource(TileTextures(vec![ | |
asset_server.load("grass.png"), | |
asset_server.load("wall.png") | |
])); | |
commands.insert_resource(TileAtlas(None)); | |
} | |
struct TileMapPipeline(Handle<PipelineDescriptor>); | |
struct TileTextures(Vec<Handle<Texture>>); | |
struct TileAtlas(Option<Handle<TextureAtlas>>); | |
fn load_atlas( | |
commands: &mut Commands, | |
tile_textures: Res<TileTextures>, | |
mut atlas: ResMut<TileAtlas>, | |
pipeline_handle: Res<TileMapPipeline>, | |
asset_server: Res<AssetServer>, | |
mut texture_atlases: ResMut<Assets<TextureAtlas>>, | |
mut meshes: ResMut<Assets<Mesh>>, | |
mut materials: ResMut<Assets<TileMapSupport>>, | |
mut color_materials: ResMut<Assets<ColorMaterial>>, | |
mut textures: ResMut<Assets<Texture>>, | |
) { | |
if atlas.0.is_none() { | |
if let LoadState::Loaded = asset_server.get_group_load_state(tile_textures.0.iter().map(|handle| handle.id)) { | |
let mut texture_atlas_builder = TextureAtlasBuilder::default(); | |
for handle in &tile_textures.0 { | |
let texture = textures.get(handle).unwrap(); | |
texture_atlas_builder.add_texture(handle.clone_weak(), &texture); | |
} | |
let texture_atlas = texture_atlas_builder.finish(&mut textures).unwrap(); | |
atlas.0 = Some(texture_atlases.add(texture_atlas)); | |
let mut rng = rand::thread_rng(); | |
let mut vertices = vec![]; | |
let mut atlas_indices:Vec<f32> = vec![]; | |
let chunk_width = 32i32; | |
let chunk_height = 48i32; | |
let step_size_x = 1.0/chunk_width as f32; | |
let step_size_y = 1.0/chunk_height as f32; | |
for y in -chunk_height/2..chunk_height/2 { | |
for x in -chunk_width/2..chunk_width/2 { | |
let index = rng.gen_range(0, 2) as f32; | |
// FIXME: I know must be a way to have a per-primitive attribute rather than per-vertex | |
atlas_indices.extend(&[index; 4]); | |
vertices.push([x as f32 * step_size_x - step_size_x/2.0, y as f32 * step_size_y - step_size_y/2.0, 0.0]); | |
vertices.push([x as f32 * step_size_x - step_size_x/2.0, y as f32 * step_size_y + step_size_y/2.0, 0.0]); | |
vertices.push([x as f32 * step_size_x + step_size_x/2.0, y as f32 * step_size_y + step_size_y/2.0, 0.0]); | |
vertices.push([x as f32 * step_size_x + step_size_x/2.0, y as f32 * step_size_y - step_size_y/2.0, 0.0]); | |
} | |
} | |
let indices = Indices::U32((0..(chunk_width*chunk_height) as u32).flat_map(|i| { | |
let i = i * 4; | |
vec![i, i+2, i+1, i, i+3, i+2] | |
}).collect()); | |
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); | |
mesh.set_indices(Some(indices)); | |
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vertices.into()); | |
mesh.set_attribute("Vertex_Tile_Index", atlas_indices.into()); | |
// Setup our world | |
commands | |
.spawn(SpriteComponents { | |
render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( | |
pipeline_handle.0.clone_weak(), | |
)]), | |
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), | |
mesh: meshes.add(mesh), | |
..Default::default() | |
}) | |
.with(atlas.0.as_ref().unwrap().clone()) | |
.with(materials.add(TileMapSupport { | |
chunk_width: chunk_width as u32, | |
chunk_height: chunk_height as u32, | |
})) | |
.spawn(Camera2dComponents::default()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment