Retro Revolution: Building a Pong Clone in Unity
Before starting you can view the game at itch.io
Analyzing Pong
Pong was one of the first video games ever made and was the first successful commercial game. When Pong was first created the developers most likely struggled with the logic for the code, however, nowadays you can make a simple two player Pong with one method call, colliders, and sprites. Pong gets harder to create once the decision to make a one-player Pong is made. In this tutorial, we will create the base gameplay for Pong and break down a very simple AI alternative that still adds to gameplay value.
We must ask, what are the core elements of Pong gameplay? Here is a list with the answer to that question:
- Player Input – We want the player to be able to move their paddle up and down so that they can hit the ball.
- Ball Collision – When the ball hits the paddle or boundaries it can’t be allowed to lose any speed.
- Boundary Collision – The ball has to be able to bounce off of the top and bottom part of the screen so that it doesn’t leave the play area.
- Enemy AI – The gameplay value of the game would be next to zero if the enemy sat on the opposite end of the screen and didn’t move.
- Spawning the Ball – When the ball hits one of the boundaries behind the paddles we need it to respawn so that we can continue the game.
- Ball to Paddle Hit Area Detection – This allows for the ball to bounce off the paddle at unique angles so that we are able to to better aim the ball when it is hit with the paddle.
With this list we can beginning programming the game.
Note that any numbers used relating to a game object’s location, rotation, scale, etc are relative and may need to be changed for your specific setup.
Setting up the Game
Now that we’ve analyzed the fundamentals of Pong we can start setting up the game. Go ahead and open Unity and create a new 2D project. Once the editor opens, set the game screen’s aspect ratio to 4:3. We are using 4:3 since this is one of the most common screen ratios and is one of the closest ratios to being a standard. Inside of the Assets
pane create four folders named Scripts
, Sprites
, Prefabs
, and Materials
. These folders will be used to hold all of our game assets.
Download the appropriate images for the game and add them to the Sprites folder (this can be done via drag and drop). The images we just added will be the sprites, interactive game objects, used in the game.
We are going to need to change the pixels per unit for the sprites so that they conform to a standard. I generally use 64 pixels per unit as this gives most sprites a crisp clean look, and keeps their relative size. You can think of pixels per unit as the density of pixels allocated in a 1×1 space in Unity’s editor.
Let’s set the squares’ pixels per unit to 64 and the circle’s pixels per unit can be set to 128. We can go on and add the three images to the hierarchy pane.
Now we need to give each asset a name and set their initial properties and tags. You can name the blue block “Player” and set the player’s x-position to 6
and its x-scale to .2
.
We are going to need to create a tag to separate the paddle game objects from other game objects. In a broad sense, you can think of Tags
as categories for game objects. Click on “Untagged” (located under the player’s name) and select “Add Tag”. Create a new tag named “Paddle”, and re-select the player game object and set its tag to Paddle.
Name the red block “Enemy”. Set the enemy’s x-position to -6
and its x-scale to .2
. Make the enemy game object’s tag Paddle.
Name the grey circle “Ball” and go on and create a new tag named “Ball”. Make sure to set the ball game object’s tag to Ball.
Adding Player Input
In the Assets pane, open the Scripts folder and create a new C# script named “PlayerController”. Open the PlayerController script in your IDE and type:
//speed of player
public float speed = 10;
//bounds of player
public float topBound = 4.5F;
public float bottomBound = -4.5F;
// Use this for initialization
void Start () {
Time.timeScale = 0;
}
void Update(){
//pauses or plays game when player hits p
if(Input.GetKeyDown(KeyCode.P) && Time.timeScale == 0){
Time.timeScale = 1;
} else if(Input.GetKeyDown(KeyCode.P) && Time.timeScale == 1){
Time.timeScale = 0;
}
}
// Update is called once per frame
void FixedUpdate () {
//get player input and set speed
float movementSpeedY = speed * Input.GetAxis("Vertical") * Time.deltaTime;
transform.Translate(0, movementSpeedY, 0);
//set bounds of player
if(transform.position.y > topBound){
transform.position = new Vector3(transform.position.x, topBound, 0);
} else if(transform.position.y < bottomBound){
transform.position = new Vector3(transform.position.x, bottomBound, 0);
}
}
Add the PlayerController script to the Player game object in the hierarchy pane by dragging the script onto the game object. Select the Player game object and add a Box Collider 2D to the game object by clicking on “Add Component” at the bottom of the Inspector pane.
Ball Collision
Open the Materials folder inside of the Assets pane. You can create a new Physics2D Material by right-clicking and selecting Create -> Physics2D Material (as in image below).
Go ahead and name the new material “Bounce”, and set its bounciness to 1 and its friction to 0.
Select the Ball game object in the hierarchy pane. Add a Circle Collider 2D to the Ball game object. Drag the bounce material to the collider’s Material property in order to set the material to bounce. By giving the Ball game object the bounce material we make it so that when the Ball game object hits the boundaries it won’t lose any speed and will bounce off in the correct angle. We also need to add a Rigidbody 2D to the Ball game object and set gravity scale to 0, then check fixed angle.
Open the Scripts folder inside of the Assets pane and create a script named “BallController”. Open the BallController script inside of your IDE and type:
//speed of the ball
public float speed = 3.5F;
//the initial direction of the ball
private Vector2 spawnDir;
//ball's components
Rigidbody2D rig2D;
// Use this for initialization
void Start () {
//setting balls Rigidbody 2D
rig2D = this.gameObject.GetComponent<Rigidbody2D>();
//generating random number based on possible initial directions
int rand = Random.Range(1,4);
//setting initial direction
if(rand == 1){
spawnDir = new Vector2(1,1);
} else if(rand == 2){
spawnDir = new Vector2(1,-1);
} else if(rand == 3){
spawnDir = new Vector2(-1,-1);
} else if(rand == 4){
spawnDir = new Vector2(-1,1);
}
//moving ball in initial direction and adding speed
rig2D.velocity = (spawnDir*speed);
}
Boundary Collision
Inside of the hierarchy pane, create two empty game objects (right-click -> create empty), and name the new game objects “RightBound” and “TopBound”. Set the RightBound’s x-position to 7 and add a Box Collider 2D to the RightBound game object. In the Inspector pane check “Is Trigger” and set the collider’s y-size to 20. Duplicate the RightBound game object (select it and Command+D). Name the duplicate “LeftBound” and set its y-position to -7.
Next, select the TopBound game object and set its y-position to 5.5, and add a Box Collider 2D to the TopBound game object. Set the collider’s x-size to 20. Duplicate the TopBound game object and name the duplicate “BottomBound”. Set the BottomBound game object’s y-position to -5.5.
We want the left and right bounds to destroy the ball when the ball gets past one of the paddles. Inside of the Assets pane open the Scripts folder and create a new C# script named “BoundController”. Open BoundController inside of your favorite IDE and type:
void OnTriggerEnter2D(Collider2D other){
//Destroys gameobject if its tag is Ball
if(other.gameObject.tag == "Ball"){
Destroy(other.gameObject);
}
}
Add the BoundController script to the LeftBound game object and the RightBound game object.
Enemy AI
Select the Enemy game object and add a Box Collider 2D. Inside of the Assets pane open the Scripts folder. Create a new C# script named “EnemyController” and type:
//Speed of the enemy
public float speed = 1.75F;
//the ball
Transform ball;
//the ball's rigidbody 2D
Rigidbody2D ballRig2D;
//bounds of enemy
public float topBound = 4.5F;
public float bottomBound = -4.5F;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void FixedUpdate () {
//finding the ball
ball = GameObject.FindGameObjectWithTag("Ball").transform;
//setting the ball's rigidbody to a variable
ballRig2D = ball.GetComponent<Rigidbody2D>();
//checking x direction of the ball
if(ballRig2D.velocity.x < 0){
//checking y direction of ball
if(ball.position.y < this.transform.position.y){
//move ball down if lower than paddle
transform.Translate(Vector3.down*speed*Time.deltaTime);
} else if(ball.position.y > this.transform.position.y){
//move ball up if higher than paddle
transform.Translate(Vector3.up*speed*Time.deltaTime);
}
}
//set bounds of enemy
if(transform.position.y > topBound){
transform.position = new Vector3(transform.position.x, topBound, 0);
} else if(transform.position.y < bottomBound){
transform.position = new Vector3(transform.position.x, bottomBound, 0);
}
}
Note: 1.75 is used for the enemy’s speed solely because it makes the enemy slow enough not to be able to get to every ball that comes its direction.
Another Note: We used fixed update because this method is better for movement since it updates the game object at a fixed rate.
Spawning the Ball
Create an empty game object inside of the hierarchy pane and name it “BallSpawner”. Next, add the Ball game object to the Prefabs folder inside of the Assets pane. We can delete the Ball game object inside of the hierarchy pane.
Open the Scripts folder inside of the Assets pane and create a new C# script named “BallSpawnerController”. Open BallSpawnerController inside of your IDE and type:
public GameObject ball;
// Use this for initialization
void Start () {
GameObject ballClone;
ballClone = Instantiate(ball, this.transform.position, this.transform.rotation) as GameObject;
ballClone.transform.SetParent(this.transform);
}
// Update is called once per frame
void Update () {
if(transform.childCount == 0){
GameObject ballClone;
ballClone = Instantiate(ball, this.transform.position, this.transform.rotation) as GameObject;
ballClone.transform.SetParent(this.transform);
}
}
Add the BallSpawnerController script to the BallSpawner game object. Now we can drag the Ball game object from the Prefabs folder into the Ball value of the BallSpawnerController.
Adding Basic Text
We need to add some text to the screen telling players to hit P to play and pause the game so that players know how to start and pause the game.
We can create a text game object (in the hierarchy pane right-click -> UI -> Text), and name the text game object PlayText. Next let’s set its Text value to “Hit P to play or pause” and center align the text. Now we can align the game object to the top of the screen and then anchor it. Let’s set its width to 200 so that all of the text is visible. Finally, set the text color to white.
Save the scene and add it to the build settings (by dragging or checking add current scene) and you’re done!
Conclusion
You have now officially created a basic one-player Pong clone in Unity2D. For more practice try to think of ways to update the game and make it better – add accelleration to the ball (the more hits, the faster the ball), add inertia to paddles, add difficulty levels by increasing the enemy speed, etc.
You can download the project on GitHub