作成日: 2022-12-10 (土)
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<Brick<'a>>,
}
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"