SkillAgentSearch skills...

FunctionalBT

Functional Behavior Tree Design Pattern: simple, fast, debug-friendly, and memory-efficient behavior tree in C#/Unity

Install / Use

/learn @dmitrybaltin/FunctionalBT

README

FBT_Logo

"Functional Behavior Tree" Design Pattern in C#

Functional-style behavior tree in C#/Unity: simple, fast, debug-friendly, and memory-efficient behavior tree in C#/Unity.

Overview

Key Features

  1. Clear and Concise Behavior Tree Definition
    Behavior trees are defined directly in code using functions calls and lambda expressions, resulting in clear and compact logic.

  2. Ease Debug
    The tree definition and execution code are the same, which means you can place breakpoints inside code fragments, and they will behave as expected. No special complex "behaviour tree debugger" is required, you will use your favorite C# IDE.

  3. Zero memory allocation
    No memory is allocated for the tree structure because it is embedded directly into the code.
    No memory is allocated for delegate instances, thanks to the use of static anonymous delegates.
    No memory is allocated to transfer arguments to functions due to the use functions with predefined argument sets (in earlier versions of C#) or 'params Collection' arguments (in C# 13) instead of 'params arrays'.

  4. High speed
    No expensive features are used (e.g., garbage collection, hash tables, closures, etc.).
    The implementation relies solely on function invocations, static delegates, conditional expressions, and loops.

  5. Minimal and highly readable code
    The entire codebase consists of just a few .cs files, totaling a few hundred lines.

Usage Example

This example shows a simple behavior tree for an NPC on a 2D surface that can idle, approach the player, and attack.

    public static class NpcFbt
    {
        public static void ExecuteBT(this NpcBoard b) =>
            b.Sequencer(    //Classic Sequencer node
                static b => b.PreUpdate(),  //The first child of Sequencer realized as a delegate Func<NpcBoard, Status> 
                static b => b.Selector(     //The decond child of Sequencer is a Classic Selector node
                    static b => b.If(       //The first child of Selector a Classic Conditional node 
                        static b => b.PlayerDistance < 1f,  //Condition
                        static b => b.Sequencer(            //This Sequencer node is executed when the condition is true 
                            static b => b.SetColor(Color.red),
                            static b => b.OscillateScale(1, 1.5f, 0.25f),
                            static b => b.AddForce(b.Config.baseClosePlayerForce))),
                    static b => b.ConditionalSequencer(     //Using ConditionalSequencer instead of If + Sequencer (see above)   
                        static b => b.PlayerDistance < 3f,
                        static b => b.SetColor(Color.magenta),
                        static b => b.SetScale(1f, 0.1f),
                        static b => b.AddForce(b.Config.baseClosePlayerForce)),
                    static b => b.ConditionalSequencer(         
                        static b => b.PlayerDistance < 8f,
                        static b => b.SetColor(Color.yellow),
                        static b => b.SetScale(1f, 1f),
                        static b => b.AddForce(b.Config.baseDistantPlayerForce)),
                    static b => b.SetColor(Color.grey),
                    static b => b.SetScale(1f, 1f)));   
    }

Key points to note:

  1. Classes
    1. NpcFbt is a custom class implementing NPC behavior tree. It is static and contains the only one extension function ExecuteBT().
    2. NpcBoard is a custom blackboard class created by the user that contains the data and methods related to NPC. NpcBoard instance is stored in an external container (e.g., a MonoBehaviour in Unity), which calls NpcBoard.Execute() during the update cycle.
  2. Methods
    1. Action(), Sequencer(), Selector(), and ConditionalAction() are extensions methods from this library, implementing different nodes.
    2. b.AddForce(), b.SetColor(), b.SetScale(), and b.PreUpdate() are defined by the user.

This implementation is simple, zero allocation and fast and focused purely on logic, making it easy to debug.

  1. Zero memory allocation
    1. static modifier before anonymous delegates guarantee avoiding closures, therefore no memory allocation required for every delegates call. Every lambda function uses the only a single internal variable, b, and there are no closures here.
    2. Functions with multiple arguments (Selector, Sequence, etc) avoid using params arrays definition that's why no memory allocated for these calls.
  2. You can set breakpoints on any anonymous delegate or tree node function. When the execution reaches these breakpoints, the debugger will pause correctly, allowing you to inspect the state at that point.

Example of debugging

For detailed examples of using Functional Behavior Tree (FBT), see the FBT Example repository, which contains ready-to-run projects demonstrating common use cases.

How does it work

  1. As usual, a special Status object is used as the return value for each node.
    public enum Status
    {
        Success = 0,
        Failure = 1,
        Running = 2,
    }
  1. Every node is realized as a static extension function but not a class.
    For example here is a full code of a Selector node:
     public static Status Selector<T>(this T board,
         Func<T, Status> f1,
         Func<T, Status> f2,
         Func<T, Status> f3 = null,
         Func<T, Status> f4 = null,
         Func<T, Status> f5 = null,
         Func<T, Status> f6 = null,
         Func<T, Status> f7 = null,
         Func<T, Status> f8 = null)
     {
         var s = f1?.Invoke(board) ?? Status.Failure; if (s is Status.Running or Status.Success) return s;
         s = f2?.Invoke(board) ?? Status.Failure; if (s is Status.Running or Status.Success) return s;
         s = f3?.Invoke(board) ?? Status.Failure; if (s is Status.Running or Status.Success) return s;
         s = f4?.Invoke(board) ?? Status.Failure; if (s is Status.Running or Status.Success) return s;
         s = f5?.Invoke(board) ?? Status.Failure; if (s is Status.Running or Status.Success) return s;
         s = f6?.Invoke(board) ?? Status.Failure; if (s is Status.Running or Status.Success) return s;
         s = f7?.Invoke(board) ?? Status.Failure; if (s is Status.Running or Status.Success) return s;
         s = f8?.Invoke(board) ?? Status.Failure; if (s is Status.Running or Status.Success) return s;

         return s;
     }
  1. Action nodes require no implementation; any Func<T, Status> delegate can serve as an action node. For example:
     public Status AddForce(float playerForce)
     {
         var force = (_playerWorldPos - _body.worldCenterOfMass) * (playerForce * Time.deltaTime);
         _body.AddForce(force, ForceMode.VelocityChange);
         return Status.Success;
     }

Installation

You can install Functional Behavior Tree (FBT) in Unity using one of the following methods:

1. Install from GitHub as a Unity Package

  1. Open your Unity project.
  2. Go to Window → Package Manager.
  3. Click the + button in the top-left corner and choose Add package from git URL....
  4. Enter the URL: https://github.com/dmitrybaltin/FunctionalBT.git
  5. Click Add. The package will be imported into your project.

2. Install via OpenUPM

  1. Open your Unity project.
  2. Go to Edit → Project Settings → Package Manager → Scoped Registries
  3. Add a new registry for OpenUPM:
    • Name: OpenUPM
    • URL: https://package.openupm.com
    • Scopes: com.baltin
  4. Open the Package Manager (Window → Package Manager).
  5. Click + → Add package from git URL... (or search in the registry if the package appears) and enter: com.baltin.fbt

3. Install as a Git Submodule

  1. Navigate to your Unity project folder in a terminal.
  2. Run
git submodule add https://github.com/dmitrybaltin/FunctionalBT.git Packages/FunctionalBT
git submodule update --init --recursive

Requirements

C# 9 (Unity 2021.2 and later) is required because of using static anonymous delegates.

Functional Behavior Tree Philosophy

This section explains the design philosophy, implementation details, and optimizations behind the Functional Behavior Tree (BT) library. It also highlights key constraints, the reasoning behind chosen approaches, and solutions to specific challenges.

Why does it created

There are many different implementations of Behavior Trees in Unity. Typically, they include a node editor, a large set of nodes, some debugging tools, and a lot of internal service code whose efficiency is hard to assess. Debugging is often a major challenge.

There are also some implementations in C#, such as Fluid Behavior Tree, but they have some drawbacks. In particular, debugging can be difficult (requiring a special debugger), the codebase is quite heavy, unnecessary memory allocations occur, and the code is not very compact.

Functional Behavior Tree is a simple software design pattern that offers the following approach.

  • Instead of a node editor, you define the behavior tree inside C# using simplest and clear syntax.
  • Instead of a using heavy libraries with ton of internal code, you use a thin simple pattern that is absolutely transparent for you.
  • Instead of a specialized debugger, you use C# debugger inside you favorite IDE.

Initial Constraints

The library was designed with the following limitations in mind:

  1. Classic Behavior Tree Execution:

    • Implements a traditional behavior tree that executes completely during each game loop cycle, rather than adopting an event-driven approach.
  2. Code-Only Implementation:

    • Focuses solely on C# code, avoiding the need for visual editors (e.g.,
View on GitHub
GitHub Stars20
CategoryDesign
Updated1mo ago
Forks1

Languages

C#

Security Score

95/100

Audited on Feb 21, 2026

No findings