docs.unity3d.com
Search Results for

    Show / Hide Table of Contents

    Explore the code of the Visual Novel Director sample

    Custom runtime

    Graph Toolkit doesn't come with a runtime, so this sample demonstrates how to create a simple custom runtime.

    This custom runtime can be found in the Runtime folder (Unity.GraphToolkit.Samples.VisualNovelDirector.asmdef assembly) and is divided into two parts:

    1. The runtime data model: VisualNovelRuntimeGraph, which stores a list of VisualNovelRuntimeGraphNodes.
    2. The runtime execution engine: VisualNovelDirector and VisualNovelRuntimeGraphNodeExecutor, which execute the graph at runtime.

    Runtime data model

    The Graph Toolkit data model has references to the editor and can't be used directly at runtime. This means you have to implement your own data model.

    In the Visual Novel Director example runtime, the VisualNovelRuntimeGraph is a [ScriptableObject] that stores a list of VisualNovelRuntimeGraphNodes using [SerializeReference].

    This custom runtime model allows you:

    • to store the minimal data required for the runtime without unnecessary authoring information, such as node positions.
      • As an example, the WaitForInputNode is completely empty because it acts as a marker in the VisualNovelRuntimeGraph without any actual state.
    • to use any preferred loading and serialization mechanism (for example MonoBehaviours or ECS).
      • As an example, VisualNovelDirector sample uses a MonoBehaviour (the VisualNovelDirector) which stores a direct reference to the VisualNovelRuntimeGraph. This lets Unity handle the loading of the asset at runtime.
    • to create complex, modular runtime behaviour from simple authoring nodes (i.e. authoring and runtime doesn't have to be 1:1).

    Runtime execution

    Graph Toolkit doesn't have a graph-based runtime engine. This means you have to implement your own runtime, which can be node-based or not.

    In this sample, the VisualNovelDirector MonoBehaviour executes nodes linearly. For simplicity, the sample excludes branching or choices. The VisualNovelDirector uses the loaded VisualNovelRuntimeGraph, public configuration fields, and asset references for all required UI elements (background image, dialogue text box, etc).

    This custom runtime executes the graph from any defined entry-point. The VisualNovelDirector is a MonoBehaviour and uses Unity events. On Start, it instantiates VisualNovelRuntimeGraphNodeExecutors and calls them in sequence, using the data in the runtime nodes.

    Sample runtime executors execute one or more specific node types. For example, SetBackgroundExecutor executes SetBackgroundNode.

    For more details on how the example runtime works (accepting input, etc), review the code and its comments.

    Debug view: visualizing runtime execution in the graph editor

    While the visual novel plays in Play Mode, the sample uses the Unity.GraphToolkit.Editor.GraphVisualization API to drive a live debug view in the graph editor. As nodes execute, the active node animates, the wire currently being followed animates, and the ports of the executing nodes display the live values being applied to the scene. This makes the runtime flow inside the graph editor visible without breakpoints or extra logging.

    All visualization code is guarded by #if UNITY_EDITOR so the runtime keeps building outside the editor.

    The visualization Context

    The visual novel runtime creates a single visualization Context at the start of execution and disposes of it when execution ends:

    using var debugContext = Registry.CreateVisualizationContext(RuntimeGraph.ID);
    

    The Context is the entry point to every visualization feature used in the sample:

    • debugContext.GetNodeReference(nodeID) and debugContext.Motion.Play/Stop for node animations.
    • debugContext.GetPortReference(portID).SetPreview/ClearPreview for port previews.
    • debugContext.GetWireReference(outputPortID, inputPortID) together with WireReference properties (IsDashed, Opacity, WidthOverride) and debugContext.Motion.Play/Stop for wire styling and animations.
    • debugContext.NodeCustomizationEnabled, debugContext.PortPreviewEnabled to toggle the entire debug view on or off.
    • debugContext.ClearAllVisualization() to reset every visual override applied through the context.

    To make the visualization features available to the runtime nodes, the importer stores the relevant port and wire IDs (InputPortIDs, OutputPortIDs, NextWireSourceId, NextWireDestinationId, and the option-specific port IDs on the TwoOptionRuntimeNode) on the runtime nodes during graph import. The runtime executor then resolves these IDs into the NodeReference, PortReference, and WireReference values used by the visualization API.

    Node animations

    While a SetDialogueRuntimeNode is being executed (its dialogue is being typed out), the sample plays a looping accent animation on the corresponding node in the graph:

    debugContext.Motion.Play(debugContext.GetNodeReference(dialogueNode.ID), 1f);
    

    When the player advances past the dialogue with a WaitForInputRuntimeNode, the animation is stopped:

    debugContext.Motion.Stop(debugContext.GetNodeReference(prevDialogueNode.ID));
    

    This is the same pattern you can reuse for any long-running operation on a node, to visually indicate that the node is currently active.

    Port previews

    Port previews display a short string next to a port in the graph editor. The sample uses them to mirror the runtime values being applied to the scene, so the graph editor doubles as a debugger.

    • SetBackgroundRuntimeNode shows the name of the sprite currently assigned to the background image on its background input port.
    • SetDialogueRuntimeNode shows the actor name, the actor sprite name, the location (Left/Right), and a truncated preview of the dialogue text on the matching input ports.
    • TwoOptionRuntimeNode shows the two option labels on its output ports. Once the player picks one, the non-selected option is wrapped in <s>...</s> so it appears struck through, which leaves a visible trace of the branch that was not taken.

    When the runtime reaches a WaitForInputRuntimeNode, the previews on every node that contributed to the current beat are cleared so the previews always reflect the current dialogue rather than accumulating stale state:

    foreach (var nodeBeforeWaitForInput in nodesBeforeWaitForInput)
    {
        foreach (var inputPortID in nodeBeforeWaitForInput.InputPortIDs)
            debugContext.GetPortReference(inputPortID).ClearPreview();
        foreach (var outputPortID in nodeBeforeWaitForInput.OutputPortIDs)
            debugContext.GetPortReference(outputPortID).ClearPreview();
    }
    

    Wire animations

    To make the active execution path obvious at a glance, every execution wire is given a base style (dashed, semi-transparent) up front:

    var wire = debugContext.GetWireReference(node.NextWireSourceId, node.NextWireDestinationId);
    wire.Opacity = 0.5f;
    wire.IsDashed = true;
    

    As the runtime moves from one node to the next, the wire about to be followed is cleared back to its default style, given a thicker width, and animated:

    nextWire.ClearCustomization();
    nextWire.WidthOverride = 10f;
    debugContext.Motion.Play(nextWire, 1f);
    

    The previously animated wire keeps the thicker width but stops animating, which leaves a visible trail of the path that has already been traversed. To avoid clearing the animation that was just (re)started, the sample only performs this transition when the active wire actually changes from one iteration to the next; some runtime node types (such as SetDialogueRuntimeNode) don't update the active wire because the conceptual "current wire" stays the same across the dialogue and its trailing WaitForInputRuntimeNode.

    For the branching TwoOptionRuntimeNode, the wire to animate is chosen based on the player's selection, using the Option1/Option2 port IDs stored on the runtime node.

    Toolbar buttons

    The sample adds two custom toolbar elements to the graph editor (under the Editor/Toolbar folder) using [GraphToolbarElement(...)]. Both are hidden outside of Play Mode because the debug view is only meaningful while the runtime is executing.

    • DebugViewToggle (DebugView.cs) – an EditorToolbarToggle that turns the whole debug view on and off by setting Context.NodeCustomizationEnabled and Context.PortPreviewEnabled on the active visualization context retrieved with Registry.GetActiveContext(graphID).
    • ClearDebugDataButton (ClearDebugDataButton.cs) – an EditorToolbarButton that calls Context.ClearAllVisualization() on the active context, which removes every node animation, port preview, and wire customization in one call.

    These two buttons together let you scope and reset the debug view while iterating on a graph in Play Mode.

    In This Article
    Back to top
    Copyright © 2026 Unity Technologies — Trademarks and terms of use
    • Legal
    • Privacy Policy
    • Cookie Policy
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)