#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // https://tetra.seventeencups.net/tutorial/01-creating-a-project 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; struct Entity { texture: Texture, position: Vec2, velocity: Vec2, } impl Entity{ fn new(texture: Texture,position: Vec2) -> Self{ Entity::with_velocity(texture,position,Vec2::zero()) } fn with_velocity(texture: Texture,position: Vec2,velocity: Vec2) -> Self { Entity{ texture, position, velocity, } } fn centre(&self)-> Vec2{ Vec2::new( self.position.x + (self.width() / 2.0), self.position.y + (self.height() / 2.0), ) } 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(), ) } } struct GameState { player1: Entity, player2: Entity, ball: Entity, } impl GameState{ fn new(ctx: &mut Context) -> tetra::Result{ let player1_texture:Texture = Texture::new(ctx,"./resources/player1.png")?; //player1_texture let player2_texture:Texture = Texture::new(ctx,"./resources/player2.png")?; let ball_texture:Texture = Texture::new(ctx,"./resources/ball.png")?; let player1_position = Vec2::new(16.0,(WINDOW_HEIGHT - player1_texture.height() as f32) / 2.0); let player2_position = Vec2::new(WINDOW_WIDTH - player2_texture.width() as f32 -16.0,(WINDOW_HEIGHT - player2_texture.height() as f32) / 2.0); let ball_position = Vec2::new(WINDOW_WIDTH / 2.0 - (ball_texture.width() as f32 / 2.0) as f32,WINDOW_HEIGHT / 2.0 - (ball_texture.height() as f32 / 2.0) as f32); let ball_speed = 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_speed), }) } } impl State for GameState{ fn draw(&mut self, ctx: &mut Context) -> tetra::Result { graphics::clear(ctx,Color::rgb(0.329,0.584,0.929)); self.player1.texture.draw(ctx,self.player1.position); self.player2.texture.draw(ctx,self.player2.position); self.ball.texture.draw(ctx,self.ball.position); Ok(()) } fn update(&mut self,ctx: &mut Context) -> tetra::Result{ //Player 1 movements if input::is_key_down(ctx, Key::W) && self.player1.position.y as i32 > 0{ self.player1.position.y -= PADDLE_SPEED; } if input::is_key_down(ctx, Key::S) && ((self.player1.position.y as i32 + self.player1.texture.height()) as f32) < WINDOW_HEIGHT{ self.player1.position.y += PADDLE_SPEED; } //player 2 movements if input::is_key_down(ctx, Key::Up) && self.player2.position.y as i32 > 0{ self.player2.position.y -= PADDLE_SPEED; } if input::is_key_down(ctx, Key::Down) && ((self.player2.position.y as i32 + self.player1.texture.height()) as f32) < WINDOW_HEIGHT{ self.player2.position.y += PADDLE_SPEED; } //Collisions let player1_bounds:Rectangle = self.player1.bounds(); let player2_bounds:Rectangle = self.player2.bounds(); let ball_bounds:Rectangle = 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 ball velocity before flipping it self.ball.velocity.x = -(self.ball.velocity.x + (BALL_ACC * self.ball.velocity.x.signum())); //Calculate the offset between the paddle center and the ball let offset = (paddle.centre().y - self.ball.centre().y) / paddle.height(); self.ball.velocity.y += PADDLE_SPIN * -offset; } self.ball.position += self.ball.velocity; //check borders if self.ball.position.y <= 0.0 || self.ball.position.y + self.ball.height() >= WINDOW_HEIGHT{ self.ball.velocity.y = -self.ball.velocity.y; } //check winner if self.ball.position.x <= 0.0{ window::quit(ctx); println!("Player 2 won!"); } if self.ball.position.x + self.ball.width() >= WINDOW_WIDTH{ window::quit(ctx); println!("Player 1 won!"); } Ok(()) } } fn main() -> tetra::Result{ ContextBuilder::new("Pong",WINDOW_WIDTH as i32,WINDOW_HEIGHT as i32) .quit_on_escape(true) .build()? .run(GameState::new) }