using UnityEngine; using System.Collections.Generic; using OctoCore.Localization; #if UNITY_EDITOR using UnityEditor; using System.IO; #endif namespace Unheimlich.Features.Dialogue.Data { public static class DialogueConversationBuilder { // Helper methods kept for compatibility but now create localized text data } #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 _nodes = new List(); private Vector2 _scrollPosition; [System.Serializable] private class NodeTemplate { public string NodeText = "Dialogue text"; public List Choices = new List { 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("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 { 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!\nTexts will be created as localized assets (you can edit FR/EN later in Inspector).", 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 { 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 OctoLocalizedTextData CreateLocalizedTextData(string text, string basePath, string suffix) { OctoLocalizedTextData textData = ScriptableObject.CreateInstance(); textData.Init(); // Set English text by default textData.Languages.Dictionary["English"] = text; textData.Languages.Dictionary["French"] = ""; // Empty, user will fill later string textDataPath = AssetDatabase.GenerateUniqueAssetPath($"{basePath}/text_{suffix}.asset"); AssetDatabase.CreateAsset(textData, textDataPath); return textData; } 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); } // Create speaker name localized text OctoLocalizedTextData speakerNameData = CreateLocalizedTextData(_speakerName, folderPath, "SpeakerName"); List createdNodes = new List(); // Create nodes for (int i = 0; i < _nodes.Count; i++) { NodeTemplate template = _nodes[i]; DialogueNode node = CreateInstance(); node.name = $"Node_{i + 1}"; // Create localized text for dialogue node.DialogueText = CreateLocalizedTextData(template.NodeText, folderPath, $"Node{i + 1}_Text"); string nodePath = $"{folderPath}/{node.name}.asset"; AssetDatabase.CreateAsset(node, nodePath); createdNodes.Add(node); } // Link choices 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); // Create localized text for choice OctoLocalizedTextData choiceTextData = CreateLocalizedTextData( choiceTemplate.Text, folderPath, $"Node{i + 1}_Choice{template.Choices.IndexOf(choiceTemplate)}" ); DialogueChoiceData choice = new DialogueChoiceData { ChoiceText = choiceTextData, NextNode = createdNodes[targetIndex] }; currentNode.Choices.Add(choice); } EditorUtility.SetDirty(currentNode); } // Create conversation DialogueConversation conversation = CreateInstance(); conversation.SpeakerName = speakerNameData; 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\n" + $"Path: {folderPath}\n\n" + $"📝 Note: All texts are in English by default.\n" + $"Open each DialogueNode in the Inspector to add French translations!", "OK" ); } } #endif }