A powerful 3D/2.5D game controller plugin for Bevy Engine.
The Abilities System is a robust and flexible framework for managing special abilities in Bevy Engine games. It provides comprehensive support for ability activation, cooldowns, energy management, and various input patterns. The system is designed to handle everything from simple toggle abilities to complex charged abilities with multiple activation conditions.
Key Features:
The primary component that defines an individual ability’s behavior and properties:
#[derive(Component, Debug, Clone, Reflect)]
pub struct AbilityInfo {
// Identity
pub name: String, // Unique name for the ability
// State
pub enabled: bool, // Whether ability is available
pub active: bool, // Whether ability is currently active
pub is_current: bool, // Whether this is the selected ability
pub status: AbilityStatus, // Current ability status
// Input Configuration
pub input_types: Vec<AbilityInputType>, // Supported input types
// Activation Constraints
pub can_be_used_only_on_ground: bool, // Ground-only restriction
pub deactivate_on_switch: bool, // Auto-deactivate on switch
// UI Integration
pub add_to_ui_wheel: bool, // Show in ability wheel
pub visible_on_wheel: bool, // Visible in wheel when selected
// Cooldown Settings
pub use_cooldown: bool, // Enable cooldown system
pub cooldown_duration: f32, // Cooldown time in seconds
pub cooldown_in_process: bool, // Currently on cooldown
pub use_cooldown_on_press_down: bool, // Start cooldown on press down
pub use_cooldown_on_press_up: bool, // Start cooldown on press up
pub use_cooldown_when_active_from_press: bool, // Cooldown timing mode
pub activate_cooldown_after_time_limit: bool, // Chain cooldown after time limit
// Time Limit Settings
pub use_time_limit: bool, // Enable duration system
pub time_limit: f32, // Maximum active duration
pub time_limit_in_process: bool, // Time limit active
pub use_time_limit_on_press_down: bool,// Start limit on press down
pub use_time_limit_on_press_up: bool, // Start limit on press up
pub use_limit_when_active_from_press: bool, // Limit timing mode
pub avoid_input_while_limit_active: bool, // Block input during time limit
pub avoid_other_abilities_while_limit_active: bool, // Block other abilities
pub reset_active_state_on_time_limit: bool, // Reset state on limit end
pub call_deactivate_on_time_limit: bool, // Call deactivate on limit end
// Energy Settings
pub use_energy: bool, // Enable energy system
pub energy_consumption_type: EnergyConsumptionType, // One-time or continuous
pub energy_amount: f32, // Energy required/consumed
pub use_energy_on_press_down: bool, // Consume on press down
pub use_energy_on_press_hold: bool, // Consume continuously while holding
pub use_energy_on_press_up: bool, // Consume on press up
pub use_energy_once_on_press_down: bool, // One-time consumption on press down
pub use_energy_once_on_press_up: bool, // One-time consumption on press up
// Input State Tracking
pub active_from_press_down: bool, // Activated via press down
pub active_from_press_up: bool, // Activated via press up
pub disable_input_in_use_on_press_down: bool, // Disable input after activation
// Validation
pub check_press_down_before_activate_up: bool, // Require press down before press up
// Timers
pub last_time_active: f32, // Last activation timestamp
pub cooldown_timer: f32, // Current cooldown progress
pub time_limit_timer: f32, // Current time limit progress
}
The manager component that tracks all abilities and handles system-wide logic:
#[derive(Component, Debug, Reflect)]
pub struct PlayerAbilitiesSystem {
// System State
pub enabled: bool, // System enabled flag
pub abilities_mode_active: bool, // Abilities mode active
pub current_ability_index: usize, // Currently selected ability index
// Energy Management
pub energy_stat_name: String, // Stat name for energy tracking
pub current_energy: f32, // Current energy amount
pub max_energy: f32, // Maximum energy capacity
// Constraints
pub disable_on_first_person: bool, // Disable in first-person mode
pub can_move: bool, // Player can move flag
pub player_busy: bool, // Player busy flag
pub ability_input_in_use: bool, // Input currently in use
pub pause_check_using_device: bool, // Pause device checks during abilities
// Temporary Ability Tracking
pub previous_ability_name: String, // Previous ability for temporary switch
}
Enumeration defining the current state of an ability:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Reflect)]
pub enum AbilityStatus {
Disabled, // Ability is disabled and cannot be used
Enabled, // Ability is enabled and available for use
Active, // Ability is currently active
OnCooldown, // Ability is on cooldown
Limited, // Ability is time-limited
}
Enumeration defining how an ability is activated:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Reflect)]
pub enum AbilityInputType {
PressDown, // Activate on button press down
PressHold, // Activate while holding button
PressUp, // Activate on button release
}
Enumeration defining how energy is consumed:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Reflect)]
pub enum EnergyConsumptionType {
None, // No energy consumption
Once, // One-time consumption on activation
Continuous, // Continuous consumption while active
}
The Abilities System uses a modular approach with specialized systems:
update_abilities - Updates cooldown and time limit timers for all abilitieshandle_ability_activation - Processes ability activation eventshandle_ability_deactivation - Processes ability deactivation eventshandle_ability_enabled_events - Processes ability enable/disable eventsAn ability transitions through several states during its lifecycle:
State Transitions:
enable() method or SetAbilityEnabledEventactivate_cooldown_after_time_limit is true)The cooldown system provides precise timing control:
Cooldown Activation Points:
use_cooldown_on_press_down = trueuse_cooldown_on_press_up = trueuse_cooldown_when_active_from_press = trueCooldown Behavior:
delta_timecooldown_in_process is set to falseOnCooldown during cooldown periodExample Configuration:
AbilityInfo {
name: "Fireball".to_string(),
use_cooldown: true,
cooldown_duration: 3.0,
use_cooldown_on_press_down: true,
..default()
}
The time limit system manages duration-based abilities:
Time Limit Activation Points:
use_time_limit_on_press_down = trueuse_time_limit_on_press_up = trueuse_limit_when_active_from_press = trueTime Limit Behavior:
delta_timeInput Blocking Options:
avoid_input_while_limit_active: Blocks ability input during time limitavoid_other_abilities_while_limit_active: Blocks other abilities during time limitExample Configuration:
AbilityInfo {
name: "Shield".to_string(),
use_time_limit: true,
time_limit: 5.0,
use_time_limit_on_press_down: true,
reset_active_state_on_time_limit: true,
avoid_other_abilities_while_limit_active: true,
..default()
}
The energy system provides resource-based ability activation:
Consumption Types:
Consumption Timing:
use_energy_on_press_down: Consume when pressing button downuse_energy_on_press_hold: Consume continuously while holdinguse_energy_on_press_up: Consume when releasing buttonOne-time Consumption:
use_energy_once_on_press_down: Consume only once on press down (not on toggle off)use_energy_once_on_press_up: Consume only once on press upEnergy Management:
PlayerAbilitiesSystem tracks current_energy and max_energyuse_power_bar() methodenergy_stat_name to integrate with stats systemExample Configuration:
PlayerAbilitiesSystem {
current_energy: 100.0,
max_energy: 100.0,
..default()
}
AbilityInfo {
name: "Heal".to_string(),
use_energy: true,
energy_consumption_type: EnergyConsumptionType::Once,
energy_amount: 30.0,
use_energy_on_press_down: true,
..default()
}
Activates when the ability button is pressed down:
Activation Flow:
active_from_press_down stateability_input_in_use flagValidation Checks:
PressDown input typecan_be_used_only_on_ground is true)use_energy is true)avoid_input_while_limit_activeToggle Behavior:
active_from_press_down between true and falseactive_from_press_down stateuse_limit_when_active_from_press to control timingActivates continuously while the ability button is held:
Activation Flow:
Use Cases:
Energy Consumption:
use_energy_on_press_hold consumes energy every frameActivates when the ability button is released:
Activation Flow:
check_press_down_before_activate_up is true:
active_from_press_down is trueactive_from_press_up stateability_input_in_use flagValidation Checks:
PressUp input typecheck_press_down_before_activate_up condition (if enabled)use_energy is true)avoid_input_while_limit_activeUse Cases:
The system supports selecting between multiple abilities:
Selection Methods:
set_current_ability_by_name(): Select by ability namecurrent_ability_indexSelection Behavior:
deactivate_on_switch is true)is_current = true)current_ability_indexprevious_ability_name for non-temporary selectionsTemporary Abilities:
input_select_and_press_down_new_ability_temporally() for temporary switchingprevious_ability_nameSwitch Example:
// Select and activate a new ability
system.set_current_ability_by_name("Fireball", &mut abilities);
if let Some(mut ability) = abilities.iter_mut().find(|a| a.is_current) {
system.input_press_down_use_current_ability(&mut ability, is_on_ground);
}
The system supports various conditional activation requirements:
Ground Restriction:
can_be_used_only_on_ground: Ability only works when character is on groundEnergy Requirements:
use_energy: Ability requires energy to activateenergy_consumption_typeState Requirements:
enabled = true)player_busy = false)Mode Restrictions:
disable_on_first_person: Disable abilities in first-person viewabilities_mode_active: Master enable/disable for all abilitiesThe system supports temporarily switching to a different ability:
Temporary Activation Flow:
previous_ability_nameAPI Methods:
input_select_and_press_down_new_ability_temporally(): Activate temporary abilityinput_select_and_press_down_new_separated_ability(): Select temporaryinput_select_and_press_hold_new_separated_ability(): Hold temporaryinput_select_and_press_up_new_separated_ability(): Release temporarycheck_previous_ability_active(): Restore previous abilityUse Cases:
Example:
// Use a health potion temporarily
system.input_select_and_press_down_new_ability_temporally(
"Use Health Potion",
true, // is_temporary
&mut abilities,
is_on_ground
);
// Later, when done:
system.check_previous_ability_active(&mut abilities);
// Automatically restores previous ability
Abilities can be restricted to ground-only usage:
Configuration:
AbilityInfo {
name: "Ground Slam".to_string(),
can_be_used_only_on_ground: true,
..default()
}
Behavior:
Use Cases:
The energy system provides comprehensive resource management:
Energy Tracking:
PlayerAbilitiesSystem {
current_energy: 100.0,
max_energy: 100.0,
energy_stat_name: "Current Energy".to_string(),
..default()
}
Energy Consumption:
AbilityInfo {
use_energy: true,
energy_consumption_type: EnergyConsumptionType::Continuous,
energy_amount: 5.0, // Consumed per frame
use_energy_on_press_hold: true,
..default()
}
Energy Management Methods:
use_power_bar(amount): Consume energyis_there_energy_available(): Check if energy > 0check_if_ability_needs_energy(): Check energy requirementenergy_stat_nameEnergy Regeneration:
use bevy::prelude::*;
use bevy_allinone::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(GameControllerPlugin)
.add_systems(Startup, setup_abilities)
.run();
}
fn setup_abilities(mut commands: Commands) {
commands.spawn((
PlayerAbilitiesSystem {
current_energy: 100.0,
max_energy: 100.0,
..default()
},
AbilityInfo {
name: "Fireball".to_string(),
enabled: true,
use_cooldown: true,
cooldown_duration: 3.0,
use_cooldown_on_press_down: true,
..default()
},
));
}
fn setup_advanced_abilities(mut commands: Commands) {
// Create abilities system
let player = commands.spawn((
PlayerAbilitiesSystem {
current_energy: 150.0,
max_energy: 150.0,
energy_stat_name: "Mana".to_string(),
abilities_mode_active: true,
..default()
},
)).id();
// Fireball ability
commands.entity(player).with_children(|parent| {
parent.spawn(AbilityInfo {
name: "Fireball".to_string(),
enabled: true,
input_types: vec![AbilityInputType::PressDown],
use_cooldown: true,
cooldown_duration: 2.5,
use_cooldown_on_press_down: true,
use_energy: true,
energy_consumption_type: EnergyConsumptionType::Once,
energy_amount: 25.0,
use_energy_on_press_down: true,
add_to_ui_wheel: true,
visible_on_wheel: true,
can_be_used_only_on_ground: false,
..default()
});
});
// Shield ability (time-limited)
commands.entity(player).with_children(|parent| {
parent.spawn(AbilityInfo {
name: "Energy Shield".to_string(),
enabled: true,
input_types: vec![AbilityInputType::PressDown],
use_time_limit: true,
time_limit: 5.0,
use_time_limit_on_press_down: true,
reset_active_state_on_time_limit: true,
avoid_other_abilities_while_limit_active: true,
use_energy: true,
energy_consumption_type: EnergyConsumptionType::Once,
energy_amount: 40.0,
use_energy_on_press_down: true,
add_to_ui_wheel: true,
visible_on_wheel: true,
deactivate_on_switch: true,
..default()
});
});
// Lightning beam (continuous)
commands.entity(player).with_children(|parent| {
parent.spawn(AbilityInfo {
name: "Lightning Beam".to_string(),
enabled: true,
input_types: vec![AbilityInputType::PressHold],
use_energy: true,
energy_consumption_type: EnergyConsumptionType::Continuous,
energy_amount: 10.0,
use_energy_on_press_hold: true,
use_energy_once_on_press_down: false,
add_to_ui_wheel: true,
visible_on_wheel: true,
..default()
});
});
}
fn handle_ability_input(
mut player_query: Query<(&mut PlayerAbilitiesSystem, &mut AbilitiesList)>,
input_state: Res<InputState>,
ground_detection: Res<GroundDetection>,
) {
for (mut abilities_system, mut abilities_list) in player_query.iter_mut() {
let is_on_ground = ground_detection.is_grounded;
// Press down input
if input_state.just_pressed(Action::UseAbility) {
if let Some(ability) = abilities_system.get_current_ability() {
if let Some(mut ability) = abilities_list.get_mut(&ability.name) {
abilities_system.input_press_down_use_current_ability(
&mut ability,
is_on_ground
);
}
}
}
// Hold input
if input_state.pressed(Action::UseAbility) {
if let Some(ability) = abilities_system.get_current_ability() {
if let Some(mut ability) = abilities_list.get_mut(&ability.name) {
abilities_system.input_press_hold_use_current_ability(
&mut ability,
is_on_ground
);
}
}
}
// Press up input
if input_state.just_released(Action::UseAbility) {
if let Some(ability) = abilities_system.get_current_ability() {
if let Some(mut ability) = abilities_list.get_mut(&ability.name) {
abilities_system.input_press_up_use_current_ability(
&mut ability,
is_on_ground
);
}
}
}
}
}
fn switch_ability(
mut player_query: Query<&mut PlayerAbilitiesSystem>,
mut abilities_query: Query<&mut AbilityInfo>,
input_state: Res<InputState>,
) {
if let Ok(mut abilities_system) = player_query.get_single_mut() {
// Next ability
if input_state.just_pressed(Action::NextAbility) {
let current_index = abilities_system.current_ability_index;
let count = abilities_system.get_number_of_available_abilities(&abilities_query);
let next_index = (current_index + 1) % count;
if let Some(ability) = abilities_query.iter().nth(next_index) {
abilities_system.set_current_ability_by_name(
&ability.name,
&mut abilities_query
);
}
}
// Previous ability
if input_state.just_pressed(Action::PreviousAbility) {
let current_index = abilities_system.current_ability_index;
let count = abilities_system.get_number_of_available_abilities(&abilities_query);
let prev_index = (current_index + count - 1) % count;
if let Some(ability) = abilities_query.iter().nth(prev_index) {
abilities_system.set_current_ability_by_name(
&ability.name,
&mut abilities_query
);
}
}
}
}
enabled flag to disable unused abilitiesfn create_custom_ability(
name: &str,
energy_cost: f32,
cooldown: f32,
) -> AbilityInfo {
AbilityInfo {
name: name.to_string(),
enabled: true,
input_types: vec![AbilityInputType::PressDown],
use_energy: true,
energy_consumption_type: EnergyConsumptionType::Once,
energy_amount: energy_cost,
use_energy_on_press_down: true,
use_cooldown: true,
cooldown_duration: cooldown,
use_cooldown_on_press_down: true,
add_to_ui_wheel: true,
visible_on_wheel: true,
deactivate_on_switch: true,
..default()
}
}
// Usage
let fireball = create_custom_ability("Fireball", 25.0, 3.0);
fn create_toggle_ability(name: &str) -> AbilityInfo {
AbilityInfo {
name: name.to_string(),
enabled: true,
input_types: vec![AbilityInputType::PressDown],
use_cooldown: false, // No cooldown for toggle
use_time_limit: false, // No time limit
disable_input_in_use_on_press_down: true, // Don't toggle off
..default()
}
}
// Usage
let flashlight = create_toggle_ability("Flashlight");
fn create_charged_ability(
name: &str,
energy_cost: f32,
charge_time: f32,
) -> AbilityInfo {
AbilityInfo {
name: name.to_string(),
enabled: true,
input_types: vec![
AbilityInputType::PressHold,
AbilityInputType::PressUp
],
use_energy: true,
energy_consumption_type: EnergyConsumptionType::Continuous,
energy_amount: energy_cost,
use_energy_on_press_hold: true,
use_time_limit: true,
time_limit: charge_time,
use_time_limit_on_press_down: true,
use_cooldown: true,
cooldown_duration: 1.0,
use_cooldown_on_press_up: true,
add_to_ui_wheel: true,
visible_on_wheel: true,
..default()
}
}
// Usage
let charged_attack = create_charged_ability("Charged Shot", 15.0, 2.0);
fn use_temporary_ability(
abilities_system: &mut PlayerAbilitiesSystem,
abilities: &mut Query<&mut AbilityInfo>,
ability_name: &str,
is_on_ground: bool,
) {
// Activate temporary ability
abilities_system.input_select_and_press_down_new_ability_temporally(
ability_name,
true, // is_temporary
abilities,
is_on_ground
);
}
// Restore when done
fn restore_previous_ability(
abilities_system: &mut PlayerAbilitiesSystem,
abilities: &mut Query<&mut AbilityInfo>,
) {
abilities_system.check_previous_ability_active(abilities);
}
#[derive(Event, Debug, Clone)]
pub struct CustomAbilityEvent {
pub ability_name: String,
pub value: f32,
}
// In your system
fn handle_custom_ability_events(
mut events: EventReader<CustomAbilityEvent>,
mut abilities_query: Query<&mut AbilityInfo>,
) {
for event in events.read() {
if let Some(mut ability) = abilities_query
.iter_mut()
.find(|a| a.name == event.ability_name)
{
// Apply custom logic
info!("Custom ability {} triggered with value {}", event.ability_name, event.value);
}
}
}
Ability doesn’t activate:
enabled flagcan_be_used_only_on_ground is true)Cooldown not working:
use_cooldown is trueuse_cooldown_on_press_down or use_cooldown_on_press_upEnergy not consuming:
use_energy is trueenergy_consumption_typeTime limit not expiring:
use_time_limit is trueAbility not switching:
is_current flagcurrent_ability_index is validPotential areas for expansion: