====== ブロック崩し ====== 作成日: 2022-12-10 (土) {{youtube:sfml-bricks.png?400|}} src/main.rs use sfml::{graphics::*, system::*, window::*}; struct MoveFlags { left: bool, right: bool, } type Brick<'a> = RectangleShape<'a>; struct Game<'a> { window: RenderWindow, paddle: RectangleShape<'a>, paddle_speed: f32, ball: CircleShape<'a>, ball_velocity: Vector2f, move_flags: MoveFlags, bricks: Vec>, } impl<'a> Game<'a> { const FPS: f32 = 60.0; // Time::seconds(1.0 / 60.0) is not allowed for const const WIDTH: u32 = 1000; const HEIGHT: u32 = 800; const PADDLE_SPEED: f32 = 300.0; const PADDLE_SIZE_X: f32 = 100.0; const PADDLE_SIZE_Y: f32 = 30.0; const BALL_SPEED: f32 = 600.0; const BALL_RADIUS: f32 = 15.0; const BRICK_COUNT_X: u32 = 10; const BRICK_COUNT_Y: u32 = 8; pub fn new() -> Game<'a> { let window = RenderWindow::new( (Self::WIDTH, Self::HEIGHT), "Pong with Rust and SFML", Style::CLOSE, &Default::default(), ); // setup paddle let paddle = RectangleShape::from_rect(FloatRect { left: (Self::WIDTH as f32 / 2.0) - Self::PADDLE_SIZE_X / 2.0, top: Self::HEIGHT as f32 - Self::PADDLE_SIZE_Y * 2.0, width: Self::PADDLE_SIZE_X, height: Self::PADDLE_SIZE_Y, }); let paddle_speed = Self::PADDLE_SPEED; // setup ball let mut ball = CircleShape::new(Self::BALL_RADIUS, 30); ball.set_position((Self::WIDTH as f32 / 2.0, Self::HEIGHT as f32 / 2.0)); ball.set_origin((Self::BALL_RADIUS, Self::BALL_RADIUS)); let ball_velocity = Vector2f::new(Self::BALL_SPEED, Self::BALL_SPEED); // setup bricks // calc brick and gap size // g = r*b // w = r*b*(n+1) + b*n // w = b*{r*(n+1) + n} // b = w / {r*(n+1) + n} let gap_ratio = 1.0 / 8.0; let brick_width = Self::WIDTH as f32 / (gap_ratio * (Self::BRICK_COUNT_X + 1) as f32 + Self::BRICK_COUNT_X as f32); let gap = brick_width * gap_ratio; let brick_yx_ratio = 1.0 / 3.0; let brick_height = brick_width * brick_yx_ratio; let mut bricks = Vec::with_capacity(Self::BRICK_COUNT_X as usize * Self::BRICK_COUNT_Y as usize); for i in 0..Self::BRICK_COUNT_Y { for j in 0..Self::BRICK_COUNT_X { let mut brick = Brick::from_rect(FloatRect { left: gap + j as f32 * (gap + brick_width), top: gap + i as f32 * (gap + brick_height), width: brick_width, height: brick_height, }); brick.set_fill_color(Color::rgb( ((Self::BRICK_COUNT_X - j) * (255 / Self::BRICK_COUNT_X)) as u8, ((i + 1) * (255 / Self::BRICK_COUNT_Y)) as u8, ((j + 1) * (255 / Self::BRICK_COUNT_X)) as u8, )); bricks.push(brick); } } Self { window, paddle, paddle_speed, ball, ball_velocity, move_flags: MoveFlags { left: false, right: false, }, bricks, } } pub fn run(&mut self) { let mut clock = Clock::start(); let mut time_since_last_update = Time::ZERO; let time_per_frame = Time::seconds(1.0 / Self::FPS); while self.window.is_open() { self.process_events(); time_since_last_update += clock.restart(); while time_since_last_update > time_per_frame { time_since_last_update -= time_per_frame; self.process_events(); self.update(&time_per_frame); } self.render(); } } fn process_events(&mut self) { while let Some(event) = self.window.poll_event() { match event { Event::Closed => self.window.close(), Event::KeyPressed { code, .. } => { if code == Key::Enter || code == Key::Escape { self.window.close(); } else { self.handle_player_input(code, true); } } Event::KeyReleased { code, .. } => self.handle_player_input(code, false), _ => {} } } } fn update(&mut self, delta_time: &Time) { self.update_paddle(delta_time); self.update_ball(delta_time); } fn update_paddle(&mut self, delta_time: &Time) { let mut movement = Vector2f::new(0.0, 0.0); if self.move_flags.left { if self.paddle.position().x > 0.0 { movement.x -= self.paddle_speed; } } if self.move_flags.right { if self.paddle.position().x + self.paddle.size().x < Self::WIDTH as f32 { movement.x += self.paddle_speed; } } self.paddle.move_(movement * delta_time.as_seconds()); } fn update_ball(&mut self, delta_time: &Time) { self.update_ball_x(delta_time); self.update_ball_y(delta_time); } fn update_ball_x(&mut self, delta_time: &Time) { self.ball .move_((self.ball_velocity.x * delta_time.as_seconds(), 0.0)); if self.ball.position().x + self.ball.radius() > Self::WIDTH as f32 { self.ball_velocity.x *= -1.0; // align to right of screen self.ball.set_position(( Self::WIDTH as f32 - self.ball.radius(), self.ball.position().y, )); } if self.ball.position().x - self.ball.radius() < 0.0 { self.ball_velocity.x *= -1.0; // align to left of screen self.ball .set_position((self.ball.radius(), self.ball.position().y)); } let paddle_rect = self.paddle.global_bounds(); let ball_rect = self.ball.global_bounds(); let mut hit = false; if ball_rect.intersection(&paddle_rect) != None { if self.ball_velocity.x < 0.0 { // align ball to right of paddle self.ball.set_position(( paddle_rect.left + paddle_rect.width + self.ball.radius(), self.ball.position().y, )); } else { // align ball to left of paddle self.ball.set_position(( paddle_rect.left - self.ball.radius(), self.ball.position().y, )) } hit = true; } self.bricks.retain(|b| { let brick_rect = b.global_bounds(); if ball_rect.intersection(&brick_rect) != None { if self.ball_velocity.x < 0.0 { // align to right of brick self.ball.set_position(( brick_rect.left + brick_rect.width + self.ball.radius(), self.ball.position().y, )); } else { // align to left of brick self.ball.set_position(( brick_rect.left - self.ball.radius(), self.ball.position().y, )); } hit = true; false } else { true } }); if hit { self.ball_velocity.x *= -1.0; } } fn update_ball_y(&mut self, delta_time: &Time) { self.ball .move_((0.0, self.ball_velocity.y * delta_time.as_seconds())); if self.ball.position().y - self.ball.radius() < 0.0 { // align to top of screen self.ball_velocity.y *= -1.0; self.ball .set_position((self.ball.position().x, self.ball.radius())); } if self.ball.position().y + self.ball.radius() > Self::HEIGHT as f32 { // align to bottom of screen self.ball_velocity.y *= -1.0; self.ball.set_position(( self.ball.position().x, Self::HEIGHT as f32 - self.ball.radius(), )); } let paddle_rect = self.paddle.global_bounds(); let ball_rect = self.ball.global_bounds(); let mut hit = false; if ball_rect.intersection(&paddle_rect) != None { if self.ball_velocity.y < 0.0 { // align to bottom of paddle self.ball .set_position((self.ball.position().x, paddle_rect.top + paddle_rect.height)); } else { // align to top of paddle self.ball .set_position((self.ball.position().x, paddle_rect.top - self.ball.radius())); } hit = true; } self.bricks.retain(|b| { let brick_rect = b.global_bounds(); if ball_rect.intersection(&brick_rect) != None { if self.ball_velocity.y < 0.0 { // align to bottom of brick self.ball.set_position(( self.ball.position().x, brick_rect.top + brick_rect.height + self.ball.radius(), )); } else { // align top top of brick self.ball.set_position(( self.ball.position().x, brick_rect.top - self.ball.radius(), )); } hit = true; false } else { true } }); if hit { self.ball_velocity.y *= -1.0; } } fn render(&mut self) { self.window.clear(Color::BLUE); self.window.draw(&self.paddle); self.window.draw(&self.ball); for brick in &self.bricks { self.window.draw(brick); } self.window.display(); } fn handle_player_input(&mut self, code: Key, pressed: bool) { match code { Key::Left => self.move_flags.left = pressed, Key::Right => self.move_flags.right = pressed, _ => {} } } } fn main() { let mut game = Game::new(); game.run(); } Cargo.toml [package] name = "bricks" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] sfml = "0.19.0"