다이얼로그 작업중

This commit is contained in:
skrwns304@gmail.com
2026-06-09 15:51:29 +09:00
parent 54c6ddee0a
commit 404921f815
172 changed files with 9854 additions and 26 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 25c0bc052eb686040a094b80e9456edc
timeCreated: 1647540495

View File

@@ -0,0 +1,330 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.GraphToolkit.Editor;
using UnityEditor.AssetImporters;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// VisualNovelDirectorImporter is a <see cref="ScriptedImporter"/> that imports the <see cref="VisualNovelDirectorGraph"/>
/// and builds the corresponding <see cref="VisualNovelRuntimeGraph"/>.
/// </summary>
[ScriptedImporter(1, VisualNovelDirectorGraph.AssetExtension)]
internal class VisualNovelDirectorImporter : ScriptedImporter
{
/// <summary>
/// Unity calls this method when the editor imports the asset. This method then processes the imported <see cref="VisualNovelDirectorGraph"/>.
/// </summary>
/// <param name="ctx">The asset import context.</param>
public override void OnImportAsset(AssetImportContext ctx)
{
var graph = GraphDatabase.LoadGraphForImporter<VisualNovelDirectorGraph>(ctx.assetPath);
// The `graph` may be null if the `GraphDatabase.LoadGraphForImporter` method
// fails to load the asset from the specified `ctx.assetPath`.
// This can occur under the following circumstances:
// - The asset path is incorrect, or the asset does not exist at the specified location.
// - The asset located at the specified path is not of type `VisualNovelDirectorGraph`.
// - The asset file itself is problematic. For example, it is corrupted, or stored in an unsupported format.
//
// Best practice to deal with serialization is to thoroughly validate and safeguard against
// impaired or incomplete data, to account for potential deserialization issues.
if (graph == null)
{
Debug.LogError($"Failed to load Visual Novel Director graph asset: {ctx.assetPath}");
return;
}
// Get the first Start Node
// (Only using the first node is a simplification we made for this sample)
var startNodeModel = graph.GetNodes().OfType<StartNode>().FirstOrDefault();
if (startNodeModel == null)
{
// No need to log an error here, as the VisualNovelDirectorGraphProcessor is already logging an error in the console
// See VisualNovelDirectorGraph.CheckGraphErrors(GraphLogger).
return;
}
// Build the runtime asset by walking the graph and adding the relevant nodes.
var runtimeAsset = ScriptableObject.CreateInstance<VisualNovelRuntimeGraph>();
BuildRuntimeGraph(startNodeModel, runtimeAsset);
// Add the runtime object to the graph asset and set it to be the main asset.
// This allows the same asset to be used in inspectors wherever a runtime asset is expected.
// Refer to the BasicVisualNovelCanvas.prefab for an example of this.
ctx.AddObjectToAsset("RuntimeAsset", runtimeAsset);
ctx.SetMainObject(runtimeAsset);
}
/// <summary>
/// Builds the runtime graph by traversing all reachable nodes from the start node.
/// Supports both linear and branching paths.
/// </summary>
/// <param name="startNode">The start node of the graph</param>
/// <param name="runtimeAsset">The runtime asset to populate</param>
static void BuildRuntimeGraph(INode startNode, VisualNovelRuntimeGraph runtimeAsset)
{
// Map from editor nodes to their starting index in the runtime nodes list
var nodeToRuntimeIndex = new Dictionary<INode, int>();
// Queue for breadth-first traversal
var nodesToProcess = new Queue<INode>();
// Start with the first node after the start node
var firstNode = GetNextNode(startNode);
if (firstNode != null)
{
nodesToProcess.Enqueue(firstNode);
}
// Process all reachable nodes
while (nodesToProcess.Count > 0)
{
var currentNode = nodesToProcess.Dequeue();
// Skip if we've already processed this node
if (nodeToRuntimeIndex.ContainsKey(currentNode))
continue;
// Record the starting index for this node's runtime nodes
var startIndex = runtimeAsset.Nodes.Count;
nodeToRuntimeIndex[currentNode] = startIndex;
// Convert the editor node to runtime node(s)
var runtimeNodes = TranslateNodeModelToRuntimeNodes(currentNode);
runtimeAsset.Nodes.AddRange(runtimeNodes);
// Enqueue connected nodes based on node type
if (currentNode is TwoOptionNode dialogueOptionNode)
{
// For branching nodes, enqueue both branches
var option1Node = GetNextNodeFromPort(dialogueOptionNode, TwoOptionNode.OUT_PORT_OPTION1_NAME);
var option2Node = GetNextNodeFromPort(dialogueOptionNode, TwoOptionNode.OUT_PORT_OPTION2_NAME);
if (option1Node != null)
nodesToProcess.Enqueue(option1Node);
if (option2Node != null)
nodesToProcess.Enqueue(option2Node);
}
else
{
// For linear nodes, enqueue the next node
var nextNode = GetNextNode(currentNode);
if (nextNode != null)
nodesToProcess.Enqueue(nextNode);
}
}
// Second pass: set up NextNodeIndex references
SetupNodeReferences(runtimeAsset, nodeToRuntimeIndex, startNode);
}
/// <summary>
/// Sets up the NextNodeIndex references for all runtime nodes.
/// </summary>
static void SetupNodeReferences(VisualNovelRuntimeGraph runtimeAsset, Dictionary<INode, int> nodeToRuntimeIndex, INode startNode)
{
var processedNodes = new HashSet<INode>();
var nodesToProcess = new Queue<INode>();
var firstNode = GetNextNode(startNode);
if (firstNode != null)
{
nodesToProcess.Enqueue(firstNode);
}
while (nodesToProcess.Count > 0)
{
var currentNode = nodesToProcess.Dequeue();
if (!processedNodes.Add(currentNode))
continue;
if (!nodeToRuntimeIndex.TryGetValue(currentNode, out var currentRuntimeIndex))
continue;
// Handle different node types
if (currentNode is TwoOptionNode optionNode)
{
// For branching nodes, set both branch indices
var option1Node = GetNextNodeFromPort(optionNode, TwoOptionNode.OUT_PORT_OPTION1_NAME);
var option2Node = GetNextNodeFromPort(optionNode, TwoOptionNode.OUT_PORT_OPTION2_NAME);
var twoOptionRuntimeNode = runtimeAsset.Nodes[currentRuntimeIndex] as TwoOptionRuntimeNode;
if (twoOptionRuntimeNode != null)
{
twoOptionRuntimeNode.Option1NextNodeIndex = option1Node != null && nodeToRuntimeIndex.TryGetValue(option1Node, out var idx1) ? idx1 : -1;
twoOptionRuntimeNode.Option2NextNodeIndex = option2Node != null && nodeToRuntimeIndex.TryGetValue(option2Node, out var idx2) ? idx2 : -1;
}
// Enqueue both branches for processing
if (option1Node != null)
nodesToProcess.Enqueue(option1Node);
if (option2Node != null)
nodesToProcess.Enqueue(option2Node);
}
else
{
// For linear nodes, set NextNodeIndex
var nextNode = GetNextNode(currentNode);
// Get all runtime nodes created from this editor node
var runtimeNodes = TranslateNodeModelToRuntimeNodes(currentNode);
for (int i = 0; i < runtimeNodes.Count; i++)
{
var runtimeNodeIndex = currentRuntimeIndex + i;
var runtimeNode = runtimeAsset.Nodes[runtimeNodeIndex];
// If this is the last runtime node from this editor node
if (i == runtimeNodes.Count - 1)
{
// Point to the next editor node's first runtime node
runtimeNode.NextNodeIndex = nextNode != null && nodeToRuntimeIndex.TryGetValue(nextNode, out var nextIdx) ? nextIdx : -1;
}
else
{
// Point to the next runtime node in the sequence
runtimeNode.NextNodeIndex = runtimeNodeIndex + 1;
}
}
if (nextNode != null)
nodesToProcess.Enqueue(nextNode);
}
}
}
/// <summary>
/// Gets the node connected to a specific output port.
/// </summary>
/// <param name="currentNode">The node to get the output from</param>
/// <param name="portName">The name of the output port</param>
/// <returns>The connected node, or null if not connected</returns>
static INode GetNextNodeFromPort(INode currentNode, string portName)
{
var outputPort = currentNode.GetOutputPortByName(portName);
var nextNodePort = outputPort?.firstConnectedPort;
return nextNodePort?.GetNode();
}
/// <summary>
/// Gets the node that is executed after the given node.
/// </summary>
/// <param name="currentNode">The current node</param>
/// <returns>The next node in the graph</returns>
static INode GetNextNode(INode currentNode)
{
var outputPort = currentNode.GetOutputPortByName(VisualNovelNode.EXECUTION_PORT_DEFAULT_NAME);
var nextNodePort = outputPort.firstConnectedPort;
var nextNode = nextNodePort?.GetNode();
return nextNode;
}
/// <summary>
/// Converts a <see cref="VisualNovelNode"/> to a list of one or more runtime <see cref="VisualNovelRuntimeNode"/>s.
/// </summary>
/// <param name="nodeModel">The <see cref="VisualNovelNode"/> to convert.</param>
/// <returns>
/// A list of <see cref="VisualNovelRuntimeNode"/>s that represent the runtime behavior of the input node.
/// Multiple runtime nodes may be generated from a single input <see cref="VisualNovelNode"/>.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown if the <see cref="NodeModel"/> passed in is unsupported and cannot be converted.
/// </exception>
/// <remarks>
/// This conversion is not always 1:1. For example: the <see cref="SetDialogueNode"/> node is converted to
/// a <see cref="SetDialogueRuntimeNode"/> and a <see cref="WaitForInputRuntimeNode"/>. This is so that the
/// runtime pauses execution and waits for player input after a dialogue is displayed. This approach allows
/// more complex behaviour to be composed of multiple simpler runtime nodes.
/// <br/><br/>
/// </remarks>
static List<VisualNovelRuntimeNode> TranslateNodeModelToRuntimeNodes(INode nodeModel)
{
var returnedNodes = new List<VisualNovelRuntimeNode>();
switch (nodeModel)
{
case SetBackgroundNode setBackgroundNodeModel:
returnedNodes.Add(new SetBackgroundRuntimeNode
{
BackgroundSprite = GetInputPortValue<Sprite>(setBackgroundNodeModel.GetInputPortByName(SetBackgroundNode.IN_PORT_BACKGROUND_NAME))
});
// Note: We deliberately don't add a WaitForInputRuntimeNode here to enable updating multiple
// visual novel elements (the background, music, dialogue, etc) all at once. This creates a seamless
// transition involving more than one element.
break;
case SetDialogueNode setDialogueNodeModel:
returnedNodes.Add(new SetDialogueRuntimeNode
{
ActorName = GetInputPortValue<string>(setDialogueNodeModel.GetInputPortByName(SetDialogueNode.IN_PORT_ACTOR_NAME_NAME)),
ActorSprite = GetInputPortValue<Sprite>(setDialogueNodeModel.GetInputPortByName(SetDialogueNode.IN_PORT_ACTOR_SPRITE_NAME)),
LocationIndex = (int)GetInputPortValue<SetDialogueNode.Location>(setDialogueNodeModel.GetInputPortByName(SetDialogueNode.IN_PORT_LOCATION_NAME)),
DialogueText = GetInputPortValue<string>(setDialogueNodeModel.GetInputPortByName(SetDialogueNode.IN_PORT_DIALOGUE_NAME))
});
// Insert a WaitForInputNode after dialogue to create the expected visual novel behaviour.
// This ensures narrative flow pauses until the player signals readiness to continue.
returnedNodes.Add(new WaitForInputRuntimeNode());
break;
case WaitForInputNode _:
returnedNodes.Add(new WaitForInputRuntimeNode());
break;
case TwoOptionNode twoOptionNodeModel:
returnedNodes.Add(new TwoOptionRuntimeNode
{
Option1Text = GetInputPortValue<string>(twoOptionNodeModel.GetInputPortByName(TwoOptionNode.IN_PORT_OPTION1_NAME)),
Option2Text = GetInputPortValue<string>(twoOptionNodeModel.GetInputPortByName(TwoOptionNode.IN_PORT_OPTION2_NAME))
});
break;
default:
throw new ArgumentException($"Unsupported node model type: {nodeModel.GetType()}");
}
return returnedNodes;
}
/// <summary>
/// Gets the value of an input port on a node.
/// <br/><br/>
/// The value is obtained from (in priority order):<br/>
/// 1. Connections to the port (variable nodes, constant nodes, wire portals)<br/>
/// 2. Embedded value on the port<br/>
/// 3. Default value of the port<br/>
/// </summary>
static T GetInputPortValue<T>(IPort port)
{
T value = default;
// If port is connected to another node, get value from connection
if (port.isConnected)
{
switch (port.firstConnectedPort.GetNode())
{
case IVariableNode variableNode:
variableNode.variable.TryGetDefaultValue<T>(out value);
return value;
case IConstantNode constantNode:
constantNode.TryGetValue<T>(out value);
return value;
default:
break;
}
}
else
{
// If port has embedded value, return it.
// Otherwise, return the default value of the port
port.TryGetValue(out value);
}
return value;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5cc0cd77ad7bd1646b4bbaa06b9ca408
timeCreated: 1740674216

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 603dae51e7f0bd74199c3ec0dcc55a8f
timeCreated: 1741946956

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5704810c9af26f5448573e38148dbb5f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
using System;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// Represents the Set Background Node in the Visual Novel Director tool.
/// </summary>
/// <remarks>
/// Is converted to a <see cref="SetBackgroundRuntimeNode"/> for the runtime.
/// </remarks>
[Serializable]
internal class SetBackgroundNode : VisualNovelNode
{
public static readonly string IN_PORT_BACKGROUND_NAME = "Background";
/// <summary>
/// Defines the output for the node.
/// </summary>
/// <param name="context">The scope to define the node.</param>
protected override void OnDefinePorts(IPortDefinitionContext context)
{
AddInputOutputExecutionPorts(context);
context.AddInputPort<Sprite>(IN_PORT_BACKGROUND_NAME);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fcad573acd6e41f68c6e963a725d1853
timeCreated: 1743698947

View File

@@ -0,0 +1,48 @@
using System;
using UnityEngine;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// Represents the Set Dialogue Node in the Visual Novel Director tool.
/// </summary>
/// <remarks>
/// Is converted to a <see cref="SetDialogueRuntimeNode"/> for the runtime.
/// </remarks>
[Serializable]
internal class SetDialogueNode : VisualNovelNode
{
public const string IN_PORT_ACTOR_NAME_NAME = "ActorName";
public const string IN_PORT_ACTOR_SPRITE_NAME = "ActorSprite";
public const string IN_PORT_LOCATION_NAME = "ActorLocation";
public const string IN_PORT_DIALOGUE_NAME = "Dialogue";
public enum Location
{
Left = 0,
Right = 1
}
/// <summary>
/// Defines the output for the node.
/// </summary>
/// <param name="context">The scope to define the node.</param>
protected override void OnDefinePorts(IPortDefinitionContext context)
{
AddInputOutputExecutionPorts(context);
context.AddInputPort<string>(IN_PORT_ACTOR_NAME_NAME)
.WithDisplayName("Actor Name")
.Build();
context.AddInputPort<Sprite>(IN_PORT_ACTOR_SPRITE_NAME)
.WithDisplayName("Actor Sprite")
.Build();
context.AddInputPort<Location>(IN_PORT_LOCATION_NAME)
.WithDisplayName("Actor Location")
.Build();
context.AddInputPort<string>(IN_PORT_DIALOGUE_NAME)
.Build();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b615d15a39ee45b18faded43e7ebb6a3
timeCreated: 1743699457

View File

@@ -0,0 +1,28 @@
using System;
using Unity.GraphToolkit.Editor;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// Represents the Start Node in the Visual Novel Director tool.
/// </summary>
/// <remarks>
/// The start node serves as the entry point to the visual novel graph.
/// </remarks>
[Serializable]
internal class StartNode : VisualNovelNode
{
/// <summary>
/// Defines the output for the node.
/// </summary>
/// <param name="context">The scope to define the node.</param>
protected override void OnDefinePorts(IPortDefinitionContext context)
{
// Start is a special node that has no input, so we don't call DefineCommonPorts
context.AddOutputPort(EXECUTION_PORT_DEFAULT_NAME)
.WithDisplayName(string.Empty)
.WithConnectorUI(PortConnectorUI.Arrowhead)
.Build();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7287891ea1ec438f9fd806f827c2cfb2
timeCreated: 1743616343

View File

@@ -0,0 +1,56 @@
using System;
using Unity.GraphToolkit.Editor;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// Represents a Two Dialogue Option Node in the Visual Novel Director tool.
/// </summary>
/// <remarks>
/// This node presents two dialogue options to the player and branches the narrative
/// based on their choice. Is converted to a <see cref="TwoOptionRuntimeNode"/> for the runtime.
/// </remarks>
[Serializable]
internal class TwoOptionNode : VisualNovelNode
{
public const string IN_PORT_OPTION1_NAME = "Option1";
public const string IN_PORT_OPTION2_NAME = "Option2";
public const string OUT_PORT_OPTION1_NAME = "Option1Execution";
public const string OUT_PORT_OPTION2_NAME = "Option2Execution";
/// <summary>
/// Defines the ports for the node.
/// </summary>
/// <param name="context">The scope to define the node.</param>
protected override void OnDefinePorts(IPortDefinitionContext context)
{
// Input execution port
context.AddInputPort(EXECUTION_PORT_DEFAULT_NAME)
.WithDisplayName(string.Empty)
.WithConnectorUI(PortConnectorUI.Arrowhead)
.Build();
context.AddInputPort<string>(IN_PORT_OPTION1_NAME)
.WithDisplayName("Option 1")
.Build();
context.AddInputPort<string>(IN_PORT_OPTION2_NAME)
.WithDisplayName("Option 2")
.Build();
// Two output execution ports for branching
context.AddOutputPort(OUT_PORT_OPTION1_NAME)
.WithDisplayName("Option 1")
.WithConnectorUI(PortConnectorUI.Arrowhead)
//.AsVertical()
.Build();
context.AddOutputPort(OUT_PORT_OPTION2_NAME)
.WithDisplayName("Option 2")
.WithConnectorUI(PortConnectorUI.Arrowhead)
//.AsVertical()
.Build();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 39468cd3f080f8b47819d2cd9bc57f5c

View File

@@ -0,0 +1,33 @@
using System;
using Unity.GraphToolkit.Editor;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// Visual Novel Director Base Node model.
/// </summary>
/// <remarks> It is best practice to group all the nodes of a tool under a base node. It improves organization,
/// scalability, maintenance and debugging.</remarks>
[Serializable]
internal abstract class VisualNovelNode : Node
{
public const string EXECUTION_PORT_DEFAULT_NAME = "ExecutionPort";
/// <summary>
/// Defines common input and output execution ports for all nodes in the Visual Novel Director tool.
/// </summary>
/// <param name="scope">The scope to define the node.</param>
protected void AddInputOutputExecutionPorts(IPortDefinitionContext context)
{
context.AddInputPort(EXECUTION_PORT_DEFAULT_NAME)
.WithDisplayName(string.Empty)
.WithConnectorUI(PortConnectorUI.Arrowhead)
.Build();
context.AddOutputPort(EXECUTION_PORT_DEFAULT_NAME)
.WithDisplayName(string.Empty)
.WithConnectorUI(PortConnectorUI.Arrowhead)
.Build();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0ac21a44da3824d4c98464ff10ad58e6
timeCreated: 1647524465

View File

@@ -0,0 +1,30 @@
using System;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// Represents the Wait For Input Node in the Visual Novel Director tool.
/// <br/><br/>
/// Use this when you want to have an explicit pause / wait for player input after you change an element
/// (background, etc.) of the visual novel.
/// </summary>
/// <remarks>
/// Is converted to a <see cref="WaitForInputRuntimeNode"/> for the runtime.
/// <br/><br/>
/// The <see cref="SetDialogueNode"/> automatically waits for input after it executes because it creates
/// a <see cref="WaitForInputRuntimeNode"/> when converted to the runtime graph. This is to match the typical
/// expected behaviour of visual novel dialogue waiting for player input between dialogue lines.
/// </remarks>
[Serializable]
internal class WaitForInputNode : VisualNovelNode
{
/// <summary>
/// Defines the output for the node.
/// </summary>
/// <param name="context">The scope to define the node.</param>
protected override void OnDefinePorts(IPortDefinitionContext context)
{
AddInputOutputExecutionPorts(context);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2d2a06b9574e4493aeaa55e6b50e57e0
timeCreated: 1743699516

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.GraphToolkit.Editor;
using UnityEditor;
namespace Unity.GraphToolkit.Samples.VisualNovelDirector.Editor
{
/// <summary>
/// Represents the Visual Novel Director graph.
/// </summary>
/// <remarks> This is the entry point for the Visual Novel Director tool, it represents a Visual Novel Director graph.
/// This class extends the Graph Toolkit <see cref="Graph"/> class, which provides a range of default behaviors,
/// reducing the amount of code required to quickly implement a basic graph tool.
/// It also provides customization by allowing you to override specific methods and can be decorated by attributes to
/// tailor the tool's functionality.
/// For example, in this class, we override the OnGraphChanged method to add our custom handling for graph errors and warnings.
/// We also decorated it with the <see cref="GraphAttribute"/>, which specifies the asset extension.
/// </remarks>```
[Serializable]
[Graph(AssetExtension)]
internal class VisualNovelDirectorGraph : Graph
{
const string k_graphName = "Visual Novel Graph";
/// <summary>
/// The file extension for Visual Novel Director graphs.
/// </summary>
/// <remarks> In Unity, the extension is used to select the right importer, so it must be unique.</remarks>
internal const string AssetExtension = "vnd";
/// <summary>
/// Creates a new Visual Novel Director graph asset file in the project window.
/// </summary>
/// <remarks>This is also where we add the shortcut to create a new graph from the editor Asset menu.</remarks>
[MenuItem("Assets/Create/Graph Toolkit Samples/Visual Novel Director Graph")]
static void CreateAssetFile()
{
GraphDatabase.PromptInProjectBrowserToCreateNewAsset<VisualNovelDirectorGraph>(k_graphName);
}
/// <summary>
/// Called when the graph changes.
/// </summary>
/// <param name="infos">The GraphLogger object to which errors and warnings are added.</param>
/// <remarks>
/// This method is triggered whenever the graph is modified. It calls `CheckGraphErrors` to validate the graph
/// and report any issues.
/// </remarks>
public override void OnGraphChanged(GraphLogger infos)
{
base.OnGraphChanged(infos);
CheckGraphErrors(infos);
}
/// <summary>
/// Checks the graph for errors and warnings and adds them to the result object.
/// </summary>
/// <param name="infos">Object implementing <see cref="GraphLogger"/> interface and containing
/// collected errors and warnings</param>
/// <remarks>Errors and warnings are reported by adding them to the GraphLogger object,
/// which is the default reporting mechanism for a Graph Toolkit tool. </remarks>
void CheckGraphErrors(GraphLogger infos)
{
List<StartNode> startNodes = GetNodes().OfType<StartNode>().ToList();
switch (startNodes.Count)
{
case 0:
infos.LogError("Add a StartNode in your Visual Novel graph.", this);
break;
case >= 1:
{
foreach (var startNode in startNodes.Skip(1))
{
infos.LogWarning($"VisualNovelDirector only supports one StartNode per graph. Only the first created one will be used.", startNode);
}
break;
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c758821cbc96e7a4683ec8b226d2cc8e

View File

@@ -0,0 +1,21 @@
{
"name": "Unity.GraphToolkit.Samples.VisualNovelDirector.Editor",
"rootNamespace": "Unity.GraphToolkit.Samples.VisualNovelDirector.Editor",
"references": [
"Unity.GraphToolkit.Common.Editor",
"Unity.GraphToolkit.Utility.Editor",
"Unity.GraphToolkit.Samples.VisualNovelDirector",
"Unity.GraphToolkit.Editor"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0b053859551fb114d88470e7062594e0
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: