Unity 2D Pong Game



This Tutorial will show how to make a 2D Pong Game in the Unity Game Engine with only 38 lines of code. Everything will be explained step-by-step so everyone can understand it.

Here is a preview of the final game:



Foreword

Our two-dimensional Pong game will be inspired by the original Pong game from 1972.

We will begin by thinking about the game rules and taking a first look at the Unity engine.

Please note that the Microsoft Windows operating system was used for this tutorial, however this tutorial can be followed on Windows, Mac OS or Linux versions of Unity. If you use a different operating system, you will need to adapt the location of your project accordingly.

Game Concept



The game rules should be very similar to the original Pong. The right player scores a point if the ball hits the left wall. The left player scores a point if the ball hits the right wall. If it hits the top or bottom wall then it bounces off. Each player will have a racket that can be moved up and down to hit back the ball.

Our rackets should have an influence on the ball's outgoing angle:





If the racket hits the ball at the top corner, then it should bounce off towards our top border.

corner, then it should bounce off towards our top border. If the racket hits the ball at the center , then it should bounce off towards the right, and not up or down at all.

, then it should bounce off towards the right, and not up or down at all. If the racket hits the ball at the bottom corner, then it should bounce off towards our bottom border.

About the Unity Engine

In order to make our idea a real game, we will have to download and install the Unity Engine first. After clicking on the link, simply select the Download Unity Hub option. This will download the Unity Hub installer. Once the download has completed, simply open the downloaded file and follow the setup wizard, accepting the default options as you go. Please note that Mac OS and Linux users may have additional steps to complete the Unity Hub installation, which are outside the scope of this tutorial.

If Unity Hub does not automatically launch after installation completes, simply launch it from the Windows Start Menu or desktop icon (if one was created). If you're not using Windows, then you will need to adapt this step to your environment.

Installing Unity through the Unity Hub

Note: Please skip this section if you're already using Unity 2018.3 or above.

Once launched, you will be presented the Unity Hub. Unity Hub requires a Unity ID to get past the activation process and while we could have installed Unity 2018.3 manually, the Hub makes it much easier to get things rolling.

A Unity ID is free. Either create an ID or sign into a existing account and then you will be asked to locate or grab a version of Unity to kick things off. Click on the Download button as shown in the screenshot, and you will be presented with the list of Official Releases on the Unity download server. The version at time of writing we are going to install is Unity 2018.3.14f1. If a newer version of Unity is available that has the 2018.3 prefix, then you can safely use that version. Of course, newer versions of Unity can be used but unless you have a good reason to, stay on course.

Select the Download button next to the Unity 2018.3.14f1 option. This will present you with the following:

You don't need any of the extra platform options. If English is not your native language, you may want to take a language pack - some exist for Korean and Japanese, for example. For this tutorial, just click Done.

Your computer may ask you for your username and password as part of the setup process. On Windows, you may have to simply click Yes when it asks Do you want to allow Unity Hub to make changes to your device?. On Mac, it may ask you to enter your login username and password. This installation process will take a while, even on a really fast connection - so it's best you grab a drink before continuing.

If you haven't already installed Microsoft Visual Studio, you will probably find the Unity installation process will download and install it as well. You can disable this optional installation, but you will be missing out on a proper code editor. That said, you can still complete this tutorial using Notepad or Notepad++, it's just a matter of preference. Note: Visual Studio installation is outside the scope of this tutorial, but it should be easy enough to follow the on-screen prompts.

While Unity is downloading, let's talk about the Engine a bit. If someone would have to summarize Unity in one word, it would be simplicity.

Unity is the first Game Engine that is really simple to use (even for normal people), while still being perfectly suitable for professional games that demand high performance and realistic graphics. In Unity, everything is really simple - no matter if it's creating Animations, making a car explode, creating 2D and 3D Games or just making a world with realistic Physics.

One of the greatest features about Unity is the deployment: after creating our game once, we can deploy it to Windows, Mac and Linux desktop targets. Unity also supports mobile platforms such as Android and iOS as well as consoles such as the Nintendo 3DS and Switch, Xbox One, Playstation 4 and more. What usually takes months of hard work can be done with just one click in Unity.

The exact name of the engine is Unity3D, it does however support 2D Game Development since version 4.3. We will be using the 2018.3 version in this Tutorial.

Basically we are doing the same things over and over again in Unity: we create a Project, we put a few things into our Scene and move them around interactively until they are where we want them to be, and then we do some Scripting to implement game logic, respond to events or control our hero.

Unity's uses the C# language for scripting, which is what we will be using for this Tutorial.

Starting Unity

Once Unity has finished downloading and installing via the Unity Hub, simply click on "New" in the Unity Hub menu, and a screen like the following will appear:

As shown above, for the Project Name we will call it Pong. For Unity version, make sure 2018.3.14f1 is selected (remember, your screen will look different than the screenshot above). Make sure the Template is set to 2D and the location is set to somewhere accessible. Unity Analytics is not required so you can disable that option if it is enabled by default. Once you are happy, simply click Create Project.

Unity will take a few moments depending on your computer's hardware to get started and create a new blank project. It really is that easy!

Exploring Unity

Let's close the Welcome screen and get familiar with the Engine a bit. Here are the main Elements and what they do:



Hierarchy:

On the left side we can see the Hierarchy, which contains a list of all the things that are currently in our game. As we can see, currently it's only the Main Camera, which was added by Unity automatically to make our life easier.

Project Area:

The Project Area contains all kinds of Assets like Textures, 3D Models or Scripts. If we want, we can use those Assets in our game by dragging them into the Hierarchy or into the Scene.

Scene:

We can see the game world in the Scene view. We can find any game object in the Scene by double clicking it in the Hierarchy. To navigate around the Scene, we just click into it once and then use the left, right and middle mouse buttons. Don't be afraid to play around with it a bit until you've mastered it.

Inspector:

The Inspector shows the properties of the currently selected object(s). For example, if we select the Main Camera in the Hierarchy then we can see its Position, Rotation, Name and all other things that are relevant about it.

Unity uses a Component-Based Object system, much like other Game Engines. Everything is just an empty Object first. Then things are added to it, for example a Light, a Position, a Texture, a 3D Model and more. In case of the Camera, the Components are Transform, Camera and Audio Listener. All those Components are put onto one Object, and all together make our Main Camera which can view something, hear sounds, change its position and so on. Don't worry if this sounds confusing, we will do it over and over again and soon we will see that this is the easiest way possible to create games.

Moving, Rotating, Scaling and more:

Let's have some fun and move around the Camera. We can either change the position, rotate it or make it bigger (or smaller). That's what those buttons are for:



The Buttons are as follows:

- The Hand Icon (first button) allows you to look around the scene.

- The Cross-Arrows (second button) allows you to move objects around (Hotkey: W).

- The Circular Arrows (third button) allows you to rotate objects (Hotkey: E) on the X, Y and Z Axes.

- The Expanding Cube (or the box with 4 diagonal arrows) scales objects (Hotkey: R), so we can make them bigger or smaller.

- The Square with circular corners (fifth button) allows easy movement of UI Canvas elements in the 2D UI Canvas (Hotkey: T).

- The last (sixth) button is a new multi-purpose tool (Hotkey: Y) as it allows you to move, rotate and scale all in one go.

You probably don't need to worry about the forth, fifth or sixth buttons for this tutorial. Just click one of the other Buttons and then interact with the Blue/Green/Red lines around the Camera in the Scene View:



The Play Button

Let's run our game and see how it looks by pressing the Play Button:



When pressing Play, we always see the Game World through the eyes of our Camera. So far we only see the blue background because there is nothing in front of the camera yet.

Note: to go back to the editing mode, press the Play button again (to stop playing).

That's it, now we know everything about Unity that we need to know to make a 2D Pong Game. Since Unity has already given us a sample scene, go to the File Menu, and choose New Scene. If asked if you want to save changes, choose No or Discard. Then on the newly created scene, save the scene (Top Menu: File -> Save Scene). When prompted for a filename, call the scene MainScene.

Camera Setup

Alright, we will begin by modifying the Camera so that it shows the game in the correct size and with the right background color. We can modify the Camera settings by first selecting it in the Hierarchy:



Afterwards we can see the settings in the Inspector. We will change the Background color to black and adjust the Size so that it fits our game later on:



Creating the Walls

The Wall Texture

Let's add four walls to our game. All we need to make a wall is a so called Sprite, or in other words: a Texture.

We will use one horizontal Sprite for the top and bottom walls and one vertical Sprite for the left and right walls:



WallHorizontal.png

WallVertical.png

Note: right click each image, select Save As... and save them all in the project's Assets folder.

Alright now we can see them in Unity's Project Area:



Note: select Assets instead of Favorites in the Project area in case you don't see them.

The Wall Import Settings

Let's select both wall images and then take a look at the Inspector, where we will apply the following Import Settings:



Note: the Filter Mode and Format can be used to decide between quality and performance. A Pixels Per Unit value of 1 means that 1 x 1 pixels will fit into one unit in the game world. We will use this value for all our textures, because the ball will be 1 x 1 pixels later on, which should be exactly one unit in the game world.

Modifying the Import Settings might seem like a weird thing to do if you are new to Unity. As a matter of fact, our game would work just fine without ever touching the Import Settings at all. However in 2D games it's usually a good idea to modify those settings so that the world size is something reasonable (we don't want a 100 meter huge racket, this could make the physics somewhat tricky).

Adding the Walls to the Game World

So in order to add the Walls to our game, all we have to do is drag them from the Project Area into the Scene:



We will drag each texture into the Scene twice so that we have 4 walls:



Afterwards we position the walls so that they look like a rectangle with the Camera in the center:



Note: we can position the walls by either dragging them around, or by selecting them and then changing their Position in the Inspector.

Renaming the Walls

We will also rename the Walls to WallLeft, WallRight, WallTop and WallBottom so that we don't lose the overview later on. Renaming is very easy, all we have to do is right click a wall in the Hierarchy and select Rename:



Here is what our Hierarchy looks like afterwards:



Wall Physics

Right now we can see the walls in the game, but they aren't real walls yet. They are just images in the game world, a purely visual effect.

We want the walls to be real walls so that the Rackets and the Ball will collide with them instead of just going right through them.

Unity comes with an incredibly powerful physics engine, and all that we have to do is tell Unity that our walls are supposed to be Colliders. We will select all the walls in the Hierarchy:



Afterwards we click on the Add Component button in the Inspector and then select Physics 2D -> Box Collider 2D, or thanks to Unity's search option we simply start typing box:



Note: whatever we do in the Inspector will be done for all objects that are selected in the Hierarchy. And because we selected all four walls, they will all have a Collider now.

Now we can see that all our walls have a Box Collider 2D component in the Inspector:



Note: the -- values are the ones that are different between the selected GameObjects.

If we take a look at the Scene then we can also see that each wall is now surrounded by a green rectangle:



Note: the green rectangles are the colliders. They are only shown in the Scene and not in the final game.

We can also select only a single wall to see all the values correctly:



Adding the Dotted Line

Alright, if you got this far, then good work! Let's add the dotted line in the middle. We will use the following texture:



DottedLine.png

Note: right click on the image, select Save As... and save it in the project's Assets folder.

We will select it in the Project Area and then apply the same Import Settings that we used before:



Afterwards we can drag it from the Project Area into the Scene. We will position it in the middle of the game:



Note: the dotted line is a great example to understand how Unity's Physics work. Right now the dotted line is just a texture. Just something that we can see. The ball will not collide with the dotted line unless we add a Collider to it (which we won't, because the ball is not supposed to collide with it).

Creating the Rackets

The Racket Texture

We will use yet another white texture for the rackets:



Racket.png

Note: right click on the image, select Save As... and save it in the project's Assets folder.

We will use the following Import Settings for it:



Our game will have two players, one on the left and one on the right. So let's drag the racket into the game twice and position it once on the left and once on the right:



Renaming the Rackets

Again to make our lives easier later on, we will rename the two rackets to RacketLeft and RacketRight in the Hierarchy:



Racket Physics

Okay so our Rackets should make use of Unity's Physics Engine. At first they should be able to collide with the wall, hence why we add colliders again by selecting both Rackets in the Hierarchy, then pressing Add Component -> Physics 2D -> Box Collider 2D in the Inspector so they look like the screenshot that follows. If you did the search trick as mentioned earlier, Box Collider 2D will appear already.



The player should also be able to move the Racket upwards and downwards later on. But the Rackets should stop moving upwards (or downwards) when they collide with a wall.

What sounds like some complicated math will be ridiculously easy in Unity, because a Rigidbody does just that. It always adjusts an object's position so it's physically correct. For example, it can automatically apply gravity to the object, or it can make sure that our Rackets will never move through a wall.

Note: as a rule of thumb, everything physical that moves through the game world will need a Rigidbody.

To add a Rigidbody to our Rackets we just select both of them in the Hierarchy again, then take a look in the Inspector and press Add Component -> Physics 2D -> Rigidbody 2D. We then modify the Rigidbody 2D to disable Gravity (because there is no gravity in a pong game) by setting Gravity Scale to 0, enable Freeze Rotation Z (the rackets should never rotate) under Constraints. Then we set the Collision Detection to Continuous and enable Interpolation by selecting Interpolate from the drop-down that appears next to Interpolate. This ensures that the physics are as exact as possible:



Racket Movement

Let's make sure that players can move their rackets. That kind of custom behavior usually requires Scripting. With both rackets still selected, we will click on Add Component -> New Script and finally name it MoveRacket.



Afterwards we can double click the Script in the Project Area in order to open it:



Here is what our Script currently looks like:



using System.Collections ;

using UnityEngine ;



public class MoveRacket : MonoBehaviour

{

// Start is called before the first frame update

void Start ( )

{



}



// Update is called once per frame

void Update ( )

{



}

}

The Start function is automatically called by Unity when starting the game. The Update function is automatically called over and over again, roughly 60 times per second.

But there is another Update function, it's called FixedUpdate. This one is also called over and over again, but in a fixed time interval. Unity's Physics are calculated in the exact same time interval, so it's usually a good idea to use FixedUpdate when doing Physics stuff (we want to move Rackets that have Colliders and RigidBodys, hence Physics stuff).

Okay so let's remove the Start and Update functions and create a FixedUpdate function instead:



using System.Collections ;

using UnityEngine ;



public class MoveRacket : MonoBehaviour

{

void FixedUpdate ( )

{



}

}

Note: it's important that we name it exactly FixedUpdate because this is the name that Unity expects. We can also make functions with different names, but they wouldn't automatically be called by Unity then.

The rackets have a Rigidbody component and we will use the Rigidbody's velocity property for movement. The velocity is always the movement direction multiplied by the speed.

The direction will be a Vector2 with a x component (horizontal direction) and a y component (vertical direction). The following image shows a few Vector2 examples:



The rackets should only move upwards and downwards, which means that the x component will always be 0 and the y component will be 1 for upwards, -1 for downwards or 0 for not moving.

The y value depends on the user input. We could either check for all kinds of key presses (wsad, arrow keys, gamepad sticks and so on), or we could simply use Unity's GetAxisRaw function:



using System.Collections ;

using UnityEngine ;



public class MoveRacket : MonoBehaviour

{

void FixedUpdate ( )

{

float v = Input . GetAxisRaw ( "Vertical" ) ;

}

}

Now we can use GetComponent to access the racket's Rigidbody component and then set its velocity:



using System.Collections ;

using UnityEngine ;



public class MoveRacket : MonoBehaviour

{

void FixedUpdate ( )

{

float v = Input . GetAxisRaw ( "Vertical" ) ;

GetComponent < Rigidbody2D > ( ) . velocity = new Vector2 ( 0 , v ) ;

}

}

We will also add a speed variable to our Script, so that we can control the racket's movement speed:



using System.Collections ;

using UnityEngine ;



public class MoveRacket : MonoBehaviour

{

public float speed = 30 ;



void FixedUpdate ( )

{

float v = Input . GetAxisRaw ( "Vertical" ) ;

GetComponent < Rigidbody2D > ( ) . velocity = new Vector2 ( 0 , v ) ;

}

}

We made the speed variable public so that we can always modify it in the Inspector without changing the Script:



Now we can modify our Script to make use of the speed variable:



using System.Collections ;

using UnityEngine ;



public class MoveRacket : MonoBehaviour

{

public float speed = 30 ;

void FixedUpdate ( )

{

float v = Input . GetAxisRaw ( "Vertical" ) ;

GetComponent < Rigidbody2D > ( ) . velocity = new Vector2 ( 0 , v ) * speed ;

}

}

If we save the Script and press Play then we can now move the rackets:



There is just one problem, we can't move the rackets separately yet.

Adding a Movement Axis

Right now, both our Scripts check the "Vertical" Input axis for the movement calculations. Let's create a new Axis variable so that we can change the Input Axis in the Inspector:



using System.Collections ;

using UnityEngine ;



public class MoveRacket : MonoBehaviour

{

public float speed = 30 ;

public string axis = "Vertical" ;



void FixedUpdate ( )

{

float v = Input . GetAxisRaw ( axis ) ;

GetComponent < Rigidbody2D > ( ) . velocity = new Vector2 ( 0 , v ) * speed ;

}

}

Let's select Edit -> Project Settings. This will bring up a pop-up window with a variety of settings you can tweak, as shown below. From the left-side menu, select Input. This will show something like the following:



Here we can modify the current Vertical axis so that it only uses the W and S keys. We will also make it use only Joystick 1. Click the first Vertical entry (usually straight under Horizontal) and make it look like the following:



Now we will increase the Size by one in order to add a new axis:



We will name it Vertical2 and modify it accordingly:



Afterwards we will select the RacketRight GameObject and change the MoveRacket Script's Axis property to Vertical2:



If we press Play then we can now move the rackets seperately:



Note: instead of using axes, we could also create a up and down key variable in our Script and then set it to w/s for the left racket and UpArrow/DownArrow for the right racket. However by using axes we end up with far less code and perfect gamepad/joystick/keyboard support.

Creating the Ball

The Ball Texture

Almost there! Creating the Ball will be easy again. At first we save the following texture to our Project's Assets folder:



Ball.png

Note: right click on the image, select Save As... and save it in the project's Assets folder.

We will use the following Import Settings for it:



Now we can drag it from the Project Area into the middle of the Scene:



The Ball Collider

Our Ball should make use of Unity's Physics again, so let's select the Ball object if it's not already selected and choose Add Component -> Physics 2D -> Box Collider 2D to add a Collider:



Our ball is supposed to bounce off walls. For example when flying directly towards a wall it should bounce off in the exact opposite direction. When flying in a 45° angle towards a wall, it should bounce off in a -45° angle and so on.

This sounds like some complicated math that could be done with Scripting. But since we are lazy, we will just let Unity take care of the bouncing off thing by assigning a Physics Material to the Collider that makes it bounce off things all the time.

At first we right click in our Project Area and selected Create -> Physics 2D Material which we will name BallMaterial:



Now we can modify it in the Inspector to make it bounce off:



Then we drag the material from the Project Area into the Material slot of the Ball's Collider:



And that's all. Now the ball will bounce off in case it collides with things in the game.

The Ball Rigidbody

In order to make our ball move through the game world, we will add a Rigidbody2D to it again by selecting Add Component -> Physics 2D -> Rigidbody 2D.

Note: remember, every Physics thing that is supposed to move through the game world will need a Rigidbody.

We will modify the Rigidbody component in several ways:



We don't want it to use Gravity

We want it to have a very small Mass so it doesn't push away the Rackets when colliding

We don't want it to rotate

We use Interpolate and Continous Collision Detection for exact physics



Note: those modifications are not very obvious to beginners. The usual workflow is to add a Rigidbody, test the game and then modify the Rigidbody in case of undesired effects like a too big mass.

Okay so there is one more thing to do before we see some cool ball movement. We will select Add Component -> New Script and name it Ball. Afterwards we will double click the Script in the Project Area in order to open it:



using UnityEngine ;

using System.Collections ;

using System.Collections.Generic ;



public class Ball : MonoBehaviour {



// Use this for initialization

void Start ( ) {



}



// Update is called once per frame

void Update ( ) {



}

}

Let's remove the Update function because we won't need it. Instead we will use the Start function to give the ball some initial velocity. Yet again we will use a direction multiplied by a speed:



using UnityEngine ;

using System.Collections ;



public class Ball : MonoBehaviour {

public float speed = 30 ;



void Start ( ) {

// Initial Velocity

GetComponent < Rigidbody2D > ( ) . velocity = Vector2 . right * speed ;

}

}

Now if we press play, we can see the Ball bounce off the walls and rackets:



Again, we didn't have to worry about any complicated math. Unity took care of it for us with its powerful Physics engine.

The Ball Racket Collision Angle

Our game already looks a lot like Pong, but there is one more important modification to be done. We explained in the very beginning that the ball's outgoing angle should depend on where it hit the racket:



This way the players can shoot the ball into whatever direction they please, which adds a huge tactical component to the game.

Let's modify our Ball Script to use the OnCollisionEnter2D function that is automatically called by Unity upon colliding with something else:



using UnityEngine ;

using System.Collections ;



public class Ball : MonoBehaviour {

public float speed = 30 ;



void Start ( ) {

// Initial Velocity

GetComponent < Rigidbody2D > ( ) . velocity = Vector2 . right * speed ;

}



void OnCollisionEnter2D ( Collision2D col ) {

// Note: 'col' holds the collision information. If the

// Ball collided with a racket, then:

// col.gameObject is the racket

// col.transform.position is the racket's position

// col.collider is the racket's collider

}

}

So now we need a function that calculates the ball's velocity depending on where it hit the racket. The following image shows the Vector2 for several movement directions again:



Now the x value is obvious, it's -1 in case it bounces off the right racket and it's 1 in case it bounces off the left racket. What we need to think about is the y value, which will be somewhere between -1 and 1. All we really need to calculate is this:



|| 1 <- at the top of the racket

||

|| 0 <- at the middle of the racket

||

|| -1 <- at the bottom of the racket

Or in other words: we just have to find out where the ball is in relation to the racket. Or in other words: we just have to divide the ball's y coordinate by the racket's height. Here is our function:



float hitFactor ( Vector2 ballPos, Vector2 racketPos,

float racketHeight ) {

// ascii art:

// || 1 <- at the top of the racket

// ||

// || 0 <- at the middle of the racket

// ||

// || -1 <- at the bottom of the racket

return ( ballPos . y - racketPos . y ) / racketHeight ;

}

Note: we subtract the racketPos.y from the ballPos.y to have a relative position.

Here is how our final OnCollisionEnter2D function looks:



void OnCollisionEnter2D ( Collision2D col ) {

// Note: 'col' holds the collision information. If the

// Ball collided with a racket, then:

// col.gameObject is the racket

// col.transform.position is the racket's position

// col.collider is the racket's collider



// Hit the left Racket?

if ( col . gameObject . name == "RacketLeft" ) {

// Calculate hit Factor

float y = hitFactor ( transform . position ,

col . transform . position ,

col . collider . bounds . size . y ) ;



// Calculate direction, make length=1 via .normalized

Vector2 dir = new Vector2 ( 1 , y ) . normalized ;



// Set Velocity with dir * speed

GetComponent < Rigidbody2D > ( ) . velocity = dir * speed ;

}



// Hit the right Racket?

if ( col . gameObject . name == "RacketRight" ) {

// Calculate hit Factor

float y = hitFactor ( transform . position ,

col . transform . position ,

col . collider . bounds . size . y ) ;



// Calculate direction, make length=1 via .normalized

Vector2 dir = new Vector2 ( - 1 , y ) . normalized ;



// Set Velocity with dir * speed

GetComponent < Rigidbody2D > ( ) . velocity = dir * speed ;

}

}

Note: please read through the comments in order to understand what's going on.

If we press play, we can now influence the ball's bouncing direction depending on where we hit it with the Racket.

Summary

Congratulations for making it this far!

In this Tutorial we learned how to install and use Unity, create a basic Scene with just some textures, use Unity's 2D Physics engine and create Scripts to add custom game mechanics.

When making further improvements to the game, always remember: Unity is simple, you can do almost everything with just a few mouse clicks or a few lines of C# code. There are tons of features that can be added to make the game as fun as possible:



Add a Trail Effect like shown at the top

Add the old Pong Sound that we all love

Add a Score

Increase the Ball's speed over time

Add an AI enemy

Add a menu and a credits screen

The Project Files can be downloaded here. If you get a prompt to upgrade the Unity project from Unity or the Unity Hub, simply click Upgrade.