Tutorial: Pong game in Rust 🦀
Tags: rust, rustseries
Published: 2024-05-17 19:56:52

---
title: Tutorial: Pong game in Rust 🦀
published: true
date: 2024-05-17 08:53:28 UTC
tags: Rust, rustlang, rustseries
canonical_url: https://eleftheriabatsou.hashnode.dev/tutorial-pong-game-in-rust
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7k5y98bws2llf24kfykw.jpeg
---

Hello, amazing people and welcome back to my blog! Today we're going to learn how to build a Pong game using the [piston engine](https://docs.rs/piston/latest/piston/) as well as the [OpenGL graphics library](https://blog.logrocket.com/understanding-opengl-basics-rust/).

In the end, we will have a board with 2 paddles, one on the left and one on the right side, and one ball. We'll also have 2 players who will be able to handle the left and the right paddles with Y and X keys and the up and down arrows.

Let's build this. 🎾
{% embed https://twitter.com/BatsouElef/status/1791392429288898693 %}

## Dependencies

**Toml File**

```
[dependencies]
piston = "0.35.0"
piston2d-graphics = "0.24.0"
pistoncore-glutin_window = "0.43.0"
piston2d-opengl_graphics = "0.50.0"

```

First, we need the piston engine itself, then we'll need our piston 2D graphics and we'll need our `pistoncore-glutin_window` and our `piston2d-opengl_graphics`.

**_Tip_** : _When you write the dependencies you can use inside the quotes an asterisk for the version number. Then go to the terminal, type_`cargo update`_and this will update all of your dependencies in the_`cargo.lock`_file. If we go to the_`lock`_file, we can search out the libraries, then copy the number and replace the asterisk back in the_`toml`_file._

```
piston = "*"
```

_The reason it's important to use static versions is just in case the library actually changes. If the syntax changes, then the game will not work properly anymore because we will be behind._

**Main rs File**

Let's go to the main.rs file and bring in all of our external libraries and make some imports!

```
extern crate glutin_window;
extern crate graphics;
extern crate opengl_graphics;
extern crate piston;

```

I will need the `process` the `piston::window` so that we can set up our window, the `event_loop` to set up our event settings, we'll need our `piston::input` for `Key, PressEvent` etc and then we'll also need our `glutin_window` (this allows us to create an OpenGL window) and `opengl_graphics` which contains our `GlGraphics, OpenGL`.

```
use std::process;
use piston::window::WindowSettings;
use piston::event_loop::{EventSettings, Events};
use piston::input::{Button, Key, PressEvent, ReleaseEvent, RenderArgs, RenderEvent, UpdateArgs, UpdateEvent};
use glutin_window::GlutinWindow;
use opengl_graphics::{GlGraphics, OpenGL};

```

Now we want to create a structure called `App`.

- Inside of this, we will have a field called `gl` which will connect to our `GlGraphics` type. Then we'll have our left score as well as the left position and left velocity. This will correspond to the left paddle.

- Then we'll have the right score, the right position, and the right velocity which will correspond to our right paddle. All these are `i32` .

- Then we'll have `ball_x` and `ball_y` and `velocity_x` and `velocity_y`. All four of these will correspond with our [ball.](http://ball.Now)

_Tip: If you want to write a more full featured application you can split these off into their own objects._

```
pub struct App {
    gl: GlGraphics,
    left_score: i32,
    left_pos: i32,
    left_vel: i32,
    right_score: i32,
    right_pos: i32,
    right_vel: i32,
    ball_x: i32,
    ball_y: i32,
    vel_x: i32,
    vel_y: i32,
}

```

Next, we want to create an implementation block for our application. Inside of it we need a render method. This method will take in a mutable `self` and our arguments (which will be a reference to our render arguments).

- We want to have an import for graphics so that we can easily get to it inside of this function.

- We will also create some constants for the colors of our game. They will be `[f32; 4].` Our background color will be the color in the background of our window. Whereas our foreground color will be the color that we paint our paddles and our ball with.

- We're going to create a variable called `left` which will be a rectangle square. This takes in scalars for `x` and `y` as well as size and we're just going to make our `x` and `y` zero and then we're going to make the `size` 50 so that it has some width.

- Then we're going to create a variable called `left_pos` and this will take our `self` from our struct and cast it into an `f64`.

- We'll do the same for our `right` and `right_pos`.

- Last but not least, for our ball, we also want it to be a square. We don't want it to have any specific `x` and `y` values but we want it to be a square of size 10. Then we want to do what we did for our positions for `ball_x` and `ball_y`. These are going to cast our `i32` into `f64` for the rendering.

```
impl App {
    fn render(&mut self, args: &RenderArgs) {
        use graphics::*;

        const BACKGROUND: [f32; 4] = [0.0, 0.5, 0.5, 1.0];
        const FOREGROUND: [f32; 4] = [0.0, 0.0, 1.0, 1.0];

        let left = rectangle::square(0.0, 0.0, 50.0);
        let left_pos = self.left_pos as f64;
        let right = rectangle::square(0.0, 0.0, 50.0);
        let right_pos = self.right_pos as f64;

        let ball = rectangle::square(0.0, 0.0, 10.0);
        let ball_x = self.ball_x as f64;
        let ball_y = self.ball_y as f64;
.
.
.
}

```

Then we want to call `self.gl.draw` and this is a method that's included inside of our `opengl_graphics`library. This takes in our `args.viewport` and then it takes in a closure which takes in `c` and `gl` (`c` being the `context` and `gl` being our `opengl graphic` renderer.) Inside of this closure we first want to `clear` our board and apply the `background`. We run this method called `clear` which takes in the `color` that we want the background to be and then the actual renderer which is the `gl`.

Then we want to create a rectangle. This will be our foreground color. It will be on the left as well and it will have a `transform` of `-40` and then our `left_pos` and the `gl`.

Our right paddle things are going to be slightly different on the `transform`. We're going to color it with the foreground color and we're still going to put in the right variable but we also need to `transform` it by `args.width` so the `width` of the actual `args` from the `viewport` as a `f64` and then we're going to have `-10`.

Now we want to render our `ball` with the `foreground` color. Let's add `c.transform.trans(ball_x, ball_y)` and lastly let's add the `gl` as well.

**To recap this part** : First we are defining our paddles as squares and then we're using the `transform` to stretch our paddles downwards towards the bottom so that they actually are rectangles rather than squares.

```
self.gl.draw(args.viewport(), |c, gl| {
            clear(BACKGROUND, gl);
            rectangle(FOREGROUND, left, c.transform.trans(-40.0, left_pos), gl);
            rectangle(
                FOREGROUND,
                right,
                c.transform.trans(args.width as f64 - 10.0, right_pos),
                gl,
            );
            rectangle(FOREGROUND, ball, c.transform.trans(ball_x, ball_y), gl);
        });

```

## The update method

Now we need to create an `update` method. Our update method will be where all of our game logic lies. We're taking in a mutable `self` so a mutable version of our `struct` and then we're going to take in our `UpdateArgs` which are sort of like our render args except made for updating.

Now we're going to write a few `if` statements, buckle up! 🙂

Our first two if statements check to see if our paddles are about to go off the screen.

In the first if statement we're checking to see if our left paddle's velocity is `== 1` and if `self.left_pos < 291`. In other words it's going off the bottom of the screen or we're checking to see if the left velocity `== -1` and `&& self.left_pos >= 1` , in other words it's going off the top of the screen. If that happens then we want to increment the actual paddle so that it comes back onto the screen. So as soon as the paddle goes off the screen just slightly it will be incremented back onto the screen.

We do the same for the right paddle.

```
fn update(&mut self, _args: &UpdateArgs) {
        if (self.left_vel == 1 && self.left_pos < 291)
            || (self.left_vel == -1 && self.left_pos >= 1)
        {
            self.left_pos += self.left_vel;
        }
        if (self.right_vel == 1 && self.right_pos < 291)
            || (self.right_vel == -1 && self.right_pos >= 1)
        {
            self.right_pos += self.right_vel;
        }
.       
.
.

```

The next part gives our ball some velocity. We want our ball to be moving in the `x` direction always so we increment it with our velocity `x`.

`self.ball_x += self.vel_x;`

Then we want to check to see if the ball has gone off of the right side of the screen, if `self.ball_x > 502`. If it is, we want to then reverse its velocity in the `x` direction, `self.vel_x = -self.vel_x;` Basically, if it's going right and it hits the paddle then it will automatically go to the left. If it's going right and it misses the paddle and goes off the screen then when we reset it. It will automatically be moving towards the left paddle. Then we check to see if ball y `self.ball_y < self.right_pos || self.ball_y > self.right_pos + 50`. In other words, we check to see if our ball has gone past our right paddle and if it has, then we increment our left score +1, `self.left_score += 1;`

Then we have a little statement that says that `if self.left_score >= 5` then we say `println!("Left wins!");` and then we `process::exit(0);`. If we do go past the right paddle then we want to reset the ball at `256` for its `ball_x` and `171` for its `ball_y`.

```
    self.ball_x += self.vel_x;
        if self.ball_x > 502 {
            self.vel_x = -self.vel_x;
            if self.ball_y < self.right_pos || self.ball_y > self.right_pos + 50 {
                self.left_score += 1;
                if self.left_score >= 5 {
                    println!("Left wins!");
                    process::exit(0);
                }
                self.ball_x = 256;
                self.ball_y = 171;
            }
        }

```

Similarly, we'll work on the left paddle. You can check the code below for minor differences.

```
        if self.ball_x < 1 {
            self.vel_x = -self.vel_x;
            if self.ball_y < self.left_pos || self.ball_y > self.left_pos + 50 {
                self.right_score += 1;
                if self.right_score >= 5 {
                    println!("Right wins!");
                    process::exit(0);
                }
                self.ball_x = 256;
                self.ball_y = 171;
            }
        }

```

The last thing we want in this method is to allow our ball to bounce off of the top and the bottom of the screen. So we will do `self.ball_y += self.vel_y;`. Then we want to check to see if the ball has hit the bottom of the screen or if it has hit the top of the screen. If it has hit either one then we reverse the velocity.

```
        self.ball_y += self.vel_y;
        if self.ball_y > 332 || self.ball_y < 1 {
            self.vel_y = -self.vel_y;
        }

```

## The press method

So now we want to create a method to handle the keys. We will call this method `press`. It takes in our immutable `self` and the arguments which is a reference to our `Button` enum. We will use an `if let` binding to destruct our arguments and if the pattern is similar to a reference to `Button` keyboard then we want to take the `key` out and we want to `match` on `key`.

- If `Key::Up` we want the right paddle's velocity to move -1.

- If `Key::Down` we want the right velocity to move 1

- Likewise, we'll code `w`,`s`, and any other key `_`.

```
fn press(&mut self, args: &Button) {
        if let &Button::Keyboard(key) = args {
            match key {
                Key::Up => {
                    self.right_vel = -1;
                }
                Key::Down => {
                    self.right_vel = 1;
                }
                Key::W => {
                    self.left_vel = -1;
                }
                Key::S => {
                    self.left_vel = 1;
                }
                _ => {}
            }
        }
    }

```

## The release method

Now we also want to have a release method. It will be the same as our `press` method except for one minor difference. We're going to run an `if let` binding on `args` to see if `let &Button::Keyboard(key) = args` and if it does then we're going to `match` on `key` and:

- If `Key::Up` was pressed but let go then we want to set the right paddle's velocity to 0 (to elaborate, if for instance the player is hitting up and then they let go then we want to immediately stop the velocity.)

- Likewise, we'll code `Down`, `w`,`s`, and any other key `_`.

```
fn release(&mut self, args: &Button) {
        if let &Button::Keyboard(key) = args {
            match key {
                Key::Up => {
                    self.right_vel = 0;
                }
                Key::Down => {
                    self.right_vel = 0;
                }
                Key::W => {
                    self.left_vel = 0;
                }
                Key::S => {
                    self.left_vel = 0;
                }
                _ => {}
            }
        }
    }

```

## The main function

Let's finally build up the window and make the game work! 🤗

We're going to bind `let opengl = OpenGL::V3_2;` , then we're going to say `let mut window: GlutinWindow = WindowSettings::new("Pong", [512, 342])` with pong's dimensions at 512 by 342 and then we'll have `.exit_on_esc(true)` so that if an individual hits the escape key it will exit the window, then we want to `build` the window and we want to `unwrap` it so that we can get back the actual value.

```
fn main() {
    let opengl = OpenGL::V3_2;
    let mut window: GlutinWindow = WindowSettings::new("Pong", [512, 342])
        /* .opengl(opengl) */
        .exit_on_esc(true)
        .build()
        .unwrap();

```

Now we want to instantiate our app structure and `let mu app` equal to `App`. Then we're going to have:

- Our `gl` is our `GlGraphics::new(opengl)` so this will be bound to our `opengl` buffer.

- We want to set our `left_score` equal to 0, our `left_pos` equal to 1and the `left_ vel` equal to 0.

- Likewise fo the right side.

- Our `ball_x` and `ball_y` will be 0 with its `vel_x` and `vel_y` being 1.

```
 let mut app = App {
        gl: GlGraphics::new(opengl),
        left_score: 0,
        left_pos: 1,
        left_vel: 0,
        right_score: 0,
        right_pos: 1,
        right_vel: 0,
        ball_x: 0,
        ball_y: 0,
        vel_x: 1,
        vel_y: 1,
    };

```

Next, we want to create an `events` variable, this will `let mut events = Events::new(EventSettings::new());`. Now let's create a loop. The loop will continue to iterate through as long as we have a new event. And we finally want to call the functions we created above according to our user's action (key presses).

```
    let mut events = Events::new(EventSettings::new());
    while let Some(e) = events.next(&mut window) {
        if let Some(r) = e.render_args() {
            app.render(&r);
        }

        if let Some(u) = e.update_args() {
            app.update(&u);
        }

        if let Some(b) = e.press_args() {
            app.press(&b);
        }

        if let Some(b) = e.release_args() {
            app.release(&b);
        }
    }
```

## Run it 🏃

Are you still here? That's it folks, we made it! Time to run it.

Simply type in your terminal `cargo run` and you should see the board. Call a friend and start playing. 🤗

Use the keys `w` and `s` and your friend the arrows `up` and `down`. Who is going to win?!

Find the code [here](https://github.com/EleftheriaBatsou/pong-game-rust/tree/main):

{% embed https://github.com/EleftheriaBatsou/pong-game-rust %}

Happy Rust Coding! 🤞🦀

* * *

👋 Hello, I'm Eleftheria, **Community Manager,** developer, public speaker, and content creator.

🥰 If you liked this article, consider sharing it.

🔗 [**All links**](https://limey.io/batsouelef) | [**X**](https://twitter.com/BatsouElef) | [**LinkedIn**](https://www.linkedin.com/in/eleftheriabatsou/)