A powerful 3D/2.5D game controller plugin for Bevy Engine.
The Camera System is a comprehensive, modular camera controller designed for 3D/2.5D games built with Bevy 0.18. It provides sophisticated camera controls, physics-based collision detection, dynamic state management, and advanced cinematic features. The system is built with flexibility and performance in mind, supporting multiple camera modes, real-time effects, and seamless integration with other game systems.
The Camera System follows a plugin-based architecture with clear separation of concerns:
Camera Plugin
├── Core Components (CameraController, CameraState)
├── Movement Systems (Follow, Rotation, Collision)
├── Effect Systems (Shake, Bob, FOV)
├── Targeting Systems (Marking, Locking)
├── Cinematic Systems (Waypoints, Zones, Bounds)
├── Advanced Features (Effects, Captures, Vehicles)
└── Integration Systems (State Offsets, Lean, Transparency)
The system integrates deeply with:
The Camera System uses a right-handed 3D coordinate system with the following conventions:
The camera system operates on a pivot-based architecture where the camera position is calculated relative to a target pivot point:
Camera states are managed through a sophisticated state machine that supports:
All camera movements use exponential smoothing for natural motion:
// Exponential smoothing formula
let alpha = 1.0 - (-speed * delta_time).exp();
current_value = current_value + (target_value - current_value) * alpha;
This provides frame-rate independent smoothing with configurable response curves.
The system heavily relies on Avian3D’s spatial query system for:
The primary controller component that manages camera behavior and configuration:
#[derive(Component, Debug, Reflect)]
#[reflect(Component)]
pub struct CameraController {
pub follow_target: Option<Entity>,
pub mode: CameraMode,
pub current_side: CameraSide,
// Sensitivity Settings
pub rot_sensitivity_3p: f32,
pub rot_sensitivity_1p: f32,
pub aim_zoom_sensitivity_mult: f32,
// Angular Limits
pub min_vertical_angle: f32,
pub max_vertical_angle: f32,
// Distance Settings
pub distance: f32,
pub min_distance: f32,
pub max_distance: f32,
// Smoothing Parameters
pub smooth_follow_speed: f32,
pub smooth_rotation_speed: f32,
pub pivot_smooth_speed: f32,
pub distance_smooth_speed: f32,
// Dynamic Offsets
pub side_offset: f32,
pub default_pivot_offset: Vec3,
pub aim_pivot_offset: Vec3,
pub crouch_pivot_offset: Vec3,
// Leaning Configuration
pub lean_amount: f32,
pub lean_angle: f32,
pub lean_speed: f32,
pub lean_raycast_dist: f32,
pub lean_wall_angle: f32,
// Field of View Settings
pub default_fov: f32,
pub aim_fov: f32,
pub fov_speed: f32,
// Collision Settings
pub use_collision: bool,
pub collision_radius: f32,
// Targeting System
pub target_lock: TargetLockSettings,
// State Management
pub states: Vec<CameraStateInfo>,
pub current_state_name: String,
// Baseline Configuration
pub base_mode: CameraMode,
pub base_distance: f32,
pub base_fov: f32,
pub base_pivot_offset: Vec3,
pub base_transition_speed: f32,
pub enabled: bool,
}
Key Properties:
follow_target: Entity the camera should track (typically player character)mode: Current camera mode (ThirdPerson, FirstPerson, Locked, etc.)sensitivity_*: Mouse/control sensitivity multipliers for different modesmin/max_vertical_angle: Pitch limits in degreesdistance_*: Camera distance from pivot pointsmooth_*_speed: Interpolation speeds for different camera aspectspivot_offset_*: Pivot point offsets for different player stateslean_*: Wall-leaning configuration and raycast settingsfov_*: Field of view settings for different camera statescollision_*: Camera collision detection configurationtarget_lock: Target acquisition and locking parametersstates: Predefined camera states for different game situationsbase_*: Baseline values for smooth restoration after overridesRuntime camera state tracking component:
#[derive(Component, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct CameraState {
pub yaw: f32,
pub pitch: f32,
pub current_distance: f32,
pub current_pivot: Vec3,
pub current_side_interpolator: f32,
pub current_lean: f32,
pub noise_offset: Vec2,
pub bob_offset: Vec3,
pub is_aiming: bool,
pub is_crouching: bool,
pub fov_override: Option<f32>,
pub fov_override_speed: Option<f32>,
}
Properties:
yaw, pitch: Current camera rotation in degreescurrent_distance: Actual distance from pivot (may differ from target)current_pivot: Current world-space pivot positioncurrent_side_interpolator: Smoothed side value (-1.0 to 1.0)current_lean: Current lean anglenoise_offset: Additive rotation offsets from effectsbob_offset: Position offsets from bobbing effectsis_aiming, is_crouching: State flags for dynamic behaviorfov_override, fov_override_speed: Temporary FOV overridesTarget tracking and management component:
#[derive(Component, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct CameraTargetState {
pub marked_target: Option<Entity>,
pub locked_target: Option<Entity>,
pub is_locking: bool,
}
Properties:
marked_target: Currently highlighted/marked target entitylocked_target: Entity currently locked by camerais_locking: Whether target locking is active#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect, Default)]
pub enum CameraMode {
#[default]
ThirdPerson,
FirstPerson,
Locked,
SideScroller,
TopDown,
}
Mode Characteristics:
Trigger volumes that modify camera behavior:
#[derive(Component, Debug, Reflect)]
#[reflect(Component)]
pub struct CameraZone {
pub settings: CameraZoneSettings,
pub priority: i32,
}
Configuration for zone-based camera modifications:
#[derive(Debug, Clone, Reflect)]
pub struct CameraZoneSettings {
pub mode: CameraMode,
pub distance: Option<f32>,
pub pivot_offset: Option<Vec3>,
pub fov: Option<f32>,
pub fixed_yaw: Option<f32>,
pub fixed_pitch: Option<f32>,
pub follow_rotation: bool,
pub look_at_player: bool,
pub transition_speed: f32,
}
#[derive(Component, Debug, Reflect)]
#[reflect(Component)]
pub struct CameraWaypoint {
pub wait_time: f32,
pub movement_speed: Option<f32>,
pub rotation_speed: Option<f32>,
pub rotation_mode: WaypointRotationMode,
pub look_at_target: Option<Entity>,
}
#[derive(Component, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct CameraWaypointTrack {
pub waypoints: Vec<Entity>,
pub loop_track: bool,
}
#[derive(Component, Debug, Reflect)]
#[reflect(Component)]
pub struct CameraShakeInstance {
pub camera_entity: Option<Entity>,
pub name: String,
pub timer: f32,
pub duration: f32,
pub intensity: f32,
pub pos_amount: Vec3,
pub rot_amount: Vec3,
pub pos_speed: Vec3,
pub rot_speed: Vec3,
pub pos_smooth: f32,
pub rot_smooth: f32,
pub current_pos: Vec3,
pub current_rot: Vec3,
pub decrease_in_time: bool,
}
#[derive(Component, Debug, Reflect)]
#[reflect(Component)]
pub struct CameraBobState {
pub phase: f32,
pub current_pos_offset: Vec3,
pub current_rot_offset: Vec3,
pub idle: BobPreset,
pub walk: BobPreset,
pub sprint: BobPreset,
pub aim: BobPreset,
}
#[derive(Resource, Reflect, Clone)]
#[reflect(Resource)]
pub struct TransparencySettings {
pub enabled: bool,
pub alpha_target: f32,
pub fade_speed: f32,
pub ray_radius: f32,
}
#[derive(Resource, Reflect, Clone)]
#[reflect(Resource)]
pub struct PlayerCullingSettings {
pub enabled: bool,
pub min_dist: f32,
pub fade_speed: f32,
pub min_alpha: f32,
}
The default camera mode providing a behind-shoulder perspective:
Characteristics:
Configuration:
camera.mode = CameraMode::ThirdPerson;
camera.distance = 4.0;
camera.min_distance = 1.5;
camera.max_distance = 8.0;
camera.side_offset = 0.5;
Dynamic Behavior:
Eye-level perspective with limited vertical rotation:
Characteristics:
Configuration:
camera.mode = CameraMode::FirstPerson;
camera.rot_sensitivity_1p = 0.1;
camera.min_vertical_angle = -85.0;
camera.max_vertical_angle = 85.0;
camera.use_collision = false; // Optional
Special Considerations:
Fixed orientation camera that follows character movement:
Characteristics:
Configuration:
camera.mode = CameraMode::Locked;
camera.smooth_rotation_speed = 15.0;
camera.distance = 3.0;
2D-style camera with limited depth:
Characteristics:
Configuration:
camera.mode = CameraMode::SideScroller;
camera.distance = 6.0;
camera.min_vertical_angle = 0.0;
camera.max_vertical_angle = 0.0;
Overhead perspective with orthographic or perspective projection:
Characteristics:
Configuration:
camera.mode = CameraMode::TopDown;
camera.distance = 15.0;
camera.default_fov = 75.0;
camera.pivot_smooth_speed = 20.0;
The target locking system provides sophisticated enemy tracking:
Automatic target highlighting based on:
Scoring Algorithm:
let score = dist * 0.5 + angle * 2.0;
Configuration:
camera.target_lock.enabled = true;
camera.target_lock.max_distance = 30.0;
camera.target_lock.fov_threshold = 45.0;
camera.target_lock.scan_radius = 2.0;
Manual target acquisition with:
Locking Behavior:
Physics-based collision detection prevents camera clipping:
pub fn handle_camera_collision(
spatial_query: SpatialQuery,
mut query: Query<(&CameraController, &CameraState, &mut Transform)>,
) {
for (camera, state, mut transform) in query.iter_mut() {
if !camera.use_collision { continue; }
let start = state.current_pivot;
let direction = transform.back();
let max_dist = state.current_distance;
let filter = SpatialQueryFilter::default();
if let Some(hit) = spatial_query.cast_ray(start, direction, max_dist, true, &filter) {
transform.translation = start + direction * (hit.distance - camera.collision_radius);
}
}
}
Camera leans against walls to maintain visibility:
Leaning Process:
Configuration:
camera.lean_amount = 0.4;
camera.lean_angle = 15.0;
camera.lean_raycast_dist = 0.8;
camera.lean_wall_angle = 5.0;
Area-based camera behavior modification:
Uses spatial point intersections to detect player position within zones:
let intersections = spatial_query.point_intersections(player_pos, &filter);
Zones are evaluated by priority with the highest priority taking effect:
for &zone_ent in tracker.active_zones.iter() {
if let Ok(zone) = zone_query.get(zone_ent) {
if zone.priority > max_priority {
max_priority = zone.priority;
best_zone = Some(zone_ent);
}
}
}
Zone transitions use exponential smoothing:
let alpha = 1.0 - (-speed * dt).exp();
controller.distance = controller.distance + (target_dist - controller.distance) * alpha;
Complex camera movements through predefined paths:
Waypoint Configuration:
let waypoint = CameraWaypoint {
wait_time: 2.0,
movement_speed: Some(5.0),
rotation_speed: Some(10.0),
rotation_mode: WaypointRotationMode::LookAtTarget,
look_at_target: Some(player_entity),
};
Rotation Modes:
UseWaypointRotation: Use waypoint’s rotationFaceMovement: Look in direction of movementLookAtTarget: Focus on specific entityCamera cutscenes integrate with:
Procedural camera movement for impact feedback:
Shake Types:
Shake Parameters:
let shake = CameraShakeInstance {
duration: 0.5,
intensity: 1.0,
pos_amount: Vec3::new(0.05, 0.05, 0.05),
rot_amount: Vec3::new(2.0, 2.0, 2.0),
pos_speed: Vec3::new(15.0, 15.0, 15.0),
rot_speed: Vec3::new(15.0, 15.0, 15.0),
decrease_in_time: true,
};
Movement-based camera simulation:
Bob Presets:
Bob Calculation:
let target_pos = Vec3::new(
(t * preset.pos_speed.x).sin() * preset.pos_amount.x,
(t * preset.pos_speed.y).sin() * preset.pos_amount.y,
(t * preset.pos_speed.z).cos() * preset.pos_amount.z,
);
Dynamic FOV changes based on:
state.fov_override = Some(target_fov);
state.fov_override_speed = Some(speed);
let alpha = 1.0 - (-speed * time.delta_secs()).exp();
p.fov = p.fov + (target_rad - p.fov) * alpha;
The camera system integrates with the Input System for:
pub struct InputState {
pub look: Vec2, // Mouse input
pub aim_pressed: bool, // Aim state
pub switch_camera_mode_pressed: bool,
pub side_switch_pressed: bool,
pub lock_on_pressed: bool,
}
pub struct CharacterMovementState {
pub current_speed: f32,
pub is_sprinting: bool,
pub is_crouching: bool,
pub is_climbing: bool,
// ... additional states
}
// Spawn camera with player tracking
let camera_entity = commands.spawn((
Camera3d::default(),
CameraController {
follow_target: Some(player_entity),
mode: CameraMode::ThirdPerson,
distance: 4.0,
..default()
},
CameraState::default(),
CameraTargetState::default(),
CameraBobState::default(),
Transform::from_xyz(0.0, 5.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
)).id();
let custom_state = CameraStateInfo {
name: "Combat Mode".to_string(),
cam_position_offset: Vec3::new(0.0, 0.0, 0.0),
pivot_position_offset: Vec3::new(0.5, 1.5, 0.0),
initial_fov: 50.0,
max_fov: 80.0,
lean_enabled: false,
..default()
};
camera_controller.states.push(custom_state);
// Create a stealth zone with different camera behavior
commands.spawn((
Collider::cuboid(10.0, 5.0, 10.0),
Transform::from_xyz(0.0, 2.5, 0.0),
CameraZone {
settings: CameraZoneSettings {
mode: CameraMode::FirstPerson,
distance: Some(1.0),
fov: Some(70.0),
pivot_offset: Some(Vec3::new(0.0, 1.8, 0.0)),
transition_speed: 3.0,
..default()
},
priority: 10,
},
));
// Create waypoints for a cutscene
let mut waypoints = Vec::new();
// Waypoint 1: Wide shot
let wp1 = commands.spawn((
Transform::from_xyz(10.0, 8.0, 10.0),
CameraWaypoint {
wait_time: 0.0,
movement_speed: Some(2.0),
rotation_speed: Some(5.0),
rotation_mode: WaypointRotationMode::LookAtTarget,
look_at_target: Some(player_entity),
..default()
},
)).id();
waypoints.push(wp1);
// Waypoint 2: Close-up
let wp2 = commands.spawn((
Transform::from_xyz(2.0, 2.0, 2.0),
CameraWaypoint {
wait_time: 3.0,
movement_speed: Some(1.0),
rotation_speed: Some(8.0),
rotation_mode: WaypointRotationMode::LookAtTarget,
look_at_target: Some(player_entity),
..default()
},
)).id();
waypoints.push(wp2);
// Create the track
commands.spawn((
CameraWaypointTrack {
waypoints,
loop_track: false,
},
CameraWaypointFollower {
current_track: Some(track_entity),
current_waypoint_index: 0,
waiting_timer: 0.0,
is_moving: false,
},
));
// Queue a shake for an explosion
let shake_request = ShakeRequest {
name: "Explosion".to_string(),
intensity: 1.5,
duration: Some(0.8),
};
commands.insert_resource(ShakeQueue(vec![shake_request]));
// Create a persistent shake source
commands.spawn((
Transform::from_xyz(explosion_x, explosion_y, explosion_z),
PointShake {
name: "Grenade Explosion".to_string(),
radius: 15.0,
intensity: 2.0,
active: true,
shake_using_distance: true,
},
));
// Programmatic target locking
let mut camera_target_state = camera_target_state_query.get_mut(camera_entity).unwrap();
if let Some(target_entity) = find_target_in_crosshair() {
camera_target_state.locked_target = Some(target_entity);
camera_target_state.is_locking = true;
}
// Listen for target lock events
fn on_target_locked(mut event_reader: EventReader<TargetLockEvent>) {
for event in event_reader.read() {
info!("Locked onto target: {:?}", event.target_entity);
// Trigger UI updates, audio cues, etc.
}
}
// Scope aiming effect
let mut camera_state = camera_state_query.get_mut(camera_entity).unwrap();
camera_state.fov_override = Some(25.0); // Sniper scope
camera_state.fov_override_speed = Some(15.0);
// Enable camera transparency for nearby objects
commands.insert_resource(TransparencySettings {
enabled: true,
alpha_target: 0.3,
fade_speed: 8.0,
ray_radius: 0.3,
});
// Efficient spatial query usage
let filter = SpatialQueryFilter::from_excluded_entities([player_entity]);
if let Some(hit) = spatial_query.cast_ray(start, direction, max_dist, true, &filter) {
// Process collision
}
// Optimized query pattern
pub fn optimized_camera_update(
time: Res<Time>,
mut camera_query: Query<(&CameraController, &mut CameraState), With<MainCamera>>,
// ... other filtered queries
) {
// System implementation
}
Organize camera behavior into logical state modules:
pub struct CameraStateModule {
pub name: String,
pub enter_fn: fn(&mut CameraController),
pub update_fn: fn(&mut CameraController, &CameraState, f32),
pub exit_fn: fn(&mut CameraController),
}
Keep camera concerns separate:
Always use smooth interpolation for camera movements:
let alpha = 1.0 - (-speed * delta_time).exp();
current_value = current_value + (target_value - current_value) * alpha;
Maintain camera state across scene transitions:
// Save camera state before level change
let saved_state = camera_controller.serialize();
// Restore after level load
camera_controller.deserialize(saved_state);
Implement input buffering for responsive camera controls:
pub struct CameraInputBuffer {
pub look_input: Vec2,
pub buffered_look: Vec2,
pub input_buffer_time: f32,
}
Scale sensitivity appropriately for different frame rates:
let frame_rate_multiplier = 60.0 / actual_frame_rate;
let scaled_sensitivity = base_sensitivity * frame_rate_multiplier;
// Optimized collision checking
let collision_mask = CollisionGroups::new(
Group::GROUP_1, // Camera collision group
Group::GROUP_2 | Group::GROUP_3, // Environment collision groups
);
Implement efficient transparency sorting:
pub struct TransparencyManager {
pub sorted_surfaces: Vec<TransparentSurface>,
pub update_frequency: f32,
pub last_update_time: f32,
}
Pool frequently created/destroyed camera components:
pub struct CameraComponentPool {
pub shake_instances: Vec<CameraShakeInstance>,
pub target_states: Vec<CameraTargetState>,
pub available_entities: Vec<Entity>,
}
Properly manage camera resources:
impl Drop for CameraEffectManager {
fn drop(&mut self) {
// Cleanup effects, audio, resources
self.cleanup_active_effects();
self.stop_all_audio();
}
}
Implement debug visualization for camera state:
#[cfg(debug_assertions)]
pub fn debug_camera_state(
gizmos: Gizmos,
camera_query: Query<(&CameraController, &CameraState, &Transform)>,
) {
for (controller, state, transform) in camera_query.iter() {
// Draw pivot point
gizmos.circle(state.current_pivot, 0.2, Color::YELLOW);
// Draw camera frustum
draw_camera_frustum(gizmos, transform, state.current_distance);
// Draw target lock
if let Some(target) = state.locked_target {
draw_target_lock(gizmos, target, Color::RED);
}
}
}
Implement comprehensive logging:
pub struct CameraMetrics {
pub frame_time: f32,
pub spatial_queries_per_frame: u32,
pub collision_checks_per_frame: u32,
pub active_zones: u32,
pub active_effects: u32,
}
Symptoms:
Possible Causes:
Solutions:
// Increase smoothing speeds
camera.smooth_follow_speed = 20.0;
camera.smooth_rotation_speed = 25.0;
camera.pivot_smooth_speed = 15.0;
// Check system ordering
.add_systems(Update, (
update_camera_state_offsets,
update_target_marking,
update_target_lock,
).chain())
// Optimize spatial queries
let filter = SpatialQueryFilter::from_excluded_entities([player_entity]);
Symptoms:
Possible Causes:
Solutions:
// Adjust collision settings
camera.use_collision = true;
camera.collision_radius = 0.3;
// Verify spatial query setup
let direction = transform.back(); // Ensure correct direction
let max_dist = state.current_distance;
// Check collision layers
let filter = SpatialQueryFilter::default();
// Ensure proper collision groups
Symptoms:
Possible Causes:
Solutions:
// Adjust target lock settings
camera.target_lock.max_distance = 50.0;
camera.target_lock.fov_threshold = 60.0;
camera.target_lock.scan_radius = 3.0;
// Verify target components
for (entity, target_gt, health, name) in target_query.iter() {
if health.current <= 0.0 { continue; } // Ensure health > 0
// Check other required components
}
Symptoms:
Possible Causes:
Solutions:
// Increase FOV transition speed
camera.fov_speed = 15.0;
// Clear existing overrides
state.fov_override = None;
// Ensure projection component exists
if let Projection::Perspective(ref mut p) = *projection {
// FOV updates should work correctly
}
Symptoms:
Possible Causes:
Solutions:
// Check zone collision setup
commands.spawn((
Collider::cuboid(10.0, 5.0, 10.0), // Ensure proper collider
CameraZone { /* ... */ },
));
// Adjust priority and transition speed
CameraZone {
priority: 10, // Higher priority
settings: CameraZoneSettings {
transition_speed: 10.0, // Faster transitions
// ...
}
}
Symptoms:
Possible Causes:
Solutions:
// Optimize queries
Query<(&CameraController, &mut CameraState), With<MainCamera>>
// Use query filtering
Query<&Transform, (With<CameraController>, Without<Player>)>
// Implement query caching
fn system_with_cached_query(
cached_transforms: QueryCache<(&Transform, &CameraController)>,
// ...
) {
// Use cached results
}
Enable Debug Visualization:
#[cfg(debug_assertions)]
fn debug_camera_systems(
mut commands: Commands,
camera_query: Query<(&CameraController, &CameraState)>,
) {
for (controller, state) in camera_query.iter() {
println!("Camera Mode: {:?}", controller.mode);
println!("Distance: {}", state.current_distance);
println!("Pivot: {:?}", state.current_pivot);
println!("Yaw: {}, Pitch: {}", state.yaw, state.pitch);
}
}
Visualize Spatial Queries:
fn debug_spatial_queries(
spatial_query: SpatialQuery,
gizmos: Gizmos,
camera_query: Query<(&CameraState, &Transform)>,
) {
for (state, transform) in camera_query.iter() {
// Draw collision ray
let direction = transform.back();
gizmos.ray(
state.current_pivot,
direction * state.current_distance,
Color::RED,
);
}
}
Frame Timing Analysis:
struct CameraProfiler {
last_frame_time: Instant,
frame_times: Vec<f32>,
}
fn profile_camera_performance(
mut profiler: ResMut<CameraProfiler>,
time: Res<Time>,
) {
let frame_time = time.delta_secs();
profiler.frame_times.push(frame_time);
if profiler.frame_times.len() > 100 {
let avg_frame_time: f32 = profiler.frame_times.iter().sum::<f32>() / 100.0;
println!("Average camera frame time: {:.3}ms", avg_frame_time * 1000.0);
profiler.frame_times.clear();
}
}
Common Problems:
Debug Steps:
fn debug_input_integration(
input: Res<InputState>,
mut camera_query: Query<&mut CameraController>,
) {
println!("Look input: {:?}", input.look);
println!("Aim pressed: {}", input.aim_pressed);
for mut controller in camera_query.iter_mut() {
println!("3P Sensitivity: {}", controller.rot_sensitivity_3p);
println!("1P Sensitivity: {}", controller.rot_sensitivity_1p);
}
}
Common Problems:
Debug Steps:
fn debug_physics_integration(
spatial_query: SpatialQuery,
gizmos: Gizmos,
camera_query: Query<(&CameraState, &Transform)>,
) {
for (state, transform) in camera_query.iter() {
// Visualize raycast
let ray_origin = state.current_pivot;
let ray_direction = transform.back();
let ray_length = state.current_distance;
gizmos.ray(ray_origin, ray_direction * ray_length, Color::BLUE);
// Test actual collision
if let Some(hit) = spatial_query.cast_ray(
ray_origin,
ray_direction,
ray_length,
true,
&SpatialQueryFilter::default()
) {
gizmos.circle(hit.point, 0.1, Color::GREEN);
}
}
}
Detection:
Investigation:
fn debug_memory_usage(
entities: Res<Entities>,
camera_shakes: Query<&CameraShakeInstance>,
) {
let active_shakes = camera_shakes.iter().count();
let total_entities = entities.iter().count();
println!("Active camera shakes: {}", active_shakes);
println!("Total entities: {}", total_entities);
if active_shakes > 100 {
warn!("High number of active camera shakes!");
}
}
Detection:
Investigation:
fn debug_system_conflicts(
system_order: Res<SystemExecutionOrder>,
) {
for (system_name, order) in system_order.iter() {
println!("System '{}' runs at order {}", system_name, order);
}
}
// Check for conflicting updates
fn check_component_updates(
camera_query: Query<&CameraController>,
previous_states: Query<&CameraController>,
) {
// Compare states to detect unexpected changes
}
Implementation:
struct CameraPerformanceMetrics {
spatial_queries_per_frame: HashMap<String, u32>,
component_updates_per_frame: HashMap<String, u32>,
memory_usage_per_frame: f32,
}
fn collect_performance_metrics(
mut metrics: ResMut<CameraPerformanceMetrics>,
spatial_queries: Query<(&SpatialQuery, &CameraController)>,
) {
metrics.spatial_queries_per_frame.insert(
"spatial_query_count".to_string(),
spatial_queries.iter().count() as u32,
);
}
Data Collection:
struct CameraMetricsHistory {
frame_time_history: Vec<f32>,
spatial_query_history: Vec<u32>,
max_history_size: usize,
}
fn update_metrics_history(
mut history: ResMut<CameraMetricsHistory>,
current_metrics: &CameraPerformanceMetrics,
) {
history.frame_time_history.push(current_metrics.avg_frame_time);
history.spatial_query_history.push(current_metrics.spatial_queries);
if history.frame_time_history.len() > history.max_history_size {
history.frame_time_history.remove(0);
}
}
The Camera System provides a comprehensive, modular solution for 3D game camera management in Bevy. With its extensive feature set, performance optimizations, and integration capabilities, it serves as a robust foundation for various game genres and camera requirements.
The system is designed for extensibility, with potential future additions including:
The Camera System documentation provides the foundation for implementing sophisticated camera behaviors in your Bevy-based game projects, with the flexibility to adapt to diverse gameplay requirements and performance constraints.
This documentation covers the Camera System as of the latest version. For updates and additional resources, refer to the main project repository and related documentation.