Unity 2D Pac-Man Tutorial

Foreword

Let's make a Pac-Man inspired game in Unity. The original game was released in October 1980 and soon became the most famous arcade game of all time. The game took the world by storm and due to its popularity, Unity Technologies included a tiny easter-egg featuring Pac-Man in their game engine:

By the end of this tutorial, we have a fully functional Pac-Man clone with only 62 lines of code by utilizing Unity's powerful 2D features. We'll focus on the Maze, the Ghosts, the food (pellets) and of course, the hero himself: Pac-Man.

As with our other tutorials, we'll keep things as simple as possible so everyone can understand it. Here is a preview of the final game in action:



Let’s begin!

Requirements

Knowledge

This 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 good to go. And if you didn't or you don’t fully understand the basics, don't worry about it too much as we’ve got you covered.

Feel free to read our easier Unity Tutorials like the Unity 2D Pong Game to get a feeling for the engine first. By completing those tutorials before attempting this one, you will feel at ease with this tutorial.

Unity Version

Our Pac-Man Tutorial will be developed with Unity 2018.4 (also known as 2018 LTS). Unity 2018.3 and newer versions should work fine as well, older versions of Unity may or may not work. Please adapt the instructions to your version accordingly - most of the time, newer versions of Unity have User Interface differences that can throw people off-guard.

Project Setup

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



We will name it PacMan, save it in a location such as C:\GameDev, select the 2D Game template and click Create Project, as shown below:



Once Unity has created our project and loaded the editor which may take a few minutes depending on your computers' hardware, 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:

Creating 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:



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:



However, we're not done yet. We need to modify the Import Settings in the Inspector as shown:



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:



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



Note: We make sure Z is set to 0 as we are working in a 2D environment. Z axis can be used to move elements away/towards the camera for added 3D effects, but in our Pac-Man project, we need to ensure that it's kept set as 0.

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. Luckily, this is very simple to do.

We will select Add Component -> Physics 2D -> Box Collider 2D in the

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:



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:



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



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 aligned perfectly. For example if we zoom in then the green line should still perfectly wrap around the blue box:



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:



Note: Later on, you may experience Pac-Man getting stuck in the maze or have trouble moving. This is due to the Maze Colliders are not perfectly aligned. Please double check your Center and Size properties.

Here is the final result:



Note: Adjusting all the colliders can be annoying and time consuming, but trust us - it's worth it.

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:



Up

Right

Down

Left

Let's draw all the animations in one image, where there is one animation per row:



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:



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:



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:



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:



Creating the Pac-Man Animations

Now that we have the animation slices imported into Unity, we now need to create our 4 animations from the slices Unity generated. As a reminder, here are the animations that we will need:



Pac-Man facing right (Slice 0, 1 and 2)

Pac-Man facing left (Slice 3, 4 and 5)

Pac-Man facing up (Slice 6, 7 and 8)

Pac-Man facing down (Slice 9, 10 and 11)

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



And then dragging them into the 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:



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:



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:



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



Again we only need one animation state machine, so let's delete the other three:



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:



right

left

up

down

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.

With that out the way, let's double click the pacman_0 animation state machine file in our Project Area:



Now we can see the state machine in the Animator:



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:



This process can be repeated for the remaining two 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:



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:

- If DirY > 0 then go to up (like DirY=1, DirY=2, DirY=3 and so on)

- If DirY < 0 then go to down (like DirY=-1, DirY=-2, DirY=-3 and so

on)

- If DirX > 0 then go to right (like DirX=1, DirX=2, DirX=3 and so

on)

- If DirX < 0 then go to left (like DirX=-1, DirX=-2, DirX=-3 and so

on)

Let's take a look at the left area of the Animator and select the 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:

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:



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 like so:



Note: press the + at the bottom right to add a Condition.

Let's also disable Can Transition To Self in the Settings:



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:

- Any State to left with the Condition DirX < -0.1

- Any State to up with the Condition DirY > 0.1

- Any State to down with the Condition DirY < -0.1

Here is how it looks in the Animator now:



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:



If we press Play then we can already see the 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.



Note: You can right click the pacman_0 entry in the list above and choose Rename, select it and press F2 or select it and in the Inspector, change the pacman_0 text to just pacman.

We will also change its position to (14, 14) so it's not outside the maze anymore:



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:



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

With the preparation out of the way, 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. While this would be trivial to implement, in reality the controls wouldn't feel very good.

Instead we will try to be more faithful to the arcade classic, meaning 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 click Create. We will also move the Script into a new Scripts folder in the Project Area (just to keep things clean):



Let's double click the Script file, so it loads up in a script editor (usually Visual Studio, but may vary between platforms).

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:



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:

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 Pac-Man's Sprite Renderer 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:

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 know them as pellets, 'fruits' or just 'food'. We will begin by drawing a small 2x2 px image of a Pac-Dot:



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:



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: 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:



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



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



So that we can collapse the maze GameObject in order to have a clean view at the Hierarchy:



The Ghosts

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:

- right

- left

- up

- down

The first one will be the red ghost, also known as 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:



Creating the Animations

Remember how we created Pac-Man's animations? Well, we need to do it again for our new enemy.

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:



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:

- Slice 2 and 3 as left

- Slice 4 and 5 as up

- Slice 6 and 7 as down

Cleaning up after Unity

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



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



The Animation State Machine

Let's double click the blinky_0 file to open the 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:



Any State to right with the Condition DirX > 0.1

to with the Condition Any State to left with the Condition DirX < -0.1

to with the Condition Any State to up with the Condition DirY > 0.1

to with the Condition Any State to down with the Condition DirY < -0.1

Here is the final animation state machine in the 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:



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



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:



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:



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:



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, click Create 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 like our Pac-Man code does:

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, because ghosts won't be going anywhere without some movement speed...

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. If Pac-Man does not die when colliding with a ghost, make sure the Pac-Man object is exactly named pacman - no white space or hypen between pac and man.

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:

Note: a Gizmo is just a visual helper, we don't see it when playing the game. You can choose a different color for your way points, but we used Red to match Blinky.

Let's also position it at (15, 20). Here is how it looks in the Scene now, just keep in mind that you may need to zoom in the scene view in order to see the Gizmos:

Now we can duplicate the Waypoint, rename it to Blinky_Waypoint1 and

position it at (10, 20):



Then Blinky_Waypoint2 at (10, 14):



And Blinky_Waypoint3 at (19, 14):



And finally Blinky_Waypoint4 at (19, 20):



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:

Note: As you add waypoints and add them to Blinky's GhostMove script like as shown above, the Size parameter of the Waypoints foldout will increase. In our case, we have 25 waypoints already defined. Make sure you populate all slots! If any say "Missing" or "None", right click that entry and choose Delete Array Element. Otherwise you will get a NullReferenceError, which means your script tried to access something that didn't exist. This will usually cause the Ghost to stop moving, and may spam the Debug Console with errors.

If we press Play then we can see how Blinky moves along the waypoints. Poor Pac-Man gets eliminated before he even gets a chance to move!

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

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:



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:



How many pellets can you eat before the Ghosts take you out?

Summary

Congratulations! If you made it this far, you successfully made your own Pac-Man clone.

Just to recap, in this tutorial we 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:



Sound (who doesn't want the classic Waka Waka sound effect?)

Player Scoring and High Scores

Difficulty Settings

Advanced AI, based on the arcade originals AI

Portals, which warp you across the other side of the room

More Levels

Lives

Energizers and Power-Ups, like the Super Pellet from the original game

More Animations