sprite_material/
sprite_material.rs1use bevy::{
4 input::common_conditions::input_just_pressed, prelude::*, render::render_resource::AsBindGroup,
5 shader::ShaderRef,
6};
7
8const 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 #[uniform(20)]
33 burn_edge_color: LinearRgba,
34 #[uniform(20)]
35 amount: f32,
36 #[uniform(20)]
37 burn_edge: f32,
38
39 #[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 _ => { }
150 }
151 }
152}