Docs – Portals United https://www.portalsunited.com:443 Wed, 02 Jul 2025 15:03:02 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.2 https://www.portalsunited.com:443/wp-content/uploads/2024/10/cropped-pu-favicon-alt-32x32.png Docs – Portals United https://www.portalsunited.com:443 32 32 World Builder – Input Signal Type https://www.portalsunited.com:443/docs/input-signal-node-type/ https://www.portalsunited.com:443/docs/input-signal-node-type/#respond Wed, 02 Jul 2025 14:59:27 +0000 https://www.portalsunited.com:443/?post_type=docs&p=1810 As the Portal Hopper is built on top of the new Input System, you can have access to this functionality via this Signal Type. The Input system uses strings to describe device attributes such as controller buttons or keyboard keys. NOTE: Our current implementation treats any input as a button, so no analog values can be accessed.

To read the A button from the right hand controller, create a signal of type input and enter the string <XRController>{RightHand}/{PrimaryButton}

Here are some more useful examples:
Button A: <XRController>{RightHand}/{PrimaryButton}
Button B: <XRController>{RightHand}/{secondaryButton
Button X: <XRController>{LeftHand}/{PrimaryButton}
Button Y: <XRController>{LeftHand}/{SecondaryButton}
Keyboard A:
<Keyboard>/a

Some control strings require modifiers such as Shift or Control. These modifiers are appended to the string using a ‘|’ (Pipe) symbol. For instance, to define Ctrl-A, use <Keyboard>/a|/<Keyboard>/ctrl
You can add up to 2 modifiers to the string.

Please refer to the Unity manuals for input mappings for XR devices.

]]>
https://www.portalsunited.com:443/docs/input-signal-node-type/feed/ 0
How to Work With Multiple Locations https://www.portalsunited.com:443/docs/how-to-work-with-multiple-locations/ https://www.portalsunited.com:443/docs/how-to-work-with-multiple-locations/#respond Wed, 18 Jun 2025 13:45:33 +0000 https://www.portalsunited.com:443/?post_type=docs&p=1776 The XR4ED shop cannot handle projects with multiple locations on its own. This is due to 2 factors

  • the ids of items in the store change whenever you create an update. So, uploading a new vrml file would break the references of other locations which reference this vrml link
  • the ids of the vrml files are decided when the vrml file uploads, so the Change Location Node cannot know the ids of the other locations.

To be clear, after exporting your project to the shop, all the vrml files you created contain correct references to their corresponding location description files (ending in .wbz). So, you could download these vrml files to e.g. put them on your desktop and launch any location in the hopper by double-clicking the vrml file. But you could not use a Change Location Node to reference another location from within this location, because you don’t know it’s name (link) in the shop.

To get around this dilemma (only applies to projects with multiple locations), you need a tiny bit of space on any external server.

Step 1: edit your project so that all Change Location Nodes use external references to your server/folder. For instance to HTTP://MyDomain.web/MyFolder/location2.vrml.

Step 2: Upload your project as usual including all locations

Step 3: then, download all the vrml files from the shop and copy them to your server/domain. In this example the domain is MyDomain.web and you have some space there in a folder called MyFolder.

Step 4 (optional): you delete all the vrml files in the shop except the vrml file which represents the start location, so the shop only offers one entry point to your project. This prevents that the user who bought your app hops directly into your 2nd location before finishing the first

Step 5: you upload all vrml files onto your server at MyDomain.web/MyProject. The Change Location Node already references the vrml file at this location and not the shop. Thus, the Portal Hopper will download the vrml files for each location from your server and the location description files from the XR4ED Shop.

]]>
https://www.portalsunited.com:443/docs/how-to-work-with-multiple-locations/feed/ 0
Launching the Portal Hopper with Parameters https://www.portalsunited.com:443/docs/launching-the-portal-hopper-with-parameters/ https://www.portalsunited.com:443/docs/launching-the-portal-hopper-with-parameters/#respond Fri, 06 Jun 2025 08:21:02 +0000 https://www.portalsunited.com:443/?post_type=docs&p=1666 The Portal Hopper can be called either directly by clicking on its app icon or indirectly by displaying a link on a website or inside a document by adding a link which starts with “hopper:”.

Example:
“hopper:https://vrweb.somedomain.com/index.vrml” will start the Hopper and load and execute the index.vrml file.

The Portal Hopper can Install and display an avatar by passing these parameters to the hopper:
-al pass asset bundle name of an avatar to the Hopper
-an pass avatar name in bundle to the Hopper (may be left out and the Hopper will use the bundle name as avatar name)

For testing, the hopper can map all links to a different domain.
-map <local path>
This can be used to test a project locally. If left off, the mapping goes to “http://localhost”. The domain name is then mapped to a folder name

Example:
-map http://localhost maps the link “https://vrweb.mydomain.com/myProject/location1.vrml” to “http://localhost/vrweb.mydomain.com/myProject/location1.vrml”

This can be very useful in conjunction with XAMPP or MAMP servers which provide you with a local HTTP server (localhost). For the above example using -map, the referenced files need to be located in <MAMP or XAMPP install folder>/htdocs/vrweb.mydomain.com/myProject

]]>
https://www.portalsunited.com:443/docs/launching-the-portal-hopper-with-parameters/feed/ 0
World Builder – Custom Nodes Tutorial Guide https://www.portalsunited.com:443/docs/custom-nodes-tutorial-guide/ https://www.portalsunited.com:443/docs/custom-nodes-tutorial-guide/#respond Thu, 08 May 2025 12:10:17 +0000 https://www.portalsunited.com:443/?post_type=docs&p=1530 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!

You can find all of our currently implemented Nodes here.

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

  1. Add the MyDebugNode to your node graph
  2. Connect the flow execution from a previous node to its input
  3. Connect a string value to the string input, or manually enter text
  4. Connect the flow output to any subsequent nodes
  5. 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

  1. Add the MyLerpFloat node to your graph
  2. Connect float values to the x and y inputs
  3. Connect a value between 0-1 to the t input
  4. Use the result output anywhere you need the interpolated value
  5. 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

  1. Add the MyRoundToFloatNode to your graph
  2. Connect a float value to the Value input
  3. Set the Decimal places input (e.g., 2 for two decimal places)
  4. Use the Rounded Value output anywhere you need the formatted number
  5. 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

  1. Add the MyVideoPlayerNode to your graph
  2. Connect the “object” input to a GameObject that has a VideoPlayer component
  3. In the node editor, select a video asset or enter a URL
  4. Connect the appropriate flow inputs:
    • Use “Play” to start playback
    • Use “Stop” to end playback
    • Use “Set Time” to jump to a specific timestamp
  5. Connect the “Run” output to any nodes that should execute after video control
  6. 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

  1. Add your new node to your project
  2. Use it in a node graph
  3. Test all inputs and outputs to verify functionality
  4. 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:

  1. A unique GUID to identify the node type
  2. Clearly defined inputs and outputs
  3. Well-implemented functionality
  4. Proper execution flow management

Happy node creating!

]]>
https://www.portalsunited.com:443/docs/custom-nodes-tutorial-guide/feed/ 0
World Builder – Guide to Implementing Custom Nodes https://www.portalsunited.com:443/docs/guide-to-implementing-custom-nodes/ https://www.portalsunited.com:443/docs/guide-to-implementing-custom-nodes/#respond Thu, 08 May 2025 12:02:59 +0000 https://www.portalsunited.com:443/?post_type=docs&p=1528 Introduction

Custom nodes allow you to extend the functionality of our node editor system with your own specialized logic. This guide walks you through the process of creating and implementing custom nodes that integrate seamlessly with the existing system.

You can download our standard Nodes, which are currently installed in the World Builder and Portal Hopper here.

Prerequisites

  • Basic understanding of C# and Unity
  • Access to the node system codebase
  • Visual Studio or another C# IDE

The Anatomy of a Custom Node

Every custom node in our system follows a consistent structure:

namespace MyNodes // Your namespace here
{
    public class MyCustomNode : NodeBase
    {
        // 1. Unique Identifier (required)
        public static string NODE_GUID => "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

        // 2. Display Name (required)
        public static string NODE_CLASS_NAME => "My Custom Node";

        // 3. GUID and Name Implementation
        public override Guid NodeClassGuid => Guid.Parse(NODE_GUID);
        public override string NodeClassName => NODE_CLASS_NAME;

        // 4. Input/Output Type Definitions
        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> { /* Input types go here */ };

        private static List<NodeFactory.VarType> m_OutputTypes = 
            new List<NodeFactory.VarType> { /* Output types go here */ };

        // 5. Input/Output References
        private InputBase m_SomeInput;
        private FloatOutput m_SomeOutput;

        // 6. Constructors (both required)
        public MyCustomNode(NodeTree nodeTree, Guid nodeInstanceGuid) :
            base(nodeTree, nodeInstanceGuid, null, m_InputTypes, m_OutputTypes)
        {
            // Constructor initialization
        }

        public MyCustomNode(NodeTree nodeTree, NodeFile nodeFile, NodeFileEntry nodeFileEntry) :
            base(nodeTree, nodeFileEntry.Identity, nodeFileEntry, m_InputTypes, m_OutputTypes)
        {
            // Load saved data if needed
        }

        // 7. I/O Setup Method
        protected override void MakeIO(NodeFileEntry nodeFileEntry)
        {
            // Configure inputs and outputs
        }

        // 8. Node Functionality Methods
        // Your custom logic here
    }
}

Step-by-Step Implementation Guide

Step 1: Create a New Class File

Create a new C# script file with your node name (e.g., MyCustomNode.cs).

Step 2: Define the Class Structure

Your class should inherit from NodeBase and implement INodeBase if needed:

using System;
using System.Collections.Generic;
using WorldBuilder.Nodes;
using WorldBuilder.Nodes.IO;
using Worldbuilder.Nodes.FileIO;

namespace MyNodes
{
    public class MyCustomNode : NodeBase
    {
        // Implementation will go here
    }
}

Step 3: Generate a Unique GUID

Every node needs a unique identifier. Generate a GUID using one of these methods:

  • Option 1: Use Visual Studio’s Tools > Create GUID
  • Option 2: Use an online GUID generator
  • Option 3: Run this C# code: Console.WriteLine(Guid.NewGuid().ToString());

Then add it to your node class:

public static string NODE_GUID => "b4fc3090-faf0-4417-88b9-92c04a986117"; // Your unique GUID
public static string NODE_CLASS_NAME => "My Custom Node";

public override Guid NodeClassGuid => Guid.Parse(NODE_GUID);
public override string NodeClassName => NODE_CLASS_NAME;

IMPORTANT: Never reuse GUIDs between different node types. Each node class must have its own unique GUID.

Step 4: Define Input and Output Types

Specify what kinds of data your node will accept and produce:

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,     // Execution flow input
        NodeFactory.VarType.Float     // Float value input
    };

private static List<NodeFactory.VarType> m_OutputTypes =
    new List<NodeFactory.VarType> { 
        NodeFactory.VarType.Flow,     // Execution flow output
        NodeFactory.VarType.String    // String value output
    };

Common variable types:

  • NodeFactory.VarType.Flow – Execution flow
  • NodeFactory.VarType.Float – Floating point number
  • NodeFactory.VarType.Int – Integer
  • NodeFactory.VarType.String – Text string
  • NodeFactory.VarType.Bool – Boolean (true/false)
  • NodeFactory.VarType.GameObject – Unity GameObject reference

Step 5: Create Input/Output References

Create member variables to store references to your inputs and outputs:

private InputBase m_FlowInput;
private InputBase m_FloatInput;
private FloatOutput m_ResultOutput;

Step 6: Implement Both Constructors

You need two constructors for your node:

// Constructor for new node instances
public MyCustomNode(NodeTree nodeTree, Guid nodeInstanceGuid) :
    base(nodeTree, nodeInstanceGuid, null, m_InputTypes, m_OutputTypes)
{
    // Any initialization code
}

// Constructor for loading existing nodes
public MyCustomNode(NodeTree nodeTree, NodeFile nodeFile, NodeFileEntry nodeFileEntry) :
    base(nodeTree, nodeFileEntry.Identity, nodeFileEntry, m_InputTypes, m_OutputTypes)
{
    // Load any saved data from nodeFile if needed
}

Step 7: Configure Inputs and Outputs

Override the MakeIO method to set up your inputs and outputs:

protected override void MakeIO(NodeFileEntry nodeFileEntry)
{
    // Set up flow input (execution)
    m_FlowInput = Inputs[0];
    m_FlowInput.DisplayName = "Execute";
    m_FlowInput.RunAction = () => YourActionMethod();

    // Set up value input
    m_FloatInput = Inputs[1];
    m_FloatInput.DisplayName = "Value";

    // Set up outputs
    OutputBase flowOutput = Outputs[0];
    flowOutput.DisplayName = "Next";

    m_ResultOutput = Outputs[1] as FloatOutput;
    m_ResultOutput.DisplayName = "Result";
    m_ResultOutput.SetGetValueFunc(() => CalculateResult());
}

Step 8: Implement Node Functionality

Add methods that implement your node’s actual behavior:

private void YourActionMethod()
{
    // Do something when the node is executed
    float value = m_FloatInput.GetFloat();

    // Your logic here

    // Continue execution flow
    Run(); // This triggers the next node in the flow
}

private float CalculateResult()
{
    // Calculate and return a value
    return m_FloatInput.GetFloat() * 2.0f;
}

Example Node Implementation

Let’s create a simple node that multiplies a float value by 2:

using System;
using System.Collections.Generic;
using WorldBuilder.Nodes;
using WorldBuilder.Nodes.IO;
using Worldbuilder.Nodes.FileIO;

namespace MyNodes
{
    public class MyDoubleValueNode : NodeBase
    {
        public static string NODE_GUID => "2a7b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
        public static string NODE_CLASS_NAME => "Double Value";

        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 };

        private static List<NodeFactory.VarType> m_OutputTypes =
            new List<NodeFactory.VarType> { NodeFactory.VarType.Float };

        private InputBase m_ValueInput;
        private FloatOutput m_ResultOutput;

        public MyDoubleValueNode(NodeTree nodeTree, Guid nodeInstanceGuid) :
            base(nodeTree, nodeInstanceGuid, null, m_InputTypes, m_OutputTypes)
        {
        }

        public MyDoubleValueNode(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_ResultOutput = Outputs[0] as FloatOutput;
            m_ResultOutput.DisplayName = "Doubled Value";
            m_ResultOutput.SetGetValueFunc(CalculateDouble);
        }

        private float CalculateDouble()
        {
            return m_ValueInput.GetFloat() * 2.0f;
        }
    }
}

Common Node Types and Their Implementation

Value Processing Node

Like our example above, focuses on transforming values.

Flow Control Node

Controls execution flow with multiple output paths:

// In MakeIO method
m_ConditionInput = Inputs[1]; // Boolean input
Outputs[0].DisplayName = "True"; // First flow output
Outputs[1].DisplayName = "False"; // Second flow output

// In RunAction method
if (m_ConditionInput.GetBool())
    RunAt(0); // Run the "True" path
else
    RunAt(1); // Run the "False" path

Event Handling Node

Responds to system events:

// Subscribe to events in the constructor
EventManager.Instance.OnSomeEvent += HandleEvent;

// Handle the event
private void HandleEvent(EventArgs args)
{
    // Do something and then continue flow
    Run();
}

// Clean up in Dispose method
public override void Dispose()
{
    EventManager.Instance.OnSomeEvent -= HandleEvent;
    base.Dispose();
}

Advanced Topics

Saving Custom Data

If your node needs to save additional data:

// Override GetExtraData
public override string GetExtraData()
{
    return JsonConvert.SerializeObject(myCustomData);
}

// Override SetExtraData
public override void SetExtraData(string extraData)
{
    myCustomData = JsonConvert.DeserializeObject<MyCustomDataType>(extraData);
}

Custom Node Editor UI

For nodes that need custom interface elements:

public override void InsertVisual(NodeTreeVisualizer visualizer, VisualElement root, Vector2 position)
{
    base.InsertVisual(visualizer, root, position);

    // Add your custom UI elements
    IUIElementFactory elementFactory = NodeTree.ReferenceProvider.ElementFactory;
    elementFactory.CreateButton(m_NodeBox, "My Button", OnButtonClick);
}

private void OnButtonClick()
{
    // Handle button click
}

Troubleshooting

Node Doesn’t Appear in Editor

  • Ensure your namespace is correct
  • Verify that the GUID is unique and properly formatted
  • Check that your node class is public

Node Inputs/Outputs Don’t Work

  • Make sure the index in Inputs[x] matches the order in m_InputTypes
  • Verify that you’re using the correct getter method (GetFloat(), GetString(), etc.)
  • Check that you’ve called Run() to continue execution flow

Runtime Errors

  • Verify all inputs have proper null checks
  • Ensure any saved data is properly deserialized
  • Check for any event subscriptions that might not be unsubscribed

Conclusion

Following this guide, you should now be able to create custom nodes that extend the functionality of the node system. Remember to always generate unique GUIDs for each node type, and to properly set up inputs and outputs.

Feel free to explore the example nodes provided in the documentation to see more complex implementations and patterns.

]]>
https://www.portalsunited.com:443/docs/guide-to-implementing-custom-nodes/feed/ 0
Portal Hopper – Development Toolkits https://www.portalsunited.com:443/docs/portal-hopper-development-toolkits/ https://www.portalsunited.com:443/docs/portal-hopper-development-toolkits/#respond Wed, 30 Apr 2025 13:56:47 +0000 https://www.portalsunited.com:443/?post_type=docs&p=1487 Here you can find two toolkits available for you on GitHub:

the Designer Toolkit for creating Unity Asset Bundles:

And the Developer Toolkit for developing Hopper technologies:

Please note that no new code can be added to the Portal Hopper via Asset Bundles, as all executable code must be present at the time the app is built.

Code can only added via World Builder’s Runtime Scripting.

]]>
https://www.portalsunited.com:443/docs/portal-hopper-development-toolkits/feed/ 0
World Builder – Runtime Scripting Assemblies https://www.portalsunited.com:443/docs/world-builder-runtime-scripting-assemblies/ https://www.portalsunited.com:443/docs/world-builder-runtime-scripting-assemblies/#respond Fri, 11 Apr 2025 11:16:38 +0000 https://www.portalsunited.com:443/?post_type=docs&p=1452 Available Assemblies:

System

UnityEngine

UnityEngine.CoreModule

UnityEngine.PhysicsModule

UnityEngine.UI

UnityEngine.UIElementsModule

Newtonsoft.Json

netstandard

mscorlib

1. System

Overview:
This is the core assembly of the .NET framework. It provides the fundamental classes and base types needed by nearly every application.

Key Components:

  • Namespaces: System, System.Collections, System.IO, System.Threading, etc.
  • Common Types: Object, String, DateTime, collections like List<T> and Dictionary<TKey, TValue>, exception handling classes, and more.

Use Cases:

  • Basic data types and runtime support.
  • File and stream manipulation.
  • General-purpose utilities.

2. System.Core

Overview:
This assembly enriches your applications with LINQ (Language Integrated Query) capabilities and additional core functionalities such as extension methods, lambda expressions, and more.

Key Components:

  • Namespaces: System.Linq, System.Linq.Expressions
  • Common Types: LINQ extension methods provided in Enumerable and Queryable.

Use Cases:

  • Querying and manipulating collections in a fluent, readable style.
  • Enhancing readability and expressiveness of your code with Lambda Expressions.

3. UnityEngine

Overview:
This assembly is at the heart of Unity scripting. It provides all the base classes and functions needed to build interactive content on Unity.

Key Components:

  • Namespaces: UnityEngine
  • Common Types: GameObject, Component, MonoBehaviour, Transform, etc.

Use Cases:

  • Creating and managing game objects.
  • Implementing behaviors in the Unity runtime.
  • Accessing Unity-specific utilities such as logging and physics interactions (basic).

4. UnityEngine.CoreModule

Overview:
A more specific set of core functionalities provided by Unity. Many foundational parts of the UnityEngine are segmented into this module for better modularization.

Key Components:

  • Namespaces: Still under the umbrella of UnityEngine
  • Common Types: Essential classes and interfaces that support the Unity engine’s core operations.

Use Cases:

  • Handling core engine functionalities.
  • Directly interacting with the underlying systems of Unity.

5. UnityEngine.PhysicsModule

Overview:
This module specifically deals with Unity’s physics engine. It contains the components required for simulating physical behaviors.

Key Components:

  • Namespaces: UnityEngine
  • Common Types: Rigidbody, Collider, Physics, and other physics-related components.

Use Cases:

  • Adding realistic physics to your objects.
  • Handling collision detection and response.
  • Simulating environments like gravity, forces, and friction.

6. UnityEngine.UI

Overview:
This assembly brings in the classic Unity UI system. It allows you to build and manage interfaces using built-in UI components.

Key Components:

  • Namespaces: UnityEngine.UI
  • Common Types: Canvas, Button, Text, Image, etc.

Use Cases:

  • Designing interactive user interfaces.
  • Implementing menus, HUDs, and other UI elements.
  • Responding to user inputs through UI controls.

7. UnityEngine.UIElementsModule

Overview:
A newer, more flexible UI toolkit provided by Unity that offers a modern approach to UI development. It is designed to work with a hierarchical, style-driven system.

Key Components:

  • Namespaces: UnityEngine.UIElements
  • Common Types: VisualElement, StyleSheet, UIDocument, etc.

Use Cases:

  • Creating editor extensions or runtime UIs with modern styling.
  • Building dynamic, interactive UI using style sheets and hierarchical layouts.
  • Enjoying a more web-like approach to UI development within Unity.

8. Newtonsoft.Json

Overview:
This popular third-party assembly is your go-to for JSON parsing and serialization. It simplifies working with JSON data significantly.

Key Components:

  • Namespaces: Newtonsoft.Json
  • Common Types: JsonConvert, JsonSerializer, and various attribute classes for customization.

Use Cases:

  • Serializing objects to JSON strings.
  • Deserializing JSON data back into .NET objects.
  • Handling complex JSON structures with ease and flexibility.

9. netstandard

Overview:
netstandard represents a specification of .NET APIs that are available across all .NET implementations. It ensures uniformity and compatibility.

Key Components:

  • Namespaces: Provides foundational APIs common across different .NET platforms.
  • Common Types: Includes various core libraries and utilities that are standardized for cross-platform compatibility.

Use Cases:

  • Writing cross-platform libraries.
  • Ensuring compatibility across multiple .NET implementations (such as .NET Core, .NET Framework, Xamarin, etc.).

10. mscorlib

Overview:
Historically the core library for .NET applications, mscorlib contains essential types and base classes for the .NET framework.

Key Components:

  • Namespaces: System, along with many other central namespaces.
  • Common Types: System.Object, System.String, basic collection types, and more.

Use Cases:

  • It underpins much of the functionality you rely on in .NET.
  • Provides critical runtime support and base types used by most applications.

]]>
https://www.portalsunited.com:443/docs/world-builder-runtime-scripting-assemblies/feed/ 0
Add Node Script to WorldBuilder Explainer Video https://www.portalsunited.com:443/docs/add-node-script-to-worldbuilder-explainer-video/ https://www.portalsunited.com:443/docs/add-node-script-to-worldbuilder-explainer-video/#respond Thu, 20 Mar 2025 12:25:21 +0000 https://www.portalsunited.com:443/?post_type=docs&p=1365 Courtesy of Moritz Loos, 2Sync GmbH

Please note that if you change your script file after adding it, you need to remove the script manually, save the project, restart World Builder and import/add it again.

]]>
https://www.portalsunited.com:443/docs/add-node-script-to-worldbuilder-explainer-video/feed/ 0
World Builder – Group Node https://www.portalsunited.com:443/docs/world-builder-group-node/ https://www.portalsunited.com:443/docs/world-builder-group-node/#respond Thu, 13 Mar 2025 17:27:42 +0000 https://www.portalsunited.com:443/?post_type=docs&p=1301 The Group Node is a special node that can group together many nodes into one single node. It helps you save space in the logic editor.

To group nodes, select them and then right-click to open the search dialog, from you select “Group”.

The group node will show up in another color:

You can then click on the expand button next to the group node’s title. A special view of the nodes inside the group node will open.

In the right upper corner, you can edit the title of the group node. You can rename the inputs and outputs of the group using the text fields inside the Group Input and Group Output nodes. You can also add further inputs or outputs by dragging a connection away from any input or output of a node inside the group. Select “Group Connector” from the search dialog.

Use the contract button in the upper right corner of the group node work space to go back to the default logic editor work space. You will find the group node updated and selected:

]]>
https://www.portalsunited.com:443/docs/world-builder-group-node/feed/ 0
World Builder – Image Sequence Node https://www.portalsunited.com:443/docs/world-builder-image-sequence-node/ https://www.portalsunited.com:443/docs/world-builder-image-sequence-node/#respond Thu, 13 Mar 2025 17:05:39 +0000 https://www.portalsunited.com:443/?post_type=docs&p=1297 The Image Sequence node works in a very similar way to the Audio & Video Player Nodes.

You can use the flow inputs to Play and Stop the image sequence presentation. In the same way you can also go to the Next Image or Previous Image of the image sequence. You could, for example, use buttons in combination with true/false signal callbacks to trigger the flows.

Use the dropdown field inside the node to choose the image sequence asset that you want to display on your image sequence display in the location.

Use the Is Playing output to monitor if the image sequence is (still) playing.

]]>
https://www.portalsunited.com:443/docs/world-builder-image-sequence-node/feed/ 0