Documentation Portal
Documentation Portal
🤖

Advanced: Creating Custom Nodes

In order to create any additional functionality, that is not achievable within PaleBlue Trainer and Flow, you can create a custom node.

A custom node is a piece of C# code wrapped in a Flow Node, so that it can be invoked within the execution of our flow. Within this node, you can perform any action with scene and objects, and also call external APIs.

ℹ️
Custom nodes are the go-to tool when it comes to interfacing with other Unity libraries, APIs, and servers.

Making a custom node will require a bit of coding knowledge, but nothing really advanced.

To start, create a new script in your project.

image

Now, depending on what you want the node to do, there are 2 base classes that can be expanded and used: Basic Node and Generic Group Action Node.

Basic Node

This is a simple node that will execute a simple function and continue with the flow.

Opening Flow in your scene, you can now add your node to Flow and use it:

image
image

You can extend this node, by additional flow outputs, value inputs and outputs. That’s more or less the extend of the Basic Node.

In case you want to create a node that works with several objects a certain type (group actions, selections, showing and hiding etc.), you might want to use the Generic Group Action Node instead.

Generic Group Action Node

This node can process a list of objects of a certain kind. You’ve seen these nodes: they work on group of objects to e.g. show&hide them, allow to select them, and so on.

To start, we’need to understand what type of objects (object class) it will work with. It can be just GameObject, if you’re working with generic list of any scene objects.

Take a look at the following. It is a node that allows specifying a list of GameObjects in its configuration, and then will print out objects’ names when executed in runtime:

A couple of things to note here:

  • For every object on the list, RegisterSelection() is called on node execution.
  • Once the node execution is done, UnregisterSelection() will be called. Register and Unregister methods can be used to perform any kind of actions on the objects.
  • You can alternatively use HandleInInternal() method. It allows you to do something when the node is executed. In this method, you can use _actualObjects list that holds all of the objects.

With the above code, an additional Node will be available in your Flow:

image

As noted, this node will work as any other selection or group node.

using PaleBlue.Nodes;
using ParadoxNotion.Design;
using UnityEngine;
using FlowCanvas;

// Name of the node that will be shown in the Node list and Search
[Name("Double Value Node")]
// Category is the folder where this node will be
[Category("Custom")]
public class CustomNode : BasicNode
{
    ValueInput<int> _intValue;
    protected override void Invoke()
    {
        // Your code goes here
        Debug.Log($"Double value is {_intValue.value * 2}");
    }
    protected override void OnRegisterExtraPorts()
    {
        // If you need any extra inputs to work with, add them here
        _intValue = AddValueInput<int>("Int Value");
    }
}
using PaleBlue.Nodes;
using ParadoxNotion.Design;
using UnityEngine;
using FlowCanvas;

// Name of the node that will be shown in the Node list and Search
[Name("Log All Names")]
// Category is the folder where this node will be
[Category("Custom")]
// Type of Objects your node will be dealing with: GameObject in this example
public class CustomGroupNode : GenericGroupActionNode<GameObject>
{
    protected override void HandleInInternal()
    {
			// Put your custom flow input handling here
    }

		protected override void RegisterPortsExtra()
    {
			// Add your extra input/output ports here
			// Node name can also be customized here
			_name = "Log All Names Custom Node";
    }

    protected override void RegisterSelection(GameObject selectableObject)
    {
			// Perform an action on an object from the list
			Debug.Log(selectableObject.name);
    }

    protected override void UnregisterSelection(GameObject selectableObject)
    {
			// Cleanup, if your action somehow changed the object
    }
}