2. Concepts
2.1. Blueprint
A UBF Blueprint consists of the variables, logical steps, and execution flow that define the runtime functionality. Each
logical step is represented by a Node in the Blueprint, with the flow of information and execution determined by which
node inputs and outputs are joined by Connections. Blueprints must have a single entry point, defined by the
Entry Node.
To be a valid UBF Blueprint, the JSON structure must contain the version of the standard it was created against, an array of nodes, an array of Connections, an array of variables, called Bindings, and a dictionary of Functions, which represent re-usable sections of a Blueprint. Any files not conforming exactly to the schemas laid out below are not considered valid UBF files.
{
"version": "2.0.0",
"nodes": [],
"connections": [],
"bindings": [],
"fns": {}
}
2.1.1. Nodes
Each Node in a UBF Blueprint must correspond to an Action that is executed in the Interpreter at runtime. It must conform to one of the pre-defined Node structures set out in the Node Reference page. If a Node in a Blueprint has a type that is not listed, or if the inputs/outputs deviate from what is set out, it is not guaranteed to work in the Interpreters (See Custom Nodes).
From here on, Node inputs/outputs will be collectively referred to as 'Ports'.
{
"id": "Example",
"type": "Entry",
"inputs": [],
"outputs": [
{
"id": "Exec",
"type": "exec"
}
]
}
id: Must be a unique identifier string. It is unique to the Blueprint, but does not need to be globally unique. Referred to from here on as the Node ID.type: Should be one of the pre-defined Node Types set out in the Node References. This tells the interpreter which Action to run when the Node is executed during runtime.inputs: An array containing expected inputs to the Node. Inputs are fed to the Nodes via Connections to the Outputs of other Nodes. Inputs are defined by the following JSON structure:
{
"id": "Example",
"type": "string",
"value": ""
}
outputs: An array containing outputs that the Node produces. These can be passed to other Nodes via Connections. Outputs are defined by the following JSON structure:
{
"id": "Example",
"type": "float"
}
For both input and output Ports, id is an identifier unique to the Node (referred to from here on as the Port ID),
which should be used to get or set the value in the Interpreters at runtime, and the type must be a valid
UBF Type. For inputs, value must be a JSON string, number, boolean, or null value, determined by the
expected value of the Type.
2.1.2. Connections
Each Connection in a Blueprint describes the flow of either execution (via Exec Ports) or of information. This flow is strictly one way. Each Connection uses a combination of Node ID and Port ID to define which Ports to go to and from.
{
"source": "",
"target": "",
"sourceKey": "",
"targetKey": ""
}
source: The Node ID of the Node from which the data is produced.target: The Node ID of the Node to which the data should be passed.sourceKey: The Port ID of the Port that writes the data.targetKey: The Port ID of the Port that reads the data.
The Blueprint must not contain any cyclical Connections. A cyclical Connection is defined as any Connection that, when followed either directly or indirectly, leads back to the Connection's source Node.
2.1.3. Bindings
Blueprint Bindings are variables within the Blueprint. They can either be inputs, outputs, or local to the Blueprint,
depending on the scope.
input: The value of input Bindings can be set in the Blueprint itself, but typically they would be overwritten by the Interpreters or by a calling Blueprint. This allows experiences to customize the behavior of the runtime execution, without needing a new Blueprint.output: An output variable can be set in the Blueprint, and should be made available to the Interpreter or calling Blueprint once the Blueprint has finished running. This lets experiences use results obtained from the Blueprint for further logic.var: A variable that is local to the Blueprint. It cannot be customized or accessed by interpreters or calling Blueprints. Think of it like a local variable in a function.
A Blueprint can trigger another Blueprint to run via the ExecuteBlueprint Node.
The Blueprint that triggers another is refered to as a calling Blueprint.
Blueprint Bindings should be defined like so:
{
"id": "Example",
"scope": "input",
"type": "int",
"value": 0
}
id: A unique identifier string within the Blueprint. Should be used to obtain or set the value of the Binding during runtime execution.scope: A string that must be equal toinput,output, orvar.type: Determines the type of data the Binding contains. Must be a valid UBF Type.value: The value of the Binding. Must be a JSON string, number, boolean, or null value, determined by the expected value of the Type.
The following names in a UBF Blueprint are reserved:
execGraph
This means you should not create any Bindings with these values as IDs, otherwise it will cause issues.
2.1.4. Functions
When creating a Blueprint, you may find yourself repeating a set of nodes multiple times. To save time and space in your Blueprint, Functions let you re-use a set of Nodes in different parts of your Blueprint. Functions essentially act like a mini-Blueprint within your Blueprint, and contain Nodes, Connections, and Bindings just like Blueprints.
{
"nodes": [],
"connections": [],
"bindings": []
}
Functions can be called in the top-level Blueprint using the Fn Node, where the inputs and outputs on
that Node correspond to the input and output bindings set in the Function.
You can have multiple Functions defined within a Blueprint, but unlike the other properties in a Blueprint which are arrays, Functions must be defined as a dictionary. Here is an example of multiple functions within a Blueprint.
{
"version": "2.0.0",
"nodes": [],
"connections": [],
"bindings": [],
"fns": {
"function1": {
"nodes": [],
"connections": [],
"bindings": []
},
"function2": {
"nodes": [],
"connections": [],
"bindings": []
}
}
}
2.2. Catalog
A UBF Catalog is a file, separate to the Blueprint, which defines locations (URIs) for Resources that the Blueprint requires to execute. This should be used during runtime Blueprint execution to find Resources, so that they may be downloaded and used by Node Actions. Each Resource ID used in the Blueprint (See Resource Types) must have a corresponding object in the Catalog, otherwise that Resource will not be loaded at runtime.
The motivation for separating Blueprints and Catalogs is to minimize the amount of duplicate Blueprints needed for a typical game experience. If two Blueprints have the same functionality, but require different resources (e.g. Instantiating a mesh in the scene - the functionality doesn't change, but the intended mesh may), then the same Blueprint should be used, but the Catalog can be swapped out.
Catalogs are optional. If a Blueprint does not contain any Resource IDs, either as Node Ports or Blueprint Bindings, then a Catalog should not be required to run the Blueprint.
The Catalog JSON file contains the standard version that it was created with, and an array of Resources, shown here:
{
"version": "2.0.0",
"resources": []
}
2.2.1. Resources
Resources may be stored on the local machine that the Blueprint is being executed on, or they may be hosted on the internet (Such as with AWS S3). If the Interpreter has access to this location, the Resource will be downloaded at runtime. There are four types of Resources that UBF supports:
- Mesh: Represents a single 3D mesh and its attached materials. The underlying file type of the source file is
.glbor.gltf, but only a single mesh from the glTF file is considered. (Determined by the metadata below). - GLB: Represents an entire glTF file including cameras and skins. The underlying file type is
.glbor.gltf. - Texture: Represents a 2D image. The underlying file type is
.png. - Blueprint: Represents a UBF Blueprint. The underlying file type is
.ubp.json.
{
"id": "my-resource",
"uri": "https://example.com/my-resource",
"type": "Texture",
"hash": "",
"metadata": {}
}
id: A unique identifier for the Resource. Should correspond to a Resource ID used in the Blueprint.uri: The location of the Resource.type: What kind of resource this is. Can be one ofMesh,GLB,Texture, orBlueprint.hash: During runtime execution, Blueprint Resources may be cached to improve performance. Thehashmay be used to index the cache, and if used, should be re-generated every time the Resource is updated.metadata: Described below.
The location of Resources may not be known when building the Blueprints, or the location might be dynamic. Four Nodes
are provided in cases like these: CreateGLBResource,
CreateMeshResource, CreateBlueprintResource,
and CreateTextureResource. These allow Resources to be created at runtime from a
URL and metadata options.
2.2.2. Resource Metadata
Each Resource in a UBF Catalog must contain a metadata field. This is a Json object that provides extra information
about how this Resource should be handled in the interpreter. The metadata schema varies depending on the Resource type,
and for any given Resource, the metadata field must match the schema that corresponds to the Resource type given by
the type field.
Mesh
{
"meshIdentifier": "my-mesh-name"
}
meshIdentifier: Specifies which mesh from the source GLB should be used. All other meshes in the GLB will be ignored when processing the resource.
GLB
{}
This resource type has no valid metadata properties. Future versions may add properties.
Texture
{}
This resource type has no valid metadata properties. Future versions may add properties.
Blueprint
{}
This resource type has no valid metadata properties. Future versions may add properties.
2.3. Interpreters
A UBF Interpreter is responsible for turning the abstract Nodes, Connections, and Bindings described in the UBF JSON into concrete code that can be executed in a runtime environment. The UBF Standard Specification does not dictate how an Interpreter should function, but the concept is outlined here to provide clarity and context around how the UBF files are intended to work in practice.
At minimum, a UBF Interpreter should:
- Implement Actions (code) that corresponds to each Node type defined in the Node References.
- Be able to deserialize the JSON data from Blueprints and Catalogs, into data structures that can be used at runtime.
- Be able to download resources at runtime based on the URIs provided by the Catalogs.
- Iterate through each Node in the order defined by the Blueprint Connections, and run the Action for each one sequentially.
The goal of a UBF Interpreter should be to carry out the functionality described by the UBF Blueprints and Catalogs in a deterministic manner, with results that mirror as closely as possible the intentions set out by the files.
2.4. Custom Nodes
It is possible to make custom Nodes using the UBF system. If a Node defines a type that is not listed in the
Node Reference, it may still execute in an Interpreter if that Interpreter has been created with - or
extended to provide - an Action corresponding to the custom Node.
However, it is important to understand that custom Nodes are NOT part of the UBF Standard Specification, and as such, cannot be expected to run interoperably across all Interpreters.
2.5. Hierarchy
The UBF Standard provides nodes that operate on objects in a scene in the target engine, like
Spawn Model, which instantiates a GLB model in the scene and attaches it to a parent object.
Due to the differences in scene hierarchies between engines, the nodes that interact with a game scene rely on a virtual hierarchy to abstract away from the engine implementation. The virtual hierarchy is a stricly acyclical tree structure comprised of SceneNodes and Scene Components. All Nodes that would interact with the scene hierarchy in an engine must instead interact with the virtual hierarchy, and the changes should be propagated to the actual scene hierarchy. This ensures Blueprints run in a deterministic manner.
This is up to the interpreters to implement, but is documented here as this affects the expected behavior of some Nodes.
2.5.1. Scene Nodes
Scene Nodes are the building blocks for the virtual hierarchy. SceneNodes can be attached to other SceneNodes to form
the tree structure. Each SceneNode can have exactly one parent, and n number of children. In addition to referencing
the in-engine object that it represents - i.e. a GameObject in Unity, each scene node must have the following
properties:
Name: A string identifier by which the scene nodes can be filtered by using theFilterSceneObjectsnode.Parent: The SceneNode that this SceneNode is attached to. Must be null if this SceneNode is the root of the hierarchy.Children: An array of SceneNodes that are attached to this SceneNode.Scene Components: An array of the Scene Components that are attached to this SceneNode.
2.5.2. Scene Components
'Scene Components' is a blanket term used to encapsulate the Rig and MeshRenderer types. These types represent objects in the Scene, and therefore must be part of the virtual hierarchy. Scene Components can be attached to one SceneNode, but Scene Components themselves do not have children. In addition to referencing the in-engine object that it represents, each Scene Component must have the following properties:
Name: A string identifier by which the scene nodes can be filtered by using theFilterSceneObjectsnode.Parent: The SceneNode that this component is attached to.
Some nodes (see FilterSceneObjects) may take an input called Scene Objects, which
can accept types of SceneNode, MeshRenderer, and Rig, encompassing both Scene Nodes and SceneComponents. Since these
types all contain a Name property, a Node that deals exclusively with that property can handle all these types.
2.5.3. Diagram
This diagram illustrates how the virtual hierarchy is constructed, and the tables below show the concrete values of the properties for each node and component:
;
Scene Nodes
| Name | Parent | Children | Scene Components |
|---|---|---|---|
Root | null | ['Node 1', 'Node 2'] | [] |
Node 1 | Root | ['Node 3'] | [] |
Node 2 | Root | [] | ['Model 1'] |
Node 3 | Node 1 | [] | ['Model 2', 'Skeleton'] |
Scene Components
| Name | Parent |
|---|---|
Model 1 | Node 2 |
Model 2 | Node 3 |
Rig | Node 3 |