mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
sprite sheets are fully operational
This commit is contained in:
parent
8c196139d4
commit
5927bad382
22 changed files with 193 additions and 200 deletions
19
.vscode/launch.json
vendored
19
.vscode/launch.json
vendored
|
@ -1416,6 +1416,25 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug example 'sprite_sheet'",
|
||||
"env": { "CARGO_MANIFEST_DIR": "${workspaceFolder}" },
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--example=sprite_sheet",
|
||||
"--package=bevy"
|
||||
],
|
||||
"filter": {
|
||||
"name": "sprite_sheet",
|
||||
"kind": "example"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
|
|
@ -10,3 +10,7 @@
|
|||
|
||||
* amethyst
|
||||
* coffee
|
||||
|
||||
## Assets
|
||||
|
||||
* Generic RPG Pack (CC0 license) by [Bakudas](https://twitter.com/bakudas) and [Gabe Fern](https://twitter.com/_Gabrielfer)
|
BIN
assets/textures/character_run.png
Normal file
BIN
assets/textures/character_run.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
|
@ -1,11 +1,10 @@
|
|||
use crate::modules::{get_modules, get_path};
|
||||
use darling::FromMeta;
|
||||
use inflector::Inflector;
|
||||
use crate::modules::{get_modules, get_path};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Field, Fields, Path};
|
||||
|
||||
// TODO: ensure shader_def and instance/vertex are mutually exclusive
|
||||
#[derive(FromMeta, Debug, Default)]
|
||||
struct UniformAttributeArgs {
|
||||
#[darling(default)]
|
||||
|
@ -88,6 +87,7 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
|
|||
let mut texture_and_sampler_name_strings = Vec::new();
|
||||
let mut texture_and_sampler_name_idents = Vec::new();
|
||||
let mut field_infos = Vec::new();
|
||||
let mut get_field_bind_types = Vec::new();
|
||||
|
||||
let mut vertex_buffer_field_names_pascal = Vec::new();
|
||||
let mut vertex_buffer_field_types = Vec::new();
|
||||
|
@ -98,6 +98,7 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
|
|||
for (f, attrs) in field_attributes.iter() {
|
||||
let field_name = f.ident.as_ref().unwrap().to_string();
|
||||
if !attrs.ignore {
|
||||
let active_uniform_field_name = &f.ident;
|
||||
active_uniform_field_names.push(&f.ident);
|
||||
active_uniform_field_name_strings.push(field_name.clone());
|
||||
let uniform = format!("{}_{}", struct_name, field_name);
|
||||
|
@ -117,6 +118,19 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
|
|||
is_instanceable: #is_instanceable,
|
||||
}));
|
||||
|
||||
if attrs.buffer {
|
||||
get_field_bind_types.push(quote!({
|
||||
let bind_type = self.#active_uniform_field_name.get_bind_type();
|
||||
let size = if let Some(#bevy_render_path::shader::FieldBindType::Uniform { size }) = bind_type {
|
||||
size
|
||||
} else {
|
||||
panic!("Uniform field was labeled as a 'buffer', but it does not have a compatible type.")
|
||||
};
|
||||
Some(#bevy_render_path::shader::FieldBindType::Buffer { size })
|
||||
}))
|
||||
} else {
|
||||
get_field_bind_types.push(quote!(self.#active_uniform_field_name.get_bind_type()))
|
||||
}
|
||||
}
|
||||
|
||||
if attrs.shader_def {
|
||||
|
@ -192,7 +206,7 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
|
|||
fn get_field_bind_type(&self, name: &str) -> Option<#bevy_render_path::shader::FieldBindType> {
|
||||
use #bevy_render_path::shader::GetFieldBindType;
|
||||
match name {
|
||||
#(#active_uniform_field_name_strings => self.#active_uniform_field_names.get_bind_type(),)*
|
||||
#(#active_uniform_field_name_strings => #get_field_bind_types,)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ pub enum BindType {
|
|||
dynamic: bool,
|
||||
properties: Vec<UniformProperty>,
|
||||
},
|
||||
Buffer {
|
||||
StorageBuffer {
|
||||
dynamic: bool,
|
||||
readonly: bool,
|
||||
},
|
||||
|
|
|
@ -54,7 +54,7 @@ impl Node for TextureCopyNode {
|
|||
render_context.copy_buffer_to_texture(
|
||||
texture_buffer,
|
||||
0,
|
||||
(4 * texture.width) as u32,
|
||||
4 * texture.size.x() as u32,
|
||||
texture_resource,
|
||||
[0, 0, 0],
|
||||
0,
|
||||
|
|
|
@ -83,7 +83,7 @@ where
|
|||
|
||||
fn increment_uniform_counts(&mut self, uniforms: &T) {
|
||||
for (i, field_info) in T::get_field_infos().iter().enumerate() {
|
||||
if let Some(FieldBindType::Uniform { size }) =
|
||||
if let Some(FieldBindType::Uniform { size }) | Some(FieldBindType::Buffer { size }) =
|
||||
uniforms.get_field_bind_type(&field_info.name)
|
||||
{
|
||||
if let Some((ref _name, ref mut buffer_array_status)) = self.uniform_arrays[i] {
|
||||
|
@ -186,8 +186,9 @@ where
|
|||
for (i, field_info) in T::get_field_infos().iter().enumerate() {
|
||||
let bind_type = uniforms.get_field_bind_type(&field_info.name);
|
||||
match bind_type {
|
||||
Some(FieldBindType::Uniform { size }) => {
|
||||
Some(FieldBindType::Uniform { size }) | Some(FieldBindType::Buffer { size }) => {
|
||||
let (_name, uniform_buffer_status) = self.uniform_arrays[i].as_mut().unwrap();
|
||||
let range = 0..size as u64;
|
||||
let (target_buffer, target_offset) = if dynamic_uniforms {
|
||||
let buffer = uniform_buffer_status.buffer.unwrap();
|
||||
let index = uniform_buffer_status
|
||||
|
@ -199,25 +200,32 @@ where
|
|||
dynamic_index: Some(
|
||||
(index * uniform_buffer_status.aligned_size) as u32,
|
||||
),
|
||||
range: 0..size as u64,
|
||||
range,
|
||||
},
|
||||
);
|
||||
(buffer, index * uniform_buffer_status.aligned_size)
|
||||
} else {
|
||||
let resource =
|
||||
match render_resource_assignments.get(field_info.uniform_name) {
|
||||
let resource = match render_resource_assignments
|
||||
.get(field_info.uniform_name)
|
||||
{
|
||||
Some(assignment) => assignment.get_resource(),
|
||||
None => {
|
||||
let usage = if let Some(FieldBindType::Buffer { .. }) = bind_type {
|
||||
BufferUsage::STORAGE
|
||||
} else {
|
||||
BufferUsage::UNIFORM
|
||||
};
|
||||
let resource = render_resources.create_buffer(BufferInfo {
|
||||
size,
|
||||
buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM,
|
||||
buffer_usage: BufferUsage::COPY_DST | usage,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
render_resource_assignments.set(
|
||||
&field_info.uniform_name,
|
||||
RenderResourceAssignment::Buffer {
|
||||
resource,
|
||||
range: 0..size as u64,
|
||||
range,
|
||||
dynamic_index: None,
|
||||
},
|
||||
);
|
||||
|
@ -232,14 +240,16 @@ where
|
|||
+ (uniform_buffer_status.queued_buffer_writes.len()
|
||||
* uniform_buffer_status.item_size);
|
||||
let uniform_byte_len = uniforms.uniform_byte_len(&field_info.uniform_name);
|
||||
if uniform_byte_len > 0
|
||||
{
|
||||
if uniform_byte_len > 0 {
|
||||
if size != uniform_byte_len {
|
||||
panic!("The number of bytes produced for {} do not match the expected count. Actual: {}. Expected: {}.", field_info.uniform_name, uniform_byte_len, size);
|
||||
}
|
||||
|
||||
uniforms.write_uniform_bytes(&field_info.uniform_name, &mut staging_buffer
|
||||
[staging_buffer_start..(staging_buffer_start + uniform_byte_len)]);
|
||||
uniforms.write_uniform_bytes(
|
||||
&field_info.uniform_name,
|
||||
&mut staging_buffer
|
||||
[staging_buffer_start..(staging_buffer_start + uniform_byte_len)],
|
||||
);
|
||||
} else {
|
||||
panic!(
|
||||
"failed to get data from uniform: {}",
|
||||
|
@ -254,7 +264,8 @@ where
|
|||
offset: target_offset,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
Some(FieldBindType::Texture) => { /* ignore textures */ }
|
||||
None => { /* ignore None */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
bitflags::bitflags! {
|
||||
#[repr(transparent)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "trace", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct BufferUsage: u32 {
|
||||
const MAP_READ = 1;
|
||||
const MAP_WRITE = 2;
|
||||
|
@ -11,17 +12,5 @@ bitflags::bitflags! {
|
|||
const UNIFORM = 64;
|
||||
const STORAGE = 128;
|
||||
const INDIRECT = 256;
|
||||
const STORAGE_READ = 512;
|
||||
const NONE = 0;
|
||||
/// The combination of all read-only usages.
|
||||
const READ_ALL = Self::MAP_READ.bits | Self::COPY_SRC.bits |
|
||||
Self::INDEX.bits | Self::VERTEX.bits | Self::UNIFORM.bits |
|
||||
Self::STORAGE_READ.bits | Self::INDIRECT.bits;
|
||||
/// The combination of all write-only and read-write usages.
|
||||
const WRITE_ALL = Self::MAP_WRITE.bits | Self::COPY_DST.bits | Self::STORAGE.bits;
|
||||
/// The combination of all usages that the are guaranteed to be be ordered by the hardware.
|
||||
/// If a usage is not ordered, then even if it doesn't change between draw calls, there
|
||||
/// still need to be pipeline barriers inserted for synchronization.
|
||||
const ORDERED = Self::READ_ALL.bits;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ impl Default for BufferInfo {
|
|||
fn default() -> Self {
|
||||
BufferInfo {
|
||||
size: 0,
|
||||
buffer_usage: BufferUsage::NONE,
|
||||
buffer_usage: BufferUsage::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,8 +198,8 @@ fn reflect_binding(binding: &ReflectDescriptorBinding) -> BindingDescriptor {
|
|||
},
|
||||
),
|
||||
ReflectDescriptorType::StorageBuffer => (
|
||||
&binding.name,
|
||||
BindType::Buffer {
|
||||
&type_description.type_name,
|
||||
BindType::StorageBuffer {
|
||||
dynamic: false,
|
||||
readonly: true,
|
||||
},
|
||||
|
|
|
@ -70,8 +70,9 @@ impl ShaderDefSuffixProvider for bool {
|
|||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum FieldBindType {
|
||||
// TODO: maybe change this to Buffer
|
||||
Uniform { size: usize },
|
||||
Buffer,
|
||||
Buffer { size: usize },
|
||||
Texture,
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ use super::Texture;
|
|||
use anyhow::Result;
|
||||
use bevy_asset::AssetLoader;
|
||||
use std::path::Path;
|
||||
use glam::Vec2;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PngTextureLoader;
|
||||
|
@ -14,8 +15,7 @@ impl AssetLoader<Texture> for PngTextureLoader {
|
|||
reader.next_frame(&mut data)?;
|
||||
Ok(Texture {
|
||||
data,
|
||||
width: info.width as usize,
|
||||
height: info.height as usize,
|
||||
size: Vec2::new(info.width as f32, info.height as f32),
|
||||
})
|
||||
}
|
||||
fn extensions(&self) -> &[&str] {
|
||||
|
|
|
@ -8,6 +8,7 @@ use bevy_asset::{AssetEvent, Assets, Handle};
|
|||
use bevy_derive::FromResources;
|
||||
use legion::prelude::*;
|
||||
use std::{collections::HashSet, fs::File};
|
||||
use glam::Vec2;
|
||||
|
||||
pub const TEXTURE_ASSET_INDEX: usize = 0;
|
||||
pub const SAMPLER_ASSET_INDEX: usize = 1;
|
||||
|
@ -18,13 +19,12 @@ pub enum TextureType {
|
|||
|
||||
pub struct Texture {
|
||||
pub data: Vec<u8>,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub size: Vec2,
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub fn aspect(&self) -> f32 {
|
||||
self.height as f32 / self.width as f32
|
||||
self.size.y() / self.size.x()
|
||||
}
|
||||
|
||||
pub fn load(descriptor: TextureType) -> Self {
|
||||
|
@ -41,8 +41,7 @@ impl Texture {
|
|||
|
||||
Texture {
|
||||
data,
|
||||
width,
|
||||
height,
|
||||
size: Vec2::new(width as f32, height as f32)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@ impl From<&Texture> for TextureDescriptor {
|
|||
fn from(texture: &Texture) -> Self {
|
||||
TextureDescriptor {
|
||||
size: Extent3d {
|
||||
height: texture.height as u32,
|
||||
width: texture.width as u32,
|
||||
width: texture.size.x() as u32,
|
||||
height: texture.size.y() as u32,
|
||||
depth: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
|
|
|
@ -37,10 +37,6 @@ impl AppPlugin for SpritePlugin {
|
|||
.add_system_to_stage(
|
||||
stage::POST_UPDATE,
|
||||
asset_shader_def_system::<ColorMaterial>.system(),
|
||||
)
|
||||
.init_system_to_stage(
|
||||
bevy_render::stage::RENDER_RESOURCE,
|
||||
sprite_sheet_resource_provider_system,
|
||||
);
|
||||
|
||||
let resources = app.resources();
|
||||
|
|
|
@ -135,7 +135,7 @@ impl SpriteRenderGraphBuilder for RenderGraph {
|
|||
|
||||
self.add_system_node(
|
||||
node::SPRITE_SHEET,
|
||||
AssetUniformNode::<SpriteSheet>::new(true),
|
||||
AssetUniformNode::<SpriteSheet>::new(false),
|
||||
);
|
||||
|
||||
self.add_system_node(
|
||||
|
|
|
@ -4,11 +4,11 @@ layout(location = 0) in vec2 v_Uv;
|
|||
|
||||
layout(location = 0) out vec4 o_Target;
|
||||
|
||||
layout(set = 1, binding = 1) uniform texture2D SpriteSheet_texture;
|
||||
layout(set = 1, binding = 2) uniform sampler SpriteSheet_texture_sampler;
|
||||
layout(set = 1, binding = 2) uniform texture2D SpriteSheet_texture;
|
||||
layout(set = 1, binding = 3) uniform sampler SpriteSheet_texture_sampler;
|
||||
|
||||
void main() {
|
||||
o_Target = texture(
|
||||
sampler2D(SpriteSheet_texture, SpriteSheet_texture_sampler),
|
||||
v_Uv);;
|
||||
v_Uv);
|
||||
}
|
||||
|
|
|
@ -4,18 +4,9 @@ layout(location = 0) in vec3 Vertex_Position;
|
|||
layout(location = 1) in vec3 Vertex_Normal;
|
||||
layout(location = 2) in vec2 Vertex_Uv;
|
||||
|
||||
// TODO: consider swapping explicit mesh binding for this const
|
||||
// const vec2 positions[4] = vec2[](
|
||||
// vec2(0.5, -0.5),
|
||||
// vec2(-0.5, -0.5),
|
||||
// vec2(0.5, 0.5),
|
||||
// vec2(-0.5, 0.5)
|
||||
// );
|
||||
|
||||
// TODO: uncomment when instancing is implemented
|
||||
// sprite
|
||||
// layout(location = 0) in vec3 Sprite_Position;
|
||||
// // this is a vec2 instead of an int due to WebGPU limitations
|
||||
// layout(location = 1) in int Sprite_Index;
|
||||
|
||||
layout(location = 0) out vec2 v_Uv;
|
||||
|
@ -24,30 +15,37 @@ layout(set = 0, binding = 0) uniform Camera2d {
|
|||
mat4 ViewProj;
|
||||
};
|
||||
|
||||
// TODO: merge dimensions into "sprites" buffer when that is supported in the Uniforms derive abstraction
|
||||
layout(set = 1, binding = 0) uniform SpriteSheet_dimensions {
|
||||
vec2 Dimensions;
|
||||
};
|
||||
|
||||
struct Rect {
|
||||
vec2 begin;
|
||||
vec2 end;
|
||||
};
|
||||
|
||||
layout(set = 1, binding = 0) buffer SpriteSheet_sprites {
|
||||
layout(set = 1, binding = 1) buffer SpriteSheet_sprites {
|
||||
Rect[] Sprites;
|
||||
};
|
||||
|
||||
|
||||
layout(set = 2, binding = 0) uniform SpriteSheetSprite {
|
||||
vec3 SpriteSheetSprite_position;
|
||||
float SpriteSheetSprite_scale;
|
||||
uint SpriteSheetSprite_index;
|
||||
};
|
||||
|
||||
void main() {
|
||||
Rect sprite_rect = Sprites[SpriteSheetSprite_index];
|
||||
vec2 dimensions = sprite_rect.end - sprite_rect.begin;
|
||||
vec2 vertex_position = Vertex_Position.xy * dimensions;
|
||||
vec2 sprite_dimensions = sprite_rect.end - sprite_rect.begin;
|
||||
vec3 vertex_position = vec3(Vertex_Position.xy * sprite_dimensions * SpriteSheetSprite_scale, 0.0) + SpriteSheetSprite_position;
|
||||
vec2 uvs[4] = vec2[](
|
||||
vec2(sprite_rect.end.x, sprite_rect.begin.y),
|
||||
vec2(sprite_rect.begin.x, sprite_rect.end.y),
|
||||
sprite_rect.begin,
|
||||
sprite_rect.end,
|
||||
vec2(sprite_rect.begin.x, sprite_rect.end.y)
|
||||
vec2(sprite_rect.end.x, sprite_rect.begin.y),
|
||||
sprite_rect.end
|
||||
);
|
||||
v_Uv = uvs[gl_VertexIndex];
|
||||
gl_Position = ViewProj * vec4(vec3(vertex_position, 0.0) + SpriteSheetSprite_position, 1.0);
|
||||
v_Uv = uvs[gl_VertexIndex] / Dimensions;
|
||||
gl_Position = ViewProj * vec4(vertex_position, 1.0);
|
||||
}
|
|
@ -27,7 +27,7 @@ pub fn sprite_system() -> Box<dyn Schedulable> {
|
|||
if let Some(texture_handle) = material.texture {
|
||||
if let Some(texture) = textures.get(&texture_handle) {
|
||||
let aspect = texture.aspect();
|
||||
*rect.size.x_mut() = texture.width as f32 * sprite.scale;
|
||||
*rect.size.x_mut() = texture.size.x() * sprite.scale;
|
||||
*rect.size.y_mut() = rect.size.x() * aspect;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,113 +1,52 @@
|
|||
use crate::Rect;
|
||||
use bevy_app::{Events, GetEventReader};
|
||||
use bevy_asset::{AssetEvent, Assets, Handle};
|
||||
use bevy_core::bytes::AsBytes;
|
||||
use bevy_asset::Handle;
|
||||
use bevy_derive::{Bytes, Uniform, Uniforms};
|
||||
use bevy_render::{
|
||||
render_resource::{BufferInfo, BufferUsage, RenderResourceAssignment, ResourceInfo},
|
||||
renderer::{RenderResourceContext, RenderResources},
|
||||
texture::Texture,
|
||||
Renderable,
|
||||
};
|
||||
use glam::{Vec3, Vec4};
|
||||
use legion::prelude::*;
|
||||
use std::collections::HashSet;
|
||||
use bevy_render::texture::Texture;
|
||||
use glam::{Vec2, Vec3};
|
||||
|
||||
#[derive(Uniforms)]
|
||||
pub struct SpriteSheet {
|
||||
pub texture: Handle<Texture>,
|
||||
// TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer
|
||||
pub dimensions: Vec2,
|
||||
#[uniform(buffer)]
|
||||
pub sprites: Vec<Rect>,
|
||||
}
|
||||
|
||||
// NOTE: cannot do unsafe impl Byteable here because Vec3 takes up the space of a Vec4. If/when glam changes this we can swap out
|
||||
// Bytes for Byteable. https://github.com/bitshifter/glam-rs/issues/36
|
||||
// NOTE: cannot do `unsafe impl Byteable` here because Vec3 takes up the space of a Vec4. If/when glam changes this we can swap out
|
||||
// Bytes for Byteable as a micro-optimization. https://github.com/bitshifter/glam-rs/issues/36
|
||||
#[derive(Uniform, Bytes, Default)]
|
||||
pub struct SpriteSheetSprite {
|
||||
pub position: Vec3,
|
||||
pub scale: f32,
|
||||
pub index: u32,
|
||||
}
|
||||
|
||||
pub const SPRITE_SHEET_BUFFER_ASSET_INDEX: usize = 0;
|
||||
|
||||
fn remove_sprite_sheet_resource(
|
||||
render_resources: &dyn RenderResourceContext,
|
||||
handle: Handle<SpriteSheet>,
|
||||
) {
|
||||
if let Some(resource) =
|
||||
render_resources.get_asset_resource(handle, SPRITE_SHEET_BUFFER_ASSET_INDEX)
|
||||
{
|
||||
render_resources.remove_buffer(resource);
|
||||
render_resources.remove_asset_resource(handle, SPRITE_SHEET_BUFFER_ASSET_INDEX);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sprite_sheet_resource_provider_system(resources: &mut Resources) -> Box<dyn Schedulable> {
|
||||
let mut sprite_sheet_event_reader = resources.get_event_reader::<AssetEvent<SpriteSheet>>();
|
||||
(move |world: &mut SubWorld,
|
||||
render_resources: Res<RenderResources>,
|
||||
sprite_sheets: Res<Assets<SpriteSheet>>,
|
||||
sprite_sheet_events: Res<Events<AssetEvent<SpriteSheet>>>,
|
||||
query: &mut Query<(Read<Handle<SpriteSheet>>, Write<Renderable>)>| {
|
||||
let render_resources = &*render_resources.context;
|
||||
let mut changed_sprite_sheets = HashSet::new();
|
||||
for event in sprite_sheet_event_reader.iter(&sprite_sheet_events) {
|
||||
match event {
|
||||
AssetEvent::Created { handle } => {
|
||||
changed_sprite_sheets.insert(*handle);
|
||||
}
|
||||
AssetEvent::Modified { handle } => {
|
||||
changed_sprite_sheets.insert(*handle);
|
||||
remove_sprite_sheet_resource(render_resources, *handle);
|
||||
}
|
||||
AssetEvent::Removed { handle } => {
|
||||
remove_sprite_sheet_resource(render_resources, *handle);
|
||||
// if sprite sheet was modified and removed in the same update, ignore the modification
|
||||
// events are ordered so future modification events are ok
|
||||
changed_sprite_sheets.remove(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for changed_sprite_sheet_handle in changed_sprite_sheets.iter() {
|
||||
if let Some(sprite_sheet) = sprite_sheets.get(changed_sprite_sheet_handle) {
|
||||
let sprite_sheet_bytes = sprite_sheet.sprites.as_slice().as_bytes();
|
||||
let sprite_sheet_buffer = render_resources.create_buffer_with_data(
|
||||
BufferInfo {
|
||||
buffer_usage: BufferUsage::STORAGE,
|
||||
..Default::default()
|
||||
},
|
||||
&sprite_sheet_bytes,
|
||||
);
|
||||
|
||||
render_resources.set_asset_resource(
|
||||
*changed_sprite_sheet_handle,
|
||||
sprite_sheet_buffer,
|
||||
SPRITE_SHEET_BUFFER_ASSET_INDEX,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this when batching is implemented
|
||||
for (handle, mut renderable) in query.iter_mut(world) {
|
||||
if let Some(sprite_sheet_buffer) =
|
||||
render_resources.get_asset_resource(*handle, SPRITE_SHEET_BUFFER_ASSET_INDEX)
|
||||
{
|
||||
let mut buffer_size = None;
|
||||
render_resources.get_resource_info(sprite_sheet_buffer, &mut |info| {
|
||||
if let Some(ResourceInfo::Buffer(BufferInfo { size, .. })) = info {
|
||||
buffer_size = Some(*size as u64)
|
||||
}
|
||||
});
|
||||
renderable.render_resource_assignments.set(
|
||||
"SpriteSheet",
|
||||
RenderResourceAssignment::Buffer {
|
||||
dynamic_index: None,
|
||||
range: 0..buffer_size.unwrap(),
|
||||
resource: sprite_sheet_buffer,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
impl SpriteSheet {
|
||||
pub fn from_grid(
|
||||
texture: Handle<Texture>,
|
||||
size: Vec2,
|
||||
columns: usize,
|
||||
rows: usize,
|
||||
) -> SpriteSheet {
|
||||
let sprite_width = size.x() / columns as f32;
|
||||
let sprite_height = size.y() / rows as f32;
|
||||
let mut sprites = Vec::new();
|
||||
for y in 0..rows {
|
||||
for x in 0..columns {
|
||||
sprites.push(Rect {
|
||||
min: Vec2::new(x as f32 * sprite_width, y as f32 * sprite_height),
|
||||
max: Vec2::new(
|
||||
(x + 1) as f32 * sprite_width,
|
||||
(y + 1) as f32 * sprite_height,
|
||||
),
|
||||
})
|
||||
.system()
|
||||
}
|
||||
}
|
||||
SpriteSheet {
|
||||
dimensions: size,
|
||||
sprites,
|
||||
texture,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ impl WgpuFrom<&BindType> for wgpu::BindingType {
|
|||
dynamic,
|
||||
properties: _,
|
||||
} => wgpu::BindingType::UniformBuffer { dynamic: *dynamic },
|
||||
BindType::Buffer { dynamic, readonly } => wgpu::BindingType::StorageBuffer {
|
||||
BindType::StorageBuffer { dynamic, readonly } => wgpu::BindingType::StorageBuffer {
|
||||
dynamic: *dynamic,
|
||||
readonly: *readonly,
|
||||
},
|
||||
|
|
|
@ -1,36 +1,59 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy::input::system::exit_on_esc_system;
|
||||
use bevy_sprite::{Rect, SpriteSheet};
|
||||
use bevy::{input::system::exit_on_esc_system, prelude::*};
|
||||
use bevy_sprite::{SpriteSheet, SpriteSheetSprite};
|
||||
|
||||
fn main() {
|
||||
App::build()
|
||||
.init_resource::<State>()
|
||||
.add_default_plugins()
|
||||
.add_startup_system(setup.system())
|
||||
.init_system(exit_on_esc_system)
|
||||
.add_system(animate_sprite_system.system())
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
elapsed: f32,
|
||||
}
|
||||
|
||||
fn animate_sprite_system(
|
||||
mut state: ResMut<State>,
|
||||
time: Res<Time>,
|
||||
sprite_sheets: Res<Assets<SpriteSheet>>,
|
||||
mut sprite: ComMut<SpriteSheetSprite>,
|
||||
sprite_sheet_handle: Com<Handle<SpriteSheet>>,
|
||||
) {
|
||||
state.elapsed += time.delta_seconds;
|
||||
if state.elapsed > 0.1 {
|
||||
state.elapsed = 0.0;
|
||||
let sprite_sheet = sprite_sheets.get(&sprite_sheet_handle).unwrap();
|
||||
sprite.index = ((sprite.index as usize + 1) % sprite_sheet.sprites.len()) as u32;
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(
|
||||
command_buffer: &mut CommandBuffer,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut textures: ResMut<Assets<Texture>>,
|
||||
mut sprite_sheets: ResMut<Assets<SpriteSheet>>,
|
||||
) {
|
||||
let texture_handle = asset_server.load("assets/branding/icon.png").unwrap();
|
||||
let sprite_sheet = SpriteSheet {
|
||||
texture: texture_handle,
|
||||
sprites: vec![
|
||||
Rect {
|
||||
min: Vec2::new(0.0, 0.0),
|
||||
max: Vec2::new(1.0, 1.0),
|
||||
}
|
||||
]
|
||||
};
|
||||
env_logger::init();
|
||||
let texture_handle = asset_server
|
||||
.load_sync(&mut textures, "assets/textures/character_run.png")
|
||||
.unwrap();
|
||||
let texture = textures.get(&texture_handle).unwrap();
|
||||
let sprite_sheet = SpriteSheet::from_grid(texture_handle, texture.size, 7, 1);
|
||||
let sprite_sheet_handle = sprite_sheets.add(sprite_sheet);
|
||||
command_buffer
|
||||
.build()
|
||||
.add_entity(OrthographicCameraEntity::default())
|
||||
.add_entity(SpriteSheetEntity {
|
||||
sprite_sheet: sprite_sheet_handle,
|
||||
sprite: SpriteSheetSprite {
|
||||
index: 0,
|
||||
scale: 6.0,
|
||||
position: Vec3::new(0.0, 0.0, -0.5),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue