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 toCalculatePath
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 thestart
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 bemPatrolStart
mNext
should be the node at the end of the path vector (you can just useback()
to get that)- After setting
mNext
, remove the last element frommPath
(you can just usepop_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:
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:
- Set the position of the solider to
mNext
’s position - If
mPath
is not empty, you want to advance further on the path by:- Setting
mPrev
tomNext
- Setting
mNext
to the last node inmPath
- Remove the last node in
mPath
- Setting
- 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 callSetup(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.