docs/bevy/examples/ui/ui_material.rs 3.6 K · 107 lines · raw · history

1 //! Demonstrates the use of [`UiMaterials`](UiMaterial) and how to change material values
2
3 use bevy::{
4 color::palettes::css::DARK_BLUE, prelude::*, reflect::TypePath, render::render_resource::*,
5 shader::ShaderRef,
6 };
7
8 /// This example uses a shader source file from the assets subdirectory
9 const SHADER_ASSET_PATH: &str = "shaders/custom_ui_material.wesl";
10
11 fn main() {
12 App::new()
13 .add_plugins(DefaultPlugins)
14 .add_plugins(UiMaterialPlugin::<CustomUiMaterial>::default())
15 .add_systems(Startup, setup)
16 .add_systems(Update, animate)
17 .run();
18 }
19
20 fn setup(
21 mut commands: Commands,
22 mut ui_materials: ResMut<Assets<CustomUiMaterial>>,
23 asset_server: Res<AssetServer>,
24 ) {
25 // Camera so we can see UI
26 commands.spawn(Camera2d);
27
28 commands
29 .spawn(Node {
30 width: percent(100),
31 height: percent(100),
32 align_items: AlignItems::Center,
33 justify_content: JustifyContent::Center,
34 ..default()
35 })
36 .with_children(|parent| {
37 let banner_scale_factor = 0.5;
38 parent.spawn((
39 Node {
40 position_type: PositionType::Absolute,
41 width: px(905.0 * banner_scale_factor),
42 height: px(363.0 * banner_scale_factor),
43 border: UiRect::all(px(20)),
44 border_radius: BorderRadius::all(px(20)),
45 ..default()
46 },
47 MaterialNode(ui_materials.add(CustomUiMaterial {
48 color: LinearRgba::WHITE.to_f32_array().into(),
49 slider: Vec4::splat(0.5),
50 color_texture: asset_server.load("branding/banner.png"),
51 border_color: LinearRgba::WHITE.to_f32_array().into(),
52 })),
53 // UI material nodes can have outlines and shadows like any other UI node
54 Outline {
55 width: px(2),
56 offset: px(100),
57 color: DARK_BLUE.into(),
58 },
59 ));
60 });
61 }
62
63 #[derive(AsBindGroup, Asset, TypePath, Debug, Clone)]
64 struct CustomUiMaterial {
65 /// Color multiplied with the image
66 #[uniform(0)]
67 color: Vec4,
68 /// Represents how much of the image is visible
69 /// Goes from 0 to 1
70 /// A `Vec4` is used here because Bevy with webgl2 requires that uniforms are 16-byte aligned but only the first component is read.
71 #[uniform(1)]
72 slider: Vec4,
73 /// Image used to represent the slider
74 #[texture(2)]
75 #[sampler(3)]
76 color_texture: Handle<Image>,
77 /// Color of the image's border
78 #[uniform(4)]
79 border_color: Vec4,
80 }
81
82 impl UiMaterial for CustomUiMaterial {
83 fn fragment_shader() -> ShaderRef {
84 SHADER_ASSET_PATH.into()
85 }
86 }
87
88 // Fills the slider slowly over 2 seconds and resets it
89 // Also updates the color of the image to a rainbow color
90 fn animate(
91 mut materials: ResMut<Assets<CustomUiMaterial>>,
92 q: Query<&MaterialNode<CustomUiMaterial>>,
93 time: Res<Time>,
94 ) {
95 let duration = 2.0;
96 for handle in &q {
97 if let Some(mut material) = materials.get_mut(handle) {
98 // rainbow color effect
99 let new_color = Color::hsl((time.elapsed_secs() * 60.0) % 360.0, 1., 0.5);
100 let border_color = Color::hsl((time.elapsed_secs() * 60.0) % 360.0, 0.75, 0.75);
101 material.color = new_color.to_linear().to_vec4();
102 material.slider.x =
103 ((time.elapsed_secs() % (duration * 2.0)) - duration).abs() / duration;
104 material.border_color = border_color.to_linear().to_vec4();
105 }
106 }
107 }