diff --git a/Cargo.toml b/Cargo.toml index a3d3a2ab63e51..085dd985b0f71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4361,3 +4361,14 @@ name = "Extended Bindless Material" description = "Demonstrates bindless `ExtendedMaterial`" category = "Shaders" wasm = false + +[[example]] +name = "cooldown" +path = "examples/usage/cooldown.rs" +doc-scrape-examples = true + +[package.metadata.example.cooldown] +name = "Cooldown" +description = "Example for cooldown on button clicks" +category = "Usage" +wasm = true diff --git a/assets/textures/food_kenney.png b/assets/textures/food_kenney.png new file mode 100644 index 0000000000000..a5fe374eba306 Binary files /dev/null and b/assets/textures/food_kenney.png differ diff --git a/examples/README.md b/examples/README.md index 060683f96d891..8f7c73b0682b1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -67,6 +67,7 @@ git checkout v0.4.0 - [Tools](#tools) - [Transforms](#transforms) - [UI (User Interface)](#ui-user-interface) + - [Usage](#usage) - [Window](#window) - [Tests](#tests) @@ -571,6 +572,12 @@ Example | Description [Viewport Node](../examples/ui/viewport_node.rs) | Demonstrates how to create a viewport node with picking support [Window Fallthrough](../examples/ui/window_fallthrough.rs) | Illustrates how to access `winit::window::Window`'s `hittest` functionality. +## Usage + +Example | Description +--- | --- +[Cooldown](../examples/usage/cooldown.rs) | Example for cooldown on button clicks + ## Window Example | Description diff --git a/examples/usage/cooldown.rs b/examples/usage/cooldown.rs new file mode 100644 index 0000000000000..7b86c5845e827 --- /dev/null +++ b/examples/usage/cooldown.rs @@ -0,0 +1,179 @@ +//! Demonstrates implementing a cooldown in UI. +//! +//! You might want a system like this for abilities, buffs or consumables. +//! We create four food buttons to eat with 2, 1, 10, and 4 seconds cooldown. + +use bevy::{color::palettes::tailwind, ecs::spawn::SpawnIter, prelude::*}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems( + Update, + ( + activate_ability, + animate_cooldowns.run_if(any_with_component::), + ), + ) + .run(); +} + +fn setup( + mut commands: Commands, + mut texture_atlas_layouts: ResMut>, + asset_server: Res, +) { + commands.spawn(Camera2d); + let texture = asset_server.load("textures/food_kenney.png"); + let layout = TextureAtlasLayout::from_grid(UVec2::splat(64), 7, 7, None, None); + let texture_atlas_layout = texture_atlas_layouts.add(layout); + commands.spawn(( + Node { + width: Val::Percent(100.), + height: Val::Percent(100.), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + column_gap: Val::Px(15.), + ..default() + }, + Children::spawn(SpawnIter( + [ + FoodItem { + name: "an apple", + cooldown: 2., + index: 2, + }, + FoodItem { + name: "a burger", + cooldown: 1., + index: 23, + }, + FoodItem { + name: "chocolate", + cooldown: 10., + index: 32, + }, + FoodItem { + name: "cherries", + cooldown: 4., + index: 41, + }, + ] + .into_iter() + .map(move |food| build_ability(food, texture.clone(), texture_atlas_layout.clone())), + )), + )); + commands.spawn(( + Text::new("*Click some food to eat it*"), + Node { + position_type: PositionType::Absolute, + top: Val::Px(5.0), + left: Val::Px(15.0), + ..default() + }, + )); +} + +struct FoodItem { + name: &'static str, + cooldown: f32, + index: usize, +} + +fn build_ability( + food: FoodItem, + texture: Handle, + layout: Handle, +) -> impl Bundle { + let FoodItem { + name, + cooldown, + index, + } = food; + let name = Name::new(name); + + // Every food item is a button with a child node. + // The child node's height will be animated to be at 100% at the beginning + // of a cooldown, effectively graying out the whole button, and then getting smaller over time. + ( + Node { + width: Val::Px(80.0), + height: Val::Px(80.0), + flex_direction: FlexDirection::ColumnReverse, + ..default() + }, + BackgroundColor(tailwind::SLATE_400.into()), + Button, + ImageNode::from_atlas_image(texture, TextureAtlas { layout, index }), + Cooldown(Timer::from_seconds(cooldown, TimerMode::Once)), + name, + children![( + Node { + width: Val::Percent(100.), + height: Val::Percent(0.), + ..default() + }, + BackgroundColor(tailwind::SLATE_50.with_alpha(0.5).into()), + )], + ) +} + +#[derive(Component)] +struct Cooldown(Timer); + +#[derive(Component)] +#[component(storage = "SparseSet")] +struct ActiveCooldown; + +fn activate_ability( + mut commands: Commands, + mut interaction_query: Query< + ( + Entity, + &Interaction, + &mut Cooldown, + &Name, + Option<&ActiveCooldown>, + ), + (Changed, With