Menu

Beginner’s Guide: Create a Pong Clone in Unity: Part 7

January 13, 2015 - Pong Tutorial

What’s the Score?

Our game is missing something vital…a way to know who is the best player…a way for one player to beat the other…a game has to have a winner! In this part we’ll add scoring to our game. Here’s how we’ll do it: 

Goals

Adding goals uses some of the techniques we’ve already covered, so we’re going to get a bit of practice using GameObjects, collisions, and a little scripting.

Create a Goal Prefab

The Color property is on the SpriteRenderer component in the Inspector.

If you look closely in the Scene window you’ll see that the sprite is a different shape to the collider (the collider is the green square):


This is because we added the collider first (if you add the sprite first, the collider will automatically shape itself to the sprite).

To fix this:


Resetting the collider sets it to match the sprite shape.

Finally, move the goal so it overlaps one of the walls and stretch its height (just change the transform Y scale to something around 2.2). Place it so it slightly over the edge of the wall (so the ball can hit the goal) like in the image below (my goal is green and on the left). Change the transform’s Y position to zero to ensure the goal is centred.

You will notice the goal is on top of the wall, covering it where they overlap. This is because of Unity’s drawing order layers. To make the goal draw under the wall, change the Order in Layer property on the Sprite Renderer to -1.


Despite its name, GoalPrefab is currently just a GameObject, so turn it into a prefab by dragging it into the Prefabs folder, then delete the original GameObject from the Hierarchy.

Now make two copies of GoalPrefab in the scene Hierarchy and place them both appropriately (they will default to the position of the original, so move one to the opposite side of the screen by reversing its transform’s X position like we did with the walls earlier, e.g. from -7.02 to 7.02). Rename the two goals to Player1Goal and Player2Goal. Create another empty GameObject called Goals and place both goals inside.

Game Manager

 Edit GameManagerScript

Open GameManagerScript for editing. Start by adding these variables.

We can declare two variables of the same type in a single line by separating them with a comma.
int playerOneScore, playerTwoScore;

Set them to start at zero by adding these lines to the Start() method:

playerOneScore = 0;
playerTwoScore = 0;

Also add the following method:

public void GoalScored(int playerNumber)
{
    // increase the score for whichever player scored
    if(playerNumber == 1)
        playerOneScore++;
    else if (playerNumber ==2)
        playerTwoScore++;
    
    // now check if the player has won
    if(playerOneScore == 3)
        GameOver(1);
    else if (playerTwoScore ==3)
        GameOver(2);
    
} 

For the non-programmers I’ll explain that code. We made this method public so it can be called from other scripts, and we gave it a parameter playerNumber, which we will use to tell this method who scored (i.e. player 1 or player 2).

playerOneScore++ is a shorthand way of writing playerOneScore = playerOneScore + 1
(i.e. we are increasing the value by 1).

We will make our game a ‘best of 5’ (or ‘first to 3’) competition, so this is why we call a method called GameOver() when a player reaches a score of 3 (e.g. if (playerTwoScore ==3)).

We call GameOver() in the above method, so we need to create it (your script editor will probably mark those lines as errors at the moment). For now we’ll just restart the game, and we’ll add some more code to this later.

Firstly, add this variable so our script can communicate with the ball:

[SerializeField]
BallScript gameBall;

Add the following method to GameManagerScript:

void GameOver(int winner)
{
    // this is called when a player reaches 3 points 

    // reset the scores
    playerOneScore = 0;
    playerTwoScore = 0; 
    gameBall.Reset ();
}
That final line (gameBall.Reset();) will be flagged as an error until we create the Reset() method in BallScript.

Open BallScript and add this Reset() method:

public void Reset()
{
    // reset the ball position and restart the ball movement
    myBody.velocity = Vector2.zero;
    transform.position = new Vector2(0,0);
    Start(); // restart the ball 
}

This method halts the ball’s movement by setting its rigidbody’s velocity to Vector2.zero (meaning its velocity will be (0, 0) – no movement on either axis). Then the ball’s position is changed to (0, 0), which is the centre of the screen. Then finally, Start() is called, which sets the game off again.

We will need to add more here so the game doesn’t just suddenly restart, but for now we’ll leave it as it is.

Now we need to populate that gameBall variable in the GameManager script.

Make sure your scripts are saved or Unity might not realise you’ve fixed the errors pertaining to the missing methods you’ve just added.

Unity is clever enough to infer that we need a component of type BallScript, so it finds the BallScript in the Ball GameObject and adds it to the field. We now have Ball‘s instance of BallScript referenced in GameManagerScript.

 

GoalScript

Our goal script will be quite simple. It will detect when the ball comes into contact with the goal and then tell GameManagerScript that a goal has been scored. We also need to differentiate between the two different goals so we know who scored.

Open GoalScript and add the following variables:

[SerializeField]
int attackingPlayer; // which player scores into this goal
[SerializeField]
GameManagerScript gameMan; // this will hold a reference to the game manager script

Next, add some behaviour to detect when the ball collides with the goal. This is very similar to the collision detection we did on the paddles:

void OnCollisionEnter2D(Collision2D other)
{
    if(other.transform.name == "Ball")
    {
        gameMan.GoalScored(attackingPlayer);
    }
} 

When the ball collides with the goal, GoalScored() is called in GameManagerScript, and we pass which player scored by using attackingPlayer
as a parameter.

Reference GameManagerScript in the Goals

Now, set the correct player values in the Attacking Player Inspector fields for each goal. Note that the goal behind player 1 needs the value 2 because player 2 will be scoring points in that goal (and vice versa).
Save the scene and the project, then playtest the game. Though we can’t yet see the score, when a player gets to three points the game will reset.

Recap

We did a lot of scripting in Part 7, and by now you should be getting used to attaching scripts to GameObjects and using public/serialized fields to add components and values to a script instance (like when we added Attacking Player values to the goals). We also got some practice at communicating between scripts by referencing a script and calling its public methods. We now have scoring system for our game!

Continue to Part 8 where we will work on our game’s user interface by adding a score display and a menu screen, all using Unity great GUI system.

Tags: ,

16 thoughts on “Beginner’s Guide: Create a Pong Clone in Unity: Part 7

  • Pingback: Beginner’s Guide: Create a Pong Clone in Unity: Part 6 « Unity for All Unity for All

  • Pingback: Beginner’s Guide: Create a Pong Clone in Unity: Part 8 « Unity for All Unity for All

  • Pingback: Complete Unity Pong Clone Tutorial for Beginners « Unity for All Unity for All

  • Pingback: Beginner’s Guide: Create a Pong Clone in Unity: Part 6 | Unity for All

  • Some Guy

    So i downloaded the scripts from the completed project and when i try to add the script it says it has to be fixed.

    Reply
      Damien

      That will be because Unity had made some changes since this tutorial was written. Unity should be able to automatically fix everything. There are a couple of very minor changes to the way certain things are scripted, but none of the fundamentals have changed. I plan to update this tutorial series when the next major Unity update (5.4) is released.

      Reply
        Some Guy

        Ok thanks for telling me. Please notify me when the new tutorial is out

        Reply
    Gemayel

    Hi Damien,
    I seem to be getting errors using your if statement for GoalScored so l can not process further. I have even cut and pasted it into the GameManager script but it has not worked. I am using Unity 5.5 1f1 personal. Unity is showing the following:
    1. Assets/Assets/Scripts/GameManager.cs(16,1): error CS1525: Unexpected symbol `public’
    2. Assets/Assets/Scripts/GameManager.cs(16,9): error CS1547: Keyword `void’ cannot be used in this context
    3. Assets/Assets/Scripts/GameManager.cs(16,23): error CS1525: Unexpected symbol `(‘
    4. Assets/Assets/Scripts/GameManager.cs(21,3): error CS1525: Unexpected symbol `else’
    5. Assets/Assets/Scripts/GameManager.cs(49,1): error CS1525: Unexpected symbol `void’

    Reply
      Damien

      For errors like that there is most likely something missing, like a bracket or semi-colon.

      Double-check your script and make sure all brackets are correctly paired. You may have pasted over a bracket or semi-colon, or pasted in slightly the wrong place.

      Find the ‘public’ on line 16 – there is probably something missing just before that – a closing bracket for the previous method probably. It’s also possible that a semi-colon is missing from the end of a line.

      You could also download the finished project and check that part of the code.

      If you’re still stuck, paste your whole GameManager script, and I’ll see if I can find the issue.

      Reply
        Gemayel

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;

        public class GameManager : MonoBehaviour {

        // Use this for initialization
        void Start () {

        int playerOneScore, playerTwoScore;

        playerOneScore = 0;
        playerTwoScore = 0;

        public void GoalScored(int playerNumber)
        {
        // increase the score for whichever player scored
        if(playerNumber == 1)
        playerOneScore++;
        else if (playerNumber ==2)
        playerTwoScore++;

        // now check if the player has won
        if(playerOneScore == 3)
        GameOver(1);
        else if (playerTwoScore ==3)
        GameOver(2);

        }

        [SerializeField]
        BallScript gameBall;

        void GameOver(int winner)
        {
        // this is called when a player reaches 3 points

        // reset the scores
        playerOneScore = 0;
        playerTwoScore = 0;
        gameBall.Reset ();
        }
        }

        // Update is called once per frame
        void Update () {

        }
        }

        Reply
          Damien

          There is s close curly bracket ( ‘}’) missing at the end of the Start() method.

          Place a closing curly bracket after playerTwoScore = 0;

          Reply
    lily

    Hi Damien,
    Thank you so much for making this best tutorial ever , I followed your direction make my fist game,so far so good!
    I have one problem that is on part 7
    “Select GameManager in Hierarchy.
    Drag Ball from the Hierarchy over to the empty Game Ball field in the Game Manager Script.”
    I did not find “empty Game Ball field in the Game Manager Script”, so I stuck here , please help me.
    lily

    Reply
      Damien

      Do you have this:

      [SerializeField]
      BallScript gameBall;

      In your GameManager script? If you don’t have the [SerializeField] part, the field will not be visible in the Inspector.

      Another possibility is that you have that in your script but Unity is unable to compile the script due to an error elsewhere. Make sure there is no error in the Console window.

      Reply
        Leo

        Hello, I’m having the same issue as lily. It doesn’t show an error anywhere so I was wondering how/if you were able to fix the issue?

        Reply
    Most

    I have a problem. The GoalSprite is not rendered somehow.

    Reply
      Anonymous

      Make sure the goal sprite is not behind the wall.Check the sprite’s layer and order in layer properties.

      Reply

    Leave a Reply

    Your email address will not be published.