Skip to main content Link Search Menu Expand Document (external link)

Theme: auto

Frightened, Dead, and Chase

Now that the Scatter state is working, you’ll add support for the Frightened and Dead states.

Frightened

The ghosts become “frightened” when Pac-Man eats a power pellet, which calls the GhostAI::Frighten function. While Frightened, ghosts will choose a random move at each intersection for seven seconds before returning to the Scatter state.

First, add a member variable float that tracks the time spent in the current state (you’ll reuse this for other states which also have time restrictions, so it’s not specific just to frightened):

  • At the start of Update, increase this variable by delta time
  • Set the variable to 0.0f in Start
  • Every time you change a state, be sure you reset the variable to 0.0f

Changing to the Frightened State

In the GhostAI::Frighten function, don’t do anything if the state is Dead. Otherwise, you should do the following:

  1. Reset your state time variable to 0.0f
  2. If mState is not currently Frightened:
    1. Set mState to Frightened
    2. Reverse the direction of the ghost. To do this, just swap the values of mPrevNode and mNextNode and then call your function that updates the direction.
    3. Set mTargetNode to nullptr

Updating the Target Node

Now add support for the Frighten state to your function that updates mTargetNode. You will want to pick a random neighbor to where the ghost is at (which will be mNextNode, since you call this function when the ghost intersects with the next node), with the following priority:

  1. First try picking a random neighbor that is not the previous node and not type PathNode::Ghost or PathNode::Tunnel
  2. If no node satisfies (1), then try picking a random neighbor that’s not the previous node and not type PathNode::Tunnel
  3. If no node satisfies (2), then allow picking any random neighbor

Keep in mind that there are functions for selecting random values in Random.h. Make sure you use those functions rather than using rand() or the like. You can use GetIntRange to get an integer inclusive of [min, max].

Handling State Changes

After seven seconds of being frightened, you want the the ghost to change back to scatter. For simplicity, we’ll only allow the time-based state changes to occur when the ghost intersects with the next node.

Add a new function that checks whether you need to change the state. You’ll want to call this function when you intersect with the next node in Update, after setting the position but before you update the target node.

In this function, add a check that says if you’ve been in the Frightened state for more than seven seconds (using the time variable you added), set the state back to Scatter (and reset the state time to 0.0f). This will cause the ghost to change back to scatter when the seven seconds are up.

Animations and Speed

When frightened, ghosts should play the "scared0" animation during the first 5 seconds of being frightened, and then play the "scared1" animation the last 2 seconds.

For the movement speed, don’t forget that the ghosts move at a slower speed of 65.0f pixels/second when frightened.

If everything works, eating the power pellet should cause the ghosts to switch to frightened. After seven seconds, the frightened state will end and the ghosts will return back to scatter. Confirm that the animation also works as expected. It should look something like this:

Dead

Ghosts can die if Pac-Man eats them while they’re frightened. When Pac-Man eats a ghost, the GhostAI::Die function gets called. In this function:

  • Set your state time to 0
  • Change the state to Dead
  • Call your “update direction” function as that will immediately update the ghost to the correct dead animation (once you add the animations in a moment)

The behavior of a ghost in the Dead state should be as follows:

  • The target node should always be set to the mGhostPen variable in Game
  • Because the ghost pen is a ghost node, you need to make a slight change to your first attempt to select the next move. In that first loop, you should allow path nodes of type PathNode::Ghost only if the current state is Dead. Without this change, the ghost will never choose to go inside the ghost pen area since there would always be a non-ghost node to select instead.
  • In your function that checks for state change, change the state back to Scatter once the ghost reaches the mGhostPen node
  • The ghosts should move at 125.0f pixels/second
  • There are four animations to choose from, depending on the direction of travel: "deadup", "deaddown", "deadright", and "deadleft"

Your ghosts should now correctly cycle between the Scatter, Frighten, and Dead states. Initially they’ll scatter. Then if Pac-Man eats a power pellet, they should become frightened. Finally, if Pac-Man eats the ghost while frightened, they should go back to their pen. Once the ghost reaches the pen, they should path back to their scatter node in scatter state. It should look something like this:

Chase

Now it’s time to implement the Chase state. While in the chase state, the ghost paths towards a target node that is based on the type of ghost and Pac-Man’s position.

Each of the four ghosts has a different target node in chase. Check the slides for diagrams, but here are the basic descriptions:

  • Blinky (the red ghost) – Targets Pac-Man’s previous path node. (The Game has a pointer to mPlayer, and then use PacMan’s GetPrevNode() function to get this.) However, if this previous path node is type PathNode::Tunnel, you should instead pick the path node of type PathNode::Default that’s closest to Pac-Man’s position.
  • Pinky (the pink ghost) – Gets a point 80 units “in front of” Pac-Man (mPlayer has a GetPointInFrontOf helper function for this). Then, find a path node of type PathNode::Default that’s nearest to this position.
  • Inky (the teal ghost) – Get a point P that’s 40 units “in front of” Pac-Man. Then make a vector v from Blinky’s position to P. Double the length of v and add it to Blinky’s position to get a point Q. Then, find a path node of type PathNode::Default that is nearest Q. To get Blinky’s position, you can access mGhosts[0] in the Game class.
  • Clyde (the orange ghost) – Get the distance between Clyde and the player. If the distance is > 150.0f, then Clyde targets Pac-Man’s previous path node (as Blinky). Otherwise, Clyde targets his scatter node.

You can access all the path nodes via the public mPathNodes vector in Game. Because this is just stored in a vector, there is not a more efficient way to find the “closest” node to a position other than just iterating through them all.

Next, update your code that checks for state changes to alternate between Scatter and Chase as follows:

  • After 5 seconds in the Scatter state, the ghost should switch to Chase Hint: You should use your state time variable to figure out when to change states
  • After 20 seconds in the Chase state, the ghost should switch to Scatter
  • Ghosts should continue to alternate between Chase/Scatter for these durations unless either they become Frightened or Pac-Man dies.

During the Chase state, ghosts move at the same speed as in the Scatter state (so 90 pixels/second), and use the same animations.

Now your ghosts should alternate between Scatter/Chase properly. It’ll look something like this:

Once you’ve pushed this code, you’re ready to move on to part 3.