mod camera; mod config; mod content; mod input; mod proc_gen; mod renderer; mod sim; mod sprite_loader; mod vfx; use futures::executor; use glam::{IVec2, Vec2}; use std::{collections::VecDeque, sync::Arc, time::Instant}; use winit::{ application::ApplicationHandler, event::WindowEvent::{self}, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, window::Window, }; use crate::{ camera::{Camera, CameraController}, config::{CHUNK_SIZE, WINDOW_TITLE}, content::{ entities::{ entity_bullet_emitter::entity_bullet_emitter_def, entity_cube::entity_cube_def, entity_grenade::entity_grenade_def, entity_tnt::entity_tnt_def, entity_wizard::entity_wizard_def, }, materials::MaterialId, world::mines::MinesBiome, }, input::{ Input::{self}, InputManager, }, proc_gen::TilesetWorldGenerator, renderer::RendererState, sim::{entity::EntityId, rb_manager::DebugRenderMode, sim_manager::SimManager}, vfx::VfxManager, }; pub type Error = Box; pub type Result = std::result::Result; struct Config { brush_radius: f32, brush_material: MaterialId, dropper_material: MaterialId, use_threading: bool, debug_render: bool, debug_render_mode: DebugRenderMode, // proc gen // world_generator_config: WorldGeneratorConfig, } struct Diagnostics { frame_times: VecDeque, fps: f32, } struct App { // core window: Option>, renderer_state: Option, // game player: Option, input_manager: InputManager, camera_controller: Option, vfx_manager: VfxManager, sim_manager: SimManager, world_generator: TilesetWorldGenerator, // renderer last_render: Instant, // state config: Config, diagnostics: Diagnostics, } impl App { fn game_setup(&mut self) { for cx in -5..5 { for cy in -5..5 { // skip the spawn chunk if !(cx == 0 && cy == 0) { let chunk = self.world_generator.generate_chunk(IVec2::new(cx, cy)); self.sim_manager.cell_manager.upsert(cx, cy, chunk); } } } self.player = Some(self.sim_manager.create_entity(entity_wizard_def( // offset so the player is centred in the spawn chunk Vec2::ZERO + Vec2::splat(CHUNK_SIZE as f32) / 2.0, ))); self.camera_controller .as_mut() .unwrap() .track_entity(self.player.unwrap()); } // called as often as possible // delta time is the real seconds elapsed since the last time this was called fn update(&mut self, delta_time: f32) { // --TEST SPAWNING-- if self.input_manager.pressed(Input::Action1) { self.sim_manager.create_entity(entity_cube_def( self.input_manager.world_mouse_pos, self.config.dropper_material, )); } if self.input_manager.pressed(Input::Grenade) { self.sim_manager .create_entity(entity_grenade_def(self.input_manager.world_mouse_pos, 3.0)); } if self.input_manager.pressed(Input::Tnt) { self.sim_manager .create_entity(entity_tnt_def(self.input_manager.world_mouse_pos)); } if self.input_manager.pressed(Input::Action5) { self.sim_manager .create_entity(entity_wizard_def(self.input_manager.world_mouse_pos)); } if self.input_manager.pressed(Input::Action3) { self.sim_manager.create_entity(entity_bullet_emitter_def( self.input_manager.world_mouse_pos, 3.0, )); } self.sim_manager.update( &mut self.vfx_manager, &self.config, &self.input_manager, delta_time, ); // update the camera after the physics so that if e.g., the player moved, we don't have jitter on the render if let Some(camera_controller) = &mut self.camera_controller { camera_controller.update(&self.input_manager, &self.sim_manager, delta_time); } self.input_manager.reset_for_frame(); } } impl Default for App { fn default() -> Self { let config = Config { use_threading: true, brush_radius: 10.0, brush_material: MaterialId::Sand, dropper_material: MaterialId::Wood, debug_render: false, debug_render_mode: DebugRenderMode::default(), }; Self { window: None, renderer_state: None, player: None, input_manager: InputManager::new(), camera_controller: None, vfx_manager: VfxManager::new(), sim_manager: SimManager::new(), world_generator: TilesetWorldGenerator::new( "assets/tilesets/tileset1", Box::new(MinesBiome::new()), ), last_render: Instant::now(), config, diagnostics: Diagnostics { fps: 0.0, frame_times: VecDeque::new(), }, } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window = Arc::new( event_loop .create_window( Window::default_attributes() .with_title(WINDOW_TITLE) .with_maximized(true), ) .unwrap(), ); self.window = Some(window.clone()); self.renderer_state = Some(executor::block_on(RendererState::new(window.clone()))); let size = window.inner_size(); self.camera_controller = Some(CameraController::new( IVec2::new(size.width as i32, size.height as i32), camera::CameraControllerMode::Free, )); self.game_setup(); } fn window_event( &mut self, event_loop: &ActiveEventLoop, _: winit::window::WindowId, event: WindowEvent, ) { if let Some(renderer_state) = &mut self.renderer_state && let Some(window) = &mut self.window { let egui_response = renderer_state.egui_state.on_window_event(window, &event); // if egui consumed the event, it means we shouldn't treat any e.g., mouse clicks if egui_response.consumed { return; } } if let Some(camera_controller) = &self.camera_controller { self.input_manager .apply_event(&event, camera_controller.camera()); } match event { WindowEvent::Resized(size) => { if let Some(renderer_state) = &mut self.renderer_state && let Some(camera_controller) = &mut self.camera_controller { renderer_state.resize(size.width, size.height); camera_controller .camera_mut() .resize(IVec2::new(size.width as i32, size.height as i32)); } } WindowEvent::CloseRequested => { event_loop.exit(); } WindowEvent::RedrawRequested => { #[cfg(feature = "profiler")] puffin::GlobalProfiler::lock().new_frame(); puffin::profile_scope!("redraw_requested"); // compute FPS diagnostics let now = Instant::now(); let delta_time = (now - self.last_render).as_secs_f32(); self.last_render = now; self.diagnostics.frame_times.push_back(delta_time); if self.diagnostics.frame_times.len() > 30 { self.diagnostics.frame_times.pop_front(); } let average_frame_time = self.diagnostics.frame_times.iter().sum::() / self.diagnostics.frame_times.len() as f32; self.diagnostics.fps = 1.0 / average_frame_time; self.update(delta_time); if let Some(renderer_state) = &mut self.renderer_state && let Some(camera_controller) = &mut self.camera_controller { renderer_state.render( &mut self.sim_manager, &self.vfx_manager, camera_controller.camera_mut(), &mut self.config, &self.diagnostics, &self.input_manager, &self.world_generator, ); self.vfx_manager.after_render(delta_time); } } _ => {} } } fn about_to_wait(&mut self, _: &ActiveEventLoop) { if let Some(window) = &self.window { window.request_redraw(); } else { panic!("No window!") } } } #[cfg(feature = "profiler")] fn start_profiler() { let _server = puffin_http::Server::new("127.0.0.1:8585").unwrap(); puffin::set_scopes_on(true); std::mem::forget(_server); // keep serving for the process lifetime std::process::Command::new("puffin_viewer") .args(["--url", "127.0.0.1:8585"]) .spawn() .ok(); // don't die if it isn't installed } fn main() -> Result<()> { #[cfg(feature = "profiler")] start_profiler(); let event_loop = EventLoop::new()?; event_loop.set_control_flow(ControlFlow::Poll); let mut app = App::default(); event_loop.run_app(&mut app)?; Ok(()) }