Guide Extending OctoShaper

Creating Custom Nodes

A custom node is a static C# method that OctoShaper exposes in the node browser. Use one when a project-specific rule is clearer as a reusable graph operation than as a long chain of general-purpose nodes.

Mental model

A method becomes a graph operation

Discover Custom assembly OctoShaper scans the assemblies configured for the project.
Describe Node attributes Metadata gives the method a stable identity and authoring hints.
Expose Inputs + output Method parameters become ports and the return value becomes the output.
Generate Runtime-safe graph Generated graph code calls the custom method directly.

Prepare a custom node assembly

  1. Create a runtime assembly definition for the scripts that will contain your nodes.
  2. Reference the CuriousTrove.OctoShaper assembly and the CuriousTrove.OctoShaper.Core.dll precompiled assembly. Add Unity Mathematics if your nodes use its types.
  3. Open Project Settings > OctoShaper and add the exact assembly name under Custom Node Assemblies.
  4. Wait for the validation message to confirm that the assembly loads and contains at least one node definition.

Why use a dedicated assembly?

Generated graph code is compiled in its own assembly. A dedicated assembly definition gives OctoShaper a stable reference to your custom node methods in both the editor and player builds.

OctoShaper Project Settings showing a validated custom node assembly
Add the runtime assembly name under Custom Node Assemblies and confirm that OctoShaper validates it.

Write an ElementSet mutator

This node raises the existing position column. It does not create or replace rows, so it mutates the incoming ElementSet in place and returns void.

namespace MyGame.Procedural
{
    using CuriousTrove.OctoShaper.Core.Data;
    using CuriousTrove.OctoShaper.Core.Providers;

    public static class LandscapeNodes
    {
        [NodeDefinition(
            Id = "mygame.landscape.raise-height",
            Name = "Raise Height",
            CategoryId = "Custom>Landscape",
            Description = "Moves every element upward.",
            RequiredElementAttributes = new[] { "position" },
            ProvidedElementAttributes = new[] { "position" })]
        public static void RaiseHeight(
            ElementSet elements,
            [NodeInput(Name = "Height", Description = "World-space vertical offset")]
            [NodeSlider(-10d, 10d)] float height = 1f)
        {
            if (elements == null ||
                !elements.TryGetColumn(Attributes.Position, out var positions))
            {
                return;
            }

            for (int i = 0; i < positions.Count; i++)
            {
                var position = positions.GetValue(i);
                position.y += height;
                positions.SetValue(i, position);
            }
        }
    }
}

Keep the node ID globally unique and stable. Existing graph assets store that ID, so changing it later makes the old node definition unavailable.

A registered node appears under its declared category and behaves like the built-in nodes in the graph editor.

Understand the method contract

C# declaration Graph behavior
NodeDefinitionDefines the ID, display name, category, description, and ElementSet attribute contract.
Method parameterBecomes a typed node input. A C# default value becomes the initial value shown in the graph.
NodeInputOverrides the input name or description and can describe dynamic attribute-name inputs.
Return valueBecomes the node output. Use NodeOutput when it needs a clearer name or description.
NodeMin, NodeMax, NodeRange, NodeSliderConstrain numeric editing or present a bounded slider.

Prefer types that OctoShaper already exposes in graphs. Array parameters and array return values are not discovered as node definitions.

Mutating and structural nodes are different

Mutator

Keep the same ElementSet

Receive an ElementSet, edit its columns, and normally return void. Declare the attributes the node requires and provides so the graph can validate connections.

Structural

Replace the flowing ElementSet

When a node adds, removes, duplicates, or reorganizes rows by returning another set, mark the method with NodeStructural. OctoShaper then uses the returned set downstream.

The required/provided attribute lists describe graph compatibility; they are not a substitute for defensive checks inside the method. A custom node should still handle null inputs and missing columns safely.

Make the node available at runtime

  1. Confirm the node appears in the expected node-browser category.
  2. Add it to a graph and verify its ports and defaults before building more graphs around it.
  3. Run Tools > OctoShaper > Generate Graph Code, or leave automatic regeneration enabled.
  4. Keep custom nodes out of editor-only assemblies and avoid dependencies that are unavailable in player builds.

If a node disappears

Check the assembly name first, then verify that the method is static, has a NodeDefinition, uses supported graph types, and still has the same stable ID. Regenerate graph code after correcting the definition.

Keep custom nodes easy to use

  • Give inputs graph-facing names and descriptions instead of exposing implementation terminology.
  • Use sensible defaults so a newly created node already produces a meaningful result.
  • Keep one clear responsibility per node; compose larger behaviors in the graph.
  • Link attribute-heavy nodes back to the Elements mental model and use the Node Catalog as a reference for OctoShaper's built-in conventions.