다이얼로그 작업중
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25c0bc052eb686040a094b80e9456edc
|
||||
timeCreated: 1647540495
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cc0cd77ad7bd1646b4bbaa06b9ca408
|
||||
timeCreated: 1740674216
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 603dae51e7f0bd74199c3ec0dcc55a8f
|
||||
timeCreated: 1741946956
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5704810c9af26f5448573e38148dbb5f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcad573acd6e41f68c6e963a725d1853
|
||||
timeCreated: 1743698947
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b615d15a39ee45b18faded43e7ebb6a3
|
||||
timeCreated: 1743699457
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7287891ea1ec438f9fd806f827c2cfb2
|
||||
timeCreated: 1743616343
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39468cd3f080f8b47819d2cd9bc57f5c
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ac21a44da3824d4c98464ff10ad58e6
|
||||
timeCreated: 1647524465
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d2a06b9574e4493aeaa55e6b50e57e0
|
||||
timeCreated: 1743699516
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c758821cbc96e7a4683ec8b226d2cc8e
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b053859551fb114d88470e7062594e0
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user