Behavior Trees in libgdx-ai: reactive interrupts vs committed behaviors

2 weeks ago 25
ARTICLE AD BOX

I am new to game-ai and try implementing enemy AI in a libGDX game using libgdx-ai Behavior Trees.

The enemy has several awareness-based states, driven by a continuous suspicion value (0.0 .. 1.0), coming from a perception system (noise, sight, etc.).

The high-level states are:

COOL / PATROL

Default behavior when suspicion is low.

INVESTIGATE (≈ suspicion 0.2 – 0.4)

The enemy moves to the last known position of a stimulus and performs short investigative actions.

SEARCH (≈ suspicion 0.4 – 0.6)

More active searching behavior (looking around, scanning, waiting).

CHASE (≈ suspicion > 0.6)

Fully reactive pursuit of the player.

The AI must be reactive upwards:

If the player is identified at any time, the enemy must immediately interrupt whatever it is doing and switch to CHASE.

But it should commit downwards:

Once the enemy starts INVESTIGATE or SEARCH, it should finish a small sequence of actions (move → look → wait, etc.) before falling back to COOL.

It should not flicker between states every frame just because suspicion fluctuates slightly.

Current Behavior Tree Setup

The root node is a dynamicGuardSelector, roughly like this:

root dynamicGuardSelector (identified?) sequence chase (suspicious?) sequence search wait ... (irritated?) sequence investigate wait ... $idlePatrolLoop

Each branch contains either a single action or a small sequence.

The Core Problem

dynamicGuardSelector gives the reactivity I want, but makes commitment very hard:

Guards are re-evaluated every tick.

Lower-priority branches can be interrupted at any time.

If an action or sequence relies on flags or timers set in enter() / exit(), interrupts can cause:

timers to reset forever

cleanup code to never run

“sticky” states (e.g. isInvestigating never becoming false)

or the opposite: loss of commitment between sequence steps

I tried several approaches:

Latch-style decorators

Blackboard booleans (isInvestigating, isSearching, etc.)

Begin/End actions around sequences

All of these work partially, but feel fragile or hard to scale as the number of states and actions grows.

What I Am Trying to Achieve (Conceptually)

I want an AI that:

Is immediately reactive to higher-priority events.

Commits to lower-priority behaviors long enough to look intentional.

Can be composed of many small actions without fragile flag logic.

Scales reasonably well as the number of states and actions increases.

In other words: “Reactive on the way up, committed on the way down.”

How is this usually solved in libGDX / libgdx-ai?

How do you typically model interruptible but committed behaviors without building fragile custom state logic?

Any architectural guidance or examples would be greatly appreciated.

Read Entire Article