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
inStart
- 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:
- Reset your state time variable to
0.0f
- If
mState
is not currentlyFrightened
:- Set
mState
toFrightened
- Reverse the direction of the ghost. To do this, just swap the values of
mPrevNode
andmNextNode
and then call your function that updates the direction. - Set
mTargetNode
tonullptr
- Set
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:
- First try picking a random neighbor that is not the previous node and not type
PathNode::Ghost
orPathNode::Tunnel
- If no node satisfies (1), then try picking a random neighbor that’s not the previous node and not type
PathNode::Tunnel
- 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 isDead
. 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 themGhostPen
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 tomPlayer
, and then use PacMan’sGetPrevNode()
function to get this.) However, if this previous path node is typePathNode::Tunnel
, you should instead pick the path node of typePathNode::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 aGetPointInFrontOf
helper function for this). Then, find a path node of typePathNode::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 nearestQ
. To get Blinky’s position, you can accessmGhosts[0]
in theGame
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 toChase
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 toScatter
- 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.