From b495dbbdcd6664eec2a9868b70ef46945324747b Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Thu, 22 Feb 2024 13:01:00 +0100 Subject: [PATCH] basic rectangle drawing support added --- src/main.rs | 567 ++++++++++++++-------------------------------------- 1 file changed, 150 insertions(+), 417 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2360ec0..a884bfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,448 +1,181 @@ -use std::{ - num::NonZeroU32, - time::{Duration, Instant}, -}; - -use game::GameState; -use winit::{ - dpi::PhysicalSize, - event::{Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::{Key, NamedKey}, - raw_window_handle::{HasDisplayHandle, HasWindowHandle}, - window::WindowBuilder, -}; +use engine::Engine; fn main() { - let event_loop = EventLoop::new().unwrap(); - let window = WindowBuilder::new() - .with_inner_size(winit::dpi::Size::Physical(PhysicalSize::new(1200, 700))) - .build(&event_loop) - .unwrap(); - - // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't - // dispatched any events. This is ideal for games and similar applications. - event_loop.set_control_flow(ControlFlow::Poll); - - // ControlFlow::Wait pauses the event loop if no events are available to process. - // This is ideal for non-game applications that only update in response to user - // input, and uses significantly less power/CPU time than ControlFlow::Poll. - event_loop.set_control_flow(ControlFlow::Wait); - - let context = softbuffer::Context::new(window.display_handle().unwrap()).unwrap(); - let mut surface = softbuffer::Surface::new(&context, window.window_handle().unwrap()).unwrap(); - - let mut game = GameState::init(); - - event_loop - .run(|event, elwt| { - elwt.set_control_flow(ControlFlow::WaitUntil( - Instant::now() + Duration::from_millis(1000 / 60), - )); - - match event { - 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(); - // for y in 0..height.get() { - // for x in 0..width.get() { - // let red = x % 255; - // let green = y % 255; - // let blue = (x * y) % 255; - // let index = y as usize * width.get() as usize + x as usize; - // buffer[index] = blue | (green << 8) | (red << 16); - // } - // } - - buffer.fill(0); - game.update(); - game.render(&mut buffer, (width, height)); - - buffer.present().unwrap(); - } - } - Event::WindowEvent { - event: - WindowEvent::CloseRequested - | WindowEvent::KeyboardInput { - event: - KeyEvent { - logical_key: Key::Named(NamedKey::Escape), - .. - }, - .. - }, - window_id, - } if window_id == window.id() => { - elwt.exit(); - } - _ => {} - } + Engine::new() + .set_render_fn(|mut ctx| { + // draw three colored rectagles(tm) + ctx.rect_unchecked(0, 0, 100, 100, 0xff0000); + ctx.rect_unchecked(100, 0, 100, 100, 0x00ff00); + ctx.rect_unchecked(0, 100, 100, 100, 0x0000ff); }) - .unwrap(); + .run(); } -mod game { - use std::{convert::identity, num::NonZeroU32, time::Instant}; +mod engine { + const GAME_SIZE: (u32, u32) = (500, 800); + use std::{ + num::NonZeroU32, + process, + time::{Duration, Instant}, + }; use softbuffer::Buffer; - use winit::raw_window_handle::{DisplayHandle, WindowHandle}; + use winit::{ + dpi::PhysicalSize, + event::{Event, KeyEvent, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, NamedKey}, + raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle, WindowHandle}, + window::WindowBuilder, + }; - const PADDLE_SIZE: (u32, u32) = (5, 50); - const BALL_RADIUS: u32 = 10; - const BALL_VEC_RIGHT_DOWN: (i16, i16) = (8, 12); - const FIELD_SIZE: (u32, u32) = (1200, 700); - const FIELD_BORDER_WIDTH: u32 = 5; - const TICK_LEN: f32 = 1000. / 60.; - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - enum BallDirection { - RightUp, - RightDown, - LeftUp, - LeftDown, + type RenderFn = fn(&mut RenderCtx<'_, '_>); + // core game engine struct + pub struct Engine { + event_loop: EventLoop<()>, + render_fn: RenderFn, } - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - enum HorizontalCollisionCheck { - None, - Left, - Right, - } + impl Engine { + pub fn new() -> Self { + let event_loop = EventLoop::new().unwrap(); - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - enum VerticalCollisionCheck { - None, - Top, - Bottom, - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - struct CollisionCheck(HorizontalCollisionCheck, VerticalCollisionCheck); - - impl CollisionCheck { - fn check_field(obj_pos: (u32, u32), obj_size: (u32, u32)) -> Self { - let horizontal = if obj_pos.0 == 0 { - HorizontalCollisionCheck::Left - } else if (obj_pos.0 + obj_size.0) >= FIELD_SIZE.0 { - HorizontalCollisionCheck::Right - } else { - HorizontalCollisionCheck::None - }; - let vertical = if obj_pos.1 == 0 { - VerticalCollisionCheck::Top - } else if (obj_pos.1 + obj_size.1) >= FIELD_SIZE.1 { - VerticalCollisionCheck::Bottom - } else { - VerticalCollisionCheck::None - }; - Self(horizontal, vertical) - } - - fn no_collisions(&self) -> bool { - matches!( - self, - CollisionCheck(HorizontalCollisionCheck::None, VerticalCollisionCheck::None) - ) - } - } - - struct MovingObject { - prev_pos: (u32, u32), - } - - impl BallDirection { - fn get_vec(&self) -> (i16, i16) { - match self { - BallDirection::RightDown => BALL_VEC_RIGHT_DOWN, - BallDirection::RightUp => (BALL_VEC_RIGHT_DOWN.0, -BALL_VEC_RIGHT_DOWN.1), - BallDirection::LeftUp => (-BALL_VEC_RIGHT_DOWN.0, BALL_VEC_RIGHT_DOWN.1), - BallDirection::LeftDown => (-BALL_VEC_RIGHT_DOWN.0, -BALL_VEC_RIGHT_DOWN.1), - } - } - } - - pub struct GameState { - ball_pos: (u32, u32), - ball_direction: BallDirection, - paddle_r_pos: u32, - paddle_y_pos: u32, - scores: (u32, u32), - last_frame_time: Instant, - } - - impl GameState { - pub fn init() -> Self { Self { - ball_pos: (FIELD_SIZE.0 / 2, FIELD_SIZE.1 / 2), - paddle_r_pos: FIELD_SIZE.0 / 2, - paddle_y_pos: FIELD_SIZE.0 / 2, - ball_direction: BallDirection::RightDown, - scores: (0, 0), - last_frame_time: Instant::now(), + event_loop, + render_fn: |_| {}, } } - pub fn render( - &self, - buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>, - win_size: (NonZeroU32, NonZeroU32), - ) { - let win_size = (win_size.0.get(), win_size.1.get()); - self.render_field(buf, win_size); - self.render_ball(buf, win_size); + // sets the render function for the game + pub fn set_render_fn(mut self, f: RenderFn) -> Self { + self.render_fn = f; + self } - pub fn update(&mut self) { - let delta_t = self.delta_time(); + // runs the game and consumes self, this will finish the process + pub fn run(self) -> ! { + let Self { + event_loop, + render_fn, + } = self; - self.last_frame_time = Instant::now(); - self.move_ball(); - } + // set up window + let window = WindowBuilder::new() + .with_inner_size(winit::dpi::Size::Physical(PhysicalSize::new(1200, 700))) + .build(&event_loop) + .unwrap(); - fn move_ball(&mut self) { - let vec = self.ball_direction.get_vec(); - let delta_t = self.delta_time(); + // 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 new_pos = ( - self.ball_pos - .0 - .saturating_add_signed((vec.0 as f32 * delta_t) as i32) - .clamp(0, FIELD_SIZE.0 - BALL_RADIUS), - self.ball_pos - .1 - .saturating_add_signed((vec.1 as f32 * delta_t) as i32) - .clamp(0, FIELD_SIZE.1 - BALL_RADIUS), - ); - // println!("{:?} -> {:?}", self.ball_pos, new_pos); - self.ball_pos = new_pos; + event_loop + .run(|event, elwt| { + // shoddy vsync + elwt.set_control_flow(ControlFlow::WaitUntil( + Instant::now() + Duration::from_millis(1000 / 60), + )); - let check_res = CollisionCheck::check_field( - ( - new_pos.0.saturating_sub(BALL_RADIUS), - new_pos.1.saturating_sub(BALL_RADIUS), - ), - (BALL_RADIUS * 2, BALL_RADIUS * 2), - ); + match event { + // redraw + Event::NewEvents(StartCause::ResumeTimeReached { + start: _start, + requested_resume: _requested_resume, + }) => { + 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(); + buffer.fill(0); - let CollisionCheck(horizontal, vertical) = check_res; + let mut ctx = RenderCtx::new(buffer, (width, height), GAME_SIZE); - self.ball_direction = match (self.ball_direction, (horizontal, vertical)) { - ( - BallDirection::RightUp, - (HorizontalCollisionCheck::None, VerticalCollisionCheck::Bottom), - ) - | ( - BallDirection::RightDown, - (HorizontalCollisionCheck::None, VerticalCollisionCheck::None), - ) - | ( - BallDirection::LeftUp, - (HorizontalCollisionCheck::Left, VerticalCollisionCheck::None), - ) - | ( - BallDirection::LeftDown, - (HorizontalCollisionCheck::Left, VerticalCollisionCheck::Top), - ) => BallDirection::RightDown, - - ( - BallDirection::RightUp, - (HorizontalCollisionCheck::None, VerticalCollisionCheck::None), - ) - | ( - BallDirection::RightDown, - (HorizontalCollisionCheck::None, VerticalCollisionCheck::Top), - ) - | ( - BallDirection::LeftUp, - (HorizontalCollisionCheck::Left, VerticalCollisionCheck::Top), - ) - | ( - BallDirection::LeftDown, - (HorizontalCollisionCheck::Left, VerticalCollisionCheck::None), - ) => BallDirection::RightUp, - - ( - BallDirection::RightUp, - (HorizontalCollisionCheck::Right, VerticalCollisionCheck::Bottom), - ) - | ( - BallDirection::RightDown, - (HorizontalCollisionCheck::Right, VerticalCollisionCheck::None), - ) - | ( - BallDirection::LeftUp, - (HorizontalCollisionCheck::None, VerticalCollisionCheck::None), - ) - | ( - BallDirection::LeftDown, - (HorizontalCollisionCheck::None, VerticalCollisionCheck::Bottom), - ) => BallDirection::LeftUp, - - ( - BallDirection::RightUp, - (HorizontalCollisionCheck::Right, VerticalCollisionCheck::None), - ) - | ( - BallDirection::RightDown, - (HorizontalCollisionCheck::Right, VerticalCollisionCheck::Top), - ) - | ( - BallDirection::LeftUp, - (HorizontalCollisionCheck::None, VerticalCollisionCheck::Top), - ) - | ( - BallDirection::LeftDown, - (HorizontalCollisionCheck::None, VerticalCollisionCheck::None), - ) => BallDirection::LeftDown, - - other => panic!("Invalid collision: {other:#?}"), - }; - } - - fn delta_time(&self) -> f32 { - Instant::now() - .duration_since(self.last_frame_time) - .as_millis() as f32 - / TICK_LEN - } - - fn render_field( - &self, - buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>, - win_size: (u32, u32), - ) { - let field_pos = ( - (win_size.0 / 2).saturating_sub(FIELD_SIZE.0 / 2), - (win_size.1 / 2).saturating_sub(FIELD_SIZE.1 / 2), - ); - - // top border - draw_rect( - buf, - win_size, - ( - field_pos.0.saturating_sub(FIELD_BORDER_WIDTH), - field_pos.1.saturating_sub(FIELD_BORDER_WIDTH), - ), - ( - FIELD_SIZE.0.saturating_add(FIELD_BORDER_WIDTH * 2), - FIELD_BORDER_WIDTH, - ), - 0xff0000, - ); - // right border - draw_rect( - buf, - win_size, - (field_pos.0.saturating_add(FIELD_SIZE.0), field_pos.1), - (FIELD_BORDER_WIDTH, FIELD_SIZE.1), - 0x00ff00, - ); - // bottom border - draw_rect( - buf, - win_size, - ( - field_pos.0.saturating_sub(FIELD_BORDER_WIDTH), - field_pos.1.saturating_add(FIELD_SIZE.1), - ), - ( - FIELD_SIZE.0.saturating_add(FIELD_BORDER_WIDTH * 2), - FIELD_BORDER_WIDTH, - ), - 0xff00ff, - ); - // left border - draw_rect( - buf, - win_size, - (field_pos.0.saturating_sub(FIELD_BORDER_WIDTH), field_pos.1), - (FIELD_BORDER_WIDTH, FIELD_SIZE.1), - 0x00ffff, - ); - // draw midline - draw_rect( - buf, - win_size, - ((win_size.0 / 2) - (FIELD_BORDER_WIDTH / 2), field_pos.1), - (FIELD_BORDER_WIDTH, FIELD_SIZE.1), - 0x7f7f7f, - ) - } - fn render_ball( - &self, - buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>, - win_size: (u32, u32), - ) { - let field_pos = ( - (win_size.0 / 2).saturating_sub(FIELD_SIZE.0 / 2), - (win_size.1 / 2).saturating_sub(FIELD_SIZE.1 / 2), - ); - // let field_pos = identity(( - // (win_size.0 / 2).saturating_sub(FIELD_SIZE.0 / 2), - // (win_size.1 / 2).saturating_sub(FIELD_SIZE.1 / 2), - // )); - // println!("{}, {}", field_pos.0, field_pos.1); - - let ball_render_pos = ( - (self.ball_pos.0 + field_pos.0).saturating_sub(BALL_RADIUS), - (self.ball_pos.1 + field_pos.1).saturating_sub(BALL_RADIUS), - ); - - draw_rect( - buf, - win_size, - ball_render_pos, - (BALL_RADIUS * 2, BALL_RADIUS * 2), - 0xffffff, - ); - } - fn render_paddles( - &self, - buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>, - win_size: (u32, u32), - ) { - todo!() - } - fn render_paddle( - &self, - buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>, - win_size: (u32, u32), - ) { - todo!() - } - fn render_scores( - &self, - buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>, - win_size: (u32, u32), - ) { - todo!() + // render + render_fn(&mut ctx); + ctx.force_present(); + } + } + // 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); } } - fn draw_rect( - buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>, - buf_size: (u32, u32), - pos: (u32, u32), - size: (u32, u32), - color: u32, - ) { - for y in pos.1..(pos.1 + size.1) { - for x in pos.0..(pos.0 + size.0) { - let index = y as usize * buf_size.0 as usize + x as usize; - if let Some(px) = buf.get_mut(index) { - *px = color + // render context + pub struct RenderCtx<'buf, 'win> { + buffer: Buffer<'buf, DisplayHandle<'win>, WindowHandle<'win>>, + win_size: (u32, u32), + context_size: (u32, u32), + context_pos: (u32, u32), + } + + impl<'buf, 'win> RenderCtx<'buf, 'win> { + // create new render context + pub fn new( + buffer: Buffer<'buf, DisplayHandle<'win>, WindowHandle<'win>>, + win_size: (NonZeroU32, NonZeroU32), + context_size: (u32, u32), + ) -> Self { + Self { + buffer, + win_size: (win_size.0.get(), win_size.1.get()), + context_size, + context_pos: ( + (win_size.0.get() / 2).saturating_sub(context_size.0 / 2), + (win_size.1.get() / 2).saturating_sub(context_size.1 / 2), + ), + } + } + + // draw a rectangle in the context + // + // coordinates are relative to the context + pub fn rect_unchecked(&mut self, x: u32, y: u32, width: u32, height: u32, color: u32) { + // position in buffer coordinates and not relative coordinates + let x_buf_pos = self.context_pos.0 + x; + let y_buf_pos = self.context_pos.1 + y; + + for y in y_buf_pos..(y_buf_pos + height) { + for x in x_buf_pos..(x_buf_pos + width) { + let index = y as usize * self.win_size.0 as usize + x as usize; + if let Some(px) = self.buffer.get_mut(index) { + *px = color + } } } } + + pub fn force_present(self) { + self.buffer.present().unwrap() + } } }