AppleyAppley Docs
Docs/API Reference

Custom AI Systems

Build sophisticated AI with behavior trees, utility AI, and GOAP.

23 min read Advanced Updated 2026-07-01 v2.0 ~90 min to complete

Introduction

Professional games like Halo, FEAR, and The Sims use advanced AI architectures beyond simple state machines. This guide explains behavior trees, utility AI, and Goal-Oriented Action Planning (GOAP) — then builds a custom AI system in Appley.

Advanced Guide 9 of 25

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

Screenshot

Custom AI Systems
Custom AI Systems

How Professional Games Do It

1

C

h

2

S

t

3

I

m

4

A

5

A

d

6

S

c

7

U

s

8

D

e

9

A

d

10

A

I

11

I

m

12

E

n

FluxScript Implementation

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

Custom AI Systems — FluxScript
// Behavior Tree implementation for advanced enemy AI
// BT node types: Selector, Sequence, Action, Condition

func createSelector(children) {
  return {
    type: "selector",
    run: (ctx) => {
      for i = 0 to children.length {
        result = children[i].run(ctx)
        if result != "failure" return result  // success or running
      }
      return "failure"
    }
  }
}

func createSequence(children) {
  return {
    type: "sequence",
    run: (ctx) => {
      for i = 0 to children.length {
        result = children[i].run(ctx)
        if result != "success" return result  // failure or running
      }
      return "success"
    }
  }
}

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

func createAction(execute) {
  return { type: "action", run: (ctx) => execute(ctx) }
}

// Build the enemy behavior tree
on start {
  enemyBT = createSelector([
    // Priority 1: Survive
    createSequence([
      createCondition((ctx) => ctx.health < 30),
      createSelector([
        createSequence([
          createCondition((ctx) => ctx.hasMedkit),
          createAction((ctx) => useMedkit(ctx))
        ]),
        createSequence([
          createCondition((ctx) => ctx.coverAvailable),
          createAction((ctx) => moveToCover(ctx)),
          createAction((ctx) => waitInCover(ctx, 3))
        ]),
        createAction((ctx) => flee(ctx))
      ])
    ]),

    // Priority 2: Attack
    createSequence([
      createCondition((ctx) => ctx.canSeePlayer && ctx.hasAmmo),
      createSelector([
        createSequence([
          createCondition((ctx) => ctx.inAttackRange),
          createAction((ctx) => attackPlayer(ctx))
        ]),
        createSequence([
          createAction((ctx) => moveToAttackPosition(ctx)),
          createAction((ctx) => attackPlayer(ctx))
        ])
      ])
    ]),

    // Priority 3: Investigate
    createSequence([
      createCondition((ctx) => ctx.lastKnownPlayerPos != null),
      createAction((ctx) => investigate(ctx))
    ]),

    // Priority 4: Patrol
    createAction((ctx) => patrol(ctx))
  ])
}

on update(dt) {
  // Build AI context (shared blackboard)
  ctx = {
    health: self.health,
    hasMedkit: self.inventory.has("medkit"),
    hasAmmo: self.currentWeapon.ammo > 0,
    canSeePlayer: canSeePlayer(),
    inAttackRange: math.distance(self.position, player.position) < 15,
    coverAvailable: findCover() != null,
    lastKnownPlayerPos: lastSeenPlayer,
    coverPos: findCover(),
    patrolTarget: getCurrentPatrolPoint()
  }

  // Tick the behavior tree
  result = enemyBT.run(ctx)
  // result is "success", "failure", or "running"
}

// Utility AI example (alternative to BT)
func utilityAI.decide(ctx) {
  actions = [
    {
      name: "attack",
      score: 0,
      execute: (ctx) => attackPlayer(ctx)
    },
    {
      name: "takeCover",
      score: 0,
      execute: (ctx) => moveToCover(ctx)
    },
    {
      name: "reload",
      score: 0,
      execute: (ctx) => reload(ctx)
    },
    {
      name: "flank",
      score: 0,
      execute: (ctx) => flankPlayer(ctx)
    }
  ]

  // Score each action based on context
  for action in actions {
    if action.name == "attack" {
      action.score = 50
      if ctx.canSeePlayer action.score += 30
      if ctx.hasAmmo action.score += 20
      if ctx.inAttackRange action.score += 30
    }
    if action.name == "takeCover" {
      action.score = 10
      if ctx.health < 50 action.score += 40
      if ctx.coverAvailable action.score += 20
    }
    if action.name == "reload" {
      action.score = 5
      if !ctx.hasAmmo action.score += 60
      if ctx.coverAvailable action.score += 20
    }
    if action.name == "flank" {
      action.score = 15
      if ctx.canSeePlayer action.score += 10
      if ctx.squadMembers > 1 action.score += 25
    }
  }

  // Execute highest-scored action
  actions.sort((a, b) => b.score - a.score)
  if actions[0].score > 0 {
    actions[0].execute(ctx)
  }
}

Node Graph Equivalent

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

Custom AI Systems — Node Graph
[Behavior Tree Structure]
Selector (try until success)
   +-- Sequence: Survive
   |    +-- Condition: health < 30
   |    +-- Selector:
   |         +-- Sequence: hasMedkit -> useMedkit
   |         +-- Sequence: coverAvailable -> moveToCover -> wait
   |         +-- Action: flee
   +-- Sequence: Attack
   |    +-- Condition: canSeePlayer && hasAmmo
   |    +-- Selector:
   |         +-- Sequence: inRange -> attack
   |         +-- Sequence: moveToPosition -> attack
   +-- Sequence: Investigate
   |    +-- Condition: lastKnownPos != null
   |    +-- Action: investigate
   +-- Action: Patrol (fallback)

[Utility AI Scoring]
For Each Action:
   --> [Calculate: base score]
   --> [Add: context bonuses (health, ammo, distance, squad)]
   --> [Sort: by score descending]
   --> [Execute: highest scored action]

Professional Optimization Tips

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

  • Use behavior trees for complex, hierarchical AI. BTs are modular — you can swap subtrees between enemy types. A Grunt and an Elite share the same attack subtree but have different survival logic.
  • Use utility AI for autonomous NPCs. The Sims scores hundreds of actions (eat, sleep, socialize) by current needs. Utility AI produces emergent, life-like behavior without scripted sequences.
  • Use GOAP for tactical AI. FEAR enemies plan action sequences: reload -> find cover -> peek -> shoot. GOAP finds the optimal plan given preconditions and effects. It is expensive but produces intelligent behavior.
  • Add a reaction delay to AI perception. Professional AI does not react instantly. Add 200-500ms from detection to action. This feels realistic and gives players a chance to react.

Common Mistakes

Single monolithic state machine

Complex AI in one FSM becomes unmaintainable. Break into behavior trees or utility systems. FSMs are fine for simple enemies but do not scale to bosses or squad tactics.

Instant reaction to player detection

AI that instantly turns and shoots feels unfair. Add a perception delay and a turn animation. Players need time to react to being detected.