This document is about: QUANTUM 1
SWITCH TO

Blackboard

Introduction

The Blackboard is another very useful tool contained in the Bot SDK.
It is a set of Keys/Values that you can use to have your own customized data.
It is like a read/write memory for Actions and Decisions that you can have per agent.

The Blackboard is also designer friendly as it is totally defined using Data Assets.
So you can have your blackboards stored and organized on your Unity project.

PS.: it is important to notice that the Blackboard data assets are just the memory layout definition.
If you have many Bots/Entities which should have the same memory layout, then all of those can point to the same Blackboard asset.
Each of those bots will then have its own memory based on the layout.

A very simple example

So, lets say that you have a character which collects some items on the map.
On your Actions and Decisions, you might want to read the amount of items that the character already collected in order to make a specific decision.
Also, if the character collects a new item, the items count should increase.
For this, you can have an entry on your blackboard, named ItemsAmount of type Integer and manage it on your actions and decisions code.

As the blackboard is used per-entity, other entities can also read the data contained on another entity's blackboard.

Creating a Blackboard Asset

On Unity, you can create a new Blackboard asset from the right click menu on the Project tab, under Create->Quantum->Assets->AIBlackboard.
This is the initial asset state:

New Blackboard

Now you just need to create your own entries and set their types and names.

Blackboard entries

The types currently supported by the Blackboard are:

  1. Boolean;
  2. Byte;
  3. Integer;
  4. FP;
  5. Vector2;
  6. Vector3;
  7. Entity Ref.

Using the Blackboard

On your quantum_code solution, you can already use the blackboard as a component on your entities.

C#

entity Hero[8] {
    use Transform2D;
    use DynamicBody;
    use Prefab;
    use AIBlackboardComponent;
    ...
}

Important!

Before actually using the Blackboard into your project, there is a mandatory setup to be performed on code.
There is a method which returns the blackboard from a given entity so the whole blackboard setup works.

But, of course, each game will have its own entities which use the Blackboard, and this will differ from game to game.
So you need to implement this method on your game.

It is a very simple task: on the quantum_code solution, open the file named Entity.Blackboard.cs and, on the unique method declared there, uncomment the sample code and replace all the "YourEntityType" by your own entity types which has the AIBlackboardComponent.
It will end up as something like this:

C#

public unsafe partial struct Entity {
    public static AIBlackboardComponent* GetBlackboardComponent(Entity* entity) {
      switch (entity->_ref._type)
      {
        case EntityTypes.Hero:
          return &((Hero*)entity)->AIBlackboardComponent;
      }
      return null;
    }
  }

Referencing a Blackboard asset

The AIBlackboardComponent contains a field named Board, which points to the blackboard data asset that you created on Unity.
You can make the reference by using the DB or by using Asset Links.

C#

hero->AIBlackboardComponent.Board = playerData.BlackboardLink;
// Or
hero->AIBlackboardComponent.Board = DB.FindAllAssets<AIBlackboard>()[0];

This is enough to already read/write data from the entity's personal blackboard.
But there is another handy asset to be highlighted before diving into read/write snippets.

Initializing Blackboard Component Memory

Because the types and amount of different data stored on a blackboard is not created in a static context (by code generation for example) the memory for each component needs to be stored on the frame in a custom manner as UserData (opposed to being saved on the game state like all other entities/components).

Each Blackboard component needs to reserve it's memory by calling Frame.CreateBlackboardMemory() once after the entity has been created. It's essential to release the memory before the entity is destoyed some time later by calling Frame.DestroyBlackboardMemory(). When "Failed to allocate memory" messages are shown you most likely forgot to release the blackboard memory.

C#

var hero = f.CreateHero();

// Call CreateBlackboardMemory before further configuration of the blackboard component.
f.CreateBlackboardMemory(&hero->AIBlackboardComponent);

// Continue the initialization.
var data = f.GetPlayerData(player);
if (data.BlackboardInitializer.Instance != null) {
    AIBlackboardInitializer.InitializeBlackboard(f, &hero->AIBlackboardComponent, data.BlackboardInitializer.Instance, null );
}

// Call DestroyBlackboardMemory before destroying the hero
f.DestroyBlackboardMemory(&hero->AIBlackboardComponent);
f.DestroyHero(hero);

The Blackboard Initializer asset

This asset aims to help with a blackboard's initialization.
It mirrors a blackboard's entries and, for each entry, you can define a default value to be applied on the blackboard during initialization.

To create a Blackboard Initializer asset, right click on the Project tab and choose Create->Quantum->Assets->AIBlackboardInitializer.
This will be its initial state:

Blackboard Initializer Default

Then, you have to choose what will be the blackboard asset to be initialized by this asset.
When you that, the blackboard's entries will already show up on the initializer asset.
You can then click on the Add Field button if you want to define some initial value for that entry.
It will look like this:

Blackboard Default Entry

Now, on your simulation code, you can choose to initialize your AIBlackboardComponent from your initializer asset.
This is the code snippet needed for this:

C#

AIBlackboardInitializer.InitializeBlackboard(f, &guy->AIBlackboardComponent, data.BlackboardInitializer.Instance, null);

Note that data.BlackboardInitializer is just an asset link to some BlackboardInitializer asset.
You can also load it using DB.FindAsset<AIBlackboardInitializer>("Guid");

PS.: if you want, you can take a look at Bot SDK Sample which has a sample code to initialize a Blackboard component based on the Blackboard Initializer Asset.

Re-initializing Blackboards upon entity respawn

If you destroy some entity and then create that same entity again, like during some respawn, that entity's blackboard will already contain the values related to its previous life cycle.

So, it is advised that you re-initialize your Blackboard data for any spawned entity to prevent old data from being used.
The BlackboardInitializer asset is very useful in this situation.
Just execute AIBlackboardInitializer.InitializeBlackboard() for every spawned entity.

Reading and Writing to the Blackboard

To read/write data to an entity blackboard:

C#

var bb = Entity.GetBlackboardComponent(e);

// To read
var value = bb->Board.GetValue("someKey");

// To write
bb->Board.Set("someKey", value, frame, bb);

PS.: Bot SDK from version 1.0 RC2 and on already comes with some sample Actions/Decisions which reads/writes to the Blackboard.
On your quantum_code solution, you can go to the folder BotSDK/Samples and check the files IncreaseBlackboardInt.cs, SetBlackboardInt.cs and HFSM.CheckBlackboardInt.cs

The Blackboard on the Visual Editor

The Visual Editor already comes with a sub-menu for the Blackboard.
It is named Blackboard Variables and it is accessible on the left side panel:

Blackboard Visual Editor Empty

Press the + button in order to create new blackboard variables.
When creating/editing a new entry, you need to define:

  • The variable's Name, which is internally used as a Key on the blackboard asset to retrieve some variable's value;
  • The variable's Type, which you can select from the dropdown menu;
  • The HasDefault checkbox, used to inform if this variable will or not be initialized to a default value upon setup;
  • The Default value to be used.
Blackboard Visual Editor Creating Entry

You can have many entries, with many types and not all of them needs to have a default value:

Blackboard Visual Editor Many Entries

Now, whenever you compile your project, two extra data assets will automatically be created on the folder Assets/Resources/DB/CircuitExport/Blackboard_Assets:

  • A Blackboard asset which entries are the ones defined by the compiled HFSM;
  • and a Blackboard Initializer asset which entries and default values are also the ones defined by the compiled HFSM.
Blackboard Assets

Blackboard Nodes

With the variables in hand, you can drag and drop them to the graph in order to create Blackboard Nodes. These nodes always have two outbound slots: the Key and the Value.

Blackboard Assets

The Key slot is used to be linked to fields of type AIBlackboardValueKey, which can be used to replace hardcoded strings when informing the key of the variable to get/set. And, of course, by removing the hardcoded keys, the code gets more flexible on reliable. Let's analyze the get and set Before/After:

C#

var bb = Entity.GetBlackboardComponent(e);

// -- BEFORE --
// To read
var value = bb->Board.GetValue("someKey");

// To write
bb->Board.Set("someKey", value, frame, bb);


// -- AFTER --
public AIBlackboardValueKey PickupsKey;
// To read
var value = bb->Board.GetValue(PickupsKey.Key);

// To write
bb->Board.Set(PickupsKey.Key, value, frame, bb);

With that new field declared, you can go to the Visual Editor and link it to any Blackboard Node's Key slot to it. For example:

Blackboard Assets

Besides of using the Key slot like this, it is also possible to link the Value slot to define fields of that same type. The Default value defined on the left panel is the value which will be baked into the asset.

Blackboard Assets

When to use the Blackboard and its Nodes

Use it whenever you want to store some data per-entity which can change during runtime. If you need to define values which will never change, consider using the Constants panel instead.

Blackboard's Frame Methods

The Blackboard implements these Frame's partial methods: InitUser, FreeUser, AllocUser and CopyFromUser.

If your game already contains an implementation for those methods, a conflict will be generated.
To avoid this, you can set the compiler symbol USE_BLACKBOARD_FRAME_METHODS.
This will prevent the Blackboard from implementing the partial methods.
For each of those methods, you will have a substitute method.
They are: InitBlackboard, FreeBlackboard, AllocBlackboard, CopyFromUserBlackboard, SerializeBlackboard and DeserializeBlackboard.

You just need to make sure that all those methods will still be called by the Frame's equivalent methods.

Back to top