OctoCore 0.0.1

This commit is contained in:
Alexandre Coirier
2026-02-09 23:53:14 +01:00
parent 85bf743cba
commit 80d0faf8a7
235 changed files with 13656 additions and 32 deletions
@@ -0,0 +1,65 @@
using UnityEngine;
using UnityEngine.Events;
namespace OctoCore.Dialogue.Components
{
/// <summary>
/// Component to trigger dialogue events.
/// Listens to dialogue start/end and invokes UnityEvents.
/// </summary>
public class OctoDialogueEvent : MonoBehaviour
{
[SerializeField, Tooltip("Dialogue data to listen for")]
private Data.OctoDialogueData _dialogue;
[SerializeField, Tooltip("Called when the specified dialogue starts")]
private UnityEvent _onDialogueStarted;
[SerializeField, Tooltip("Called when the specified dialogue ends")]
private UnityEvent _onDialogueEnded;
void OnEnable()
{
if (OctoDialogueManager.Instance != null)
{
// Subscribe to manager events
OctoDialogueManager.Instance.GetComponent<OctoDialogueManager>()
._events.OnDialogueStarted.AddListener(OnDialogueStarted);
OctoDialogueManager.Instance.GetComponent<OctoDialogueManager>()
._events.OnDialogueEnded.AddListener(OnDialogueEnded);
}
}
void OnDisable()
{
if (OctoDialogueManager.Instance != null)
{
// Unsubscribe from manager events
OctoDialogueManager.Instance.GetComponent<OctoDialogueManager>()
._events.OnDialogueStarted.RemoveListener(OnDialogueStarted);
OctoDialogueManager.Instance.GetComponent<OctoDialogueManager>()
._events.OnDialogueEnded.RemoveListener(OnDialogueEnded);
}
}
private void OnDialogueStarted()
{
if (OctoDialogueManager.Instance == null)
return;
var currentDialogue = OctoDialogueManager.Instance.GetCurrentDialogue();
if (currentDialogue == _dialogue)
{
_onDialogueStarted?.Invoke();
}
}
private void OnDialogueEnded()
{
// Dialogue already ended, so we check if it was our dialogue
// Note: This might not work perfectly if dialogue changes rapidly
// A better implementation would track the last active dialogue
_onDialogueEnded?.Invoke();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a7c573c7064e10f4ba4855e8f448ee81
@@ -0,0 +1,291 @@
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using OctoCore.Localization;
using OctoCore.Components;
namespace OctoCore.Dialogue.Components
{
/// <summary>
/// Dialogue manager component.
/// Singleton that handles dialogue display and flow.
/// Uses UI Text components if assigned, otherwise falls back to GUI.
/// </summary>
public class OctoDialogueManager : MonoBehaviour
{
/// <summary>
/// Singleton instance.
/// </summary>
public static OctoDialogueManager Instance { get; private set; }
/// <summary>
/// References to UI components.
/// </summary>
[System.Serializable]
public class References
{
[Tooltip("Legacy UI Text component (optional)")]
public Text Text;
[Tooltip("TextMeshPro component (optional)")]
public TextMeshPro TextMeshPro;
[Tooltip("TextMeshPro UGUI component (optional)")]
public TextMeshProUGUI TextMeshProUGUI;
}
/// <summary>
/// Dialogue events.
/// </summary>
[System.Serializable]
public class Events
{
[Tooltip("Called when a dialogue starts")]
public UnityEvent OnDialogueStarted;
[Tooltip("Called when a new entry starts displaying")]
public UnityEvent OnEntryStarted;
[Tooltip("Called when a choice is selected")]
public UnityEvent OnChoiceSelected;
[Tooltip("Called when a dialogue ends")]
public UnityEvent OnDialogueEnded;
}
[SerializeField] private References _references;
[SerializeField] public Events _events;
private Data.OctoDialogueData _currentDialogue;
private int _currentEntryIndex;
private List<OctoDialogueChoice> _availableChoices = new List<OctoDialogueChoice>();
private bool _isActive;
void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
/// <summary>
/// Start a new dialogue.
/// Automatically displays the first entry.
/// </summary>
public void StartDialogue(Data.OctoDialogueData dialogue)
{
if (dialogue == null || dialogue.Entries == null || dialogue.Entries.Length == 0)
{
Debug.LogWarning("[OctoDialogueManager] Cannot start dialogue: null or empty");
return;
}
_currentDialogue = dialogue;
_currentEntryIndex = 0;
_isActive = true;
_events.OnDialogueStarted?.Invoke();
if (OctoPlayer.Instance) OctoPlayer.Instance.Pause(true); // TMP
DisplayCurrentEntry();
}
/// <summary>
/// Display the current dialogue entry.
/// </summary>
private void DisplayCurrentEntry()
{
if (_currentDialogue == null || _currentEntryIndex >= _currentDialogue.Entries.Length)
{
EndDialogue();
return;
}
OctoDialogueEntry entry = _currentDialogue.Entries[_currentEntryIndex];
_events.OnEntryStarted?.Invoke();
// Display text
string text = OctoLocalization.GetLocalizedText(entry.Text);
SetText(text);
// Build available choices
_availableChoices.Clear();
if (entry.Choices != null)
{
foreach (var choice in entry.Choices)
{
if (choice.IsAvailable())
{
_availableChoices.Add(choice);
}
}
}
}
/// <summary>
/// Select a choice by index.
/// </summary>
public void SelectChoice(int choiceIndex)
{
if (!_isActive || choiceIndex < 0 || choiceIndex >= _availableChoices.Count)
return;
OctoDialogueChoice choice = _availableChoices[choiceIndex];
_events.OnChoiceSelected?.Invoke();
if (choice.NextDialogue != null)
{
StartDialogue(choice.NextDialogue);
}
else
{
EndDialogue();
}
}
/// <summary>
/// Continue to next entry (when no choices available).
/// </summary>
public void Continue()
{
if (!_isActive)
return;
if (_availableChoices.Count == 0)
{
_currentEntryIndex++;
DisplayCurrentEntry();
}
}
/// <summary>
/// End the current dialogue.
/// </summary>
private void EndDialogue()
{
_isActive = false;
_currentDialogue = null;
_currentEntryIndex = 0;
_availableChoices.Clear();
if (OctoPlayer.Instance) OctoPlayer.Instance.Pause(false); // TMP
_events.OnDialogueEnded?.Invoke();
}
/// <summary>
/// Set text using available UI component or GUI fallback.
/// </summary>
private void SetText(string text)
{
if (_references.Text != null)
{
_references.Text.text = text;
}
else if (_references.TextMeshPro != null)
{
_references.TextMeshPro.text = text;
}
else if (_references.TextMeshProUGUI != null)
{
_references.TextMeshProUGUI.text = text;
}
// GUI fallback handled in OnGUI
}
void OnGUI()
{
// Only use GUI if no text component is assigned
if (_references.Text != null || _references.TextMeshPro != null || _references.TextMeshProUGUI != null)
return;
if (!_isActive || _currentDialogue == null)
return;
if (_currentEntryIndex >= _currentDialogue.Entries.Length)
return;
OctoDialogueEntry entry = _currentDialogue.Entries[_currentEntryIndex];
GUILayout.BeginArea(new Rect(50, Screen.height - 200, Screen.width - 100, 150));
GUILayout.Box("", GUILayout.Width(Screen.width - 100), GUILayout.Height(150));
GUILayout.BeginArea(new Rect(10, 10, Screen.width - 120, 130));
// Speaker name
if (entry.Speaker != null && entry.Speaker.Name != null)
{
string speakerName = OctoLocalization.GetLocalizedText(entry.Speaker.Name);
GUILayout.Label(speakerName, GUI.skin.box);
}
// Dialogue text
string text = OctoLocalization.GetLocalizedText(entry.Text);
GUILayout.Label(text, GUILayout.ExpandHeight(true));
// Choices
if (_availableChoices.Count > 0)
{
GUILayout.Space(10);
for (int i = 0; i < _availableChoices.Count; i++)
{
string choiceText = OctoLocalization.GetLocalizedText(_availableChoices[i].Text);
if (GUILayout.Button(choiceText))
{
SelectChoice(i);
}
}
}
else
{
GUILayout.Space(10);
if (GUILayout.Button("→"))
{
Continue();
}
}
GUILayout.EndArea();
GUILayout.EndArea();
}
/// <summary>
/// Get current dialogue data.
/// </summary>
public Data.OctoDialogueData GetCurrentDialogue()
{
return _currentDialogue;
}
/// <summary>
/// Get available choices for current entry.
/// </summary>
public List<OctoDialogueChoice> GetAvailableChoices()
{
return new List<OctoDialogueChoice>(_availableChoices);
}
/// <summary>
/// Check if dialogue is currently active.
/// </summary>
public bool IsActive()
{
return _isActive;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5a41b5f79f61ad94c9479c28b4397de4