SnowyCocoon
Back to blog
Building Behavior Trees in Unity for a Fighting Game

Building Behavior Trees in Unity for a Fighting Game

1 March 20266 min read
UnityGame AIC#GameDev

Why Behavior Trees?

When I joined 3R Games to work on Punch Lunch: Foodtruck Fighter, the combat AI was a mess of nested if-statements. A single enemy had ~400 lines of switch-case logic. Adding a new attack pattern meant touching four different files and hoping nothing broke.

Behavior Trees (BTs) solved this elegantly. Instead of hardcoded logic, you describe an agent's decision-making as a tree of composable nodes. The result is AI that is easy to read, easy to extend, and easy to debug visually.

The Core Node Types

In our implementation, every node returns one of three states: SUCCESS, FAILURE, or RUNNING.

  • Sequence — runs children left-to-right; fails on first failure (AND logic)
  • Selector — tries children until one succeeds (OR logic)
  • Decorator — wraps a single child to invert, repeat, or gate it
  • Leaf / Action — the actual game logic (move, attack, play animation)

Data-Driven Design

One of the best decisions we made was keeping the tree structure in ScriptableObject assets rather than hardcoding it in C# classes. This meant designers could tweak enemy behavior without touching code.

[CreateAssetMenu(menuName = "AI/Behavior Tree")]
public class BehaviorTree : ScriptableObject
{
    public BTNode rootNode;
    public BTNode.State treeState = BTNode.State.Running;

    public BTNode.State Update()
    {
        if (rootNode.state == BTNode.State.Running)
            treeState = rootNode.Update();
        return treeState;
    }
}

Blackboard for Shared State

Nodes need to share data (like "is the player in range?" or "what was my last attack?"). We solved this with a simple Blackboard — a dictionary on the agent's context object that any node can read from or write to.

public class BehaviorTreeRunner : MonoBehaviour
{
    public BehaviorTree tree;
    public GameObject player;

    private void Start()
    {
        tree = tree.Clone();
        tree.Bind(this);
    }

    private void Update()
    {
        tree.Update();
    }
}

What I Learned

  • Keep leaf nodes small. If a node does more than one thing, split it. The smaller the node, the easier it is to reuse and test.
  • Visualize early. We built a simple custom editor that highlights the currently executing node in red. This single tool cut debugging time by half.
  • Avoid the Blackboard bloat. It's tempting to throw everything into the blackboard. We ended up with strict naming conventions and typed wrapper methods to keep it sane.
  • BTs complement animation. Pairing BT states with Unity's Animator via parameter triggers gave us very fluid-looking enemy reactions.

The Result

The final system powers all four enemy types in Punch Lunch, each with distinct personalities and combo patterns — all built from the same reusable node library. New enemies can be prototyped in under an hour. That's the power of good architecture.