noobtuts

Unity 2D Pac-Man Tutorial

Unity 2D Pac-Man

Foreword

Let's make a Pac-Man inspired game in Unity. The original Pac-Man game was released in October 1980 and soon became the most famous arcade game of all time. The game got so popular that even Unity included a tiny part of it in their game engine:
Unity Pac-Man Icon

In this Tutorial we will make a Pac-Man clone with only 62 lines of code by using Unity's powerful 2D features. We will keep things as simple as possible and focus on the maze, the ghosts, the food and of course Pac-Man.

As usual, everything will be explained as easy as possible so everyone can understand it.

Here is a preview of the final game in action:
Unity 2D Pac-Man

Requirements

Knowledge

Our Tutorial does not require any special skills. If you know your way around Unity and heard about GameObjects, Prefabs and Transforms before, then you are ready to go. And if you didn't, don't worry about it too much.

Feel free to read our easier Unity Tutorials like Unity 2D Pong Game to get a feeling for the engine first.

Unity Version

Our Pac-Man Tutorial will be developed with Unity 5.0.0f4. Newer versions should work fine as well, older versions may or may not work. The free version of Unity 5 now comes with all the engine features, which makes it the recommended version.

Project Setup

Let's get to it. We will start Unity and select New Project:
Unity New Project

We will name it pacman, select any location like C:\, select 2D and click Create Project:
Unity Create new 2D Project

We will select the Main Camera in the Hierarchy and then set the Background Color to black. We will also adjust the Size and the Position like shown in the following image:
Camera Properties

The Maze

The Maze Sprite

Let's create the Pac-Man typical maze. We will draw one that is inspired by the original one, but not completely the same:
Pac-Man Maze
Note: right click on the image, select Save As..., navigate to the project's Assets folder and save it in a new Sprites folder.

After saving it in our Project directory we can select it in the Project Area:
Maze in Project Area

And then modify the Import Settings in the Inspector:
Maze Import Settings
Note: a Pixels Per Unit value of 8 means that 8 x 8 pixels will fit into one unit in the game world. We will use this value for all our textures. We selected the value 8 because the distance between two Pac-Dots (the food) is always 8 px and we want that distance to be 1 Unit in our game. We selected Bottom-Left for the Pivot because it makes the alignment easier later on.

Now we can drag the maze Sprite from our Project Area into the Scene:
Drag maze image into Scene

Let's take a look at the Inspector and position the maze at (0, 0) in order to keep things clean:
Maze Position

Maze Physics

Right now the maze is only an image, nothing more. It's not part of the physics world, things won't collide with it and Pac-Man could walk right through the walls. Let's change that by adding a Collider for each wall in the maze.

We will select Add Component->Physics 2D->Box Collider 2D in the Inspector:
Maze BoxCollider2D in Inspector

If we take a look in the Scene then we can see that Unity wrapped the Collider around the whole maze, which is not exactly what we want:
Maze BoxCollider in Scene

What we really want is to have a Collider around each wall of the maze. There are two ways to do this. We could either create an algorithm that reads the maze image and generates Colliders based on it, or we could just keep it simple and add all the Colliders manually.

Let's click on the Edit Collider button in the Inspector:
Edit Maze Collider
Note: this button is new in Unity 4.6, so make sure to use the latest version if you don't see a button there.

This allows us to modify the Collider in the Scene by using the green dots:
Edit Collider in Scene

We will repeat this process for every wall in our maze. All we have to do is select Add Component->Physics 2D->Box Collider 2D, press the Edit Collider button and then modify it in the Scene until it fits the next wall.

It is very important that each Collider is perfectly exact. For example if we zoom in then the green line should still perfectly wrap around the blue box:
Maze Collider zoomed in

The trick is to choose the Collider's Center and Size properties so that they are always like 1.25 or 1.5 or 1.75 or 2.00, and never like 1.24687 or 1.25788. Here are some examples:
Maze Collider Examples
Note: if later on your Pac-Man get's stuck in the maze or has trouble moving, then it's because the Maze Colliders are not perfectly exact.

Here is the final result:
Maze with all Colliders in Scene
Note: adjusting all the Colliders may take a few minutes of time.

Adding Pac-Man

The Pac-Man Sprite

Now it's time for the most important part of our game: Pac-Man. We will need one animation for each movement direction:
- right
- left
- up
- down

Let's draw all the animations in one image, where there is one animation per row:
Unity Pac-Man Animations
Note: right click on the image, select Save As... and save it in the project's Assets/Sprites folder.

We will use the following Import Settings for it:
Pac-Man Import Settings

Slicing the Pac-Man Sprite

It's important to set the Sprite Mode to Multiple, which tells Unity that there is more than one Pac-Man in our Sprite. Let's open the Sprite Editor by clicking the button:
Pac-Man Sprite Editor Button

Now we can tell Unity where each Pac-Man animation slice is located in our Sprite. We will select Slice and then slice it as 16 x 16 Grid and press the Slice button afterwards:
Pac-Man Sprite Editor Settings

Once the Sprite was sliced, we can close the Sprite Editor again. If Unity asks us about Unapplied Import Settings then we will click on Apply.

As a result we now have 12 slices under our Pac-Man Sprite in the Project Area:
Pac-Man Slices in Project Area

Creating the Pac-Man Animations

Okay so now that we have the animation slices, we can create our 4 animations from it. As a reminder, here are the animations that we will need:

Let's create the right animation. We will begin by selecting the first three slices in the Project Area:
Pac-Man Animation Right Slices

And then dragging them into the Scene:
Drag Pac-Man Animation Right Slices into Scene

Now whenever we drag several slices into the Scene, Unity will know that we want to create an animation from them, hence why it automatically asks us where to save the animation. Let's save it as right.anim in a new PacmanAnimation folder. Unity just added a pacman_0 GameObject to the Scene and two files to the Project Area:
Pac-Man Right Animation two created Files

The first file is the animation state machine that specifies things like the animation speed and blend trees. The second one is the animation itself.

We will repeat this process for the rest of the animations (Slice 3, 4, 5 for left; Slice 6, 7, 8 for up and Slice 9, 10, 11 for down).

Here is what our Hierarchy looks like afterwards:
Pac-Man Animation GameObjects in Hierarchy

Cleaning up after Unity

Unity created one GameObject for each animation, but we only need the first one as we will see soon. Let's select the other 3 and then right click and delete them:
Pac-Man Hierarchy Clean

A similar thing happened in our Project Area. We now have 4 animations and 4 animation state machines:
Pac-Man Animation in Project Area

Again we only need one animation state machine, so let's delete the other three:
Delete Pac-Man Animation in Project Area

The Pac-Man Animation State Machine

Right now we have 4 animation files, but Unity doesn't know when to play which animation yet. The solution to our problem is part of Unity's unbelievably powerful Mecanim animation system. We will need a animation state machine that has 4 states:

We will also add Transitions so Unity knows when to switch from one animation state to another.

Note: Unity will play the right animation over and over again while in right state. It will use Transitions to know when to switch to another state. Unity does all of that automatically, all we have to do is notify it about Pac-Man's movement direction from within a Script later on.

Okay so let's double click the pacman_0 animation state machine file in our Project Area:
Pac-Man Animation State Machine File in Project Area

Now we can see the state machine in the Animator:
Pac-Man Animator Default

Creating the Other States

The right state is already in the Animator, so let's add the left state by simply dragging the left.anim file from the PacmanAnimation folder from our Project Area into the Animator:
Pac-Man Animator with left States

This process can be repeated for the remaining two states:
Pac-Man Animator all States

The big deal about Mecanim is that it will take care of the animation states on its own, fully automatically. All we have to do is tell Mecanim to watch out for our Pac-Man's movement direction, nothing more. Mecanim will then switch between animation states on its own, without us having to do anything.

Creating the Parameters

So let's tell Mecanim that it should watch out for Pac Man's movement direction. A movement direction is of type Vector2, the following image shows some possible movement directions:
Vector2 Directions

Our Pac-Man will only have 4 movement directions (up, down, left, right). Which means that we only need the following 4 Transitions in our animation state machine:

Let's take a look at the left area of the Animator and select the Parameters tab:
Animator Parameters Tab

Here we will click on the + at the right and then add one Parameter of type Float with the name DirX and another Parameter of type Float with the name DirY:
Pac-Man Animator Parameters

Later on we can set those parameters from within a Script by writing:

GetComponent<Animator>().SetFloat("DirX", 0);
GetComponent<Animator>().SetFloat("DirY", 0);

Creating the Transitions

We want Unity to automatically switch to different animation states based on those parameters. Transitions are used to achieve just that. For example, we could add a Transition from left to right with the Condition that DirX > 0. However it's considered best practice to have a small error tolerance because floating point comparison is not always perfect, hence why we will use DirX > 0.1 instead.

Now we would also have to use a DirX > 0.1 Transition from every other state to right. To save us from doing all this work, we can use Unity's Any State.

The Any State stands for literally any state. So if we create a Transition from Any State to right then it's the same as creating a Transition from left, up and down to right.

Let's right click the Any State and select Make Transition. Afterwards we can drag the white arrow onto the right state:
Pac-Man Animator Transition from Any State to right

Now we can click on the white arrow and take a look at the Inspector where we can modify the Transition's Condition (or in other words, when it should switch). We will set it to DirX > 0.1:
Pac-Man Animator Transition from Any State to right in Inspector
Note: press the + at the bottom right to add a Condition.

Let's also disable Can Transition To Self in the Settings:
Pacman Animator Transition Anystate to Right without Can Transition To Self
Note: this avoids weird situations where an animation would be restarted all the time while holding down a movement key.

As result, whenever Pac-Man walks to the right (DirX > 0.1), the animator will switch to the right state and play the animation.

We will add 3 more Transitions:

Here is how it looks in the Animator now:
Pac-Man Animator with all Transitions

We are almost done with our animation state machine. There is one last adjustment to be made here, so let's select all states and then modify their Speed in the Inspector so that the animation doesn't look too fast:
Pac-Man Animator Speeds

If we press Play then we can already see the right animation:
Pac-Man right Animation

Pac-Man Naming and Positioning

We want to keep everything nice and clean, so let's select the pacman_0 GameObject in the Hierarchy and then rename it to pacman (right click it and select rename, or press F2) :
Pac-Man renamed

We will also change its position to (14, 14) so it's not outside the maze anymore:
Pac-Man Position in Inspector

Pac-Man Physics

Right now Pac-Man is only an image, nothing more. He is not part of the physics world, things won't collide with him and he can't move or anything else. We will need to add a Collider to make him part of the physics world, which means that things will collide with Pac-Man instead of walking right through him.

Pac-Man is also supposed to move around. A Rigidbody takes care of stuff like gravity, velocity and other forces that make things move. As a rule of thumb, everything in the physics world that is supposed to move around needs a Rigidbody.

Let's select the pacman GameObject in the Hierarchy, select Add Component->Physics 2D->Circle Collider 2D as well as Add Component->Physics 2D->Rigidbody 2D. It's important that we use exactly the same settings as shown in the following image:
Pac-Man Physics in Inspector

Alright, Pac-Man is now part of the physics world. He will collide with other things and other things will collide with him. This will also cause the OnCollisionEnter2D function to be called in any Script that is attached to Pac-Man.

The Movement Script

Alright now there are several ways to make Pac-Man move. The easiest way would be to create a Script that simply checks for Arrow-Key presses and then move Pac-Man a bit up/down/left/right when needed. This would work, but it wouldn't feel very good.

Instead we will try to be more exact. Whenever the player presses one of the Arrow-Keys, Pac-Man should move exactly 1 unit into the desired direction. The Pac-Dots (the food) will also be positioned with a 1 unit distance between them, so it only makes sense that Pac-Man always moves exactly one unit.

Let's select pacman in the Hierarchy and press Add Component->New Script, name it PacmanMove and select CSharp as language. We will also move the Script into a new Scripts folder in the Project Area (just to keep things clean):
Unit Movement Script in Project Area

Alright let's double click the Script so it opens:

using UnityEngine;
using System.Collections;

public class PacmanMove : MonoBehaviour {

    // Use this for initialization
    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 (this depends on the current frame rate, it can be lower if there are a lot of things on the screen).

There is yet another type of Update function, which is FixedUpdate. It's 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 always a good idea to use FixedUpdate when doing Physics stuff:

using UnityEngine;
using System.Collections;

public class PacmanMove : MonoBehaviour {

    void Start() {

    }

    void FixedUpdate() {

    }
}

We will need a public variable so that we can modify the movement speed in the Inspector later on:

using UnityEngine;
using System.Collections;

public class PacmanMove : MonoBehaviour {
    public float speed = 0.4f;

    void Start() {

    }

    void FixedUpdate() {

    }
}

We will also need a way to find out if Pac-Man can move into a certain direction or if there is a wall. So for example if we would want to find out if there is a wall at the top of Pac-Man, we could simply cast a Line from one unit above of Pac-Man to Pac-Man and see if it hit anything. If it hit Pac-Man himself then there was nothing in-between, otherwise there must have been a wall.

Here is the function:

bool valid(Vector2 dir) {
    // Cast Line from 'next to Pac-Man' to 'Pac-Man'
    Vector2 pos = transform.position;
    RaycastHit2D hit = Physics2D.Linecast(pos + dir, pos);
    return (hit.collider == GetComponent<Collider2D>());
}

Note: we simply casted the Line from the point next to Pac-Man (pos + dir) to Pac-Man himself (pos).

We will also need a function that makes Pac-Man move 1 unit into the desired direction. Of course we could just do:

transform.position += dir;

But this would look too abrupt because it's such a huge step. Instead we want him to go there smoothly, one small step at the time. Let's store the movement destination in a variable and then use FixedUpdate to go towards that destination step by step:

using UnityEngine;
using System.Collections;

public class PacmanMove : MonoBehaviour {
    public float speed = 0.4f;
    Vector2 dest = Vector2.zero;

    void Start() {
        dest = transform.position;
    }

    void FixedUpdate() {
        // Move closer to Destination
        Vector2 p = Vector2.MoveTowards(transform.position, dest, speed);
        GetComponent<Rigidbody2D>().MovePosition(p);
    }

    bool valid(Vector2 dir) {
        // Cast Line from 'next to Pac-Man' to 'Pac-Man'
        Vector2 pos = transform.position;
        RaycastHit2D hit = Physics2D.Linecast(pos + dir, pos);
        return (hit.collider == GetComponent<Collider2D>());
    }
}

Note: we used GetComponent to access Pac-Man's Rigidbody component. We then use it to do the movement (we should never use transform.position to move GameObjects that have Rigidbodies).

Let's also watch out for arrow key presses whenever we are not moving.
Note: we are not moving if the current position equals the destination.

Here is our FixedUpdate function with Input checks:

void FixedUpdate() {
    // Move closer to Destination
    Vector2 p = Vector2.MoveTowards(transform.position, dest, speed);
    GetComponent<Rigidbody2D>().MovePosition(p);

    // Check for Input if not moving
    if ((Vector2)transform.position == dest) {
        if (Input.GetKey(KeyCode.UpArrow) && valid(Vector2.up))
            dest = (Vector2)transform.position + Vector2.up;
        if (Input.GetKey(KeyCode.RightArrow) && valid(Vector2.right))
            dest = (Vector2)transform.position + Vector2.right;
        if (Input.GetKey(KeyCode.DownArrow) && valid(-Vector2.up))
            dest = (Vector2)transform.position - Vector2.up;
        if (Input.GetKey(KeyCode.LeftArrow) && valid(-Vector2.right))
            dest = (Vector2)transform.position - Vector2.right;
    }
}

Note: transform.position is casted to Vector2 because this is the only way to compare or add another Vector2. Also -Vector2.right means left and -Vector2.up means down.

If we save the Script and press Play then we can now move Pac-Man with the arrow keys:
Pac-Man Movement with Arrow-Keys

Setting the Animation Parameters

Right now we can perfectly move Pac-Man around the maze, but the animator doesn't play all the animations yet. No problem, let's just modify our code to calculate the current movement direction and then set the animator parameters:

void FixedUpdate() {
    // Move closer to Destination
    Vector2 p = Vector2.MoveTowards(transform.position, dest, speed);
    GetComponent<Rigidbody2D>().MovePosition(p);

    // Check for Input if not moving
    if ((Vector2)transform.position == dest) {
        if (Input.GetKey(KeyCode.UpArrow) && valid(Vector2.up))
            dest = (Vector2)transform.position + Vector2.up;
        if (Input.GetKey(KeyCode.RightArrow) && valid(Vector2.right))
            dest = (Vector2)transform.position + Vector2.right;
        if (Input.GetKey(KeyCode.DownArrow) && valid(-Vector2.up))
            dest = (Vector2)transform.position - Vector2.up;
        if (Input.GetKey(KeyCode.LeftArrow) && valid(-Vector2.right))
            dest = (Vector2)transform.position - Vector2.right;
    }

    // Animation Parameters
    Vector2 dir = dest - (Vector2)transform.position;
    GetComponent<Animator>().SetFloat("DirX", dir.x);
    GetComponent<Animator>().SetFloat("DirY", dir.y);
}

Note: we calculated the current movement direction with basic vector math. All we had to do was subtract the current position from the destination.

And that's all there is to it. If we save the Script and press Play then we can now see the proper animations:
Pac-Man Movement animated

Drawing Pac-Man in the Foreground

Before we start to work on the Pac-Dots, we should make sure that Pac-Man is always drawn in front of them. We are making a 2D game, so there isn't really any Z order like in 3D games. This means that Unity just draws objects as it pleases. Let's make sure that Unity always draws Pac-Man in front of everything else.

There are two ways to do this. We could either change the Sprite Renderer's Sorting Layer property or we could change the Order in Layer property. The Sorting Layer is important for bigger games with far more objects. For us it's enough to simply change the Order in Layer to 1:
Pac-Man SpriteRenderer Order in Layer
Note: Unity draws objects sorted by their order. It starts with the lowest order and continues with higher orders. So if Pac-Man has the order 1 then he's always drawn after the maze and the food and anything else with order 0. And because he's drawn after everything else, he's automatically in front of everything else.

The Pac-Dots

The little dots that Pac-Man can eat are called Pac-Dots. Some might now them as 'fruits' or just 'food'. We will begin by drawing a small 2x2 px image of a Pac-Dot:
- pacdot.png
Note: right click on the link, select Save As... and save it in the project's Assets/Sprites folder.

We will use the following Import Settings for it:
Pac-Dot Import Settings

Afterwards we can drag it into the Scene. We want to be notified when Pac-Man walks over a Pac-Dot. All we have to do is select Add Component->Physics 2D->Box Collider 2D in the Inspector and then select Is Trigger:
Pac-Dot Collider
Note: a Collider with IsTrigger enabled only receives collision information, it does not physically collide with other things.

Unity will automatically call the OnTriggerEnter2D function whenever Pac-Man or one of the Ghosts walk over the Pac-Dot.

So let's select Add Component->New Script, name it Pacdot, select CSharp, move it into our Scripts folder and then open it:

using UnityEngine;
using System.Collections;

public class Pacdot : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

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

    }
}

We won't need the Start or the Update function, so let's remove both of them. Instead we will use the previously mentioned OnTriggerEnter2D function:

using UnityEngine;
using System.Collections;

public class Pacdot : MonoBehaviour {

    void OnTriggerEnter2D(Collider2D co) {
        // Do Stuff...
    }
}

Now this one is easy. We will check if the thing that walked over the Pac-Dot was Pac-Man, and if so then we will destroy the Pac-Dot:

using UnityEngine;
using System.Collections;

public class Pacdot : MonoBehaviour {

    void OnTriggerEnter2D(Collider2D co) {
        if (co.name == "pacman")
            Destroy(gameObject);
    }
}

Note: if we wanted to implement a highscore in our game, then this would be the place to increase it.

Now we can right click the Pac-Dot in the Hierarchy, select Duplicate and move it to the next free position. It's important that we always position the Pac-Dots at rounded coordinates like (1, 2) and never (1.003, 2.05).

Here is the result after duplicating and positioning the Pac-Dot over and over again:
All Pac-Dots positioned

If we press Play then Pac-Man can now eat them:
Pac-Dots being eaten

Feel free to also move all those Pac-Dots into the maze GameObject:
Pac-Dots in Maze GameObject

So that we can collapse the maze GameObject in order to have a clean view at the Hierarchy:
Pac-Dots collapsed in Maze

The Gosts

A good Pac-Man clone needs some enemies, so let's add a few ghosts.

The Red Ghost Image

As usual we will begin by drawing a ghost Sprite with all the animations in it. Each row will contain one animation:

The first one will be the red ghost, also known as Blinky:
Pac-Man Blinky
Note: right click on the image, select Save As... and save it in the project's Assets/Sprites folder.

Let's select it in the Project Area and then modify the Import Settings in the Inspector:
Blinky Import Settings

Creating the Animations

It's important that we set the Sprite Mode to Multiple again because our Sprite contains several slices. Let's click the Sprite Editor button and slice it as a 16 x 16 grid:
Blinky Sprite Editor Settings

Afterwards we can close the Sprite Editor and press Apply. Now it's time to create the animations by dragging the slices into the Scene, just like we did with Pac-Man.

At first we will drag Slice 0 and 1 into the Scene and save the animation as right.anim in a new BlinkyAnimation folder.

We will repeat this process for:

Cleaning up after Unity

Now we can clean up the Hierarchy again by deleting the blinky_2, blinky_4 and blinky_6 GameObjects:
Delete Blinky in Hierarchy

We can also delete the blinky_2, blinky_4 and blinky_6 files from the BlinkyAnimation folder in the Project Area:
Delete Blinky in Project Area

The Animation State Machine

Let's double click the blinky_0 file to open the Animator:
Blinky default Animator

We will create the exact same animation state machine that we used for Pac-Man before. All we have to do is drag in the animations, create the Parameters and then set the Transitions:

Here is the final animation state machine in the Animator:
Blinky final Animator

Renaming and Positioning Blinky

Let's make sure to keep everything nice and clean. We will select the blinky_0 GameObject in the Hierarchy and rename it to blinky:
Blinky renamed

We will also change the position in the Inspector so that Blinky is in the middle of the maze:
Blinky Position

Ghost Physics

Alright so Blinky should be part of the Physics world again. Let's select Add Component->Physics 2D->Circle Collider 2D in the Inspector and assign the following properties:
Blinky Collider
Note: we enabled Is Trigger because Blinky is a ghost, and ghosts can walk right through things.

Blinky is also supposed to move around the maze, which means that we will need to add a Rigidbody to him. Let's select Add Component->Physics 2D->Rigidbody 2D:
Blinky Rigidbody

Bringing Blinky to the Foreground

Just like Pac-Man, we want Blinky to be drawn in the foreground, in front of the Pac-Dots. All we have to do to is set the Order in Layer value to 1:
Blinky Order in Layer

The Ghost Movement

Alright so we don't know very much about how the AI works in the original Pac-Man game, except for the fact that it's deterministic (which is a fancy word for not-random). It also appears as some ghosts are more focused on moving around the maze, while others are more focused on following Pac-Man, or perhaps even trying to position themselves in front of him.

In this Tutorial we will focus on the easiest of the AI options: moving around the maze. We will create a waypoint movement Script that makes Blinky run along a certain path. What sounds almost too simple is actually a pretty decent solution to our AI problem. The longer and the more complex the path, the harder it is for the player to evade Blinky.

Let's select Add Component->New Script, name it GhostMove, select CSharp and then move it into our Scripts folder. Afterwards we can double click it so it opens:

using UnityEngine;
using System.Collections;

public class GhostMove : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

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

    }
}

We won't need the Start or the Update function, instead we will use the FixedUpdate function (because it's meant for physics stuff like movement):

using UnityEngine;
using System.Collections;

public class GhostMove : MonoBehaviour {

    void FixedUpdate () {

    }
}

Let's add a public Transform array so that we can set the waypoints in the Inspector later on:

using UnityEngine;
using System.Collections;

public class GhostMove : MonoBehaviour {
    public Transform[] waypoints;

    void FixedUpdate () {

    }
}

Note: an array means that it's more than just one Transform.

We will also need some kind of index variable that keeps track of the waypoint that Blinky is currently walking towards:

using UnityEngine;
using System.Collections;

public class GhostMove : MonoBehaviour {
    public Transform[] waypoints;
    int cur = 0;

    void FixedUpdate () {

    }
}

Note: the current waypoint can always be accessed with waypoints[cur].

And of course a movement speed variable:

using UnityEngine;
using System.Collections;

public class GhostMove : MonoBehaviour {
    public Transform[] waypoints;
    int cur = 0;

    public float speed = 0.3f;

    void FixedUpdate () {

    }
}

Now we can use the FixedUpdate function to go closer to the current waypoint, or select the next one as soon as we reached it:

void FixedUpdate () {
    // Waypoint not reached yet? then move closer
    if (transform.position != waypoints[cur].position) {
        Vector2 p = Vector2.MoveTowards(transform.position,
                                        waypoints[cur].position,
                                        speed);
        GetComponent<Rigidbody2D>().MovePosition(p);
    }
    // Waypoint reached, select next one
    else cur = (cur + 1) % waypoints.Length;
}

Note: we used the Vector2.MoveTowards function to calculate a point that is a bit closer to the waypoint. Afterwards we set the ghost's position with rigidbody2D.MovePosition. If the waypoint is reached then we increase the cur variable by one. We also want to reset the cur to 0 if it exceeds the list length. We could use something like if (cur == waypoints.Length) cur = 0, but using the modulo (%) operator makes this look a bit more elegant.

Let's not forget to set the Animation Parameters too:

void FixedUpdate () {
    // Waypoint not reached yet? then move closer
    if (transform.position != waypoints[cur].position) {
        Vector2 p = Vector2.MoveTowards(transform.position,
                                        waypoints[cur].position,
                                        speed);
        GetComponent<Rigidbody2D>().MovePosition(p);
    }
    // Waypoint reached, select next one
    else cur = (cur + 1) % waypoints.Length;

    // Animation
    Vector2 dir = waypoints[cur].position - transform.position;
    GetComponent<Animator>().SetFloat("DirX", dir.x);
    GetComponent<Animator>().SetFloat("DirY", dir.y);
}

Great, there is one last thing to add to our Script. The ghosts should destroy Pac-Man upon colliding with him:

void OnTriggerEnter2D(Collider2D co) {
    if (co.name == "pacman")
        Destroy(co.gameObject);
}

Note: feel free to decrease Pac-Man's lives or show a Game Over screen at this point.

Adding Waypoints

Alright, let's add some waypoints for Blinky. We will begin by selecting GameObject->Create Empty from the top menu. We will rename it to Blinky_Waypoint0 and then assign a Gizmo to it so we can see it easier in the Scene:
Blinky Waypoint 0 in Inspector
Note: a Gizmo is just a visual helper, we don't see it when playing the game.

Let's also position it at (15, 20). Here is how it looks in the Scene now:
Blinky Waypoint 0 in Scene

Now we can duplicate the Waypoint, rename it to Blinky_Waypoint1 and position it at (10, 20):
Blinky Waypoint 1 in Scene

Then Blinky_Waypoint2 at (10, 14):
Blinky Waypoint 2 in Scene

And Blinky_Waypoint3 at (19, 14):
Blinky Waypoint 3 in Scene

And finally Blinky_Waypoint4 at (19, 20):
Blinky Waypoint 4 in Scene

And because our Movement Script will automatically continue to walk to the first waypoint after the last one was reached, we will have a perfect loop.

Let's select blinky in the Hierarchy again and then drag one waypoint after another into the Waypoints slot of our GhostMove Script:
Blinky GhostMove Script with Waypoints

If we press Play then we can see how Blinky moves along the waypoints:
Blinky Moving

Of course, the current waypoints are rather simple. So feel free to create a more complex route like this one:
Blinky final Waypoints

Pinky, Inky and Clyde

We don't want Blinky to feel lonely in there, so let's repeat the same work flow for Pinky, Inky and Clyde:
All Ghosts
Note: it's important that we use different waypoints and movement speeds for each ghost in order to make the game more challenging.

If we press Play then we can now play a nice round of Pac-Man:
Unity 2D Pac-Man

Summary

We just created a very solid, fast and simple 2D Pac-Man clone in Unity. We learned about Pixels to Units, Mecanim, Colliders, Rigidbodies, Layer Orders and Scripting. And even though the AI is very simple and deterministic, the game is still very challenging.

Now it's up to the reader to make the game even more fun. There are tons of features that could be added:


Download Source Code & Project Files

The Unity 2D Pac-Man Tutorial source code & project files can be downloaded by Premium members.

All Tutorials. All Source Codes & Project Files. One time Payment.
Get Premium today!