AppleyAppley Docs
Docs/API Reference

Behaviour Trees

Build modular AI behavior trees with selectors, sequences, and decorators.

24 min read Advanced Updated 2026-07-01 v2.0 ~95 min to complete

Introduction

Halo, Assassins Creed, and Splinter Cell use behavior trees for complex AI. Unlike state machines, BTs are modular, reusable, and handle concurrent behaviors gracefully. This guide builds a full BT framework in Appley.

Advanced Guide 10 of 25

This is part of the Advanced Documentation collection. Category: AI & Systems. Estimated completion: 95 minutes. Difficulty: Advanced/Professional.

Screenshot

Behaviour Trees
Behaviour Trees

How Professional Games Do It

1

U

n

2

S

e

3

D

e

4

O

r

5

I

m

6

E

a

7

U

s

8

A

9

A

d

10

D

e

11

V

i

12

P

r

FluxScript Implementation

Here is how to build this system in Appley using FluxScript (Appley Scripting):

Behaviour Trees — FluxScript
// Full behavior tree framework with decorators
// Node states: "success", "failure", "running"

func selector(children) {
  return {
    run: (ctx) => {
      for i = 0 to children.length {
        result = children[i].run(ctx)
        if result != "failure" return result
      }
      return "failure"
    }
  }
}

func sequence(children) {
  return {
    run: (ctx) => {
      for i = 0 to children.length {
        result = children[i].run(ctx)
        if result != "success" return result
      }
      return "success"
    }
  }
}

// Decorators
func repeat(child, count) {
  return {
    run: (ctx) => {
      for i = 0 to count {
        result = child.run(ctx)
        if result == "failure" return "failure"
      }
      return "success"
    }
  }
}

func invert(child) {
  return {
    run: (ctx) => {
      result = child.run(ctx)
      if result == "success" return "failure"
      if result == "failure" return "success"
      return result
    }
  }
}

func cooldown(child, seconds) {
  return {
    lastRun: 0,
    run: (ctx) => {
      if time.now() - self.lastRun < seconds return "failure"
      result = child.run(ctx)
      if result == "success" self.lastRun = time.now()
      return result
    }
  }
}

func condition(check) {
  return { run: (ctx) => check(ctx) ? "success" : "failure" }
}

func action(execute) {
  return { run: (ctx) => execute(ctx) }
}

// Guard AI behavior tree
on start {
  guardBT = selector([
    // Alarm response (highest priority)
    sequence([
      condition((ctx) => ctx.alarmActive),
      action((ctx) => moveToAlarmPoint(ctx)),
      action((ctx) => searchArea(ctx))
    ]),

    // Combat
    sequence([
      condition((ctx) => ctx.canSeeEnemy),
      selector([
        sequence([
          condition((ctx) => ctx.inCoverRange),
          cooldown(action((ctx) => shootAtEnemy(ctx)), 0.5)
        ]),
        sequence([
          action((ctx) => moveToCover(ctx)),
          cooldown(action((ctx) => shootAtEnemy(ctx)), 0.5)
        ]),
        action((ctx) => suppressEnemy(ctx))
      ])
    ]),

    // Investigation
    sequence([
      condition((ctx) => ctx.heardNoise),
      action((ctx) => investigateNoise(ctx)),
      action((ctx) => searchArea(ctx))
    ]),

    // Patrol (lowest priority)
    repeat(sequence([
      action((ctx) => moveToPatrolPoint(ctx)),
      action((ctx) -> waitAtPoint(ctx, 5))
    ]), 999)
  ])
}

on update(dt) {
  ctx = buildContext()
  guardBT.run(ctx)
}

Node Graph Equivalent

The same system implemented as a Node Graph in Appley Node Scripting:

Behaviour Trees — Node Graph
[Guard Behavior Tree]
Selector (priority order)
  +-- Sequence: Alarm Response
  |    +-- Condition: alarmActive
  |    +-- Action: moveToAlarmPoint
  |    +-- Action: searchArea
  +-- Sequence: Combat
  |    +-- Condition: canSeeEnemy
  |    +-- Selector:
  |         +-- Sequence: inCover -> shoot (cooldown 0.5s)
  |         +-- Sequence: moveToCover -> shoot
  |         +-- Action: suppress
  +-- Sequence: Investigation
  |    +-- Condition: heardNoise
  |    +-- Action: investigate -> search
  +-- Repeat 999x: Patrol
       +-- Sequence: moveToPoint -> wait 5s

Professional Optimization Tips

How AAA studios optimize these systems — applicable to your Appley project:

  • Order BT children by priority. Selectors try children in order. Put the most important behavior first (survive) and the least important last (patrol). This creates natural priority-based AI.
  • Use decorators for reuse. Instead of writing a "shoot with cooldown" action, wrap a basic "shoot" action in a Cooldown decorator. This keeps leaf nodes simple and composable.
  • Use Running state for multi-tick actions. moveToCover takes multiple frames. Return Running until arrived, then Success. The BT resumes the running node next tick without re-evaluating parents.
  • Share a blackboard (context) across the tree. Pass a context object to every node. Nodes read/write context data (health, enemy position). This decouples nodes and makes the tree reusable.

Common Mistakes

Re-evaluating the entire tree every tick

If a node returns Running, resume it next tick. Do not restart from the root every frame. This causes AI to restart actions constantly and never complete them.

No decorators — everything in leaf nodes

Decorators (cooldown, repeat, invert) keep leaf nodes clean. Without them, every action embeds cooldown/repeat logic, creating code duplication.