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,75 +1,106 @@
use engine::Engine;
fn main() {
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);
})
.run();
}
mod engine {
const GAME_SIZE: (u32, u32) = (500, 800);
use std::{ use std::{
num::NonZeroU32, num::NonZeroU32,
process,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use game::GameState; use softbuffer::Buffer;
use winit::{ use winit::{
dpi::PhysicalSize, dpi::PhysicalSize,
event::{Event, KeyEvent, WindowEvent}, event::{Event, KeyEvent, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
keyboard::{Key, NamedKey}, keyboard::{Key, NamedKey},
raw_window_handle::{HasDisplayHandle, HasWindowHandle}, raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle, WindowHandle},
window::WindowBuilder, window::WindowBuilder,
}; };
fn main() { type RenderFn = fn(&mut RenderCtx<'_, '_>);
// core game engine struct
pub struct Engine {
event_loop: EventLoop<()>,
render_fn: RenderFn,
}
impl Engine {
pub fn new() -> Self {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
Self {
event_loop,
render_fn: |_| {},
}
}
// 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,
render_fn,
} = self;
// set up window
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_inner_size(winit::dpi::Size::Physical(PhysicalSize::new(1200, 700))) .with_inner_size(winit::dpi::Size::Physical(PhysicalSize::new(1200, 700)))
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
// ControlFlow::Poll continuously runs the event loop, even if the OS hasn't // set up softbuffer
// 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 context = softbuffer::Context::new(window.display_handle().unwrap()).unwrap();
let mut surface = softbuffer::Surface::new(&context, window.window_handle().unwrap()).unwrap(); let mut surface =
softbuffer::Surface::new(&context, window.window_handle().unwrap()).unwrap();
let mut game = GameState::init();
event_loop event_loop
.run(|event, elwt| { .run(|event, elwt| {
// shoddy vsync
elwt.set_control_flow(ControlFlow::WaitUntil( elwt.set_control_flow(ControlFlow::WaitUntil(
Instant::now() + Duration::from_millis(1000 / 60), Instant::now() + Duration::from_millis(1000 / 60),
)); ));
match event { match event {
Event::WindowEvent { // redraw
window_id, Event::NewEvents(StartCause::ResumeTimeReached {
event: WindowEvent::RedrawRequested, start: _start,
} if window_id == window.id() => { requested_resume: _requested_resume,
}) => {
if let (Some(width), Some(height)) = { if let (Some(width), Some(height)) = {
let size = window.inner_size(); let size = window.inner_size();
(NonZeroU32::new(size.width), NonZeroU32::new(size.height)) (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
} { } {
surface.resize(width, height).unwrap(); surface.resize(width, height).unwrap();
let mut buffer = surface.buffer_mut().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); buffer.fill(0);
game.update();
game.render(&mut buffer, (width, height));
buffer.present().unwrap(); let mut ctx = RenderCtx::new(buffer, (width, height), GAME_SIZE);
// render
render_fn(&mut ctx);
ctx.force_present();
} }
} }
// crash game to exit lol
Event::WindowEvent { Event::WindowEvent {
window_id: _window_id,
event: event:
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
@ -80,369 +111,71 @@ fn main() {
}, },
.. ..
}, },
window_id, } => todo!(),
} if window_id == window.id() => { // potential future keyboard handling
elwt.exit(); Event::WindowEvent {
} window_id: _window_id,
event:
WindowEvent::KeyboardInput {
device_id: _device_id,
event: _event,
is_synthetic: _is_synthetic,
},
} => {}
_ => {} _ => {}
} }
}) })
.unwrap(); .unwrap();
} process::exit(0);
mod game {
use std::{convert::identity, num::NonZeroU32, time::Instant};
use softbuffer::Buffer;
use winit::raw_window_handle::{DisplayHandle, WindowHandle};
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,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HorizontalCollisionCheck {
None,
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 { // render context
prev_pos: (u32, u32), 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 BallDirection { impl<'buf, 'win> RenderCtx<'buf, 'win> {
fn get_vec(&self) -> (i16, i16) { // create new render context
match self { pub fn new(
BallDirection::RightDown => BALL_VEC_RIGHT_DOWN, buffer: Buffer<'buf, DisplayHandle<'win>, WindowHandle<'win>>,
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(),
}
}
pub fn render(
&self,
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>,
win_size: (NonZeroU32, NonZeroU32), win_size: (NonZeroU32, NonZeroU32),
) { context_size: (u32, u32),
let win_size = (win_size.0.get(), win_size.1.get()); ) -> Self {
self.render_field(buf, win_size); Self {
self.render_ball(buf, win_size); buffer,
} win_size: (win_size.0.get(), win_size.1.get()),
context_size,
pub fn update(&mut self) { context_pos: (
let delta_t = self.delta_time(); (win_size.0.get() / 2).saturating_sub(context_size.0 / 2),
(win_size.1.get() / 2).saturating_sub(context_size.1 / 2),
self.last_frame_time = Instant::now();
self.move_ball();
}
fn move_ball(&mut self) {
let vec = self.ball_direction.get_vec();
let delta_t = self.delta_time();
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;
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),
);
let CollisionCheck(horizontal, vertical) = check_res;
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!()
} }
} }
fn draw_rect( // draw a rectangle in the context
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>, //
buf_size: (u32, u32), // coordinates are relative to the context
pos: (u32, u32), pub fn rect_unchecked(&mut self, x: u32, y: u32, width: u32, height: u32, color: u32) {
size: (u32, u32), // position in buffer coordinates and not relative coordinates
color: u32, let x_buf_pos = self.context_pos.0 + x;
) { let y_buf_pos = self.context_pos.1 + y;
for y in pos.1..(pos.1 + size.1) {
for x in pos.0..(pos.0 + size.0) { for y in y_buf_pos..(y_buf_pos + height) {
let index = y as usize * buf_size.0 as usize + x as usize; for x in x_buf_pos..(x_buf_pos + width) {
if let Some(px) = buf.get_mut(index) { 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 *px = color
} }
} }
} }
} }
pub fn force_present(self) {
self.buffer.present().unwrap()
}
}
} }