A powerful 3D/2.5D game controller plugin for Bevy Engine.
The AI System in Bevy All-in-One Controller provides a comprehensive framework for creating intelligent non-player characters (NPCs) with advanced behaviors, perception, and decision-making capabilities. The system is designed to be modular, extensible, and performance-oriented.
The main AI component that controls behavior and state:
#[derive(Component, Debug, Reflect)]
pub struct AiController {
pub state: AiBehaviorState, // Current AI state
pub target: Option<Entity>, // Current target entity
pub patrol_path: Vec<Vec3>, // Patrol waypoints
pub current_waypoint_index: usize, // Current patrol index
pub detection_range: f32, // Detection range (default: 15.0)
pub attack_range: f32, // Attack range (default: 2.5)
pub patrol_speed_mult: f32, // Patrol speed multiplier (default: 0.5)
pub chase_speed_mult: f32, // Chase speed multiplier (default: 1.0)
pub wait_timer: f32, // Current wait timer
pub wait_time_between_waypoints: f32, // Waypoint wait time (default: 2.0)
pub suspicion_timer: f32, // Suspicion timer
pub max_suspicion_time: f32, // Max suspicion time (default: 5.0)
pub wander_radius: f32, // Wander radius (default: 10.0)
pub wander_center: Vec3, // Wander center point
pub target_last_position: Option<Vec3>, // Last known target position
}
Controls AI movement parameters:
#[derive(Component, Debug, Reflect, Default)]
pub struct AiMovement {
pub destination: Option<Vec3>, // Current destination
pub speed: f32, // Movement speed
pub acceleration: f32, // Movement acceleration
pub stop_distance: f32, // Stop distance from target
pub move_type: AiMovementType, // Movement type
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect, Default)]
pub enum AiMovementType {
#[default]
Walk,
Run,
Sprint,
Crouch,
}
Handles AI sensory capabilities:
#[derive(Component, Debug, Reflect)]
pub struct AiPerception {
pub fov: f32, // Field of view in degrees
pub vision_range: f32, // Vision range
pub visible_targets: Vec<Entity>, // Currently visible targets
}
Defines all possible AI states:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub enum AiBehaviorState {
Idle, // No activity
Patrol, // Following patrol path
Chase, // Pursuing target
Attack, // Attacking target
Flee, // Running away
Follow, // Following friendly entity
Hide, // Hiding from threats
Combat, // In combat mode
Turret, // Turret mode
Dead, // Dead/incapacitated
Wander, // Random wandering
Suspect, // Suspicious state
}
The AI system uses a multi-system approach with proper execution order:
update_ai_perception - Processes vision and target detectionupdate_ai_hearing - Processes sound/noise detectionhandle_friend_commands - Handles friendly AI commandsupdate_ai_behavior - Updates AI behavior state machineupdate_ai_suspicion - Manages suspicion timersupdate_ai_movement - Handles AI movementupdate_patrol - Manages patrol behaviorupdate_turrets - Handles turret AIupdate_ai_combat - Manages combat behaviorupdate_ai_hiding - Handles hiding behaviordraw_ai_vision_cones - Visualizes AI visionupdate_ai_state_visuals - Updates state visualizationupdate_faction_relations - Manages faction relationshipsalert_faction_members - Alerts faction membersThe AI uses a finite state machine with the following states:
Passive States:
Idle: AI is inactiveWander: AI moves randomly within areaPatrol: AI follows predefined pathFollow: AI follows friendly entityAlert States:
Suspect: AI is suspicious but no target confirmedChase: AI is pursuing a targetAttack: AI is attacking a targetSpecial States:
Hide: AI is hiding from threatsFlee: AI is running awayCombat: AI is in combat modeTurret: AI is in turret modeDead: AI is dead/incapacitatedThe AI transitions between states based on perception and environmental factors:
graph TD
Idle -->|Detects enemy| Chase
Idle -->|Hears noise| Suspect
Idle -->|Patrol path set| Patrol
Idle -->|Follow command| Follow
Patrol -->|Detects enemy| Chase
Patrol -->|Reaches waypoint| Patrol
Chase -->|Target in range| Attack
Chase -->|Loses target| Suspect
Chase -->|Target lost| Idle
Attack -->|Target out of range| Chase
Attack -->|Target defeated| Idle
Suspect -->|Timer expires| Idle
Suspect -->|Confirms target| Chase
Follow -->|Detects enemy| Chase
Follow -->|Loses leader| Idle
The vision system uses:
Vision Process:
The hearing system responds to noise events:
Hearing Process:
The FOV system uses:
The faction system manages relationships between different groups:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect, Default)]
pub enum FactionRelation {
#[default]
Neutral, // No special relationship
Friend, // Friendly relationship
Enemy, // Hostile relationship
}
#[derive(Resource, Debug, Reflect, Default)]
pub struct FactionSystem {
pub factions: Vec<FactionInfo>,
pub relations: Vec<FactionRelationInfo>,
}
The system automatically determines relationships:
Faction Detection Process:
The movement system provides:
The patrol system includes:
The patrol system allows AI to follow predefined paths:
#[derive(Component, Debug, Reflect, Default)]
pub struct PatrolPath {
pub waypoints: Vec<Vec3>, // List of waypoints
pub loop_path: bool, // Whether to loop the path
}
Patrol Behavior:
Specialized AI for stationary turrets:
The combat system handles:
The hiding system allows AI to:
The system provides debug visualization:
#[derive(Component, Debug, Reflect)]
pub struct AiVisionVisualizer {
pub active: bool, // Enable/disable visualization
pub normal_color: Color, // Normal state color
pub alert_color: Color, // Alert state color
}
Visual indicators for AI state:
use bevy::prelude::*;
use bevy_allinone::prelude::*;
fn spawn_basic_ai(commands: &mut Commands, position: Vec3) -> Entity {
commands.spawn((
Name::new("Basic AI"),
AiController {
state: AiBehaviorState::Idle,
detection_range: 15.0,
attack_range: 2.5,
..default()
},
AiMovement::default(),
AiPerception {
fov: 90.0,
vision_range: 20.0,
..default()
},
AiVisionVisualizer::default(),
CharacterFaction {
name: "Enemy".to_string(),
},
Transform::from_translation(position),
GlobalTransform::default(),
))
.insert((
// Add character controller components
CharacterController::default(),
CharacterMovementState::default(),
InputState::default(),
// Add physics components
RigidBody::Dynamic,
Collider::capsule(0.4, 1.0),
LockedAxes::ROTATION_LOCKED,
GravityScale(1.0),
Friction::new(0.0),
Restitution::new(0.0),
LinearVelocity::default(),
AngularVelocity::default(),
))
.id()
}
fn spawn_patrol_ai(commands: &mut Commands) -> Entity {
let patrol_path = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(5.0, 0.0, 0.0),
Vec3::new(5.0, 0.0, 5.0),
Vec3::new(0.0, 0.0, 5.0),
];
commands.spawn((
Name::new("Patrol AI"),
AiController {
state: AiBehaviorState::Patrol,
patrol_path: patrol_path,
wait_time_between_waypoints: 3.0,
patrol_speed_mult: 0.7,
..default()
},
// ... other components
))
.id()
}
fn setup_factions(mut commands: Commands) {
// Initialize faction system
let mut faction_system = FactionSystem::default();
// Add factions
faction_system.factions.push(FactionInfo {
name: "Player".to_string(),
turn_to_enemy_if_attacked: true,
turn_faction_to_enemy: false,
friendly_fire_turn_into_enemies: false,
});
faction_system.factions.push(FactionInfo {
name: "Enemy".to_string(),
turn_to_enemy_if_attacked: true,
turn_faction_to_enemy: false,
friendly_fire_turn_into_enemies: true,
});
faction_system.factions.push(FactionInfo {
name: "Neutral".to_string(),
turn_to_enemy_if_attacked: true,
turn_faction_to_enemy: false,
friendly_fire_turn_into_enemies: false,
});
// Set faction relations
faction_system.relations.push(FactionRelationInfo {
faction_a: "Player".to_string(),
faction_b: "Enemy".to_string(),
relation: FactionRelation::Enemy,
});
faction_system.relations.push(FactionRelationInfo {
faction_a: "Player".to_string(),
faction_b: "Neutral".to_string(),
relation: FactionRelation::Neutral,
});
commands.insert_resource(faction_system);
}
// Extend AiBehaviorState enum
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub enum AiBehaviorState {
// ... existing states ...
CustomState, // Your custom state
SpecialAbility, // Another custom state
}
// Add custom behavior logic
fn update_ai_behavior(
time: Res<Time>,
mut ai_query: Query<(&mut AiController, &mut InputState)>
) {
for (mut ai, mut input) in ai_query.iter_mut() {
match ai.state {
AiBehaviorState::CustomState => {
// Your custom behavior logic
input.movement = Vec2::new(1.0, 0.0); // Example movement
// Add your custom state transitions
}
AiBehaviorState::SpecialAbility => {
// Special ability logic
input.attack_pressed = true;
// Handle ability cooldowns, etc.
}
// ... existing state handling ...
}
}
}
// Add custom perception components
#[derive(Component, Debug, Reflect)]
pub struct CustomPerception {
pub custom_detection_range: f32,
pub custom_sense_type: CustomSenseType,
}
// Add custom perception system
fn update_custom_perception(
mut ai_query: Query<(&GlobalTransform, &mut AiController, &CustomPerception)>
) {
for (transform, mut ai, custom_perception) in ai_query.iter_mut() {
// Your custom perception logic
// Could detect special objects, environmental factors, etc.
if detects_custom_condition(transform.translation(), custom_perception) {
ai.state = AiBehaviorState::Chase; // Or custom state
// Set target or other parameters
}
}
}
// Extend AiMovementType
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect, Default)]
pub enum AiMovementType {
// ... existing types ...
CustomMovement, // Your custom movement type
}
// Add custom movement system
fn update_custom_movement(
time: Res<Time>,
mut query: Query<(&mut AiMovement, &mut CharacterController, &mut InputState)>
) {
for (mut movement, mut controller, mut input) in query.iter_mut() {
if movement.move_type == AiMovementType::CustomMovement {
// Your custom movement logic
// Could implement formation movement, flocking, etc.
input.movement = calculate_custom_movement_direction();
controller.run_speed = calculate_custom_speed();
}
}
}
AI doesn’t detect targets:
AI gets stuck:
AI doesn’t attack:
Performance issues:
Potential areas for expansion: