Pong

作成日: 2022-12-07 (水)

src/main.rs

use sfml::{system::*, window::*, graphics::*};

struct MoveFlags {
    up: bool,
    down: bool,
}

struct Game<'a> {
    window: RenderWindow,
    paddle: RectangleShape<'a>,
    paddle_speed: f32,
    ball: CircleShape<'a>,
    ball_velocity: Vector2f,
    move_flags: MoveFlags
}

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 = 200.0;
    const PADDLE_SIZE_X: f32 = 30.0;
    const PADDLE_SIZE_Y: f32 = 100.0;
    const BALL_SPEED: f32 = 400.0;
    const BALL_RADIUS: f32 = 20.0;

    pub fn new() -> Game<'a> {
        let window = RenderWindow::new(
            (Self::WIDTH, Self::HEIGHT),
            "Pong with Rust and SFML",
            Style::CLOSE,
            &Default::default());
            
        let mut paddle = RectangleShape::with_size(Vector2f::new(Self::PADDLE_SIZE_X, Self::PADDLE_SIZE_Y));
        paddle.set_position((Self::PADDLE_SIZE_X, (Self::HEIGHT as f32 / 2.0) - Self::PADDLE_SIZE_Y));
        
        let paddle_speed = Self::PADDLE_SPEED;
        
        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);
            
        Game {
            window,
            paddle,
            paddle_speed,
            ball,
            ball_velocity,
            move_flags: MoveFlags {
                up: false,
                down: false,
            }
        }
    }
    
    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) {
        // short names for readability
        let paddle = &mut self.paddle;
        let ball = &mut self.ball;
        let ball_velocity = &mut self.ball_velocity;

        // update paddle
        let mut movement = Vector2f::new(0.0, 0.0);
        
        if self.move_flags.up {
            if paddle.position().y > 0.0 {
                movement.y -= self.paddle_speed;
            }
        }
        if self.move_flags.down {
            if paddle.position().y + paddle.size().y < Self::HEIGHT as f32 {
                movement.y += self.paddle_speed;
            }
        }
        paddle.move_(movement * delta_time.as_seconds());
        
        // update ball
        
        // update ball x
        ball.move_((ball_velocity.x * delta_time.as_seconds(), 0.0));

        if ball.position().x + ball.radius() > Self::WIDTH as f32 {
            ball_velocity.x *= -1.0;
            // align to left of paddle
            ball.set_position((Self::WIDTH as f32 - ball.radius(), ball.position().y));
        }
        
        let paddle_rect = paddle.global_bounds();
        let mut ball_rect = ball.global_bounds(); // mut for reusing later
        if ball_rect.intersection(&paddle_rect) != None {
            ball.set_position((paddle_rect.left + paddle_rect.width + ball.radius(), ball.position().y));
            ball_velocity.x *= -1.0;
        }
        
        // update ball y
        ball.move_((0.0, ball_velocity.y * delta_time.as_seconds()));
        
        if ball.position().y - ball.radius() < 0.0 {
            ball_velocity.y *= -1.0;
            ball.set_position((ball.position().x, ball.radius()));
        }
        if ball.position().y + ball.radius() > Self::HEIGHT as f32 {
            ball_velocity.y *= -1.0;
            ball.set_position((ball.position().x, Self::HEIGHT as f32 - ball.radius()));
        }
        
        ball_rect = ball.global_bounds(); // get new rect which has been updated for x
        if ball_rect.intersection(&paddle_rect) != None {
            if ball_velocity.y > 0.0 {
                // align to top of paddle
               ball.set_position((ball.position().x, paddle_rect.top - ball.radius()));
            } else {
                // align to bottom of paddle
                ball.set_position((ball.position().x, paddle_rect.top + paddle_rect.height));
            }
            
            ball_velocity.y *= -1.0;
        }
        
        assert!(ball.position().x + ball.radius() <= Self::WIDTH as f32);
    }
    
    fn render(&mut self) {
        self.window.clear(Color::BLUE);
        self.window.draw(&self.paddle);
        self.window.draw(&self.ball);
        self.window.display();
    }
    
    fn handle_player_input(&mut self, code: Key, pressed: bool) {
        match code {
            Key::W => self.move_flags.up = pressed,
            Key::S => self.move_flags.down = pressed,
            _ => {}
        }
    }
}

fn main() {
    let mut game = Game::new();
    game.run();
}

Cargo.toml

[package]
name = "pong"
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"