Files
Ipseity-Demo/Assets/Unheimlich_POC/Scripts/Features/Dialogue/Data/DialogueConversationBuilder.cs
T
2026-03-09 17:55:23 +01:00

368 lines
13 KiB
C#

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<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!\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<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 OctoLocalizedTextData CreateLocalizedTextData(string text, string basePath, string suffix)
{
OctoLocalizedTextData textData = ScriptableObject.CreateInstance<OctoLocalizedTextData>();
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<DialogueNode> createdNodes = new List<DialogueNode>();
// Create nodes
for (int i = 0; i < _nodes.Count; i++)
{
NodeTemplate template = _nodes[i];
DialogueNode node = CreateInstance<DialogueNode>();
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<DialogueConversation>();
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
}