HTN Planner getting started

This chapter gets an NPC running HTN planning with the shortest possible path. The Gem ships a sample Domain, guard_domain (a guard that engages when an enemy is visible and patrols otherwise), which is the best starting point.

Prerequisites

  • The HtnPlanner Gem is enabled in your project (check it in Project Manager, or add HtnPlanner to the Gem list in the project’s project.json). After enablement the system entity automatically carries HtnSystemComponent; no manual step needed.
  • AssetProcessor is running: it compiles sample and project .htndomain source assets into runtime .htndomain_compiled products.

Minimal loop (five steps)

flowchart LR a["1 Enable the Gem"] --> b["2 Add HTN Agent to an entity"] b --> c["3 Assign a compiled Domain"] c --> d["4 Write WorldState to drive decisions"] d --> e["5 Enter game mode"]

1. Add the component to an entity

Select the AI entity in the Editor and add the HTN Agent component (runtime class HtnAgentComponent, editor class EditorHtnAgentComponent). Only one per entity—the component provides the exclusive HtnAgentService.

2. Assign the Domain asset

Set the Domain field in the component’s property panel to a .htndomain_compiled asset. Note that you pick the compiled product, not the .htndomain source—sources are edited in HTN Canvas and products come from AssetProcessor. Pick the guard_domain sample to start.

The remaining defaults are enough for the agent to run on its own:

FieldDefaultPurpose
Auto StarttrueRequest the first plan automatically once the asset is ready
Continuous ModetrueReplan after a plan completes so the AI keeps running
Root TaskemptyUse the Domain’s own root task
Opportunistic ReplantrueRecompute in the background when WorldState changes; preempt when strictly better

See HTN Agent component for the full field reference.

3. Drive decisions with WorldState

The agent’s decisions are driven entirely by WorldState (a per-agent fact blackboard). The guard sample reads keys such as Guard.EnemyVisible and Guard.Ammo to choose between engaging and patrolling. Game code / sensor components write facts over HtnAgentRequestBus:

#include <HtnPlanner/HtnPlannerBus.h>
#include <HtnPlanner/HtnValue.h>

// When a sensor spots an enemy:
HtnPlanner::HtnAgentRequestBus::Event(
 agentEntityId,
 &HtnPlanner::HtnAgentRequests::SetWorldStateValue,
 AZ::Crc32("Guard.EnemyVisible"),
 HtnPlanner::HtnValue(true));

Lua / ScriptCanvas equivalent (BehaviorContext category HTN):

HtnSetWorldState(self.entityId, "Guard.EnemyVisible", true)

Key point: with Opportunistic Replan enabled, every WorldState write makes the agent re-evaluate whether the current plan is still optimal—this is how the AI reacts to a changing environment.

4. Enter game mode

Press Play. Because Auto Start = true, the agent immediately:

  1. Solves an ordered list of primitive tasks (the Plan) against the current WorldState;
  2. Executes operators one by one (the guard sample uses built-in Wait / LogMessage);
  3. Replans automatically once the plan completes (Continuous Mode = true).

What happens inside the guard sample

The task decomposition tree of guard_domain (overview):

GuardDomain
└─ Root (compound task)
 ├─ Method "Engage" [Guard.EnemyVisible == true]
 │ ├─ AlertPause (primitive, .htnnode preset Sample.AlertPause: a short pause)
 │ └─ Attack (compound task)
 │ ├─ Method "Shoot" [Guard.Ammo > 0]
 │ │ └─ Fire (primitive, LogMessage; effect: Guard.Ammo decremented by 1)
 │ └─ Method "Melee" (unconditional fallback)
 │ └─ Melee (primitive, LogMessage)
 └─ Method "Patrol" (unconditional fallback)
 └─ Patrol (Domain Import ← patrol_common.htndomain)

The imported patrol_common provides the zero-parameter compound task Patrol: first GoToPost (built-in Wait, with an effect setting Patrol.AtPost), then Scan (LogMessage).

The planner tries methods in declaration order: when Guard.EnemyVisible is true it picks “Engage”, then checks Guard.Ammo to decide Fire vs. Melee. With no enemy it falls through to “Patrol”. All three primitive binding styles (C++ built-in operator via OperatorName, .htnnode preset via NodeConfigId, and Domain Import) are visible in this one sample.

Try it: in game mode, flip Guard.EnemyVisible to true in the ImGui panel (below); the agent immediately preempts the patrol plan and switches to engaging.

Verify it works

  • Run htn_dumpStats in the console: you’ll see active agent count, decomposition steps, and solve/replan counters.
  • The guard sample’s LogMessage operator prints to the console—the most direct sign of life.
  • With the ImGui Gem enabled, htn_imguiPanel true opens the live panel showing the state machine, current plan, and all WorldState key/values (Bool/Int/Float/Vector3 are editable for what-if analysis).

Debug tooling is covered in Debugging.

Next steps


Copyright © 2026 DawnEngine. All rights reserved.

DawnEngine is a commercial 3D engine distributed under the DawnEngine end-user license agreement. Engine binaries and source are proprietary and are not covered by the licenses below.

Documentation only: the prose and templates on this site are a derivative work of Open 3D Engine (O3DE) documentation by the O3DE Contributors, used under CC BY 4.0 (documentation content), Apache 2.0 (site code), and the MIT license (inline code samples).

The open-source 3D engine that DawnEngine is built on top of is Open 3D Engine . DawnEngine is not affiliated with, endorsed by, or sponsored by The Linux Foundation or the O3DE project. “O3DE” and “Open 3D Engine” are trademarks of The Linux Foundation.