Source file unity-csharp/game-ai/utility-ai/UtilityAction.cs from the GDnD code examples. Download raw file
// GDnD wiki example
// Demonstrates: utility AI building blocks - considerations with AnimationCurve response curves, multiplied into an action score
// Related pages: [[utility-ai]], [[utility-score]], [[game-ai-agent-design]]
using System;
using System.Collections.Generic;
using UnityEngine;
// Everything the villager can "sense", each already normalised to 0..1 by the agent.
public enum UtilityInput { Hunger, Tiredness, NeedMoney, HasMoney, ThreatNearby }
public enum ActionKind { Eat, Sleep, Work, Flee, Idle }
// One question an action asks about the world, e.g. "how hungry am I?".
// The response curve turns the raw 0..1 input into a 0..1 score, so a designer can
// say "hunger barely matters until it is high" by bending a curve, not editing code.
[Serializable]
public class Consideration
{
public UtilityInput input;
public AnimationCurve curve = AnimationCurve.Linear(0f, 0f, 1f, 1f);
public Consideration() { } // used by the Inspector's "+" button
public Consideration(UtilityInput input, AnimationCurve curve)
{
this.input = input;
this.curve = curve;
}
public float Score(Func<UtilityInput, float> sense) =>
Mathf.Clamp01(curve.Evaluate(Mathf.Clamp01(sense(input))));
}
[Serializable]
public class UtilityAction
{
public string name;
public ActionKind kind;
public Transform location; // where the action happens (unused by Flee)
[Range(0f, 2f)] public float weight = 1f; // designer bias for the whole action
public List<Consideration> considerations = new List<Consideration>();
// Multiplying means any consideration scoring 0 vetoes the action ("no money,
// no food"). The catch: every extra consideration below 1 drags the total down,
// so an action with four considerations is penalised against one with one.
// The geometric mean (nth root of the product) is one way to compensate.
public float Score(Func<UtilityInput, float> sense, bool compensate)
{
float product = 1f;
foreach (Consideration c in considerations)
{
product *= c.Score(sense);
if (product <= 0f) return 0f; // early out: vetoed
}
int n = considerations.Count;
if (compensate && n > 1)
product = Mathf.Pow(product, 1f / n);
return product * weight;
}
}