Creating a Simple Pong Game with Arduino
Introduction
This project is a simple pong game that is created with Arduino. The hardware consist of an Arduino Nano, an Adafruit 3.5” 320x480 Color TFT Touchscreen Breakout, and two potentiometers. The game is controlled by the potentiometers and the score is displayed on the screen. The game is created with C++ and the Arduino IDE. The game is played by two players, each controlling a paddle. The goal is to hit the ball with the paddle and make the ball hit the opponent’s wall. The first player to reach 7 points wins the game.
This is a team project that I worked on with Antonio Ruiz for the course CS 335: Inside the Box: How Computers Work. The project was completed in December 2023.
Gallary
Hardware Setup
The hardware setup is shown in the gallery section. The potentiometers are connected to the analog pins A0 and A1. The TFT screen is connected to the SPI pins of the Arduino Nano. For more information on the TFT screen, please refer to the Adafruit website, where they have a detailed tutorial on how to set up the screen to work with the Arduino.
Software Setup
Here is the code for the pong game.
Click to Expand Code
The Development Process
First Steps
The first part of the development was outputting from the Arduino Nano to the display. We use the SPI output of the Arduino Nano for this, following the guide on the Adafruit website. Once the wiring was set up, we used the Adafruit_HX8357 library to run some examples and start understanding how the display works.
The Game Physics
We then started the programming stage, creating the game physics and the skeleton of the game. The first stage was having a ball that bounced around the screen. The game physics is pretty straightforward: we have the ball’s speed and constantly update the ball’s position by adding the speed to its current position. Here, we ran into the first challenge, rendering speed.
Rendering Speed (the hard part)
None of the Adafruit_HX8357 examples have active rerendering. I mean that the examples are drawn into the screen, then fully wiped the screen, and then redrawn. The problem is that fully wiping the screen is extremely slow. The initial ball physics were working, but we were limited to rendering a frame every time the screen wiped black, so a frame around every three seconds.
So we could not rely on wiping the whole screen. We initially overcame this issue by masking the ball with a black ball and then re-rendering the white ball in the new location. This is done in the game loop. The issue with this approach is that the ball is updated too much; this slowed down the game since the Arduino Nano was extremely busy rerendering the ball. To fix this, we start a timer using millis() in the game loop and set a BALL_RATE time, which indicates when we will rerender the ball. The Millis re-rendering idea comes from this YouTube implementation: https://www.youtube.com/watch?v=ZRL0GUqebFs&t=1s&ab_channel=educ8s.tv, which is similar to our scenario, but they have a different screen.
Finally, we had fast rendering. We used the hardware SPI tft for CS/DC. tft offered many helper functions to draw circles and rectangles. So all we had to do was keep track of the positions of the ball and paddles and mask and render them when their position changed. At this stage, we had static paddles just for testing, so we set up two variable resistors as the controllers for the game. The idea is to use these to determine the location of the paddles. To connect them, we use the A0, and A1 PC pins in the arduino nano. We read the input, which ranges from 0 to 1023, normalize it, and multiply it by the display’s width minus the paddle width. This successfully gives us the relative center location of both paddles. Then we do the same mask and rendering operation on the paddle, with PADDLE_RATE, but now we are drawing a rectangle.
The Game Logic
All that was left was programming the paddle physics, which is only an if statement, where if the ball is in contact with the paddle, the ball’s speed in the Y axis is reversed by negating it. Then we separated the game loop into its own function in order to render scores and render the win state. Drawing text is very simple using the display’s tft object.
Distribution of Labor
Development took about two days of work, and maybe a little more given the quality of life additions we added such as ball acceleration, win state, etc. We worked together on this project, so we were able to help each other out if we were stuck with a problem. Zhiyang mainly worked on the wiring of the display and potentiometer, finding the library and starter code for this project, and getting the potentiometer input. Antonio mainly worked on the game logic, designing and implementing the physics of the game, as well as rendering the game on the display.
Conclusion
This project taught us about more complex I/O with the Arduino Nano controlling the display. We also applied what we learned in class using the two potentiometers as controls for the game. We learned Arduino programming and used packages to interact with the monitor using SPI. We also experimented with different rendering techniques to be as efficient as possible. We learned that the performance of the Arduino Nano can bottleneck the game’s speed, so if we rerender fewer items on the screen, the speed of the ball increases. This is one of the reasons why some video games experience unusual behavior with lower or higher FPS.