Fuzzy Logic
This sample shows how an AI can use fuzzy logic to make decisions. It also demonstrates a method for organizing different AI behaviors, similar to a state machine.Sample Overview
When you program the AI for your games, you know that your actors often have to choose between several different options. In many cases, the choice is black and white: if you see the player, attack him. However, the decision making process is often much less clear cut. If you are low on health, go find a med kit. If there is a powerup nearby, pick it up. But how do you define "low on health," or "nearby?" And what if you're both low on health and also near a powerup? Which action is more important? You can give your AI actors the ability to make these kinds of "fuzzy" decisions by using fuzzy logic.
In this sample, we use some of the behaviors introduced in the chase and evade sample to demonstrate one way that an AI actor can use fuzzy logic to make a decision. The sample employs a tank and fifteen mice. The tank chases the mice, and the mice flee from the tank. The tank uses fuzzy logic to decide which mouse would be best to chase. The user is given control over several factors that influence the tank's decision-making process.
In addition, the sample demonstrates a design pattern that can be useful when programming AI: pluggable behaviors. This pattern separates an entity from the logic for its AI behavior, allowing the behavior logic to be reused by multiple entities.
This sample is based on the Chase and Evade sample, and assumes that the reader is familiar with the code and concepts explained in that sample.
Sample Controls
This sample uses the following keyboard and gamepad controls.
| Action | Keyboard Control | Gamepad Control | Windows Phone |
|---|---|---|---|
| Select fuzzy weight. | UP ARROW, DOWN ARROW | Left thumb stick or D-Pad Up and Down | Drag bar |
| Modify fuzzy weight. | LEFT ARROW, RIGHT ARROW | Left thumb stick or D-Pad Left and Right | Drag bar |
| Exit. | ESC or ALT+F4 | BACK | BACK |
How the Sample Works
Fuzzy Logic
The most interesting part of the sample is in the function Tank.ChooseMouse, which
uses fuzzy logic to decide which mouse is best to chase. Obviously, deciding
which mouse is "best" isn't a black and white choice. For example, imagine the
tank is trying to choose between two mice. The first is only 20 units away, but is
directly behind the tank. The second is 50 units away, but straight ahead. Which of
these mice is the tank more likely to catch, and therefore the better one to chase?
The tank has three factors to consider:
- If the mouse is nearby, it is a good mouse to chase.
- If the mouse is in front of me, it is a good mouse to chase.
- If I have been chasing this mouse for a long time, it is a good mouse to chase.
Our end goal is a number that represents how "good" each mouse is. Then, it's simple to examine all of the mice, comparing each of their numbers to determine which is best. So how do we arrive at that number?
First, for each of the three factors, we generate a number from 0 to 1 that indicates how true it is. For example, if the tank is near the mouse, the value will be 1; if the tank is far away, the value will be 0. Next, we take the three values and combine them. That's the number that tells us how good each mouse is.
Calculating the Fuzzy Factors
The tricky part of this is generating the 0..1 number that represents how true each factor is. Consider the graph below, which shows how we get the number for the first factor, distance.
In this graph, we have a simple linear function that goes from 1.0 at some point MinDistance, to 0.0 at MaxDistance. The ends of the function are clamped so that the values can't go past 0 and 1. Again, all this means is that a mouse that is MinDistance units away is considered to be nearby, and is therefore a good mouse to chase. A mouse that is MaxDistance units away is not nearby, and is not a good mouse to chase. To find out how close a mouse is, all we have to do is plug its distance into our function, and we get that 0..1 number that we want.
Min and Max distance are constants that are decided upon mostly via trial and error. In this sample, a mouse that is 175 pixels away is considered far away. A mouse that is 0 pixels away is considered nearby.
We can use the same process to generate our 0..1 number for the second factor. Instead of distance, we'll use the difference between the tank's orientation and the angle to the mouse. If the difference is small, the mouse is right in front of the tank, and the value should be close to 1. If the difference is large, the value should be close to 0.
We'll need two more constants, MinAngle and MaxAngle. In the sample, MaxAngle is defined as 90 degrees.
The third factor, time, is a little different. As time increases, we want our 0..1 number to increase as well. The other two factors were the opposite: an increase in distance caused the 0..1 number to decrease. So the function for time looks like this:
Conceptually, this is the same idea as the other two, but the code differs slightly.
Combining the Fuzzy Factors
Now that we've calculated the three 0..1 fuzzy factors that tell us how good a mouse is, we have to combine them to get the finished value, which we can then compare against other mice. The easiest way to combine the three values is to simply add them together. Adding works fine in many cases, but we use a slightly more complicated technique that allows us a bit more fine-grained control over the tank's behavior.
We first give the tank a set of three "weights," one for each of the three fuzzy factors. Each weight is a value from 0 to 1, and represents how much importance is given to each factor. A weight of 0 means that the tank's AI does not consider the associated fuzzy factor to be important. A weight of 1 gives the associated factor the maximum importance.
Implementing these weights is pretty easy. When we combine the three fuzzy factors, we multiply each of the factors by its weight, and then add the three weighted factors together. The result is the finished value that we can use to compare against other mice. By multiplying each factor by its weight before combining it with the others, we get exactly the effect we are looking for: factors with relatively smaller weights have less influence on the final value, and if the weight is 0 the factor is completely ignored.
In this sample, the user can modify the weights. By default, the tank has an equal weight for all factors, so the factors are equally important. By adjusting these weights, it is simple to change the way the tank behaves. One set of weights can give a tank a very different personality than another set might. Play around, and see how different sets of weights make the tank behave differently! For example, try turning the weight for distance all the way up, and the other two weights all the way down. The tank will decide who to chase based entirely on distance, which will make him very erratic. Since time and angle are being ignored in this case, the tank will change targets often, even if the new target is directly behind him. Or, try turning the weight for time all the way up, again minimizing the other two. This will make the tank very tenacious: once he picks a target he'll stick with it, even if another mouse gets closer.
Note that the actual values of the weights are not really important: it is the ratio between the weights that is important. In other words, a tank with weights that are all 0.5 behaves the same as a tank with weights that are all 1.0.
Entities and Behaviors
Aside from fuzzy logic, the other concept that this sample introduces is a useful design pattern for AI
programming. This system is built around two central classes: Entity and Behavior.
An entity represents the actual game object, such as the Tank or the Mouse. Behaviors control what the
entities are doing at any time. This sample uses three behaviors: Chase, Wander, and Evade.
For example, if the Tank's current behavior is WanderBehavior, the tank will wander around the screen.
If it is ChaseBehavior, it will chase a mouse. This split between entities and behaviors makes the code easy
to reuse. In this sample, the Tank and Mouse both use the WanderBehavior. The division also makes
code much more legible, by splitting apart the logic of "what should I do" and "how should I do it."
In this sample, when an Entity updates, it uses the ChooseBehavior function to decide how to
act this frame. Then, it tells that behavior to update. That behavior makes the entity speed up, slow down,
and turn, so that it acts out the desired behavior.
Extending the Sample
-
Any mouse that is further than MaxDistance away is ignored. Add disqualifying times and angles; for example, limit the tank's
interest to those no more than 90 degrees away from its current direction, or force it to give up after chasing the same mouse
for 30 seconds. This could be implemented by checking the limits inside the
CalculateFuzzy*methods. The methods could be changed to return a nullable float, and would return null if the mouse should be disqualified. - The fuzzy weights are clamped between 0 and 1 for for simplicity's sake, but they do not need to be clamped. You can get some interesting behaviors by allowing negative values, such as a tank that gives up on targets as soon as another becomes available.
- Implement interesting crowd behaviors by making the mice choose to chase one another at some times, and avoid one another at others.
- Add more than just one tank to the game. They could be opponents: each one could be a different color and have a random personality.
- Use the concepts explained in this sample to add computer-controlled opponents to one of the mini games or starter kits.