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

Theme: auto

Soldier AI

In this part, you’ll add an A* implementation and implement a patrolling behavior for the soldiers.

Setting up PathFinder

The starting code includes an implementation of the PathFinder actor, which handles loading in all the path nodes for the world. It also contains a FindPath function which you will have to implement in a moment.

First, you need to allocate an instance of the PathFinder actor. Do this in Game::LoadData prior to loading in the level from the CSV file. Save the PathFinder* in a private member variable and make a public getter function.

Next, add two additional parameters to the Soldier constructor: PathNode* start and PathNode* end. Then, inside the constructor you will want to create a SoldierAI component and call the Setup function on it, passing in the start/end nodes respectively.

In SoldierAI::Setup, for now just set the mPatrolStart and mPatrolEnd member variables to start/end respectively.

Now you need to change it so when you create the Soldier from the level, you pass in the correct start/end path nodes. This is what the last 4 values from the Soldier row in the CSV are for. (You can look at the last row in ObjectsOneSoldier.csv to see this). The overall layout of the solider row is:

Soldier,x,y,width,height,rowStart,colStart,rowEnd,colEnd

Use the GetPathNode member function in PathFinder to get the two PathNodes* corresponding (rowStart, colStart) and (rowEnd, colEnd), respectively, and pass those in as the start/end nodes to the Soldier constructor.

Put a breakpoint in SoldierAI::Setup. Confirm in the debugger that you are setting mPatrolStart and mPatrolEnd correctly. Once Setup completes, mPatrolStart should have row=8, col=48, and mPatrolEnd should have row=8, col=59. Note that if you continue past this point, the game will crash in SoldierAI::Draw. This is expected for now, because you haven’t setup the path and mNext to anything yet.

Implementing A*

Now you need to implement the A* algorithm in PathFinder::CalculatePath. Given the start node and end node, you want to find the shortest path between the two, and save that path in the outPath std::vector. It returns true if a path was found and false otherwise (though in our game, the soldiers will always find a path).

Here are a few notes to keep in mind in your implementation:

  • Be sure to make a NodeInfo struct as discussed in lecture
  • Make sure your NodeInfo map and any other temporary data is local to CalculatePath and NOT member data, because each A* search needs completely fresh data
  • For the heuristic, you should just use the Euclidean distance (eg. Vector2::Distance)
  • There is no edge weight, so you have to calculate it also using Euclidean distance
  • The outPath vector should contain the nodes in reverse. Eg., index 0 of the vector should be the end node and the last index should be the node immediately after the start node
  • The path is not just what’s in the closed set. You have to reconstruct the path by following the parent pointers

Now in SoldierAI::Setup, add a call to CalculatePath to compute the path from mPatrolStart to mPatrolEnd. For the outPath parameter, pass in mPath.

Use a breakpoint to confirm that the path you get back from CalculatePath has 19 elements in it. The node at index 0 should be row=8, column=59 and the node at index 18 should be row=9, column=48. Your game will still crash if you continue past this.

Now in SoldierAI::Setup, after you calculate the path you need to initialize mPrev, mNext, and mPath as follows:

  • mPrev should just be mPatrolStart
  • mNext should be the node at the end of the path vector (you can just use back() to get that)
  • After setting mNext, remove the last element from mPath (you can just use pop_back() to remove it)

Confirm that your game no longer crashes and you see a path for the soldier near the start which looks like this: Initial soldier path

Moving Along the Path

Similar to the ghosts in the last lab, the soldier will move from mPrev to mNext over the course of several frames at the speed of SOLDIER_SPEED. When the soldier reaches the mNext node, you’ll update the prev/next nodes as needed.

For the direction of movement, we recommend making a Vector2 member variable to store the direction and a function to update the direction. Then you only need to update the direction whenever mPrev and mNext changes.

In this lab, the path nodes DO NOT have a collision component; so instead of checking for an intersection to determine if the soldier has reached the next node, you just have to calculate the distance between the soldier and mNext. If it’s less than or equal to 1.0f, then the soldier has reached the node.

When the soldier reaches the node you should:

  1. Set the position of the solider to mNext’s position
  2. If mPath is not empty, you want to advance further on the path by:
    1. Setting mPrev to mNext
    2. Setting mNext to the last node in mPath
    3. Remove the last node in mPath
  3. If mPath is empty, this means you reached the end of the path and you’ll want the soldier to start a new path but in reverse. The easiest way to do this is to call Setup(mPatrolEnd, mPatrolStart) which will reverse the patrol path (and setup this new path) every time it’s called.

You should now be able to observe the soldier walking a path back and forth like in the video below. The soldier should just seamlessly keep going back and forth forever.

Enabling all Soldiers

Once you’ve confirmed that the single soldier paths, change your level loading code so you load the full "Assets/Map/Objects.csv" file, which contains several soldiers instead of just one.

Confirm that when you switch to the file with all the soldiers, your code still runs and you see the different soldier paths.

It’s possible to incorrectly implement your A* algorithm such that while it works for a single soldier, it gets stuck in an infinite loop when using the full Objects.csv file. Make sure you test this as the spec for soldiers following the path does require that it works with all the soldiers.

Animations

Next, change it so the soldier plays the correct animation depending on the direction of travel. We recommend making a function to update the animation, and calling this at the end of the SoldierAI::Update function. The animations are:

  • Up - "WalkUp"
  • Down - "WalkDown"
  • Left - "WalkLeft"
  • Right - "WalkRight"

Keep in mind these animations should already be loaded courtesy of the LoadAnimations call you added to the Soldier constructor.

Confirm that you can now see the different soldiers pathing correctly and playing the animations as expected, as in this video:

Note that the player still will not intersect with the soldiers or bushes.

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