367 lines
13 KiB
C#
367 lines
13 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace Unheimlich.Data.Dialogues
|
|
{
|
|
public static class DialogueConversationBuilder
|
|
{
|
|
public static DialogueNode CreateSimpleNode(string dialogueText, params (string choiceText, DialogueNode nextNode)[] choices)
|
|
{
|
|
#if UNITY_EDITOR
|
|
DialogueNode node = ScriptableObject.CreateInstance<DialogueNode>();
|
|
node.DialogueText = dialogueText;
|
|
|
|
foreach (var choice in choices)
|
|
{
|
|
DialogueChoiceData choiceData = new DialogueChoiceData
|
|
{
|
|
ChoiceText = choice.choiceText,
|
|
NextNode = choice.nextNode
|
|
};
|
|
node.Choices.Add(choiceData);
|
|
}
|
|
|
|
return node;
|
|
#else
|
|
return null;
|
|
#endif
|
|
}
|
|
|
|
public static DialogueConversation CreateConversation(string speakerName, params DialogueNode[] nodes)
|
|
{
|
|
#if UNITY_EDITOR
|
|
DialogueConversation conversation = ScriptableObject.CreateInstance<DialogueConversation>();
|
|
conversation.SpeakerName = speakerName;
|
|
|
|
foreach (var node in nodes)
|
|
{
|
|
if (node != null)
|
|
conversation.DialogueNodes.Add(node);
|
|
}
|
|
|
|
return conversation;
|
|
#else
|
|
return null;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
public class DialogueCreatorWindow : EditorWindow
|
|
{
|
|
private string _speakerName = "NPC";
|
|
private string _savePath = "Assets/Unheimlich_POC/Datas/DialogueData";
|
|
private string _conversationName = "NewConversation";
|
|
|
|
private List<NodeTemplate> _nodes = new List<NodeTemplate>();
|
|
private Vector2 _scrollPosition;
|
|
|
|
[System.Serializable]
|
|
private class NodeTemplate
|
|
{
|
|
public string NodeText = "Dialogue text";
|
|
public List<ChoiceTemplate> Choices = new List<ChoiceTemplate>
|
|
{
|
|
new ChoiceTemplate { Text = "Choice 1", TargetNodeIndex = 1 },
|
|
new ChoiceTemplate { Text = "Choice 2", TargetNodeIndex = 1 }
|
|
};
|
|
public bool IsEndNode = false;
|
|
public bool Foldout = true;
|
|
}
|
|
|
|
[System.Serializable]
|
|
private class ChoiceTemplate
|
|
{
|
|
public string Text = "New choice";
|
|
public int TargetNodeIndex = 1;
|
|
}
|
|
|
|
[MenuItem("Unheimlich/Tools/Dialogue/Dialogue Creator")]
|
|
public static void ShowWindow()
|
|
{
|
|
var window = GetWindow<DialogueCreatorWindow>("Dialogue Creator");
|
|
window.minSize = new Vector2(500, 600);
|
|
window.Show();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (_nodes.Count == 0)
|
|
{
|
|
_nodes.Add(new NodeTemplate
|
|
{
|
|
NodeText = "Hello! Starting dialogue",
|
|
Choices = new List<ChoiceTemplate>
|
|
{
|
|
new ChoiceTemplate { Text = "Option 1", TargetNodeIndex = 1 },
|
|
new ChoiceTemplate { Text = "Option 2", TargetNodeIndex = 1 }
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
EditorGUILayout.Space(10);
|
|
EditorGUILayout.LabelField("Dialogue Creator", EditorStyles.boldLabel);
|
|
EditorGUILayout.HelpBox("Quickly create a complete conversation with all nodes pre-linked!", MessageType.Info);
|
|
|
|
EditorGUILayout.Space(10);
|
|
|
|
EditorGUILayout.BeginVertical("box");
|
|
EditorGUILayout.LabelField("General Configuration", EditorStyles.boldLabel);
|
|
|
|
_speakerName = EditorGUILayout.TextField("Speaker Name", _speakerName);
|
|
_conversationName = EditorGUILayout.TextField("Conversation Name", _conversationName);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.LabelField("Save Path", GUILayout.Width(150));
|
|
_savePath = EditorGUILayout.TextField(_savePath);
|
|
if (GUILayout.Button("...", GUILayout.Width(30)))
|
|
{
|
|
string newPath = EditorUtility.OpenFolderPanel("Choose Folder", _savePath, "");
|
|
if (!string.IsNullOrEmpty(newPath))
|
|
{
|
|
if (newPath.StartsWith(Application.dataPath))
|
|
_savePath = "Assets" + newPath.Substring(Application.dataPath.Length);
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
EditorGUILayout.Space(10);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.LabelField("Dialogue Structure", EditorStyles.boldLabel);
|
|
if (GUILayout.Button("+ Add Node", GUILayout.Width(150)))
|
|
{
|
|
NodeTemplate newNode = new NodeTemplate
|
|
{
|
|
NodeText = $"Node {_nodes.Count + 1}",
|
|
Choices = new List<ChoiceTemplate>
|
|
{
|
|
new ChoiceTemplate
|
|
{
|
|
Text = "Choice 1",
|
|
TargetNodeIndex = Mathf.Min(_nodes.Count + 1, _nodes.Count)
|
|
}
|
|
}
|
|
};
|
|
_nodes.Add(newNode);
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space(5);
|
|
|
|
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, GUILayout.ExpandHeight(true));
|
|
|
|
for (int i = 0; i < _nodes.Count; i++)
|
|
{
|
|
DrawNodeEditor(i);
|
|
}
|
|
|
|
EditorGUILayout.EndScrollView();
|
|
|
|
EditorGUILayout.Space(10);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.FlexibleSpace();
|
|
|
|
if (GUILayout.Button("Create Conversation", GUILayout.Height(40), GUILayout.Width(200)))
|
|
{
|
|
CreateDialogue();
|
|
}
|
|
|
|
GUILayout.FlexibleSpace();
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space(10);
|
|
}
|
|
|
|
private void DrawNodeEditor(int index)
|
|
{
|
|
NodeTemplate node = _nodes[index];
|
|
|
|
EditorGUILayout.BeginVertical("box");
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
node.Foldout = EditorGUILayout.Foldout(node.Foldout, $"Node {index + 1}" + (index == 0 ? " (START)" : ""), true);
|
|
|
|
GUILayout.FlexibleSpace();
|
|
|
|
if (_nodes.Count > 1 && GUILayout.Button("X", GUILayout.Width(25)))
|
|
{
|
|
_nodes.RemoveAt(index);
|
|
return;
|
|
}
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
if (node.Foldout)
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
|
|
EditorGUILayout.LabelField("Dialogue Text", EditorStyles.miniBoldLabel);
|
|
node.NodeText = EditorGUILayout.TextArea(node.NodeText, GUILayout.Height(60));
|
|
|
|
EditorGUILayout.Space(5);
|
|
|
|
bool isLastNode = index == _nodes.Count - 1;
|
|
|
|
if (isLastNode)
|
|
{
|
|
EditorGUILayout.HelpBox("Last node = End of conversation (no choices)", MessageType.Info);
|
|
node.IsEndNode = true;
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.LabelField("Available Choices", EditorStyles.miniBoldLabel);
|
|
|
|
for (int c = 0; c < node.Choices.Count; c++)
|
|
{
|
|
ChoiceTemplate choice = node.Choices[c];
|
|
|
|
EditorGUILayout.BeginVertical("box");
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.LabelField($"Choice {c + 1}", EditorStyles.boldLabel, GUILayout.Width(60));
|
|
|
|
if (GUILayout.Button("-", GUILayout.Width(25)))
|
|
{
|
|
node.Choices.RemoveAt(c);
|
|
break;
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.LabelField("Choice Text:", GUILayout.Width(100));
|
|
choice.Text = EditorGUILayout.TextField(choice.Text);
|
|
|
|
EditorGUILayout.Space(3);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.LabelField("Target:", GUILayout.Width(100));
|
|
|
|
string[] nodeOptions = new string[_nodes.Count];
|
|
for (int n = 0; n < _nodes.Count; n++)
|
|
{
|
|
if (n == index)
|
|
nodeOptions[n] = $"Node {n + 1} (SELF - Loop)";
|
|
else if (n == _nodes.Count - 1)
|
|
nodeOptions[n] = $"Node {n + 1} (END)";
|
|
else
|
|
nodeOptions[n] = $"Node {n + 1}";
|
|
}
|
|
|
|
choice.TargetNodeIndex = Mathf.Clamp(choice.TargetNodeIndex, 0, _nodes.Count - 1);
|
|
|
|
int selectedIndex = EditorGUILayout.Popup(choice.TargetNodeIndex, nodeOptions);
|
|
choice.TargetNodeIndex = selectedIndex;
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
if (choice.TargetNodeIndex == index)
|
|
EditorGUILayout.HelpBox("! Loop detected: this choice returns to the same node", MessageType.Warning);
|
|
else if (choice.TargetNodeIndex < index)
|
|
EditorGUILayout.HelpBox("↩ Going back to Node " + (choice.TargetNodeIndex + 1), MessageType.Info);
|
|
|
|
EditorGUILayout.EndVertical();
|
|
EditorGUILayout.Space(3);
|
|
}
|
|
|
|
if (GUILayout.Button("+ Add Choice"))
|
|
{
|
|
node.Choices.Add(new ChoiceTemplate
|
|
{
|
|
Text = $"New choice {node.Choices.Count + 1}",
|
|
TargetNodeIndex = Mathf.Min(index + 1, _nodes.Count - 1)
|
|
});
|
|
}
|
|
}
|
|
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
EditorGUILayout.EndVertical();
|
|
EditorGUILayout.Space(5);
|
|
}
|
|
|
|
private void CreateDialogue()
|
|
{
|
|
if (string.IsNullOrEmpty(_speakerName) || string.IsNullOrEmpty(_conversationName))
|
|
{
|
|
EditorUtility.DisplayDialog("Error", "Speaker name and conversation name are required!", "OK");
|
|
return;
|
|
}
|
|
|
|
if (!AssetDatabase.IsValidFolder(_savePath))
|
|
{
|
|
EditorUtility.DisplayDialog("Error", "Save path is not valid!", "OK");
|
|
return;
|
|
}
|
|
|
|
string folderPath = $"{_savePath}/{_conversationName}";
|
|
if (!AssetDatabase.IsValidFolder(folderPath))
|
|
{
|
|
string parentFolder = _savePath;
|
|
AssetDatabase.CreateFolder(parentFolder, _conversationName);
|
|
}
|
|
|
|
List<DialogueNode> createdNodes = new List<DialogueNode>();
|
|
|
|
for (int i = 0; i < _nodes.Count; i++)
|
|
{
|
|
NodeTemplate template = _nodes[i];
|
|
DialogueNode node = CreateInstance<DialogueNode>();
|
|
node.DialogueText = template.NodeText;
|
|
node.name = $"Node_{i + 1}";
|
|
|
|
string nodePath = $"{folderPath}/{node.name}.asset";
|
|
AssetDatabase.CreateAsset(node, nodePath);
|
|
createdNodes.Add(node);
|
|
}
|
|
|
|
for (int i = 0; i < _nodes.Count; i++)
|
|
{
|
|
NodeTemplate template = _nodes[i];
|
|
DialogueNode currentNode = createdNodes[i];
|
|
|
|
foreach (ChoiceTemplate choiceTemplate in template.Choices)
|
|
{
|
|
int targetIndex = Mathf.Clamp(choiceTemplate.TargetNodeIndex, 0, createdNodes.Count - 1);
|
|
|
|
DialogueChoiceData choice = new DialogueChoiceData
|
|
{
|
|
ChoiceText = choiceTemplate.Text,
|
|
NextNode = createdNodes[targetIndex]
|
|
};
|
|
currentNode.Choices.Add(choice);
|
|
}
|
|
|
|
EditorUtility.SetDirty(currentNode);
|
|
}
|
|
|
|
DialogueConversation conversation = CreateInstance<DialogueConversation>();
|
|
conversation.SpeakerName = _speakerName;
|
|
conversation.name = _conversationName;
|
|
conversation.DialogueNodes = createdNodes;
|
|
|
|
string conversationPath = $"{folderPath}/{_conversationName}.asset";
|
|
AssetDatabase.CreateAsset(conversation, conversationPath);
|
|
|
|
AssetDatabase.SaveAssets();
|
|
AssetDatabase.Refresh();
|
|
|
|
EditorGUIUtility.PingObject(conversation);
|
|
Selection.activeObject = conversation;
|
|
|
|
EditorUtility.DisplayDialog("Success", $"Conversation '{_conversationName}' created with {_nodes.Count} nodes!\n\nPath: {folderPath}", "OK");
|
|
}
|
|
}
|
|
#endif
|
|
}
|