const GAME_SIZE: (u32, u32) = (500, 800); use std::{ collections::HashMap, num::NonZeroU32, process, rc::Rc, time::{Duration, Instant}, }; use softbuffer::Buffer; use winit::{ dpi::PhysicalSize, event::{Event, KeyEvent, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, keyboard::{Key, NamedKey}, raw_window_handle::{HasDisplayHandle, HasWindowHandle}, window::WindowBuilder, }; use self::objs::{MovingObject, World}; type RenderFn = fn(&mut render::RenderCtx<'_, '_>, &mut World, &GameTimer); // core game engine struct pub struct Engine { event_loop: EventLoop<()>, render_fn: RenderFn, world: World, } impl Engine { pub fn new() -> Self { let event_loop = EventLoop::new().unwrap(); Self { event_loop, render_fn: |_, _, _| {}, world: World::new(), } } pub fn insert_into_world(&mut self, obj: Rc) -> usize { self.world.insert(obj) } // sets the render function for the game pub fn set_render_fn(mut self, f: RenderFn) -> Self { self.render_fn = f; self } // runs the game and consumes self, this will finish the process pub fn run(self) -> ! { let Self { event_loop, mut world, render_fn, } = self; // set up window let window = WindowBuilder::new() .with_inner_size(winit::dpi::Size::Physical(PhysicalSize::new(1200, 700))) .build(&event_loop) .unwrap(); // set up softbuffer let context = softbuffer::Context::new(window.display_handle().unwrap()).unwrap(); let mut surface = softbuffer::Surface::new(&context, window.window_handle().unwrap()).unwrap(); let mut timer = GameTimer::new(); event_loop .run(|event, elwt| { // shoddy vsync elwt.set_control_flow(ControlFlow::WaitUntil( Instant::now() + Duration::from_millis(1000 / 60), )); match event { // redraw Event::NewEvents(StartCause::ResumeTimeReached { .. }) => { window.request_redraw(); } Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested, } => { if window_id == window.id() { if let (Some(width), Some(height)) = { let size = window.inner_size(); (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) } { surface.resize(width, height).unwrap(); let mut buffer = surface.buffer_mut().unwrap(); let mut ctx = render::RenderCtx::new(buffer, (width, height), GAME_SIZE); // render render_fn(&mut ctx, &mut world, &timer); ctx.force_present(); timer.frame_update(); } } } // crash game to exit lol Event::WindowEvent { window_id: _window_id, event: WindowEvent::CloseRequested | WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Named(NamedKey::Escape), .. }, .. }, } => todo!(), // potential future keyboard handling Event::WindowEvent { window_id: _window_id, event: WindowEvent::KeyboardInput { device_id: _device_id, event: _event, is_synthetic: _is_synthetic, }, } => {} _ => {} } }) .unwrap(); process::exit(0); } } pub struct GameTimer { game_start: Instant, last_frame: Instant, stopwatches: HashMap, } impl GameTimer { pub fn new() -> Self { Self { game_start: Instant::now(), last_frame: Instant::now(), stopwatches: HashMap::new(), } } pub fn frame_update(&mut self) { self.last_frame = Instant::now() } pub fn delta_t(&self) -> f32 { Instant::now().duration_since(self.last_frame).as_secs_f32() } pub fn game_time_passed(&self) -> f32 { Instant::now().duration_since(self.game_start).as_secs_f32() } pub fn start_stopwatch(&mut self, name: impl ToString) { let _ = self.stopwatches.insert(name.to_string(), Instant::now()); } pub fn get_stopwatch_time(&self, name: &str) -> Option { self.stopwatches .get(name) .map(|start_time| Instant::now().duration_since(*start_time).as_secs_f32()) } pub fn delete_stopwatch(&mut self, name: &str) { let _ = self.stopwatches.remove(name); } } mod render; pub mod objs { use std::rc::Rc; use super::render::RenderCtx; pub struct World { objects: Vec>, } impl World { pub fn new() -> Self { Self { objects: Vec::new(), } } pub fn insert(&mut self, obj: Rc) -> usize { self.objects.push(obj); self.objects.len() - 1 } pub fn get(&self, i: usize) -> Rc { self.objects[i].clone() } pub fn get_mut(&mut self, i: usize) -> &mut Rc { &mut self.objects[i] } pub fn draw_all(&self, ctx: &mut RenderCtx<'_, '_>) { self.objects.iter().for_each(|obj| obj.display(ctx)) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Rect { pos: Position, size: Size, } impl Rect { pub fn new(x: u32, y: u32, height: u32, width: u32) -> Self { Self { pos: Position::new(x, y), size: Size::new(width, height), } } pub fn square(x: u32, y: u32, size: u32) -> Self { Self { pos: Position::new(x, y), size: Size::new(size, size), } } } impl Object for Rect { fn position(&self) -> Position { self.pos } fn size(&self) -> Size { self.size } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct MovingRect { prev_pos: Position, current: Rect, } impl MovingRect { pub fn new(x: u32, y: u32, height: u32, width: u32) -> Self { Self { prev_pos: Position::new(x, y), current: Rect::new(x, y, height, width), } } pub fn square(x: u32, y: u32, size: u32) -> Self { Self { prev_pos: Position::new(x, y), current: Rect::square(x, y, size), } } } impl Object for MovingRect { fn position(&self) -> Position { self.current.position() } fn size(&self) -> Size { self.current.size() } } impl MovingObject for MovingRect { fn previous_pos(&self) -> Position { self.prev_pos } fn update_pos(&mut self, x: u32, y: u32) { self.prev_pos = self.current.pos; self.current.pos = Position::new(x, y); } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Position { x: u32, y: u32, } impl Position { pub fn new(x: u32, y: u32) -> Self { Self { x, y } } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Size { width: u32, height: u32, } impl Size { pub fn new(width: u32, height: u32) -> Self { Self { width, height } } } pub trait Object { fn position(&self) -> Position; fn size(&self) -> Size; fn display(&self, ctx: &mut RenderCtx<'_, '_>) { let Position { x, y } = self.position(); let Size { width, height } = self.size(); ctx.rect(x, y, width, height, 0xffffff); } } pub trait MovingObject: Object { fn previous_pos(&self) -> Position; fn update_pos(&mut self, x: u32, y: u32); fn move_obj(&mut self, x: i32, y: i32) { let Position { x: cur_x, y: cur_y } = self.position(); self.update_pos( cur_x.saturating_add_signed(x), cur_y.saturating_add_signed(y), ) } // TODO: damage fn draw_move(&self, ctx: &mut RenderCtx<'_, '_>) { let Position { x: prev_x, y: prev_y, } = self.previous_pos(); let Size { width, height } = self.size(); ctx.rect(prev_x, prev_y, width, height, 0x000000); self.display(ctx); } } }