Skip to content

Instantly share code, notes, and snippets.

@alec-deason
Created November 14, 2020 00:36
Show Gist options
  • Save alec-deason/3d33c4520525a42cf8a3a77736032bb7 to your computer and use it in GitHub Desktop.
Save alec-deason/3d33c4520525a42cf8a3a77736032bb7 to your computer and use it in GitHub Desktop.
tile map experiment
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