//! Runtime game crate: FPS player, rendering, and starter world. #![allow(clippy::type_complexity)] pub mod editor_ext; pub mod launch; pub mod player { //! Player types re-exported from the stable sim crate. pub use sim::{CameraSensitivity, PlayerCamera}; } pub mod rendering { //! Rendering helpers re-exported from the hot-reloadable implementation. pub use game_hot::{ apply_camera_render_profile, apply_project_camera_fx, camera_auto_exposure_active, clear_viewport_camera_stack, effective_gi_path_for_camera, effective_viewport_gi_path, effective_viewport_render_stack, has_local_shadow_lights, hdr_enabled_profile, patch_camera_effects, resolve_viewport_camera_owner, strip_project_camera_fx, sync_optional_rendering_fx, sync_viewport_camera_stack, world_has_local_shadow_lights, FullscreenEffectsPlugin, SolariRaytracingSceneStats, SolariRenderingPlugin, ViewportCameraOwner, ViewportFxSnapshot, ViewportStackApply, HDR_ENV_VAR, }; } use bevy::prelude::*; use settings::ProjectSettingsPlugin; use sim::SimPlugin; pub use sim::{GameInputEnabled, GameInputFocused, SimEnabled}; /// When set, the in-process editor owns [`apply_project_camera_fx`] on a single camera. #[derive(Resource, Debug, Clone, Copy, Default)] pub struct GameRenderBootstrap { pub defer_player_camera_fx: bool, } #[cfg(not(feature = "hot-reload"))] mod systems { pub use game_hot::{ apply_ambient_light, apply_player_intent, grab_cursor, move_and_slide, setup_project_camera_effects, spawn_level, spawn_player, spawn_sun, sync_player_physics_position, sync_project_sun_visibility, sync_scene_directional_shadow_cascades, tag_player_camera, toggle_cursor_grab, update_camera_pitch, update_eye_height, update_local_player_intent, }; } #[cfg(feature = "hot-reload")] mod systems { pub use crate::hot::{ apply_ambient_light, apply_player_intent, grab_cursor, move_and_slide, setup_project_camera_effects, spawn_level, spawn_player, spawn_sun, sync_player_physics_position, sync_project_sun_visibility, sync_scene_directional_shadow_cascades, tag_player_camera, toggle_cursor_grab, update_camera_pitch, update_eye_height, update_local_player_intent, }; } #[cfg(feature = "hot-reload")] #[hot_lib_reloader::hot_module(dylib = "game_hot", lib_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/debug"))] mod hot { use avian3d::prelude::*; use bevy::input::mouse::AccumulatedMouseMotion; use bevy::light::CascadeShadowConfig; use bevy::pbr::ScatteringMedium; use bevy::prelude::*; use bevy::time::Fixed; use bevy::window::{CursorOptions, PrimaryWindow}; use game_hot::SolariRaytracingSceneStats; use protocol::PlayerInputIntent; use settings::{ ActiveCameraRenderProfile, ProjectRenderCamera, ProjectSettings, RenderingCapabilities, SimTuning, }; use shared::{InspectorOrder, LevelObject, LightDesc, ProjectSun}; use sim::{ CameraSensitivity, Crouching, GameInputEnabled, GameInputFocused, Grounded, JumpState, Player, PlayerCamera, PlayerVelocity, PlayerYawSensitivity, }; hot_functions_from_file!("crates/game_hot/src/sim_systems.rs"); // move_and_slide, sync_player_physics_position, apply_player_intent hot_functions_from_file!("crates/game_hot/src/player/spawn.rs"); hot_functions_from_file!("crates/game_hot/src/player/input.rs"); hot_functions_from_file!("crates/game_hot/src/player/camera.rs"); hot_functions_from_file!("crates/game_hot/src/rendering/camera_fx.rs"); hot_functions_from_file!("crates/game_hot/src/world/mod.rs"); hot_functions_from_file!("crates/game_hot/src/world/level.rs"); hot_functions_from_file!("crates/game_hot/src/world/lighting.rs"); #[lib_change_subscription] pub fn subscribe() -> hot_lib_reloader::LibReloadObserver {} } #[cfg(feature = "hot-reload")] pub fn hot_reload_subscribe() -> hot_lib_reloader::LibReloadObserver { hot::subscribe() } fn sim_enabled(enabled: Res) -> bool { enabled.0 } fn game_input_enabled(enabled: Res) -> bool { enabled.0 } fn register_game_systems(app: &mut App, defer_player_camera_fx: bool) { app.add_systems( FixedUpdate, ( systems::apply_player_intent, systems::move_and_slide, systems::sync_player_physics_position, ) .chain() .run_if(sim_enabled), ) .add_systems(Startup, systems::spawn_player) .add_systems( Startup, systems::grab_cursor .after(systems::spawn_player) .run_if(game_input_enabled), ) .add_systems( Update, ( systems::toggle_cursor_grab, systems::update_local_player_intent, systems::update_camera_pitch, systems::update_eye_height, ) .chain() .run_if(game_input_enabled), ) .add_systems( Startup, ( systems::apply_ambient_light, systems::spawn_sun, systems::spawn_level, ) .chain(), ); app.add_systems( Update, ( systems::sync_project_sun_visibility, systems::sync_scene_directional_shadow_cascades, ), ); if !defer_player_camera_fx { app.add_systems( PostStartup, ( systems::tag_player_camera, systems::setup_project_camera_effects, ) .chain(), ); } } /// Runtime game systems and content, without Bevy's platform/window plugins. pub struct GamePlugin; impl Plugin for GamePlugin { fn build(&self, app: &mut App) { let defer_player_camera_fx = app .world() .get_resource::() .is_some_and(|bootstrap| bootstrap.defer_player_camera_fx); app.register_type::() .init_resource::() .init_resource::() .add_plugins((ProjectSettingsPlugin, SimPlugin, bevy_ufbx::FbxPlugin)) .add_plugins(game_hot::SolariRenderingPlugin) .add_plugins(game_hot::AutoExposureRenderingPlugin) .add_plugins(game_hot::FullscreenEffectsPlugin) .insert_resource(ClearColor(Color::srgb(0.0, 0.0, 0.0))); register_game_systems(app, defer_player_camera_fx); } } /// Called by the editor after swapping the hot dylib. pub fn on_hot_reload(world: &mut World) { game_hot::on_hot_reload(world); }