// 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 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 considerations = new List(); // 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 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; } }