diff --git a/Pong-Tutorial/pong/img/ball.png b/Pong-Tutorial/pong/img/ball.png new file mode 100644 index 0000000..d6db2be Binary files /dev/null and b/Pong-Tutorial/pong/img/ball.png differ diff --git a/Pong-Tutorial/pong/src/main.rs b/Pong-Tutorial/pong/src/main.rs index e98911b..944e5f5 100755 --- a/Pong-Tutorial/pong/src/main.rs +++ b/Pong-Tutorial/pong/src/main.rs @@ -1,12 +1,16 @@ #!/usr/bin/env cargo run -use tetra::graphics::{self, Color, Texture}; +use tetra::graphics::{self, Color, Rectangle, Texture}; use tetra::input::{self, Key}; use tetra::math::Vec2; +use tetra::window; use tetra::{Context, ContextBuilder, State}; const WINDOW_WIDTH: f32 = 640.0; const WINDOW_HEIGHT: f32 = 480.0; const PADDLE_SPEED: f32 = 8.0; +const BALL_SPEED: f32 = 5.0; +const PADDLE_SPIN: f32 = 4.0; +const BALL_ACC: f32 = 0.05; fn main() -> tetra::Result { ContextBuilder::new("Pong", WINDOW_WIDTH as i32, WINDOW_HEIGHT as i32) @@ -18,17 +22,49 @@ fn main() -> tetra::Result { struct Entity { texture: Texture, position: Vec2, + velocity: Vec2, } impl Entity { fn new(texture: Texture, position: Vec2) -> Entity { - Entity { texture, position } + Entity::with_velocity(texture, position, Vec2::zero()) + } + fn with_velocity(texture: Texture, position: Vec2, velocity: Vec2) -> Entity { + Entity { + texture, + position, + velocity, + } + } + + fn width(&self) -> f32 { + self.texture.width() as f32 + } + + fn height(&self) -> f32 { + self.texture.height() as f32 + } + + fn bounds(&self) -> Rectangle { + Rectangle::new( + self.position.x, + self.position.y, + self.width(), + self.height(), + ) + } + fn centre(&self) -> Vec2 { + Vec2::new( + self.position.x + (self.width() / 2.0), + self.position.y + (self.height() / 2.0), + ) } } struct GameState { player1: Entity, player2: Entity, + ball: Entity, } impl GameState { @@ -45,9 +81,18 @@ impl GameState { (WINDOW_HEIGHT - player2_texture.height() as f32) / 2.0, ); + let ball_texture = Texture::new(ctx, "./img/ball.png")?; + let ball_position = Vec2::new( + WINDOW_WIDTH / 2.0 - ball_texture.width() as f32 / 2.0, + WINDOW_HEIGHT / 2.0 - ball_texture.height() as f32 / 2.0, + ); + + let ball_velocity = Vec2::new(-BALL_SPEED, 0.0); + Ok(GameState { player1: Entity::new(player1_texture, player1_position), player2: Entity::new(player2_texture, player2_position), + ball: Entity::with_velocity(ball_texture, ball_position, ball_velocity), }) } } @@ -70,14 +115,57 @@ impl State for GameState { self.player2.position.y += PADDLE_SPEED; } + self.ball.position += self.ball.velocity; + + let player1_bounds = self.player1.bounds(); + let player2_bounds = self.player2.bounds(); + let ball_bounds = self.ball.bounds(); + + let paddle_hit = if ball_bounds.intersects(&player1_bounds) { + Some(&self.player1) + } else if ball_bounds.intersects(&player2_bounds) { + Some(&self.player2) + } else { + None + }; + + if let Some(paddle) = paddle_hit { + // Increase the ball's velocity, then flip it. + self.ball.velocity.x = + -(self.ball.velocity.x + (BALL_ACC * self.ball.velocity.x.signum())); + + // Calculate the offset between the paddle and the ball, as a number between + // -1.0 and 1.0. + let offset = (paddle.centre().y - self.ball.centre().y) / paddle.height(); + + // Apply the spin to the ball. + self.ball.velocity.y += PADDLE_SPIN * -offset; + } + + if self.ball.position.y <= 0.0 || self.ball.position.y + self.ball.height() >= WINDOW_HEIGHT + { + self.ball.velocity.y = -self.ball.velocity.y; + } + + if self.ball.position.x < 0.0 { + window::quit(ctx); + println!("Player 2 wins!"); + } + + if self.ball.position.x > WINDOW_WIDTH { + window::quit(ctx); + println!("Player 1 wins!"); + } + Ok(()) } fn draw(&mut self, ctx: &mut Context) -> tetra::Result { - graphics::clear(ctx, Color::rgb(0.392, 0.584, 0.929)); + graphics::clear(ctx, Color::rgb(0., 0., 0.)); graphics::draw(ctx, &self.player1.texture, self.player1.position); graphics::draw(ctx, &self.player2.texture, self.player2.position); + graphics::draw(ctx, &self.ball.texture, self.ball.position); Ok(()) }