Skip to main content

sprite_material/
sprite_material.rs

1//! Demonstrates using `SpriteMaterial`s to create custom materials for sprites
2
3use bevy::{
4    input::common_conditions::input_just_pressed, prelude::*, render::render_resource::AsBindGroup,
5    shader::ShaderRef,
6};
7
8/// This example uses a shader source file from the assets subdirectory
9const SHADER_ASSET_PATH: &str = "shaders/sprite_material.wesl";
10
11fn main() {
12    App::new()
13        .add_plugins((
14            DefaultPlugins,
15            SpriteMaterialPlugin::<DissolveMaterial>::default(),
16        ))
17        .add_systems(Startup, setup)
18        .add_systems(
19            Update,
20            (
21                dissolve_on_input.run_if(input_just_pressed(KeyCode::Space)),
22                update_dissolve,
23            )
24                .chain(),
25        )
26        .run();
27}
28
29#[derive(AsBindGroup, Asset, Reflect, Clone)]
30struct DissolveMaterial {
31    // Start at 20 to not collide with the sprite's bindings
32    #[uniform(20)]
33    burn_edge_color: LinearRgba,
34    #[uniform(20)]
35    amount: f32,
36    #[uniform(20)]
37    burn_edge: f32,
38
39    // WebGL2 requires structs to be aligned to 16 bytes
40    #[cfg(feature = "webgl2")]
41    #[uniform(20)]
42    _webgl2_padding_12b: u32,
43    #[cfg(feature = "webgl2")]
44    #[uniform(20)]
45    _webgl2_padding_16b: u32,
46}
47
48impl MaterialExtension2d for DissolveMaterial {
49    fn fragment_shader() -> Option<ShaderRef> {
50        Some(SHADER_ASSET_PATH.into())
51    }
52}
53
54impl DissolveMaterial {
55    fn new(amount: f32) -> Self {
56        Self {
57            amount,
58            burn_edge: 0.05,
59            burn_edge_color: LinearRgba::rgb(1.0, 0.66, 0.0),
60
61            #[cfg(feature = "webgl2")]
62            _webgl2_padding_12b: 0,
63            #[cfg(feature = "webgl2")]
64            _webgl2_padding_16b: 0,
65        }
66    }
67}
68
69#[derive(Component, FromTemplate, Clone, Copy)]
70#[component(immutable)]
71enum DissolveState {
72    #[default]
73    Visible,
74    Dissolving {
75        progress: f32,
76    },
77    Appearing {
78        progress: f32,
79    },
80    Hidden,
81}
82
83fn setup(mut commands: Commands) {
84    commands.spawn(Camera2d);
85    commands.spawn(Text::new("Space to dissolve"));
86
87    commands.queue_spawn_scene(bsn! {
88        Sprite {
89            image: "branding/bevy_bird_dark.png",
90        }
91        DissolveState::Visible
92        SpriteMaterial<DissolveMaterial>(asset_value(DissolveMaterial::new(0.0)))
93    });
94}
95
96fn dissolve_on_input(dissolving: Query<(Entity, &DissolveState)>, mut commands: Commands) {
97    for (entity, state) in dissolving {
98        let new = match *state {
99            DissolveState::Visible => DissolveState::Dissolving { progress: 0.0 },
100            DissolveState::Hidden => DissolveState::Appearing { progress: 0.0 },
101            DissolveState::Dissolving { progress } => DissolveState::Appearing {
102                progress: 1.0 - progress,
103            },
104            DissolveState::Appearing { progress } => DissolveState::Dissolving {
105                progress: 1.0 - progress,
106            },
107        };
108
109        commands.entity(entity).insert(new);
110    }
111}
112
113fn update_dissolve(
114    dissolving: Query<(Entity, &SpriteMaterial<DissolveMaterial>, &DissolveState)>,
115    time: Res<Time>,
116    mut materials: ResMut<Assets<DissolveMaterial>>,
117    mut commands: Commands,
118) {
119    for (entity, material, state) in dissolving {
120        match *state {
121            DissolveState::Dissolving { mut progress } => {
122                progress += time.delta_secs();
123                let new = if progress >= 1.0 {
124                    progress = 1.0;
125                    DissolveState::Hidden
126                } else {
127                    DissolveState::Dissolving { progress }
128                };
129                commands.entity(entity).insert(new);
130
131                if let Some(mut material) = materials.get_mut(&material.0) {
132                    material.amount = progress;
133                }
134            }
135            DissolveState::Appearing { mut progress } => {
136                progress += time.delta_secs();
137                let new = if progress >= 1.0 {
138                    progress = 1.0;
139                    DissolveState::Visible
140                } else {
141                    DissolveState::Appearing { progress }
142                };
143                commands.entity(entity).insert(new);
144
145                if let Some(mut material) = materials.get_mut(&material.0) {
146                    material.amount = 1.0 - progress;
147                }
148            }
149            _ => { /* ignore */ }
150        }
151    }
152}