Enemy AI Tasks - UQcsse3200/2026-studio-1 GitHub Wiki
Enemy AI Tasks
Overview
An enemy decides what to do through its AITaskComponent. The component holds a list of PriorityTasks. Every frame it asks each task for its priority and runs the one with the highest value. A task returns a negative priority when it does not apply, so it is never chosen.
When the highest-priority task changes, the old task is stopped and the new one is started. This is the whole decision system: there is no state machine, only tasks competing by priority.
The framework (ai/tasks/)
| Type | Role |
|---|---|
Task |
the interface: create, start, update, stop, getStatus |
PriorityTask |
a Task that also reports getPriority() |
DefaultTask |
an abstract base class that tracks the status and the owner |
TaskRunner |
what a task is given so it can reach its entity |
AITaskComponent |
the component: holds the tasks, picks the highest priority each frame |
The tasks (components/tasks/)
| Task | Priority | What it does |
|---|---|---|
WanderTask |
1 (always) | walks to a random nearby point, waits, repeats |
PlatformWanderTask |
1 (always) | a WanderTask that casts a ray down so the enemy does not walk off a ledge |
BoundedPlatformWanderTask |
1 (always) | a PlatformWanderTask that stays within a radius of where the enemy started |
ChaseTask |
10 when the target is in view | walks towards the target |
MeleeAttackTask |
15 when the target is in melee range | triggers meleeAttack every frame |
RangedAttackTask |
given, when the target is in range | triggers its ranged event every frame |
ChargeTask |
16 while a charge is running, or when the target is in the aggro radius and the charge is ready | starts a charge |
StandStillTask |
given | stops and faces the target (friendly NPCs) |
RetaliateTask |
given, when provoked | a StandStillTask that fights back once attacked |
MovementTask, WaitTask |
none | helpers used inside the wander and chase tasks |
How priority picks an attack
An attack task returns its priority only while the target is inside its range. It does not look at the cooldown: the attack component enforces that. So the task that runs is simply the highest priority among the tasks whose range covers the target.
The Cyclops uses this to choose between three attacks by distance:
| Distance to target | Task that runs | Why |
|---|---|---|
| up to 2.5 | stomp (priority 15) | highest priority and in range |
| 2.5 to 7 | laser (priority 13) | the stomp is out of range |
| 7 to 11 | rock (priority 12) | only the rock still reaches |
| beyond 11 | chase (priority 10) or wander (1) | no attack is in range |
A side effect is that the rock is never thrown up close and the laser is never fired at long range. That is deliberate.
Which enemy has which tasks
| Enemy | Tasks, highest priority first |
|---|---|
| Skeleton | melee attack, chase, platform wander |
| Ranged Skeleton | ranged attack, melee attack, chase, platform wander |
| Minotaur | charge (16), melee attack (15), chase (10), platform wander (1) |
| Centaur | charge (16), ranged attack (12), chase (10), platform wander (1) |
| Cyclops | stomp (15), laser (13), rock (12), chase (10), platform wander (1) |
| Medusa | melee attack, ranged attack (gaze), bounded platform wander |
| Cerberus | melee attack |
| Zeus | melee attack, ranged attack (lightning), chase, platform wander |
| Harpy | melee attack, chase, wander |
| Ranged Harpy | ranged attack, chase, wander |
The charge has the highest priority (16) so that, once the target is inside the aggro radius and the charge is off cooldown, it wins over a melee or ranged attack. Every enemy built on the platformer base also carries that base's melee task, whether or not it has a melee attack component.
How a task reaches the rest of the enemy
A task never calls an attack component directly. It triggers an event on its owner's entity:
MeleeAttackTask.update()triggersmeleeAttackwith the target.RangedAttackTask.update()triggers its event name (defaultrangedAttack) with the target and aProjectileType.start()triggers the same name withStarton the end.ChargeTask.start()callsChargeComponent.startChargewith the target's position.WanderTaskandChaseTasktriggerwanderStartandchaseStart, which the animation controllers use.
Movement tasks set a target on PhysicsMovementComponent, which does the walking.
Adding a new task
- Extend
DefaultTaskand implementPriorityTask. - Return a negative number from
getPriority()whenever the task should not run. - Pick a priority relative to the existing ones: wander 1, chase 10, attacks 12 to 15.
- Add it in the enemy's factory method with
aiComponent.addTask(...).
UML
Class diagram of the task hierarchy: AITaskComponent, the Task and PriorityTask interfaces, DefaultTask, and each task.

Class diagram of the tasks and the components and services each one depends on.

Enemy AI Tasks: Testing Plan
The framework tests are in ai/tasks/ and the task tests are in components/tasks/. Tasks are tested with a mocked GameTime and a real AITaskComponent, following the pattern in ChaseTaskTest.
AITaskComponentTest, DefaultTaskTest
Tests were used to ensure the following situations were working:
- The task with the highest priority is the one that runs
- A task with a negative priority is never chosen
- The old task is stopped and the new one started when the highest priority changes
DefaultTasktracks its status and its owner
ChaseTaskTest, WanderTaskTest, MovementTaskTest, WaitTaskTest
Tests were used to ensure the following situations were working:
- An enemy chases a target that is in view and stops when it is not
- An enemy wanders to a point, waits, and repeats
- A movement task reaches its target and reports that it has finished
- A wait task finishes after its duration
PlatformWanderTaskTest, BoundedPlatformWanderTaskTest
Tests were used to ensure the following situations were working:
- An enemy stays on its platform instead of walking off a ledge
- The
wanderStartevent is still sent, as it is forWanderTask - A bounded wander stays inside its radius of the starting point
MeleeAttackTaskTest, ChargeTaskTest, RetaliateTaskTest
Tests were used to ensure the following situations were working:
- Each task reports its priority only when it applies
MeleeAttackTasktriggersmeleeAttackwith the targetChargeTaskstarts a charge when the target is in the aggro radius and the charge is readyRetaliateTaskonly fights back once the NPC has been provoked
RangedAttackEventContractTest
Tests were used to ensure the following situations were working:
RangedAttackTasksends both the target and the projectile type with its event
Manual testing
Which attack the Cyclops picks is decided by priority and range together, so it is also checked by playing: stand at long range from a Cyclops and it throws a rock; at mid range it fires its laser; next to it, it stomps.