Files
Ipseity-Demo/Assets/Unheimlich_POC/Scripts/Datas/Dialogues/DialogueConversationBuilder.cs
T

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
}