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,8 @@
fileFormatVersion: 2
guid: 1be27ce87e441a545bd6f3fdd3c18b61
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: be1a654d39699564cb1dc1342aa93085
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
using UnityEngine;
namespace OctoCore.Dialogue.Data
{
/// <summary>
/// Dialogue data containing a sequence of entries.
/// </summary>
[CreateAssetMenu(fileName = "dialogue_", menuName = "OctoCore/Dialogue/Dialogue Data", order = 1)]
public class OctoDialogueData : ScriptableObject
{
/// <summary>
/// Sequence of dialogue entries.
/// First entry starts automatically.
/// </summary>
[Tooltip("Sequence of dialogue entries")]
public OctoDialogueEntry[] Entries;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0c6fdb9b9b4581f4b912b64147fa3969
@@ -0,0 +1,25 @@
using UnityEngine;
using OctoCore.Localization;
namespace OctoCore.Dialogue.Data
{
/// <summary>
/// Speaker data for dialogue system.
/// Contains localized name and sprite.
/// </summary>
[CreateAssetMenu(fileName = "speaker_", menuName = "OctoCore/Dialogue/Speaker Data", order = 0)]
public class OctoSpeakerData : ScriptableObject
{
/// <summary>
/// Localized name of the speaker.
/// </summary>
[Tooltip("Localized name of the speaker")]
public OctoLocalizedTextData Name;
/// <summary>
/// Speaker portrait sprite.
/// </summary>
[Tooltip("Speaker portrait sprite")]
public Sprite Sprite;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a14c37f184ae07b4b8f641e7e2896ed7
@@ -0,0 +1,43 @@
using UnityEngine;
using OctoCore.Localization;
namespace OctoCore.Dialogue
{
/// <summary>
/// Dialogue choice with condition and next dialogue.
/// </summary>
[System.Serializable]
public class OctoDialogueChoice
{
/// <summary>
/// Localized text for this choice.
/// </summary>
[Tooltip("Localized text for this choice")]
public OctoLocalizedTextData Text;
/// <summary>
/// Condition to display this choice.
/// If null or condition is true, choice is available.
/// </summary>
[Tooltip("Condition to display this choice (null = always show)")]
public OctoDialogueCondition Condition;
/// <summary>
/// Next dialogue to load when this choice is selected.
/// If null, dialogue ends.
/// </summary>
[Tooltip("Next dialogue when selected (null = end dialogue)")]
public Data.OctoDialogueData NextDialogue;
/// <summary>
/// Check if this choice should be available.
/// </summary>
public bool IsAvailable()
{
if (Condition == null)
return true;
return Condition.Evaluate();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7b828f206b626c94a964cbb5e4aaddc4
@@ -0,0 +1,90 @@
using UnityEngine;
using OctoCore.Save.Data;
namespace OctoCore.Dialogue
{
/// <summary>
/// Condition for dialogue choices.
/// Supports OctoDataBool, OctoDataInt, and OctoDataFloat.
/// </summary>
[System.Serializable]
public class OctoDialogueCondition
{
/// <summary>
/// Source data to evaluate (OctoDataBool, OctoDataInt, or OctoDataFloat).
/// </summary>
[Tooltip("Source data to evaluate")]
public OctoData Source;
/// <summary>
/// Operation to perform.
/// </summary>
[Tooltip("Operation to perform")]
public OctoDialogueOperation Operation = OctoDialogueOperation.Equal;
/// <summary>
/// Value to compare against.
/// For bool: 0 = false, 1 = true
/// For int/float: direct comparison
/// </summary>
[Tooltip("Value to compare against (bool: 0=false, 1=true)")]
public float Value;
/// <summary>
/// Evaluate if the condition is met.
/// </summary>
public bool Evaluate()
{
if (Source == null)
return true; // No condition = always true
float sourceValue = GetSourceValue();
switch (Operation)
{
case OctoDialogueOperation.Equal:
return Mathf.Approximately(sourceValue, Value);
case OctoDialogueOperation.NotEqual:
return !Mathf.Approximately(sourceValue, Value);
case OctoDialogueOperation.GreaterThan:
return sourceValue > Value;
case OctoDialogueOperation.GreaterOrEqual:
return sourceValue >= Value;
case OctoDialogueOperation.LessThan:
return sourceValue < Value;
case OctoDialogueOperation.LessOrEqual:
return sourceValue <= Value;
default:
return true;
}
}
private float GetSourceValue()
{
if (!OctoCore.Instance)
return 0f;
if (Source is OctoDataBool dataBool)
{
return OctoCore.Instance.Save.Get(dataBool) ? 1f : 0f;
}
else if (Source is OctoDataInt dataInt)
{
return OctoCore.Instance.Save.Get(dataInt);
}
else if (Source is OctoDataFloat dataFloat)
{
return OctoCore.Instance.Save.Get(dataFloat);
}
Debug.LogWarning("[OctoDialogueCondition] Unsupported data type: " + Source.GetType().Name);
return 0f;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 553d5d510ff034f44b70a8a032565a4e
@@ -0,0 +1,31 @@
using UnityEngine;
using OctoCore.Localization;
namespace OctoCore.Dialogue
{
/// <summary>
/// Single dialogue entry with speaker, text, and choices.
/// </summary>
[System.Serializable]
public class OctoDialogueEntry
{
/// <summary>
/// Speaker for this entry.
/// </summary>
[Tooltip("Speaker for this entry")]
public Data.OctoSpeakerData Speaker;
/// <summary>
/// Localized dialogue text.
/// </summary>
[Tooltip("Localized dialogue text")]
public OctoLocalizedTextData Text;
/// <summary>
/// Available choices for this entry.
/// If empty, dialogue continues automatically or ends.
/// </summary>
[Tooltip("Available choices (empty = auto-continue or end)")]
public OctoDialogueChoice[] Choices;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ec95cc134f0fa214eb3cd8c0e9016347
@@ -0,0 +1,26 @@
namespace OctoCore.Dialogue
{
/// <summary>
/// Operations for dialogue conditions.
/// </summary>
public enum OctoDialogueOperation
{
/// <summary>Equal (bool, int, float)</summary>
Equal,
/// <summary>Not equal (bool, int, float)</summary>
NotEqual,
/// <summary>Greater than (int, float only)</summary>
GreaterThan,
/// <summary>Greater or equal (int, float only)</summary>
GreaterOrEqual,
/// <summary>Less than (int, float only)</summary>
LessThan,
/// <summary>Less or equal (int, float only)</summary>
LessOrEqual
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a4c04f460edc6d949933bd4237fb2e0a