1. MyDebugNode Tutorial #
The Debug Node lets you print messages to the Unity console – perfect for tracking values and debugging your node graphs during runtime!
Purpose #
This node takes a string input and logs it to the Unity console, making it easy to debug values and track execution flow in your node-based applications.
Step-by-Step Tutorial #
1. Create the Node Script #
using System;
using System.Collections.Generic;
using UnityEngine;
using WorldBuilder.Nodes;
using Worldbuilder.Nodes.FileIO;
using WorldBuilder.Nodes.IO;
namespace MyNodes
{
public class MyDebugNode : NodeBase
{
public static string NODE_GUID => "b4fc3090-faf0-4417-88b9-92c04a986117";
public static string NODE_CLASS_NAME => "MyDebugNode";
public override Guid NodeClassGuid => Guid.Parse(NODE_GUID);
public override string NodeClassName => NODE_CLASS_NAME;
public static List<NodeFactory.VarType> InputTypes => m_InputTypes;
public static List<NodeFactory.VarType> OutputTypes => m_OutputTypes;
private static List<NodeFactory.VarType> m_InputTypes =
new List<NodeFactory.VarType> { NodeFactory.VarType.Flow, NodeFactory.VarType.String };
private static List<NodeFactory.VarType> m_OutputTypes =
new List<NodeFactory.VarType> { NodeFactory.VarType.Flow };
private InputBase m_StringInput;
public MyDebugNode(NodeTree nodeTree, Guid nodeInstanceGuid) :
base(nodeTree, nodeInstanceGuid, null, m_InputTypes, m_OutputTypes)
{
}
public MyDebugNode(NodeTree nodeTree, NodeFile nodeFile, NodeFileEntry nodeFileEntry)
: base(nodeTree, nodeFileEntry.Identity, nodeFileEntry, m_InputTypes, m_OutputTypes)
{
}
protected override void MakeIO(NodeFileEntry nodeFileEntry)
{
Inputs[0].RunAction = RunAction;
m_StringInput = Inputs[1];
}
public void RunAction()
{
string text = m_StringInput.GetString();
Debug.Log("MyDebugNode: " + text);
Run();
}
}
}
How It Works #
Node Properties: #
- Flow Input: Triggers the node execution
- String Input: The message to be logged to the console
- Flow Output: Continues execution to the next node
Implementation Details: #
- Uses Unity’s
Debug.Log()
method to output messages to the console - Prefixes messages with “MyDebugNode:” for easy identification
- Calls
Run()
to continue execution to the next node in the sequence
Runtime Behavior: #
- When the flow input is triggered, the node gets the current string value
- Logs the string to the Unity console
- Continues execution flow to any connected node
Using the Node #
- Add the MyDebugNode to your node graph
- Connect the flow execution from a previous node to its input
- Connect a string value to the string input, or manually enter text
- Connect the flow output to any subsequent nodes
- When executed, the node will print your string to the console and continue execution
2. MyLerpFloat Tutorial #
The Lerp Float Node performs linear interpolation between two values – perfect for creating smooth transitions and animations in your node-based applications!
Purpose #
This node takes two float values (x and y) and a t parameter (0-1), then performs linear interpolation to calculate a value between them.
Step-by-Step Tutorial #
1. Create the Node Script #
using System;
using System.Collections.Generic;
using WorldBuilder.Nodes.IO;
using UnityEngine;
using Worldbuilder.Nodes.FileIO;
namespace WorldBuilder.Nodes
{
public class MyLerpFloat : NodeBase, INodeBase
{
public static string NODE_GUID => "69eaba67-8d94-476b-9ec1-9f2b6643b650";
public static string NODE_CLASS_NAME => "My Lerp (x,y,t)";
public override Guid NodeClassGuid => Guid.Parse(NODE_GUID);
public override string NodeClassName => NODE_CLASS_NAME;
public static List<NodeFactory.VarType> InputTypes => m_InputTypes;
public static List<NodeFactory.VarType> OutputTypes => m_OutputTypes;
private static List<NodeFactory.VarType> m_InputTypes = new List<NodeFactory.VarType> {
NodeFactory.VarType.Float,
NodeFactory.VarType.Float,
NodeFactory.VarType.Float
};
private static List<NodeFactory.VarType> m_OutputTypes = new List<NodeFactory.VarType> {
NodeFactory.VarType.Float
};
private InputBase m_ValueA;
private InputBase m_ValueB;
private InputBase m_Scalar;
private FloatOutput m_ResultOutput;
public MyLerpFloat(NodeTree nodeTree, Guid nodeInstanceGuid) :
base(nodeTree, nodeInstanceGuid, null, m_InputTypes, m_OutputTypes)
{
}
public MyLerpFloat(NodeTree nodeTree, NodeFile nodeFile, NodeFileEntry nodeFileEntry) :
base(nodeTree, nodeFileEntry.Identity, nodeFileEntry, m_InputTypes, m_OutputTypes)
{
}
protected override void MakeIO(NodeFileEntry nodeFileEntry)
{
m_ValueA = Inputs[0];
m_ValueA.DisplayName = "x";
m_ValueB = Inputs[1];
m_ValueB.DisplayName = "y";
m_Scalar = Inputs[2];
m_Scalar.DisplayName = "t";
m_ResultOutput = Outputs[0] as FloatOutput;
m_ResultOutput.DisplayName = "result";
m_ResultOutput.SetGetValueFunc(Calc);
}
private float Calc()
{
float scalar = Math.Clamp(m_Scalar.GetFloat(), 0.0f, 1.0f);
float a = m_ValueA.GetFloat();
float b = m_ValueB.GetFloat();
return Mathf.Lerp(a, b, scalar);
}
}
}
How It Works #
Node Properties: #
- x Input: The starting value
- y Input: The ending value
- t Input: The interpolation parameter (0-1)
- result Output: The interpolated value
The Math Behind It: #
- Linear interpolation calculates a point between two values
- When t=0, the result equals x
- When t=1, the result equals y
- When t is between 0 and 1, the result is proportionally between x and y
- The formula is: result = x + (y – x) * t
Implementation Details: #
- Uses Unity’s
Mathf.Lerp()
function for the actual calculation - Clamps the t parameter between 0 and 1 to ensure valid results
- Returns the interpolated value through the output
Using the Node #
- Add the MyLerpFloat node to your graph
- Connect float values to the x and y inputs
- Connect a value between 0-1 to the t input
- Use the result output anywhere you need the interpolated value
- For animation, try connecting a time-based value to t
3. MyRoundToFloat Tutorial #
The Round To Float Node rounds decimal values to a specified precision – perfect for cleaning up calculations and displaying user-friendly numbers!
Purpose #
This node takes a float value and rounds it to a specified number of decimal places, making it ideal for formatting numbers for display or ensuring consistent precision.
Step-by-Step Tutorial #
1. Create the Node Script #
using System;
using System.Collections.Generic;
using Worldbuilder.Nodes.FileIO;
using WorldBuilder.Nodes.IO;
namespace WorldBuilder.Nodes
{
public class MyRoundToFloatNode : NodeBase
{
public static string NODE_GUID => "04a383f8-03df-46d4-b336-dfd4ed1685c6";
public static string NODE_CLASS_NAME => "My Round To Float";
public override Guid NodeClassGuid => Guid.Parse(NODE_GUID);
public override string NodeClassName => NODE_CLASS_NAME;
public static List<NodeFactory.VarType> InputTypes => m_InputTypes;
public static List<NodeFactory.VarType> OutputTypes => m_OutputTypes;
private static List<NodeFactory.VarType> m_InputTypes = new List<NodeFactory.VarType> {
NodeFactory.VarType.Float,
NodeFactory.VarType.Int
};
private static List<NodeFactory.VarType> m_OutputTypes = new List<NodeFactory.VarType> {
NodeFactory.VarType.Float
};
private InputBase m_ValueInput;
private InputBase m_NrOfDecimalsInput;
private FloatOutput m_ResultOutput;
public MyRoundToFloatNode(NodeTree nodeTree, Guid nodeInstanceGuid) :
base(nodeTree, nodeInstanceGuid, null, m_InputTypes, m_OutputTypes)
{
}
public MyRoundToFloatNode(NodeTree nodeTree, NodeFile nodeFile, NodeFileEntry nodeFileEntry) :
base(nodeTree, nodeFileEntry.Identity, nodeFileEntry, m_InputTypes, m_OutputTypes)
{
}
protected override void MakeIO(NodeFileEntry nodeFileEntry)
{
m_ValueInput = Inputs[0];
m_ValueInput.DisplayName = "Value";
m_NrOfDecimalsInput = Inputs[1];
m_NrOfDecimalsInput.DisplayName = "Decimal places";
m_ResultOutput = Outputs[0] as FloatOutput;
m_ResultOutput.DisplayName = "Rounded Value";
m_ResultOutput.SetGetValueFunc(() => RoundWithDecimalPlaces(m_NrOfDecimalsInput.GetInt()));
}
private float RoundWithDecimalPlaces(int nrOfDecimalPlaces) =>
(float)Math.Round(m_ValueInput.GetFloat(), nrOfDecimalPlaces);
}
}
How It Works #
Node Properties: #
- Value Input: The float value to be rounded
- Decimal places Input: Integer specifying how many decimal places to keep
- Rounded Value Output: The result after rounding
Implementation Details: #
- Uses the
Math.Round()
method to perform precision rounding - Takes an integer parameter to specify the number of decimal places
- Returns the rounded value as a float
- Uses a lambda expression to connect the rounding function to the output
Runtime Behavior: #
- When the output value is requested, the node reads both inputs
- Applies the Math.Round function with the specified precision
- Returns the rounded float value through the output
Using the Node #
- Add the MyRoundToFloatNode to your graph
- Connect a float value to the Value input
- Set the Decimal places input (e.g., 2 for two decimal places)
- Use the Rounded Value output anywhere you need the formatted number
- Perfect for display values, currency, or measurements
4. MyVideoPlayerNode Tutorial #
The Video Player Node controls video playback within your application – perfect for creating interactive media experiences and presentations!
Purpose #
This advanced node allows you to play, stop, and control video playback on GameObjects with video components, supporting both asset-based videos and URL streams.
Step-by-Step Tutorial #
1. Create the Node Script #
// This is a complex node, so I'll provide a simplified version of the key parts
public class MyVideoPlayerNode : NodeBase, INodeBase
{
public class VideoSourceData
{
public string AssetGuidString = null;
public string VideoUrl = null;
}
public static string NODE_GUID => "c9cfd216-9103-471c-8642-fe07c025e350";
public static string NODE_CLASS_NAME => "My Video Player";
// Input/Output definitions with five inputs and two outputs
private static List<NodeFactory.VarType> m_InputTypes = new List<NodeFactory.VarType>
{
NodeFactory.VarType.Flow, // Play flow
NodeFactory.VarType.Flow, // Stop flow
NodeFactory.VarType.Flow, // Set Time flow
NodeFactory.VarType.Float, // Time value
NodeFactory.VarType.GameObject // Target object
};
private static List<NodeFactory.VarType> m_OutputTypes = new List<NodeFactory.VarType>
{
NodeFactory.VarType.Flow, // Execution flow output
NodeFactory.VarType.Bool // Is Playing status
};
// Input/Output references
private InputBase m_PlayFlowIn;
private InputBase m_StopFlowIn;
private InputBase m_SetTimeFlowIn;
private InputBase m_TimeFloatIn;
private InputBase m_GameObjectIn;
private BoolOutput m_IsPlayingOutput;
private VideoSourceData m_VideoSourceData = new();
// MakeIO method connects the inputs and outputs
protected override void MakeIO(NodeFileEntry nodeFileEntry)
{
// Set up flow inputs with their actions
m_PlayFlowIn = Inputs[0];
m_PlayFlowIn.DisplayName = "Play";
m_PlayFlowIn.RunAction = () => Play();
m_StopFlowIn = Inputs[1];
m_StopFlowIn.DisplayName = "Stop";
m_StopFlowIn.RunAction = () => Stop();
m_SetTimeFlowIn = Inputs[2];
m_SetTimeFlowIn.DisplayName = "Set Time";
m_SetTimeFlowIn.RunAction = () => ChangeTime();
// Set up value inputs
m_TimeFloatIn = Inputs[3];
m_TimeFloatIn.DisplayName = "time";
m_GameObjectIn = Inputs[4];
m_GameObjectIn.DisplayName = "object";
// Set up outputs
OutputBase flowOutput = Outputs[0];
flowOutput.DisplayName = "Run";
m_IsPlayingOutput = Outputs[1] as BoolOutput;
m_IsPlayingOutput.DisplayName = "Is Playing";
}
// Action implementations
private void Play()
{
// Play video on the target object
// [Implementation details omitted for brevity]
Run(); // Continue execution flow
}
private void Stop()
{
// Stop video on the target object
}
private void ChangeTime()
{
// Change playback time to the specified value
}
}
How It Works #
Node Properties: #
- Play Input (Flow): Begins video playback
- Stop Input (Flow): Stops video playback
- Set Time Input (Flow): Sets the playback position
- Time Input (Float): The timestamp to seek to (in seconds)
- Object Input (GameObject): The GameObject containing the video player
- Run Output (Flow): Continues execution flow after playback control
- Is Playing Output (Bool): Indicates if the video is currently playing
Implementation Details: #
- Supports both asset-based videos (from your project) and URL-based streaming
- Stores video source data that persists when saving the node graph
- Connects to Unity’s VideoPlayer component to control playback
- Provides custom UI elements in the node editor for setting video sources
Advanced Features: #
- Provides IsPlaying status output for conditional logic
- Maintains state information even when playback was triggered elsewhere
- Has UI in the editor for selecting video assets or entering URLs
Using the Node #
- Add the MyVideoPlayerNode to your graph
- Connect the “object” input to a GameObject that has a VideoPlayer component
- In the node editor, select a video asset or enter a URL
- Connect the appropriate flow inputs:
- Use “Play” to start playback
- Use “Stop” to end playback
- Use “Set Time” to jump to a specific timestamp
- Connect the “Run” output to any nodes that should execute after video control
- Use the “Is Playing” boolean output for conditional logic
Creating Your Own Custom Nodes #
Now that you’ve seen several examples, let’s go through the process of creating your own custom node:
Step 1: Plan Your Node’s Functionality #
- Determine what your node will do
- Decide what inputs and outputs it needs
- Consider any special data it might need to store
Step 2: Generate a Unique GUID #
Every node needs a unique identifier. Generate a GUID using one of these methods:
- Use Visual Studio’s Tools > Create GUID
- Use an online GUID generator
- Execute
Guid.NewGuid().ToString()
in C#
Step 3: Create the Node Class #
Create a new C# file named after your node (e.g., MyCustomNode.cs
) with this structure:
using System;
using System.Collections.Generic;
using WorldBuilder.Nodes;
using WorldBuilder.Nodes.IO;
using Worldbuilder.Nodes.FileIO;
namespace MyNodes
{
public class MyCustomNode : NodeBase
{
// 1. Define the unique GUID and name
public static string NODE_GUID => "your-generated-guid-here";
public static string NODE_CLASS_NAME => "My Custom Node";
public override Guid NodeClassGuid => Guid.Parse(NODE_GUID);
public override string NodeClassName => NODE_CLASS_NAME;
// 2. Define inputs and outputs
public static List<NodeFactory.VarType> InputTypes => m_InputTypes;
public static List<NodeFactory.VarType> OutputTypes => m_OutputTypes;
private static List<NodeFactory.VarType> m_InputTypes = new List<NodeFactory.VarType> {
// List your input types here
};
private static List<NodeFactory.VarType> m_OutputTypes = new List<NodeFactory.VarType> {
// List your output types here
};
// 3. Create references to inputs and outputs
private InputBase m_SomeInput;
private FloatOutput m_SomeOutput;
// 4. Create constructors
public MyCustomNode(NodeTree nodeTree, Guid nodeInstanceGuid) :
base(nodeTree, nodeInstanceGuid, null, m_InputTypes, m_OutputTypes)
{
}
public MyCustomNode(NodeTree nodeTree, NodeFile nodeFile, NodeFileEntry nodeFileEntry) :
base(nodeTree, nodeFileEntry.Identity, nodeFileEntry, m_InputTypes, m_OutputTypes)
{
// Load any saved data if needed
}
// 5. Configure inputs and outputs
protected override void MakeIO(NodeFileEntry nodeFileEntry)
{
// Configure inputs
m_SomeInput = Inputs[0];
m_SomeInput.DisplayName = "My Input";
// For flow inputs, assign the action
if (InputTypes[0] == NodeFactory.VarType.Flow)
Inputs[0].RunAction = MyAction;
// Configure outputs
m_SomeOutput = Outputs[0] as FloatOutput;
m_SomeOutput.DisplayName = "My Output";
m_SomeOutput.SetGetValueFunc(CalculateOutput);
}
// 6. Implement node functionality
private void MyAction()
{
// Your node's action logic here
// Continue execution flow
Run();
}
private float CalculateOutput()
{
// Your calculation logic here
return 0.0f;
}
}
}
Step 4: Test Your Node #
- Add your new node to your project
- Use it in a node graph
- Test all inputs and outputs to verify functionality
- Debug any issues using Unity’s console
Best Practices for Custom Nodes #
1. Keep It Simple #
- Focus each node on a specific, well-defined task
- Avoid creating “super nodes” that try to do too many things
2. Use Descriptive Names #
- Give your node a clear, descriptive name
- Use display names that explain what each input and output does
3. Handle Edge Cases #
- Check for null values and other potential errors
- Provide sensible defaults when inputs are not connected
4. Document Your Nodes #
- Add comments explaining complex logic
- Consider creating a README for your custom node library
5. Optimize Performance #
- Avoid expensive operations in frequently called methods
- Cache results when appropriate to avoid redundant calculations
Conclusion #
Custom nodes are a powerful way to extend your node-based application with new functionality. By following the patterns shown in these examples, you can create your own nodes that integrate seamlessly with the existing system.
Remember that the key elements of any node are:
- A unique GUID to identify the node type
- Clearly defined inputs and outputs
- Well-implemented functionality
- Proper execution flow management
Happy node creating!