.. _create_ai: Creating and extending AI ====================================== Starting with version 2.0, the AI is config-driven and modular: an **AIData** asset holds tunable settings for seven always-active behaviour concerns (Construction, Production, Squad, Scouting, Economy, Utility, Defense), and an **AIController** ticks one executor per concern each match. Most tuning — difficulty, build order, squad composition, targeting — is done entirely from the Inspector, without writing any code. Code is only needed if you want a genuinely new kind of behaviour, or your own AI/data subclass. Creating an AI preset **************************************** Create an **AIData** asset via ``Assets > Create > RTS Starter Kit > AI Settings``. It has a top-level ``Think Time`` (delay between AI decisions) and seven fixed fields, each always active — leave a concern's fields at their default/empty values to effectively turn it off: .. code-block:: csharp public class AIData : ScriptableObject { public float ThinkTime; public ConstructionModuleData Construction; public ProductionModuleData Production; public SquadModuleData Squad; public ScoutingModuleData Scouting; public EconomyModuleData Economy; public UtilityModuleData Utility; public DefenseModuleData Defense; } Targeting units by role, not by asset (multi-faction AI) **************************************************************** ``ConstructionModuleData.BuildingRules`` and ``ProductionModuleData.UnitsToBuild`` (both covered below) don't have to point at a fixed ``UnitData`` asset - by default they do (**Direct** mode), but each entry is actually an ``AIUnitReference``, which also supports **By Role** mode: instead of a fixed asset, you give it a ``Role`` string tag, resolved at runtime against the current player's own faction (``FactionData.OwnProductionCategories``). This is what lets one AI preset be reused across several factions - as long as each faction tags its own matching unit/building with the same role, the preset doesn't need any faction-specific copy. Roles are free-form string tags, not a fixed enum - tag a unit or building via ``UnitData.AIRole`` (the same field Squad composition uses, see below). The Inspector shows a dropdown of already-known roles (the project-wide master list, ``GameSettings.AIRoles``) with a "+" button to add a new one inline, right from the field. If a role is later renamed or removed from that list while still assigned somewhere, the field keeps its (now stale) value instead of silently clearing it, and shows a warning instead. If a faction has several units/buildings sharing the same Role (e.g. Barracks of different tiers), ``Tier`` (also on ``UnitData``) disambiguates which one a By Role reference resolves to: - Left at the default ``-1`` (auto), it picks the highest Tier among candidates that are currently unlocked (``UnitData.RequiredBuildingToUnlock`` satisfied) - so the AI naturally starts with a low-tier unit/building and switches to a higher-tier one of the same role as it becomes available, without needing a separate rule per tier. - Pinned to a specific value (0+), it requires an exact Tier match instead - use this when a rule needs its own tuning (weight, time-gate, etc.) distinct from other tiers of the same role. Construction — building the base **************************************** ``ConstructionModuleData.BuildingRules`` is a weighted list of buildings the AI can construct; the highest-weight eligible rule wins each time the AI decides to build something. Each rule points at a building via ``Building`` (an ``AIUnitReference`` - Direct or By Role, see above), and can also be: - **Time-gated** (``TimeGate``/``TimeValue``) — always eligible, or only before/after a certain match time. - **Capped** (``MaxCount``) — counted against buildings the AI has ever finished of that type, so a destroyed building can always be rebuilt once it's below the cap again. - **Focus-scaled** (``FocusAffinity``/``FocusInfluence``) — its weight is multiplied depending on the AI's current Economy/Military focus (see Utility AI below). Leave ``FocusAffinity`` at ``None`` to keep the weight constant. - **Electricity-aware** (``ElectricityDeficitPriorityMultiplier``) — multiplies the rule's weight whenever the AI is at or over its electricity capacity (only checked if Electricity is enabled in Game Settings). Set this above 1 on your Power Plant rule so the AI reliably prioritizes power once it runs short. Each building is queued into the production line matching its own category (e.g. Defense buildings go to the Defense line), so one busy line never blocks a building from an unrelated, idle line. Production — buying combat units **************************************** ``ProductionModuleData.UnitsToBuild`` is a list of unit waves (``UnitsList``: a list of units - each an ``AIUnitReference``, Direct or By Role, see above - plus an optional ``BuildTimeType``/``TimeValue`` time gate, same semantics as Construction's). The AI cycles through waves in order, looping back to the start unless ``StopOnListEnd`` is set. The AI only ever has one unit "in flight" per wave slot at a time — it won't select the next wave entry until the previously ordered unit has actually finished building, so it never piles up a backlog in one production line. Squad — grouping and attacking **************************************** Newly built combat units are grouped into squads, then sent to attack once a squad is ready. Units currently on scouting duty (see Scouting below) are never grouped into a squad. - **Composition** (``SquadModuleData.Composition``, optional) — a list of ``SquadRoleWeight`` (a Role string tag - the same free-form tag used by By Role ``AIUnitReference``, see above - plus a desired count). A squad fills each listed role up to its desired count before its first attack. Units are tagged with a role via ``UnitData.AIRole`` — units whose role isn't listed in ``Composition`` at all still join a still-forming squad instead of being stranded alone. - If ``Composition`` is empty, squads are just grouped by flat ``GroupSize`` instead. Set ``GroupSizeMax`` above ``GroupSize`` to have each squad roll its own random target size in that range once, when it's formed, instead of every squad always using the same fixed size. - **Adaptive composition** — each ``SquadRoleWeight`` entry can set ``MaxAirCounterBonus`` (default 0, off): its effective Desired Count grows (up to Desired Count + this bonus) proportionally to how much of the enemy's spotted combat force turns out to be flying. Set this on an AntiAir role entry so squads bring more AA once the enemy is confirmed fielding aircraft, without needing to hand-tune composition per matchup. Honors ``RequireVisibleTargets`` the same way the ``ArmyRatio`` Utility AI consideration does (see below). - Once a squad has attacked at least once, it keeps fighting with whatever survivors it has left as soon as it's free again (e.g. its target died) — only a squad's very first attack waits for full composition/size. - ``RequireVisibleTargets`` — if enabled, the AI only attacks enemy units currently within its own units' vision range (honest targeting), instead of always knowing exact enemy positions regardless of Fog of War. When enabled, target selection also considers any enemy building previously spotted by Scouting (see below), even if nothing of the AI's currently sees it - without ``RequireVisibleTargets``, the AI already "cheats" and sees every enemy directly, so remembered sightings don't add anything in that mode. - Target scoring weights (``TargetDistanceWeight``/``TargetValueWeight``/``TargetThreatWeight``) tune which enemy unit a squad picks: closer, tougher (higher max health), and armed targets are preferred, each independently weighted. - **Target coordination** — when picking a target, a squad prefers one no other squad is already attacking, only falling back to an already-covered target if nothing else is visible. This spreads simultaneous squads across different targets instead of all of them piling onto the single best-scoring one. Scouting — finding the enemy **************************************** ``ScoutingModuleData`` keeps a small pool of units permanently wandering the map to locate the enemy, and remembers enemy buildings once spotted, so Squad (above) can send an attack at one even after it's no longer in sight: - ``MaxScoutingUnits`` — how many units this AI keeps on permanent scouting duty at once. Only freshly spawned units are ever considered for scouting duty, before Squad gets a chance to group them — a unit is decided once, at spawn, to be either a scout or an army unit for its whole lifetime, never both, so existing squads are never pulled apart to fill the quota, and there's no tug-of-war between a scouting order and a squad order for the same unit. - ``GridResolution`` — the map is divided into a ``GridResolution`` x ``GridResolution`` grid; scouts cycle through a shuffled pass over the grid's NavMesh-sampled cell centers, so multiple scouts spread across different areas instead of clumping on the same point, and the whole map gets covered before any cell repeats. - ``WaypointTimeout`` — if a scout hasn't reached its current waypoint within this many seconds (e.g. stuck on pathing), it's redirected to the next one instead of waiting forever. - ``StartDelay`` — delay from game start before units start being assigned to scouting duty. Spotting an enemy building isn't limited to units actively scouting - any of the AI's own units can add one to memory just by having it within vision range. Scouting only decides *who* goes looking for the enemy; vision itself is inherent to every unit. A remembered building is forgotten as soon as it's destroyed, and also - if ``MemoryDuration`` is set above 0 - once it hasn't been re-seen for that many seconds (0, the default, means never expire; a building's position doesn't itself go stale, but this keeps a very old unconfirmed sighting from being a permanent target while ``RequireVisibleTargets`` is on, and bounds memory growth on very long matches). Economy — keeping harvesters up **************************************** ``EconomyModuleData`` replenishes harvesters: ``TargetHarvesterCount``/``BuyDelay`` control how many it tries to keep alive and how often it checks. Unlike the other concerns, it has no unit reference of its own - the harvester unit to build comes from the current player's ``FactionData.Harvester``, so the same AIData preset works across every faction that sets one. Leave ``FactionData.Harvester`` empty on a faction to keep harvester replenishment off for it. It also auto-repairs buildings: any of this AI's own buildings whose health drops below ``AutoRepairHealthThreshold`` (a fraction of max health) gets a ``Repair`` component attached automatically - the same mechanism the player's Repair button uses, so it spends the same resources at the same rate and removes itself once the building is back to full health. Set ``AutoRepairHealthThreshold`` to 0 to disable auto-repair entirely. Finally, it can grant a straightforward difficulty handicap, separate from any of the behaviour tuning above: ``StartingResourceBonus`` is added once, effectively at match start, and ``PeriodicResourceGrant`` (if above 0) is added repeatedly every ``GrantInterval`` seconds. Both default to 0 - no effect unless you opt in, e.g. to make an "Easy" preset weaker or a "Brutal" one richer without touching how it actually plays. Defense — reacting to being attacked **************************************** ``DefenseModuleData`` redirects nearby idle/available combat units to fight off an enemy that comes within ``DetectionRadius`` of one of this AI's own buildings - without it, the AI only ever attacks outward via Squad and never reacts to being attacked at home. - ``ResponseRadius`` — how far from the threatened building this AI will pull units from to respond. Keeps a small raid near one base from draining units out of an ongoing offensive elsewhere on the map - only units already nearby (mostly idle ones) get pulled in. - ``MaxDefendersPerThreat`` — caps how many units respond to a single attacker at once. - Every threatened building is handled the same tick (not one-at-a-time like Construction's building placement) - a defense response never waits behind an unrelated, simultaneous threat elsewhere. Units already responding to the same attacker aren't re-ordered every tick, and scouting units are never pulled off scouting duty to defend. Utility AI — dynamic Economy/Military focus **************************************************** ``UtilityModuleData`` is an optional decision layer that continuously scores configured **Actions** and writes the result into two shared 0..1 values, ``EconomyFocus``/``MilitaryFocus`` (both default to the neutral 0.5, so a preset without any Utility Actions behaves exactly as if this concern didn't exist). Other modules read those focus values back: - ``EconomyModule``/``ProductionModule`` scale their buy delay by focus (faster when the matching focus is high, slower when low). - ``ConstructionModule`` scales a ``BuildingRule``'s weight by focus, if that rule opted in via ``FocusAffinity``. An **Action** (``AIUtilityActionData``) targets one focus axis (``AIFocusTarget.Economy`` or ``.Military``) and holds one or more **Considerations** (``AIConsideration``): each consideration reads one raw game-state input (``AIConsiderationInput`` — ``IncomeRate``, ``HarvesterSaturation``, ``ResourceStockpileRatio``, ``ArmyRatio``, ``TimeSinceMatchStart``), normalizes it by ``NormalizationReference``, and maps it to a 0..1 score via a designer-tunable ``ResponseCurve``. An action's score is the product of all its considerations' scores; the highest-scoring action for each axis wins that axis every ``EvaluationInterval`` seconds, and the focus value smoothly moves toward it by ``FocusSmoothing``. For example, an Economy-axis action with an ``IncomeRate`` consideration whose curve scores low income highly will push ``EconomyFocus`` up whenever the AI's income drops — which in turn speeds up ``EconomyModule``'s harvester buying and boosts any ``BuildingRule`` with ``FocusAffinity: Economy`` (like a Power Plant or Refinery rule). ``RequireVisibleArmyCount`` extends the same Fog-of-War honesty as Squad's ``RequireVisibleTargets``, but for the ``ArmyRatio`` consideration. Per-player AI presets (difficulty/personality) **************************************************** An AI preset can be selected per player in the lobby, independently of difficulty being baked into one shared asset. ``GameSettings.AIPresets`` lists every selectable preset (populated automatically from all ``AIData`` assets), and the lobby's AI preset dropdown writes the chosen one into ``PlayerSettings.SelectedAIPreset``. If a player leaves it unset, ``MapData.DefaultAIPreset`` is used instead. Debugging: the AI Inspector **************************************** ``RTS Starter Kit > AI Inspector`` (Editor menu) is a Play Mode-only tool for picking one AI player and watching its live state update continuously: current resources (including electricity used/total), every production building's queues (click a queue's header to select its building in the scene), and the current Utility AI state (income rate, Economy/Military focus values). It has no effect outside Play Mode and shows a status message if the game hasn't finished booting yet. Extending with your own AI logic **************************************** **Adding a whole new concern** (an eighth module alongside Construction/Production/Squad/Scouting/Economy/Utility/Defense): create a data class in the Data assembly and its matching executor in the AI assembly, then wire it into your own ``AIController`` subclass. .. code-block:: csharp // Data assembly - fields only, no gameplay logic [Serializable] public class MyModuleData : BaseAIModuleData { public float SomeSetting = 1f; } // AI assembly - the logic that reads MyModuleData public class MyModule : BaseAIModule { readonly MyModuleData data; public MyModule(MyModuleData data) => this.data = data; public override void Tick(AIContext context) { // context gives you Units, GameSettings, SelfPlayer, Enemies, // SelfCommandCenter, EconomyFocus/MilitaryFocus, etc. } } // Your own AIData subclass with the new field public class MyAIData : AIData { public MyModuleData MyExtra = new (); } // Your own AIController subclass public class MyAIController : AIController { protected MyAIData MyData => (MyAIData)aiData; // aiData is protected, cast once here protected override void BuildModules() { base.BuildModules(); modules.Add(new MyModule(MyData.MyExtra)); } } Create a ``MyAIData`` asset instead of a plain ``AIData`` one — Unity's Inspector accepts it anywhere an ``AIData`` reference is expected (``GameSettings.AIPresets``, ``MapData.DefaultAIPreset``, etc.), no other change needed. **Using your own AIController subclass**: override ``AISystem``'s factory method so it creates your subclass instead of the base one - the rest of ``AISystem`` (updating, disposing, registering with ``AIService``) works with any ``AIController`` subtype unchanged: .. code-block:: csharp public class MyAISystem : AISystem { protected override AIController CreateAIController() => new MyAIController(); } Then register ``MyAISystem`` instead of the stock ``AISystem`` in your own ``IBootData.MakeGameModules()`` (see the Dependency Injection tutorial for how the boot module list works). **Other useful overrides on AIController** (without a subclass of your own module list): ``UpdateAction()`` runs every frame regardless of Think Time, ``HandleActions()`` runs on the Think Time cadence after all modules ticked, ``OnHarvesterSpawned()`` fires when this AI's harvester is spawned, ``OnAIAddedToPlayer()`` fires once the controller is set up for its player. .. toctree::