Linking the octo localization system to the dialogue system

This commit is contained in:
2026-02-17 19:06:37 +01:00
parent 79da2e5722
commit ec89813b1b
62 changed files with 832 additions and 120 deletions
@@ -1,4 +1,5 @@
using UnityEngine;
using OctoCore.Localization;
namespace Unheimlich.Data.Dialogues
{
@@ -6,13 +7,8 @@ namespace Unheimlich.Data.Dialogues
public class DialogueChoiceData
{
[Header("Choice Content")]
[TextArea(2, 5)]
[Tooltip("Text displayed on the choice button")]
public string ChoiceText;
[Header("Localization")]
[Tooltip("Localization key for the choice text")]
public string ChoiceTextLocalizationKey;
[Tooltip("Localized text displayed on the choice button")]
public OctoLocalizedTextData ChoiceText;
[Header("Flow")]
[Tooltip("Direct reference to the next DialogueNode")]
@@ -21,10 +17,18 @@ namespace Unheimlich.Data.Dialogues
[Header("Conditions (Optional)")]
[Tooltip("Required condition to display this choice (optional, for future use)")]
public string RequiredCondition;
public string GetLocalizedChoiceText()
{
if (ChoiceText != null)
return OctoLocalization.GetLocalizedText(ChoiceText);
return string.Empty;
}
public bool IsValid()
{
return !string.IsNullOrEmpty(ChoiceText) && NextNode != null;
return ChoiceText != null && NextNode != null;
}
}
}
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using UnityEngine;
using OctoCore.Localization;
namespace Unheimlich.Data.Dialogues
{
@@ -7,8 +8,8 @@ namespace Unheimlich.Data.Dialogues
public class DialogueConversation : ScriptableObject
{
[Header("Speaker Information")]
[Tooltip("Name of the speaker (common to all texts in this conversation)")]
public string SpeakerName;
[Tooltip("Localized name of the speaker (common to all texts in this conversation)")]
public OctoLocalizedTextData SpeakerName;
[Header("Display Mode")]
[Tooltip("Choose how to display dialogue text: World (3D space) or Screen (Canvas UI)")]
@@ -17,10 +18,14 @@ namespace Unheimlich.Data.Dialogues
[Header("Dialogue Flow")]
[Tooltip("Ordered list of dialogue nodes for this conversation")]
public List<DialogueNode> DialogueNodes = new List<DialogueNode>();
[Header("Localization")]
[Tooltip("Localization ID for speaker name (optional)")]
public string SpeakerNameLocalizationKey;
public string GetLocalizedSpeakerName()
{
if (SpeakerName != null)
return OctoLocalization.GetLocalizedText(SpeakerName);
return string.Empty;
}
public DialogueNode GetFirstNode()
{
@@ -1,55 +1,19 @@
using UnityEngine;
using System.Collections.Generic;
using OctoCore.Localization;
#if UNITY_EDITOR
using UnityEditor;
using System.IO;
#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
// Helper methods kept for compatibility but now create localized text data
}
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
{
@@ -108,7 +72,7 @@ public class DialogueCreatorWindow : EditorWindow
{
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("Dialogue Creator", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("Quickly create a complete conversation with all nodes pre-linked!", MessageType.Info);
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);
@@ -290,6 +254,21 @@ public class DialogueCreatorWindow : EditorWindow
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))
@@ -311,20 +290,27 @@ public class DialogueCreatorWindow : EditorWindow
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.DialogueText = template.NodeText;
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];
@@ -334,9 +320,16 @@ public class DialogueCreatorWindow : EditorWindow
{
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 = choiceTemplate.Text,
ChoiceText = choiceTextData,
NextNode = createdNodes[targetIndex]
};
currentNode.Choices.Add(choice);
@@ -345,8 +338,9 @@ public class DialogueCreatorWindow : EditorWindow
EditorUtility.SetDirty(currentNode);
}
// Create conversation
DialogueConversation conversation = CreateInstance<DialogueConversation>();
conversation.SpeakerName = _speakerName;
conversation.SpeakerName = speakerNameData;
conversation.name = _conversationName;
conversation.DialogueNodes = createdNodes;
@@ -359,7 +353,14 @@ public class DialogueCreatorWindow : EditorWindow
EditorGUIUtility.PingObject(conversation);
Selection.activeObject = conversation;
EditorUtility.DisplayDialog("Success", $"Conversation '{_conversationName}' created with {_nodes.Count} nodes!\n\nPath: {folderPath}", "OK");
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
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using UnityEngine;
using OctoCore.Localization;
namespace Unheimlich.Data.Dialogues
{
@@ -7,13 +8,8 @@ namespace Unheimlich.Data.Dialogues
public class DialogueNode : ScriptableObject
{
[Header("Content")]
[TextArea(3, 10)]
[Tooltip("Text displayed for this dialogue node")]
public string DialogueText;
[Header("Localization")]
[Tooltip("Localization key for the dialogue text")]
public string DialogueTextLocalizationKey;
[Tooltip("Localized text displayed for this dialogue node")]
public OctoLocalizedTextData DialogueText;
[Header("Choices")]
[Tooltip("List of available choices for this dialogue. If empty, this is an end node")]
@@ -22,6 +18,14 @@ namespace Unheimlich.Data.Dialogues
[Header("End Node Settings")]
[Tooltip("For end nodes (no choices), delay in seconds before automatic close. 0 = requires player interaction to close")]
public float AutoCloseDelay = 3f;
public string GetLocalizedDialogueText()
{
if (DialogueText != null)
return OctoLocalization.GetLocalizedText(DialogueText);
return string.Empty;
}
public bool IsEndNode()
{
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c07f3bd13606c2c4aa5fbbc6c175809e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,65 @@
using UnityEngine;
using OctoCore.Localization;
using UnityEngine.InputSystem;
namespace Unheimlich.Tools
{
/// <summary>
/// Simple debug script to switch language at runtime.
/// Attach to any GameObject and press keys to change language.
/// </summary>
public class LanguageSwitcher : MonoBehaviour
{
[Header("Info")]
[Tooltip("Current language")]
[SerializeField] private OctoLanguages _currentLanguage;
void Start()
{
if (OctoCore.OctoCore.Instance != null && OctoCore.OctoCore.Instance.Localization != null)
{
_currentLanguage = OctoCore.OctoCore.Instance.Localization.WrittenLanguage;
UnityEngine.Debug.Log($"[LanguageSwitcher] Current language: {_currentLanguage}");
UnityEngine.Debug.Log($"[LanguageSwitcher] Press F1 for English, F2 for French");
}
}
void Update()
{
if (OctoCore.OctoCore.Instance == null || OctoCore.OctoCore.Instance.Localization == null)
return;
if (Keyboard.current == null)
return;
if (Keyboard.current.f1Key.wasPressedThisFrame)
{
SetLanguage(OctoLanguages.English);
}
else if (Keyboard.current.f2Key.wasPressedThisFrame)
{
SetLanguage(OctoLanguages.French);
}
}
private void SetLanguage(OctoLanguages language)
{
OctoCore.OctoCore.Instance.Localization.SetLanguageWritten(language);
_currentLanguage = language;
UnityEngine.Debug.Log($"<color=green>[LanguageSwitcher]</color> Language changed to: <color=yellow>{language}</color>");
}
void OnGUI()
{
// Display current language in top-left corner
if (OctoCore.OctoCore.Instance != null && OctoCore.OctoCore.Instance.Localization != null)
{
GUI.color = Color.yellow;
GUI.Label(new Rect(10, 10, 300, 30), $"Language: {_currentLanguage}");
GUI.Label(new Rect(10, 30, 300, 30), $"[F1] EN | [F2] FR");
GUI.color = Color.white;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 86180434dad93e344b4a5229575d4757
@@ -6,7 +6,6 @@ using Unheimlich.Data.Dialogues;
public class DialogueConversationEditor : Editor
{
private SerializedProperty _speakerNameProp;
private SerializedProperty _speakerNameLocalizationKeyProp;
private SerializedProperty _displayModeProp;
private SerializedProperty _dialogueNodesProp;
@@ -37,7 +36,6 @@ public class DialogueConversationEditor : Editor
private void OnEnable()
{
_speakerNameProp = serializedObject.FindProperty("SpeakerName");
_speakerNameLocalizationKeyProp = serializedObject.FindProperty("SpeakerNameLocalizationKey");
_displayModeProp = serializedObject.FindProperty("DisplayMode");
_dialogueNodesProp = serializedObject.FindProperty("DialogueNodes");
}
@@ -50,7 +48,6 @@ public class DialogueConversationEditor : Editor
EditorGUILayout.LabelField("Conversation Setup", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_speakerNameProp);
EditorGUILayout.PropertyField(_speakerNameLocalizationKeyProp, new GUIContent("Localization Key (Optional)"));
EditorGUILayout.Space(5);
@@ -6,14 +6,12 @@ using Unheimlich.Data.Dialogues;
public class DialogueNodeEditor : Editor
{
private SerializedProperty _dialogueTextProp;
private SerializedProperty _dialogueTextLocalizationKeyProp;
private SerializedProperty _choicesProp;
private SerializedProperty _autoCloseDelayProp;
private void OnEnable()
{
_dialogueTextProp = serializedObject.FindProperty("DialogueText");
_dialogueTextLocalizationKeyProp = serializedObject.FindProperty("DialogueTextLocalizationKey");
_choicesProp = serializedObject.FindProperty("Choices");
_autoCloseDelayProp = serializedObject.FindProperty("AutoCloseDelay");
}
@@ -26,7 +24,6 @@ public class DialogueNodeEditor : Editor
EditorGUILayout.LabelField("Node Content", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_dialogueTextProp);
EditorGUILayout.PropertyField(_dialogueTextLocalizationKeyProp, new GUIContent("Localization Key (Optional)"));
EditorGUILayout.Space(10);
@@ -89,7 +86,7 @@ public class DialogueNodeEditor : Editor
DialogueChoiceData newChoice = new DialogueChoiceData
{
ChoiceText = "New choice",
ChoiceText = null, // Will need to assign OctoLocalizedTextData manually
NextNode = null
};
@@ -0,0 +1,127 @@
using UnityEngine;
using UnityEditor;
using OctoCore.Localization;
using System.IO;
namespace Unheimlich.Editor
{
[CustomPropertyDrawer(typeof(OctoLocalizedTextData))]
public class LocalizedTextFieldDrawer : PropertyDrawer
{
private const float LINE_HEIGHT = 20f;
private const float SPACING = 2f;
private const float LABEL_WIDTH = 80f;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
// Height for label + EN field + FR field
return LINE_HEIGHT * 3 + SPACING * 2;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
OctoLocalizedTextData textData = property.objectReferenceValue as OctoLocalizedTextData;
// If null, create new OctoLocalizedTextData
if (textData == null)
{
textData = CreateLocalizedTextData(property);
property.objectReferenceValue = textData;
property.serializedObject.ApplyModifiedProperties();
}
// Draw label
Rect labelRect = new Rect(position.x, position.y, position.width, LINE_HEIGHT);
EditorGUI.LabelField(labelRect, label, EditorStyles.boldLabel);
// Draw EN field
Rect enRect = new Rect(position.x, position.y + LINE_HEIGHT + SPACING, position.width, LINE_HEIGHT);
DrawLanguageField(enRect, textData, OctoLanguages.English, "EN");
// Draw FR field
Rect frRect = new Rect(position.x, position.y + (LINE_HEIGHT + SPACING) * 2, position.width, LINE_HEIGHT);
DrawLanguageField(frRect, textData, OctoLanguages.French, "FR");
EditorGUI.EndProperty();
}
private void DrawLanguageField(Rect position, OctoLocalizedTextData textData, OctoLanguages language, string labelText)
{
if (textData == null) return;
// Ensure dictionary is initialized
if (textData.Languages.Dictionary == null || textData.Languages.Dictionary.Count == 0)
{
#if UNITY_EDITOR
textData.Init();
#endif
}
// Get language key
string langKey = language.ToString();
// Get current value
string currentValue = "";
if (textData.Languages.Dictionary.TryGetValue(langKey, out string value))
{
currentValue = value;
}
// Draw label
Rect labelRect = new Rect(position.x, position.y, LABEL_WIDTH, position.height);
EditorGUI.LabelField(labelRect, labelText + ":");
// Draw text field
Rect fieldRect = new Rect(position.x + LABEL_WIDTH, position.y, position.width - LABEL_WIDTH, position.height);
EditorGUI.BeginChangeCheck();
string newValue = EditorGUI.TextField(fieldRect, currentValue);
if (EditorGUI.EndChangeCheck())
{
// Update via indexer (Dictionary is read-only property but supports indexer)
textData.Languages.Dictionary[langKey] = newValue;
EditorUtility.SetDirty(textData);
AssetDatabase.SaveAssets();
}
}
private OctoLocalizedTextData CreateLocalizedTextData(SerializedProperty property)
{
// Get the object being edited
Object targetObject = property.serializedObject.targetObject;
string assetPath = AssetDatabase.GetAssetPath(targetObject);
if (string.IsNullOrEmpty(assetPath))
return null;
string directory = Path.GetDirectoryName(assetPath);
string fileName = Path.GetFileNameWithoutExtension(assetPath);
string propertyPath = property.propertyPath.Replace(".", "_");
// Create unique name
string textDataName = $"text_{fileName}_{propertyPath}";
string textDataPath = $"{directory}/{textDataName}.asset";
// Check if already exists
OctoLocalizedTextData existingData = AssetDatabase.LoadAssetAtPath<OctoLocalizedTextData>(textDataPath);
if (existingData != null)
return existingData;
// Create new OctoLocalizedTextData
OctoLocalizedTextData newTextData = ScriptableObject.CreateInstance<OctoLocalizedTextData>();
// Initialize via Init() method
newTextData.Init();
// Save asset
AssetDatabase.CreateAsset(newTextData, textDataPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
return newTextData;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6566d44f45287f943b77aa92aaefeeb0
@@ -49,7 +49,6 @@ namespace Unheimlich.Handlers.DetectiveBoard
DetectiveBoardManager.Instance.UnlockClue(_clueToUnlock);
_hasUnlocked = true;
Debug.Log($"[ClueUnlockOnInteraction] Unlocked clue: {_clueToUnlock.Title} via interaction");
}
if (_inspectableItem != null)
@@ -22,7 +22,7 @@ namespace Unheimlich.Interactions.FloatingWords
public string GetChoiceText()
{
return DialogueData?.ChoiceText ?? string.Empty;
return DialogueData?.GetLocalizedChoiceText() ?? string.Empty;
}
public DialogueNode GetNextNode()
@@ -2,12 +2,17 @@ using UnityEngine;
using System.Collections.Generic;
using Unheimlich.Data.Dialogues;
using Unheimlich.Gameplay.Player;
using OctoCore.Localization;
namespace Unheimlich.Interactions.FloatingWords
{
public class ObjectDescriptionTrigger : MonoBehaviour
{
[Header("Description")]
[Tooltip("Localized description text (recommended)")]
[SerializeField] private OctoLocalizedTextData _descriptionTextLocalized;
[Tooltip("Fallback description text if localized version is not set")]
[SerializeField] private string _descriptionText = "Mysterious Object";
[Header("Settings")]
@@ -99,12 +104,16 @@ namespace Unheimlich.Interactions.FloatingWords
ObjectDescriptionTriggerReference triggerRef = _wordsRotator.AddComponent<ObjectDescriptionTriggerReference>();
triggerRef.Trigger = this;
string descText = _descriptionTextLocalized != null
? OctoLocalization.GetLocalizedText(_descriptionTextLocalized)
: _descriptionText;
List<DialogueChoiceData> dummyChoices = new List<DialogueChoiceData>();
for (int i = 0; i < _settings.WordCount; i++)
{
dummyChoices.Add(new DialogueChoiceData
{
ChoiceText = _descriptionText,
ChoiceText = _descriptionTextLocalized,
NextNode = null
});
}
@@ -158,8 +158,8 @@ namespace Unheimlich.Managers
}
Transform anchor = _currentChoiceAnchor;
string speakerName = _currentConversation.SpeakerName;
string dialogueText = _currentNode.DialogueText;
string speakerName = _currentConversation.GetLocalizedSpeakerName();
string dialogueText = _currentNode.GetLocalizedDialogueText();
DialogueDisplayMode displayMode = _currentConversation.DisplayMode;
_activeDialogueUI.ShowDialogueBox(speakerName, dialogueText, displayMode);
@@ -42,27 +42,21 @@ namespace Unheimlich.Managers
private void Start()
{
Debug.Log("[InputManager] Start() called");
if (OctoCore.OctoCore.Instance == null)
{
Debug.LogError("[InputManager] OctoCore.Instance is NULL!");
return;
}
Debug.Log("[InputManager] OctoCore.Instance exists");
if (OctoCore.OctoCore.Instance.Inputs == null)
{
Debug.LogError("[InputManager] OctoCore.Instance.Inputs is NULL!");
return;
}
Debug.Log("[InputManager] OctoCore.Instance.Inputs exists");
// Ensure we're subscribed
SubscribeAllInputs();
Debug.Log("[InputManager] Initialized and subscribed to OctoCore inputs.");
}
private void OnDestroy()
@@ -114,7 +114,6 @@ namespace Unheimlich.UI.DetectiveBoard
private void OnClueUnlocked(ClueData clueData)
{
Debug.Log($"[DetectiveBoardCanvas] OnClueUnlocked received for: {clueData.ClueID}");
AddClueNode(clueData);
}