There are times when we have to deal with gray areas, instead of binary-based values, to make decisions, and fuzzy logic is a set of mathematical techniques that help us with this task.
Imagine that we're developing an automated driver. A couple of available actions are steering and speed control, both of which have a range of degrees. Deciding how to take a turn, and at which speed, is what will make our driver different and possibly smarter. That's the type of gray area that fuzzy logic helps represent and handle.
This recipe requires a set of states indexed by continuous integer numbers. As this representation varies from game to game, we handle the raw input from such states, along with their fuzzification, in order to have a good general-purpose fuzzy decision maker. Finally, the decision maker returns a set of fuzzy values representing the degree of membership of each state.
We will create two base classes and our fuzzy decision maker:
MembershipFunction
:using UnityEngine; using System.Collections; public class MembershipFunction : MonoBehaviour { public int stateId; public virtual float GetDOM(object input) { return 0f; } }
FuzzyRule
class:using System.Collections; using System.Collections.Generic; public class FuzzyRule { public List<int> stateIds; public int conclusionStateId; }
FuzzyDecisionMaker
class:using UnityEngine; using System.Collections; using System.Collections.Generic; public class FuzzyDecisionMaker : MonoBehaviour { }
public Dictionary<int,float> MakeDecision(object[] inputs, MembershipFunction[][] mfList, FuzzyRule[] rules) { Dictionary<int, float> inputDOM = new Dictionary<int, float>(); Dictionary<int, float> outputDOM = new Dictionary<int, float>(); MembershipFunction memberFunc; // next steps }
foreach (object input in inputs) { int r, c; for (r = 0; r < mfList.Length; r++) { for (c = 0; c < mfList[r].Length; c++) { // next step } } } // steps after next
memberFunc = mfList[r][c]; int mfId = memberFunc.stateId; float dom = memberFunc.GetDOM(input); if (!inputDOM.ContainsKey(mfId)) { inputDOM.Add(mfId, dom); outputDOM.Add(mfId, 0f); } else inputDOM[mfId] = dom;
foreach (FuzzyRule rule in rules) { int outputId = rule.conclusionStateId; float best = outputDOM[outputId]; float min = 1f; foreach (int state in rule.stateIds) { float dom = inputDOM[state]; if (dom < best) continue; if (dom < min) min = dom; } outputDOM[outputId] = min; }
return outputDOM;
We make use of the boxing/unboxing technique for handling any input via the object data type. The fuzzification process is done with the help of our own membership functions, derived from the base class that we created in the beginning. Then, we take the minimum degree of membership for the input state for each rule and calculate the final degree of membership for each output state given the maximum output from any of the applicable rules.
We can create an example membership function to define whether an enemy is in enraged mode, knowing that its life points (ranging from 0 to 100) are equal to or less than 30.
The following is the code for the example MFEnraged
class:
using UnityEngine; using System; using System.Collections; public class MFEnraged : MembershipFunction { public override float GetDOM(object input) { if ((int)input <= 30) return 1f; return 0f; } }
It's worth noting that it is a common requirement to have a complete set of rules; one for each combination of states from each input. This makes the recipe lack in scalability, but it works well for a smaller number of input variables and a small number of states per variable.