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::{
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()
}
}
}