Targeting Algorithm for Hunter Enemy

One of the more important pieces of code I did this week is the hunter’s targeting (aggro) algorithm. The hunter is the first enemy we made (and the only one so far), and what it did earlier was chase the player character if it gets too close. But we wanted to expand on this and make it chase other targets too, based on a priority system.

Our other programmer, Sebastian was the one who initially coded the hunter movement and I didn’t want to mess up his code. So I implemented the targeting system in a way that it doesn’t alter his much. I did this by making two functions, invoked one after the other in every update loop of the hunter’s code. The first one generates a list of all targets in the nearby area and the second one decides which the hunter should follow, based on the priority system. Once this was done, all I had to do was replace the target from Sebastian’s code with the new one calculated by the two functions. Or in simpler terms, point the hunter to a new target! The two functions look like this.

void generateList()
{
targets.Clear();

targets.Add(GameObject.FindGameObjectWithTag("Player"));

GameObject[] berries = GameObject.FindGameObjectsWithTag("Berry");
GameObject[] child = GameObject.FindGameObjectsWithTag("Child");

if (berries.Length > 0)
{
foreach (GameObject entity in berries)
{
if (
(entity.GetComponent<powerUp>().currentState != powerUp.State.notPickedUp)
&& ((Vector3.Distance(entity.transform.position, transform.position)) < aggroDistance) ) { targets.Add(entity); } } } if (child.Length > 0)
{
foreach (GameObject entity in child)
{
if ((Vector3.Distance(entity.transform.position, transform.position)) < aggroDistance)
{
targets.Add(entity);
}
}
}
}
void setTarget()
{
foreach (GameObject prey in targets)
{
if (hunterTarget != null)
{
if (prey.tag == hunterTarget.tag)
{
if ((Vector3.Distance(prey.transform.position, transform.position)) < (Vector3.Distance(hunterTarget.transform.position, transform.position)))
{
hunterTarget = prey;
}
}

else if (prey.tag == "Berry")
{
hunterTarget = prey;
}
else if (prey.tag == "Child" && hunterTarget.tag != "Berry")
{
hunterTarget = prey;
}
else if (prey.tag == "Player" && (hunterTarget.tag != "Child" && hunterTarget.tag != "Berry"))
{
hunterTarget = prey;
}
}
else
{
hunterTarget = GameObject.FindGameObjectWithTag("Player");
}

}
}

Now, before I go into detail about the code, I would like to mention one thing. The list container in C#/Unity is awesome. It is easy to use and makes life easier! Why? Because of easy creation, iteration and element removal. The removal especially, because it can be done by specific element, rather than specific index (specific index is possible as well)! That said, I should also mention that I ran into some problems with it. For one thing, I had trouble adding elements to the list from a different script (even thought it was public static at the time) with this.gameObject (I have no idea why and if anyone knows, please let me know). For another, some of the objects actually get deleted through the course of the game, which leaves the hunter with a null target (the hunter either stood still or started moving erratically following this). The null target throws an exception as well. However, I did manage to fix this by adding an if statement to check for null targets and assign a temporary one when it happens.

Anyway, here’s how the code works. First, it adds the player to the list of targets. Then it scans the scene for all berries and children and adds them to the list if they are within the aggro radius defined by Sebastian. Berries are only added if they are not in notPickedUp state (see my previous blog post for details). The hunter script then sorts through the list and finds the target for the hunter to chase, based on the priority system. The priority goes: closer target of same type as current target > berry > children > player (as suggested by our designer). The target gets reset to the player if the hunter’s current target is destroyed. This is temporary and gets changed very quickly because both the list creation function and the target pick function are called each update. Once the target is picked, Sebastian’s code takes over and moves the hunter accordingly.

hunter-aggro
Light berry being used as a distraction

I also want to mention the list itself that I used to store the target GameObjects. It is currently declared as follows.

private List<GameObject> targets;

As I mentioned earlier, this was a public static variable at start (in fact, it was coded that way till I noticed something while writing this post). If it is public, any other function can edit it. Now, this was the original intended functionality, but I wasn’t able to implement it so it makes sense to have it as private, away from easy (unwanted) modifications. But I also removed the static part from it. Why was it there in the first place? Why did I remove it? Well the first one is simple. To make it easier to add and remove elements through other scripts. I wanted to add and remove berries and children to the list as they spawn (or get destroyed), directly from their behavior scripts. But then why remove it? This is because of an over-sight that would break the hunter behavior itself. If it were static, all hunters would have the same list. But they might have different targets nearby, so they may not follow the right one! For instance, one hunter may have the player nearby while another, a child. If both pairs are out of each others’ ranges, the function will still only calculate one target for both of them (the child because of the priority list)! And only one of the hunters will chase the target because the other one doesn’t have a target in range!

And here’s a video of the code in action.

2 thoughts on “Targeting Algorithm for Hunter Enemy

  1. The post highlights what you have done well. But there are some things you could change to make it clearer. Firstly I think the code snippets are not needed, and would be much clearer to just explain what you do in pseudocode. Actually, I think the part where you explain how the code works communicates this fairly well, and you probably dont even need pseudocode. I like the youtube video to show it in action, although it could be done as a GIF too. Still a video/GIF is a good way to show it in action.

    As for how you achieved this, again the code snippets are a bit unclear but you explain everything well through text. Although in the end where you talk about the list being static or not, I’m left a bit unsure about whether “If it were static, all hunters would have the same list. But they might have different targets nearby, so they may not follow the right one! For instance, one hunter may have the player nearby while another, a child” is a bad thing or not. Is the idea to have the hunters following the same targets or not? It was good that you reflected on the issues with the null refenence though.

    I think you clearly explain why you did this, and why choose the method you did. Alhtough again, I’m not sure on why you changed the list from static, I think it would be good if you just briefly explain what the idea with multiple hunters targetting.

    Overall a good post, using a video is perfect for communicating what your code does.For next time maybe leave out the code and think about using a GIF instead of an embedded youtube link.

    Like

    1. I provided the code so that anyone who wishes to recreate any of the mechanics would find it easier (perhaps). It is an optional read for the programmers interested in knowing how exactly it works.

      The static list might actually be useful in a different context, like a hive mentality. But in this context (and what I perhaps didn’t explain well enough) is that the hunters are too spread out for a single map wide target to be within range of all of them.

      Anyways, thanks for the reply. The feedback is appreciated!

      Like

Leave a comment