Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/usage/cooldown.rs
6592 views
1
//! Demonstrates implementing a cooldown in UI.
2
//!
3
//! You might want a system like this for abilities, buffs or consumables.
4
//! We create four food buttons to eat with 2, 1, 10, and 4 seconds cooldown.
5
6
use bevy::{color::palettes::tailwind, ecs::spawn::SpawnIter, prelude::*};
7
8
fn main() {
9
App::new()
10
.add_plugins(DefaultPlugins)
11
.add_systems(Startup, setup)
12
.add_systems(
13
Update,
14
(
15
activate_ability,
16
animate_cooldowns.run_if(any_with_component::<ActiveCooldown>),
17
),
18
)
19
.run();
20
}
21
22
fn setup(
23
mut commands: Commands,
24
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
25
asset_server: Res<AssetServer>,
26
) {
27
commands.spawn(Camera2d);
28
let texture = asset_server.load("textures/food_kenney.png");
29
let layout = TextureAtlasLayout::from_grid(UVec2::splat(64), 7, 7, None, None);
30
let texture_atlas_layout = texture_atlas_layouts.add(layout);
31
commands.spawn((
32
Node {
33
width: percent(100),
34
height: percent(100),
35
align_items: AlignItems::Center,
36
justify_content: JustifyContent::Center,
37
column_gap: px(15),
38
..default()
39
},
40
Children::spawn(SpawnIter(
41
[
42
FoodItem {
43
name: "an apple",
44
cooldown: 2.,
45
index: 2,
46
},
47
FoodItem {
48
name: "a burger",
49
cooldown: 1.,
50
index: 23,
51
},
52
FoodItem {
53
name: "chocolate",
54
cooldown: 10.,
55
index: 32,
56
},
57
FoodItem {
58
name: "cherries",
59
cooldown: 4.,
60
index: 41,
61
},
62
]
63
.into_iter()
64
.map(move |food| build_ability(food, texture.clone(), texture_atlas_layout.clone())),
65
)),
66
));
67
commands.spawn((
68
Text::new("*Click some food to eat it*"),
69
Node {
70
position_type: PositionType::Absolute,
71
top: px(12),
72
left: px(12),
73
..default()
74
},
75
));
76
}
77
78
struct FoodItem {
79
name: &'static str,
80
cooldown: f32,
81
index: usize,
82
}
83
84
fn build_ability(
85
food: FoodItem,
86
texture: Handle<Image>,
87
layout: Handle<TextureAtlasLayout>,
88
) -> impl Bundle {
89
let FoodItem {
90
name,
91
cooldown,
92
index,
93
} = food;
94
let name = Name::new(name);
95
96
// Every food item is a button with a child node.
97
// The child node's height will be animated to be at 100% at the beginning
98
// of a cooldown, effectively graying out the whole button, and then getting smaller over time.
99
(
100
Node {
101
width: px(80),
102
height: px(80),
103
flex_direction: FlexDirection::ColumnReverse,
104
..default()
105
},
106
BackgroundColor(tailwind::SLATE_400.into()),
107
Button,
108
ImageNode::from_atlas_image(texture, TextureAtlas { layout, index }),
109
Cooldown(Timer::from_seconds(cooldown, TimerMode::Once)),
110
name,
111
children![(
112
Node {
113
width: percent(100),
114
height: percent(0),
115
..default()
116
},
117
BackgroundColor(tailwind::SLATE_50.with_alpha(0.5).into()),
118
)],
119
)
120
}
121
122
#[derive(Component)]
123
struct Cooldown(Timer);
124
125
#[derive(Component)]
126
#[component(storage = "SparseSet")]
127
struct ActiveCooldown;
128
129
fn activate_ability(
130
mut commands: Commands,
131
mut interaction_query: Query<
132
(
133
Entity,
134
&Interaction,
135
&mut Cooldown,
136
&Name,
137
Option<&ActiveCooldown>,
138
),
139
(Changed<Interaction>, With<Button>),
140
>,
141
mut text: Query<&mut Text>,
142
) -> Result {
143
for (entity, interaction, mut cooldown, name, on_cooldown) in &mut interaction_query {
144
if *interaction == Interaction::Pressed {
145
if on_cooldown.is_none() {
146
cooldown.0.reset();
147
commands.entity(entity).insert(ActiveCooldown);
148
**text.single_mut()? = format!("You ate {name}");
149
} else {
150
**text.single_mut()? = format!(
151
"You can eat {name} again in {} seconds.",
152
cooldown.0.remaining_secs().ceil()
153
);
154
}
155
}
156
}
157
158
Ok(())
159
}
160
161
fn animate_cooldowns(
162
time: Res<Time>,
163
mut commands: Commands,
164
buttons: Query<(Entity, &mut Cooldown, &Children), With<ActiveCooldown>>,
165
mut nodes: Query<&mut Node>,
166
) -> Result {
167
for (entity, mut timer, children) in buttons {
168
timer.0.tick(time.delta());
169
let cooldown = children.first().ok_or("No child")?;
170
if timer.0.just_finished() {
171
commands.entity(entity).remove::<ActiveCooldown>();
172
nodes.get_mut(*cooldown)?.height = percent(0);
173
} else {
174
nodes.get_mut(*cooldown)?.height = percent((1. - timer.0.fraction()) * 100.);
175
}
176
}
177
178
Ok(())
179
}
180
181