basic rectangle drawing support added

This commit is contained in:
Schrottkatze 2024-02-22 13:01:00 +01:00
parent 1f2105f3be
commit b495dbbdcd
Signed by: schrottkatze
SSH key fingerprint: SHA256:hXb3t1vINBFCiDCmhRABHX5ocdbLiKyCdKI4HK2Rbbc

View file

@ -1,448 +1,181 @@
use std::{ use engine::Engine;
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,
};
fn main() { fn main() {
let event_loop = EventLoop::new().unwrap(); Engine::new()
let window = WindowBuilder::new() .set_render_fn(|mut ctx| {
.with_inner_size(winit::dpi::Size::Physical(PhysicalSize::new(1200, 700))) // draw three colored rectagles(tm)
.build(&event_loop) ctx.rect_unchecked(0, 0, 100, 100, 0xff0000);
.unwrap(); ctx.rect_unchecked(100, 0, 100, 100, 0x00ff00);
ctx.rect_unchecked(0, 100, 100, 100, 0x0000ff);
// 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();
}
_ => {}
}
}) })
.unwrap(); .run();
} }
mod game { mod engine {
use std::{convert::identity, num::NonZeroU32, time::Instant}; const GAME_SIZE: (u32, u32) = (500, 800);
use std::{
num::NonZeroU32,
process,
time::{Duration, Instant},
};
use softbuffer::Buffer; 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); type RenderFn = fn(&mut RenderCtx<'_, '_>);
const BALL_RADIUS: u32 = 10; // core game engine struct
const BALL_VEC_RIGHT_DOWN: (i16, i16) = (8, 12); pub struct Engine {
const FIELD_SIZE: (u32, u32) = (1200, 700); event_loop: EventLoop<()>,
const FIELD_BORDER_WIDTH: u32 = 5; render_fn: RenderFn,
const TICK_LEN: f32 = 1000. / 60.;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BallDirection {
RightUp,
RightDown,
LeftUp,
LeftDown,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] impl Engine {
enum HorizontalCollisionCheck { pub fn new() -> Self {
None, let event_loop = EventLoop::new().unwrap();
Left,
Right,
}
#[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 { Self {
ball_pos: (FIELD_SIZE.0 / 2, FIELD_SIZE.1 / 2), event_loop,
paddle_r_pos: FIELD_SIZE.0 / 2, render_fn: |_| {},
paddle_y_pos: FIELD_SIZE.0 / 2,
ball_direction: BallDirection::RightDown,
scores: (0, 0),
last_frame_time: Instant::now(),
} }
} }
pub fn render( // sets the render function for the game
&self, pub fn set_render_fn(mut self, f: RenderFn) -> Self {
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>, self.render_fn = f;
win_size: (NonZeroU32, NonZeroU32), self
) {
let win_size = (win_size.0.get(), win_size.1.get());
self.render_field(buf, win_size);
self.render_ball(buf, win_size);
} }
pub fn update(&mut self) { // runs the game and consumes self, this will finish the process
let delta_t = self.delta_time(); pub fn run(self) -> ! {
let Self {
event_loop,
render_fn,
} = self;
self.last_frame_time = Instant::now(); // set up window
self.move_ball(); let window = WindowBuilder::new()
} .with_inner_size(winit::dpi::Size::Physical(PhysicalSize::new(1200, 700)))
.build(&event_loop)
.unwrap();
fn move_ball(&mut self) { // set up softbuffer
let vec = self.ball_direction.get_vec(); let context = softbuffer::Context::new(window.display_handle().unwrap()).unwrap();
let delta_t = self.delta_time(); let mut surface =
softbuffer::Surface::new(&context, window.window_handle().unwrap()).unwrap();
let new_pos = ( event_loop
self.ball_pos .run(|event, elwt| {
.0 // shoddy vsync
.saturating_add_signed((vec.0 as f32 * delta_t) as i32) elwt.set_control_flow(ControlFlow::WaitUntil(
.clamp(0, FIELD_SIZE.0 - BALL_RADIUS), Instant::now() + Duration::from_millis(1000 / 60),
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;
let check_res = CollisionCheck::check_field( match event {
( // redraw
new_pos.0.saturating_sub(BALL_RADIUS), Event::NewEvents(StartCause::ResumeTimeReached {
new_pos.1.saturating_sub(BALL_RADIUS), start: _start,
), requested_resume: _requested_resume,
(BALL_RADIUS * 2, BALL_RADIUS * 2), }) => {
); 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)) { // render
( render_fn(&mut ctx);
BallDirection::RightUp, ctx.force_present();
(HorizontalCollisionCheck::None, VerticalCollisionCheck::Bottom), }
) }
| ( // crash game to exit lol
BallDirection::RightDown, Event::WindowEvent {
(HorizontalCollisionCheck::None, VerticalCollisionCheck::None), window_id: _window_id,
) event:
| ( WindowEvent::CloseRequested
BallDirection::LeftUp, | WindowEvent::KeyboardInput {
(HorizontalCollisionCheck::Left, VerticalCollisionCheck::None), event:
) KeyEvent {
| ( logical_key: Key::Named(NamedKey::Escape),
BallDirection::LeftDown, ..
(HorizontalCollisionCheck::Left, VerticalCollisionCheck::Top), },
) => BallDirection::RightDown, ..
},
( } => todo!(),
BallDirection::RightUp, // potential future keyboard handling
(HorizontalCollisionCheck::None, VerticalCollisionCheck::None), Event::WindowEvent {
) window_id: _window_id,
| ( event:
BallDirection::RightDown, WindowEvent::KeyboardInput {
(HorizontalCollisionCheck::None, VerticalCollisionCheck::Top), device_id: _device_id,
) event: _event,
| ( is_synthetic: _is_synthetic,
BallDirection::LeftUp, },
(HorizontalCollisionCheck::Left, VerticalCollisionCheck::Top), } => {}
) _ => {}
| ( }
BallDirection::LeftDown, })
(HorizontalCollisionCheck::Left, VerticalCollisionCheck::None), .unwrap();
) => BallDirection::RightUp, process::exit(0);
(
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!()
} }
} }
fn draw_rect( // render context
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>, pub struct RenderCtx<'buf, 'win> {
buf_size: (u32, u32), buffer: Buffer<'buf, DisplayHandle<'win>, WindowHandle<'win>>,
pos: (u32, u32), win_size: (u32, u32),
size: (u32, u32), context_size: (u32, u32),
color: u32, context_pos: (u32, u32),
) { }
for y in pos.1..(pos.1 + size.1) {
for x in pos.0..(pos.0 + size.0) { impl<'buf, 'win> RenderCtx<'buf, 'win> {
let index = y as usize * buf_size.0 as usize + x as usize; // create new render context
if let Some(px) = buf.get_mut(index) { pub fn new(
*px = color 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()
}
} }
} }