In this tutorial series, we'll show you how to recreate the classic Arkanoid (or Breakout) game in Unity, using Unity's native 2D tools. In each post, we'll focus on a specific part of the game; in this post, we'll get the paddle and the ball moving.
Where We Left Off
In the previous tutorial, you set up the project, imported the assets, and prepared the scene for the first level. If you haven't completed the previous tutorial we strongly recommend you to do so before starting this one.
Final Preview
Take a look at this demo to see what we're aiming for across the whole series:
And here's what we'll have at the end of this part:
Player Movement
You already have the player's paddle in the interface; now you must interact with it. You will move it in a horizontal way, left and right. In order to create that movement by input, you need to create a Script.
Scripts are snippets of programming code that do specific tasks. Unity can handle three programming languages: Boo, C#, and JavaScript. All scripts created in this tutorial will be written in C# (but feel free to implement them in another language if you prefer).
To create a script, go to your Project tab, select the folder Scripts, and right-click it to pop up a menu. Select Create > C# Script. A new file called NewBehaviourScript
will appear; rename it to PlayerScript
. In the Inspector tab you will see the contents of the script itself.
Double-click the script to edit it. The MonoDevelop IDE will open by default, but you can configure another code editor to open it if you prefer.
using UnityEngine; using System.Collections; public class NewBehaviourScript : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }
All Unity scripts will have two methods by default:
Start()
: used to initialise any variables or parameters that we need in our code.Update()
: called every single frame of the game, and so used to update the game state.
To move the player, you need two pieces of information:
- player position
- player velocity
Thus, create two variables (global class variables) to store that information:
public float playerVelocity; private Vector3 playerPosition;
As you may have noticed, playerVelocity
is a public variable, while playerPosition
is private. Why did we do this? Well, Unity allows you to change the values of public variables inside the editor without the need to change the actual code; this is very useful when you have parameters that need to be continuously adjusted. The velocity is one of those cases, so we make it public.
Save the script (in MonoDevelop) and move to the Unity editor again.
You now have the script; you just need to assign it to the player's paddle object. Select the paddle from the Hierarchy tab, and in the Inspector, click Add Component. Now type the name of the script you just saved and add it to the game object.
For more information on the concept of Components and how they work in Unity, see Unity: Now You're Thinking With Components.
Another way to add the script to the component would be to drag it from its folder and drop it over the Add Component area. You should see something like this in your Inspector:
As you can see, since we left the playerVelocity
variable public, you can now set its value in the editor. For now, set the value to 0.3
and return to the code.
You will now initialise the playerPosition
. To do this, you have to access the game object's position in the script's Start
method:
// Use this for initialization void Start () { // get the initial position of the game object playerPosition = gameObject.transform.position; }
Now we have defined the paddle's initial position and velocity, but we still have to move it. For that, we will edit the Update
method.
Since we only want to object to move horizontally, we can use the GetAxis
method of the Input
class to find which direction the player's input is pointing along the horizontal axis (-1
for left, +1
for right), multiply this value by the velocity, add this value to the paddle's current X-position, and update the current playerPosition
to match.
While we're dealing with input, we also want to make sure the game exits when the player press the Esc key.
The complete snippet is below:
using UnityEngine; using System.Collections; public class PlayerScript : MonoBehaviour { public float playerVelocity; private Vector3 playerPosition; // Use this for initialization void Start () { // get the initial position of the game object playerPosition = gameObject.transform.position; } // Update is called once per frame void Update () { // horizontal movement playerPosition.x += Input.GetAxis ("Horizontal") * playerVelocity; // leave the game if (Input.GetKeyDown(KeyCode.Escape)){ Application.Quit(); } // update the game object transform transform.position = playerPosition; } }
Save the script and return to the Unity editor. Now press Play and try to move the player with the left and right arrows.
Defining the Game Area
You probably noticed that the paddle can move outside the level. This happens because there are no boundaries defined for the game area.
To create the boundary, first create another public variable—call it boundary
.
This variable will store the maximum X position that the paddle can move to. Since we're going to build the level in a symmetrical form around the position (0, 0, 0)
, this means that the absolute boundary value will be the same for both positive and negative X.
To avoid letting the player cross the boundary, we'll add a couple of if
conditions. Basically, we'll say that if the x value of the position is greater than the boundary value, then the x value will be set equal to the boundary. This way, we ensure that the paddle can never leave the game area. The relevant code is below:
using UnityEngine; using System.Collections; public class PlayerScript : MonoBehaviour { public float playerVelocity; private Vector3 playerPosition; public float boundary; // Use this for initialization void Start () { // get the initial position of the game object playerPosition = gameObject.transform.position; } // Update is called once per frame void Update () { // horizontal movement playerPosition.x += Input.GetAxis("Horizontal") * playerVelocity; // leave the game if (Input.GetKeyDown(KeyCode.Escape)){ Application.Quit(); } // update the game object transform transform.position = playerPosition; // boundaries if (playerPosition.x < -boundary) { transform.position = new Vector3 (-boundary, playerPosition.y, playerPosition.z); } if (playerPosition.x > boundary) { transform.position = new Vector3(boundary, playerPosition.y, playerPosition.z); } } }
Now, go back to the editor and work out the best value for the boundary
. By dragging the paddle's position on the X-axis, you can see that the perfect value is 5.46
(in our project at least).
In the Inspector, reset the X value of the paddle position to 0
, and input 5.46
on the Boundary parameter under the Player Script component.
Press Play in the editor. Now you can move the paddle, but only within the game area.
Creating Physical Properties
In this particular game, we need to create physical properties for three components: the paddle, the ball, and the walls. Since we are creating a 2D game, we will use and apply 2D colliders. (A collider is a particular type of component that allows their associated objects to react to other colliders.)
Start by adding a collider to our paddle. In the Hierarchy, select our paddle object. Move to the Inspector, click Add Component and type collider
. As you can see in the screenshot below, several types of colliders are available for you to use. Each individual collider has specific properties that resemble the related object.
Since the player has a rectangle format, we'll use the Box Collider 2D. Select it, and the component will be automatically added to your player object. The values for the size and the center position of the collider are already defined; these default values will work for you, so you don't need to change them.
Now, do the same process for the three bars and the block. (Hierarchy > Inspector > Add Component > Box Collider 2D).
All your game objects now have colliders, expect the ball. Since the ball has a different shape, you will have to use a different collider. Select it and, in the Inspector tab, add the component Circle Collider 2D.
The Circle Collider 2D is similar to the Box Collider, except that instead of the Size parameter that defines the width and height of the box, it has a Radius parameter that defines the radius of the circle.
Bouncing Material
In order to make the ball bounce, we need to create a bouncing physical material and attach it to the ball. Unity already has that specific material created, so we just need to add it.
On the Project tab, create a new folder inside the Asset folder and name it Physics
. Select this folder, click Create, and choose Physics2D Material. Name your new physics material BallPhysicsMaterial
.
The material has two parameters: Friction and Bounciness. Since you want a pure bouncy behaviour, you have to change the values accordingly: 0
for Friction, 1
for Bounciness.
Now that you have the material ready, apply it to the ball. Select the ball game object on the Hierarchy tab and move to the Inspector. If you take a closer look at your Circle Collider 2D, you will notice that this component has a parameter called Material; here, you can attach any physical material you want to that object.
To attach the BallPhysicsMaterial, just select it and drag it to the Material parameter on the collider.
Adding a Rigid Body
In order for the ball to move under the control of physics, we need to add one last thing: a rigidbody component. Select the ball object and add the component RigidBody 2D. This component has several parameters that can be adjusted. Since the ball will only move by bouncing, you should change the Gravity Scale parameter from 1
to 0
—this way, we ensure that the ball is not affected by gravity. The rest of the parameters can be left as default:
Programming the Ball Object
The ball will be completely dynamic, so we need to create a custom script for that behaviour.
Create a new script (we'll use C# again) and name it BallScript
. Assign that new script to the ball object (Hierarchy > Inspector > Add Component).
Now, lets analyze the rules and properties of the ball, before coding them into the script:
- The ball should have two states: inactive (follows the paddle before the game begins) and active (bounces off the paddle, walls, and blocks).
- The ball will only become active once the player pushes a specific key on the keyboard.
- When the ball becomes active, we apply a force to it to start it moving.
- If the ball falls off the screen, it should be reset.
Based on that information, we will first create the global variables ballIsActive
, ballPosition
, and ballInitialForce
.
private bool ballIsActive; private Vector3 ballPosition; private Vector2 ballInitialForce;
Now that you have the variables set, you should initialise the game object. In the Start
method, you should:
- Create a force to push the ball.
- Set the ball to inactive.
- Store the ball position.
Here's how that looks:
void Start () { // create the force ballInitialForce = new Vector2 (100.0f,300.0f); // set to inactive ballIsActive = false; // ball position ballPosition = transform.position; }
Here, we apply a force of 300
along the Y-axis to make the ball move upwards, and 100
along the X-axis to make the ball move diagonally instead of directly upwards. This way, you force the player to move the paddle position.
In the Update
method, we'll define the ball's behaviour Let's approach this case by case.
First, we need to handle the user input; we will use the default Jump
button of Unity as and action key. The key associated to Jump
can be changed, but the default is the Space bar on the keyboard.
void Update () { // check for user input if (Input.GetButtonDown ("Jump") == true) { } }
The next thing to do is to check the ball state, since the action key should only work when the ball is inactive:
void Update () { // check for user input if (Input.GetButtonDown ("Jump") == true) { // check if is the first play if (!ballIsActive){ } } }
Let's assume that this is at the start of the game, so we need to apply a force to the ball and set it active. The new Update
method should look like:
void Update () { // check for user input if (Input.GetButtonDown ("Jump") == true) { // check if is the first play if (!ballIsActive){ // add a force rigidbody2D.AddForce(ballInitialForce); // set ball active ballIsActive = !ballIsActive; } } }
Now, Play your project and test the Jump
action; you will notice that the ball gets pushed up and starts bouncing around just like it should. However, you'll also notice that the ball does not follow the paddle's position around when it is inactive. Stop the game and let's change that.
In the Update
method, we need to check the ball state and, if the ball is inactive, we need to make sure that the X value of the ball is the same as the X value of the paddle.
Ah, but how can we access the player position if it is in another game object? Simple: we just create a game object variable and store a reference to the paddle object in it. So, add the paddle object as a public global variable of type GameObject
:
private bool ballIsActive; private Vector3 ballPosition; private Vector2 ballInitialForce; // GameObject public GameObject playerObject;
Back to the Update
method. We will check the ball state and the paddle reference. If the state is inactive and the paddle object is not null (that is, we have a reference to it), the ball should follow the paddle's position. The complete Update
is now:
void Update () { // check for user input if (Input.GetButtonDown ("Jump") == true) { // check if is the first play if (!ballIsActive){ // add a force rigidbody2D.AddForce(ballInitialForce); // set ball active ballIsActive = !ballIsActive; } } if (!ballIsActive && playerObject != null){ // get and use the player position ballPosition.x = playerObject.transform.position.x; // apply player X position to the ball transform.position = ballPosition; } }
Save your script and return to the editor. As you may have noticed, the paddle reference is public, meaning that you will pass it on the editor. On the Hierarchy tab, select the ball. In the Inspector, you will notice the Player Object parameter in our script is currently None
.
To make this a reference to the player paddle game object, simply drag and drop the paddle game object from the Hierarchy to the Player Object parameter in the script.
Click the Play button, and test your game. You will now see that the ball will follow the player before the Jump
key is pressed.
Resetting the Game
There is just one more thing you need to do in order to finish the script. As you may have noticed, if the player does not catch the ball it will fall from the set and the game will not reset. Let's modify the Update
method to fix that.
We'll check if the ball is active and if its Y position is less than -6
. If so, we'll set the ball to inactive and reset the ball position to the player position. The snippet for that looks like this:
if (ballIsActive && transform.position.y < -6) { ballIsActive = !ballIsActive; ballPosition.x = playerObject.transform.position.x; ballPosition.y = -4.2f; transform.position = ballPosition; }
If you save the script and check the editor, you can see that every time the ball falls from the screen, the ball position is repositioned to the player position and set to inactive. However, if you start playing you will notice a strange behaviour Every time the ball falls, the next time you push the action key its speed increases. Why is that?
Well, remember that we add a force to the ball when it launches—so, every time you reset the game, you keep adding force to the rigid body, making the game insanely hard just after a few tries. So, the question is, how do we solve this?
In order to keep the same overall force applied, we need to make sure that you clear all forces applied to the ball every time you reset it. To do this, we can simply activate the Is Kinematic
parameter every time the ball stops, and deactivate it when the game starts. This parameter determines whether or not the ball is affected by physics, and disabling it will make sure that all forces applied before will disappear.
Add two more lines to your Update
method to do this:
rigidbody2D.isKinematic = false
before the force is added, andrigidbody2D.isKinematic = true
when we want the ball to start moving again.
In the end, the complete Update
method should look like this:
public class BallScript : MonoBehaviour { private bool ballIsActive; private Vector3 ballPosition; private Vector2 ballInitialForce; // GameObject public GameObject playerObject; // Use this for initialization void Start () { // create the force ballInitialForce = new Vector2 (100.0f,300.0f); // set to inactive ballIsActive = false; // ball position ballPosition = transform.position; } // Update is called once per frame void Update () { // check for user input if (Input.GetButtonDown ("Jump") == true) { // check if is the first play if (!ballIsActive){ // reset the force rigidbody2D.isKinematic = false; // add a force rigidbody2D.AddForce(ballInitialForce); // set ball active ballIsActive = !ballIsActive; } } if (!ballIsActive && playerObject != null){ // get and use the player position ballPosition.x = playerObject.transform.position.x; // apply player X position to the ball transform.position = ballPosition; } // Check if ball falls if (ballIsActive && transform.position.y < -6) { ballIsActive = !ballIsActive; ballPosition.x = playerObject.transform.position.x; ballPosition.y = -4.2f; transform.position = ballPosition; rigidbody2D.isKinematic = true; } } }
Now, you can Play the game and verify all the aforementioned-implemented features.
Next Time
This concludes the second part of the series. You have now implemented custom scripts, physics colliders, and programmed user input. Next time, we'll set up the blocks.
If you have any questions or feedback on what we've covered so far, please leave a comment below.
Clik here to view.Image may be NSFW.
Clik here to view.Image may be NSFW.
Clik here to view.