Files
Ipseity-Demo/Packages/OctoCore/Runtime/Scripts/OctoSettings.cs
T
2026-02-09 23:53:14 +01:00

699 lines
24 KiB
C#

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using TMPro;
using UnityEngine;
using OctoCore.Inputs;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
namespace OctoCore
{
/// <summary>
/// Global settings for the OctoCore framework.
/// Configure save system, input mappings, localization, and UI settings.
/// </summary>
[CreateAssetMenu(fileName = "OctoSettings", menuName = "Octo/Settings Global", order = 0)]
public class OctoSettings : ScriptableObject
{
/// <summary>
/// Save system configuration.
/// </summary>
[System.Serializable]
public class SaveSettings
{
/// <summary>
/// All boolean save data keys in the project.
/// </summary>
public global::OctoCore.Save.Data.OctoDataBool[] Booleans;
/// <summary>
/// All integer save data keys in the project.
/// </summary>
public global::OctoCore.Save.Data.OctoDataInt[] Integers;
/// <summary>
/// All float save data keys in the project.
/// </summary>
public global::OctoCore.Save.Data.OctoDataFloat[] Floats;
/// <summary>
/// All string save data keys in the project.
/// </summary>
public global::OctoCore.Save.Data.OctoDataString[] Strings;
/// <summary>
/// All vector save data keys in the project.
/// </summary>
public global::OctoCore.Save.Data.OctoDataVector[] Vectors;
}
/// <summary>
/// Input mapping configuration.
/// </summary>
[System.Serializable]
public class MappingSettings
{
[System.Serializable]
public class Icon
{
public bool UseName = true;
public Sprite Sprite;
}
[System.Serializable]
public class ActionData
{
[HideInInspector]
public string Name;
[HideInInspector]
public string MapName;
public OctoActions Action;
#if ENABLE_INPUT_SYSTEM
[Tooltip("Reference to the InputAction from the asset")]
public InputActionReference Reference;
#endif
public Icon Icon;
public ActionData(OctoActions action, string name, string mapName)
{
Action = action;
Name = name;
MapName = mapName;
}
}
/// <summary>
/// Reference Input Action Asset.
/// </summary>
#if ENABLE_INPUT_SYSTEM
[Tooltip("Assign your Input Action Asset here")]
public InputActionAsset InputActionAsset;
#endif
/// <summary>
/// Generated Actions.
/// </summary>
[Tooltip("Actions (generated automatically)")]
public List<ActionData> Actions = new List<ActionData>();
/// <summary>
/// Get action map name for a specific action.
/// </summary>
public string GetActionMapName(OctoActions action)
{
foreach (var actionData in Actions)
{
if (actionData.Action == action)
return actionData.MapName;
}
return null;
}
#if ENABLE_INPUT_SYSTEM && UNITY_EDITOR
/// <summary>
/// Get InputActionReference for a specific action.
/// </summary>
public InputActionReference GetReference(OctoActions action)
{
foreach (var actionData in Actions)
{
if (actionData.Action == action)
return actionData.Reference;
}
return null;
}
#endif
}
/// <summary>
/// Language configuration.
/// </summary>
[System.Serializable]
public class LanguagesSettings
{
/// <summary>
/// Font override settings for specific languages.
/// </summary>
[System.Serializable]
public class Override
{
[HideInInspector] public string Name;
/// <summary>
/// The language this override applies to.
/// </summary>
public Localization.OctoLanguages Language;
/// <summary>
/// Custom font for this language.
/// </summary>
public TMP_FontAsset Font;
/// <summary>
/// Size multiplier for this language's text.
/// </summary>
public float SizeMultiplier = 1;
}
/// <summary>
/// Default language to use when no preference is set.
/// </summary>
public Localization.OctoLanguages Default;
/// <summary>
/// Save data key for written language preference.
/// </summary>
public global::OctoCore.Save.Data.OctoDataString SaveWritten;
/// <summary>
/// Save data key for audio language preference.
/// </summary>
public global::OctoCore.Save.Data.OctoDataString SaveAudio;
/// <summary>
/// Font overrides for specific languages.
/// </summary>
public Override[] Overrides;
}
/// <summary>
/// Configuration for UI scaling.
/// </summary>
[System.Serializable]
public class UiSettings
{
[System.Serializable]
public class Sprites
{
public Sprite DefaultButton;
}
/// <summary>
/// Reference screen size for UI scaling calculations.
/// </summary>
public Vector2Int ReferenceSize = new Vector2Int(1920, 1080);
/// <summary>
/// Reference screen size for UI scaling calculations.
/// </summary>
public Sprites IconsBackground;
}
/// <summary>
/// Scene management configuration.
/// </summary>
[System.Serializable]
public class ScenesSettings
{
/// <summary>
/// Fade transition settings.
/// </summary>
[System.Serializable]
public class FadeTransitionSettings
{
/// <summary>
/// Fade in duration in seconds.
/// </summary>
[Tooltip("Fade in duration")]
public float FadeInDuration = 0.5f;
/// <summary>
/// Fade out duration in seconds.
/// </summary>
[Tooltip("Fade out duration")]
public float FadeOutDuration = 0.5f;
/// <summary>
/// Fade color.
/// </summary>
[Tooltip("Fade color")]
public Color FadeColor = Color.black;
}
/// <summary>
/// All scene data in the project.
/// </summary>
[Tooltip("All scene data in the project")]
public Navigation.Data.OctoSceneData[] SceneDatas;
/// <summary>
/// Use Addressables for scene loading.
/// Requires OCTO_ADDRESSABLES scripting define symbol.
/// </summary>
[Tooltip("Use Addressables for scene loading (requires OCTO_ADDRESSABLES define)")]
public bool UseAddressables = false;
/// <summary>
/// Global fade transition settings.
/// </summary>
[Tooltip("Global fade transition settings")]
public FadeTransitionSettings FadeSettings = new FadeTransitionSettings();
}
/// <summary>
/// Save system settings.
/// </summary>
[OctoReadOnly] public SaveSettings Save;
/// <summary>
/// Input mapping settings.
/// </summary>
public MappingSettings Mapping;
/// <summary>
/// Language system settings.
/// </summary>
public LanguagesSettings Languages;
/// <summary>
/// Scene management settings.
/// </summary>
public ScenesSettings Scenes;
/// <summary>
/// UI settings.
/// </summary>
public UiSettings Ui;
#region DATAS SET
#if UNITY_EDITOR
void OnValidate()
{
Init();
}
/// <summary>
/// Initializes and validates settings in the Unity Editor.
/// </summary>
public void Init()
{
// Update language override names
foreach (LanguagesSettings.Override langOverride in Languages.Overrides)
{
langOverride.Name = langOverride.Language.ToString();
}
}
/// <summary>
/// Scans the project for all OctoData assets and populates the Save settings.
/// </summary>
public void GetAllDatas()
{
List<global::OctoCore.Save.Data.OctoDataBool> booleans = new();
List<global::OctoCore.Save.Data.OctoDataInt> integers = new();
List<global::OctoCore.Save.Data.OctoDataFloat> floats = new();
List<global::OctoCore.Save.Data.OctoDataString> strings = new();
List<global::OctoCore.Save.Data.OctoDataVector> vectors = new();
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(global::OctoCore.Save.Data.OctoData).Name);
for (int i = 0; i < guids.Length; i++)
{
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
global::OctoCore.Save.Data.OctoData data = (global::OctoCore.Save.Data.OctoData)UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath, typeof(global::OctoCore.Save.Data.OctoData));
if (data)
{
data.Id = data.name;
UnityEditor.EditorUtility.SetDirty(data);
if (data is global::OctoCore.Save.Data.OctoDataBool)
booleans.Add((global::OctoCore.Save.Data.OctoDataBool)data);
if (data is global::OctoCore.Save.Data.OctoDataInt)
integers.Add((global::OctoCore.Save.Data.OctoDataInt)data);
if (data is global::OctoCore.Save.Data.OctoDataFloat)
floats.Add((global::OctoCore.Save.Data.OctoDataFloat)data);
if (data is global::OctoCore.Save.Data.OctoDataString)
strings.Add((global::OctoCore.Save.Data.OctoDataString)data);
if (data is global::OctoCore.Save.Data.OctoDataVector)
vectors.Add((global::OctoCore.Save.Data.OctoDataVector)data);
}
}
Save.Booleans = booleans.ToArray();
Save.Integers = integers.ToArray();
Save.Floats = floats.ToArray();
Save.Strings = strings.ToArray();
Save.Vectors = vectors.ToArray();
UnityEditor.EditorUtility.SetDirty(this);
UnityEditor.AssetDatabase.SaveAssets();
}
/// <summary>
/// Scans the project for all OctoSceneData assets and populates the Scenes settings.
/// </summary>
public void GetAllSceneDatas()
{
List<Navigation.Data.OctoSceneData> sceneDatas = new();
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(Navigation.Data.OctoSceneData).Name);
for (int i = 0; i < guids.Length; i++)
{
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
Navigation.Data.OctoSceneData sceneData = (Navigation.Data.OctoSceneData)UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath, typeof(Navigation.Data.OctoSceneData));
if (sceneData)
{
sceneData.Id = sceneData.name;
UnityEditor.EditorUtility.SetDirty(sceneData);
sceneDatas.Add(sceneData);
}
}
Scenes.SceneDatas = sceneDatas.ToArray();
UnityEditor.EditorUtility.SetDirty(this);
UnityEditor.AssetDatabase.SaveAssets();
Debug.Log("[OctoSettings] Found " + sceneDatas.Count + " Scene Datas");
}
#endif
#if ENABLE_INPUT_SYSTEM && UNITY_EDITOR
private static string GetActionsFilePath()
{
// Find OctoInputs.cs to locate the package
string[] guids = AssetDatabase.FindAssets("OctoInputs t:script");
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.Contains("/Inputs/OctoInputs.cs"))
{
// Found it! Same directory
string directory = Path.GetDirectoryName(path);
return Path.Combine(directory, "OctoActions.cs").Replace("\\", "/");
}
}
// Fallback
return "Assets/OctoCore/Generated/OctoActions.cs";
}
private const string ActionsBackupFile = "Assets/OctoCore/Editor/OctoActions.json";
public void GenerateInputs()
{
Debug.Log("[OctoCore] GenerateInputs() called");
if (Mapping.InputActionAsset == null)
{
EditorUtility.DisplayDialog("Error", "Please assign an Input Actions asset first!", "OK");
return;
}
string actionsFile = GetActionsFilePath();
Debug.Log($"[OctoCore] Input Action Asset: {Mapping.InputActionAsset.name}");
Debug.Log($"[OctoCore] Target file: {actionsFile}");
// Ensure directory exists
string directory = Path.GetDirectoryName(actionsFile);
if (!Directory.Exists(directory))
{
Debug.Log($"[OctoCore] Creating directory: {directory}");
Directory.CreateDirectory(directory);
}
Debug.Log("[OctoCore] Calling GenerateActionsEnum...");
// Generate enum file
GenerateActionsEnum(Mapping.InputActionAsset, actionsFile);
Debug.Log("[OctoCore] GenerateActionsEnum completed");
// Refresh to compile new enums
AssetDatabase.Refresh();
EditorUtility.DisplayProgressBar("Generating Inputs", "Waiting for compilation...", 0.5f);
// Wait a bit longer for compilation with multiple delays
EditorApplication.delayCall += () =>
{
EditorApplication.delayCall += () =>
{
EditorApplication.delayCall += () =>
{
EditorUtility.ClearProgressBar();
// Populate mappings after compilation
PopulateMappings();
// Mark data as dirty to save changes
EditorUtility.SetDirty(this);
EditorUtility.DisplayDialog(
"Success",
$"Input enums generated successfully!\n\n" +
$"Generated in: {actionsFile}\n" +
$"Backup saved in: {ActionsBackupFile}\n" +
$"Total Actions: {CountTotalActions(Mapping.InputActionAsset)}\n\n" +
$"Note: Backup is automatically restored after package updates!",
"OK"
);
};
};
};
}
public void PopulateMappings()
{
Mapping.Actions.Clear();
foreach (var map in Mapping.InputActionAsset.actionMaps)
{
string mapName = CleanName(map.name);
foreach (var action in map.actions)
{
string actionName = CleanName(action.name);
string fullName = $"{mapName}_{actionName}";
if (!System.Enum.TryParse<OctoActions>(fullName, out var actionEnum))
{
Debug.LogWarning($"[OctoSettings] Could not parse action: {fullName}");
continue;
}
// Create action data with map name
var actionData = new MappingSettings.ActionData(actionEnum, action.name, map.name);
// Find InputActionReference in the asset's sub-assets
actionData.Reference = FindInputActionReferenceInAsset(Mapping.InputActionAsset, action);
if (actionData.Reference == null)
{
Debug.LogWarning($"[OctoSettings] No InputActionReference found for {map.name}/{action.name}");
}
Mapping.Actions.Add(actionData);
}
}
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
}
private InputActionReference FindInputActionReferenceInAsset(InputActionAsset asset, InputAction action)
{
// Get the asset path
string assetPath = AssetDatabase.GetAssetPath(asset);
// Load all sub-assets (InputActionReference are stored as sub-assets)
var allAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
foreach (var subAsset in allAssets)
{
if (subAsset is InputActionReference reference)
{
if (reference.action == action)
{
return reference;
}
}
}
return null;
}
public int CountTotalActions(InputActionAsset asset)
{
int count = 0;
foreach (var map in asset.actionMaps)
{
count += map.actions.Count;
}
return count;
}
private void GenerateActionsEnum(InputActionAsset inputActions, string outputFile)
{
Debug.Log($"[OctoCore] GenerateActionsEnum started for: {inputActions.name}");
StringBuilder sb = new StringBuilder();
List<ActionBackupData> backupData = new List<ActionBackupData>();
sb.AppendLine("// ═══════════════════════════════════════════════════════════════════════════");
sb.AppendLine("// AUTO-GENERATED FILE - DO NOT EDIT MANUALLY");
sb.AppendLine("// ═══════════════════════════════════════════════════════════════════════════");
sb.AppendLine("// Generated by OctoSettings");
sb.AppendLine("// Backup saved in: Assets/OctoCore/Generated/OctoActions.json");
sb.AppendLine("// To regenerate: OctoSettings > Mapping > Click 'Generate Inputs'");
sb.AppendLine($"// Generated from: {inputActions.name}");
sb.AppendLine($"// Generated on: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}");
sb.AppendLine("// ═══════════════════════════════════════════════════════════════════════════");
sb.AppendLine();
sb.AppendLine("namespace OctoCore.Inputs");
sb.AppendLine("{");
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// Actions from Unity Input Actions.");
sb.AppendLine($" /// Generated from: {inputActions.name}");
sb.AppendLine(" /// Format: ActionMap_ActionName");
sb.AppendLine(" /// Values are stable hashes to prevent serialization issues when regenerating.");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" public enum OctoActions");
sb.AppendLine(" {");
bool first = true;
foreach (var map in inputActions.actionMaps)
{
string mapName = CleanName(map.name);
foreach (var action in map.actions)
{
string actionName = CleanName(action.name);
string fullName = $"{mapName}_{actionName}";
// Generate stable hash from name
int hash = GetStableHash(fullName);
// Save to backup data
backupData.Add(new ActionBackupData
{
Name = fullName,
Hash = hash,
MapName = map.name,
ActionName = action.name
});
if (!first)
sb.AppendLine(",");
sb.AppendLine($" /// <summary>{map.name} > {action.name}</summary>");
sb.Append($" {fullName} = {hash}");
first = false;
}
}
sb.AppendLine();
sb.AppendLine(" }");
sb.AppendLine("}");
// Write enum file
File.WriteAllText(outputFile, sb.ToString());
Debug.Log($"[OctoCore] OctoActions.cs written to: {outputFile}");
Debug.Log($"[OctoCore] File contains {backupData.Count} actions");
// Force Unity to see the change
AssetDatabase.ImportAsset(outputFile, ImportAssetOptions.ForceUpdate);
// Save backup JSON
SaveActionsBackup(backupData);
}
[System.Serializable]
private class ActionBackupData
{
public string Name;
public int Hash;
public string MapName;
public string ActionName;
}
[System.Serializable]
private class ActionsBackup
{
public string GeneratedFrom;
public string GeneratedOn;
public List<ActionBackupData> Actions;
}
private void SaveActionsBackup(List<ActionBackupData> actions)
{
// Ensure directory exists
string directory = Path.GetDirectoryName(ActionsBackupFile);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
var backup = new ActionsBackup
{
GeneratedFrom = Mapping.InputActionAsset.name,
GeneratedOn = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
Actions = actions
};
string json = JsonUtility.ToJson(backup, true);
File.WriteAllText(ActionsBackupFile, json);
Debug.Log($"[OctoCore] Actions backup saved to: {ActionsBackupFile}");
}
/// <summary>
/// Generate a stable hash from a string (same string always gives same hash).
/// </summary>
private int GetStableHash(string str)
{
unchecked
{
int hash1 = 5381;
int hash2 = hash1;
for (int i = 0; i < str.Length && str[i] != '\0'; i += 2)
{
hash1 = ((hash1 << 5) + hash1) ^ str[i];
if (i == str.Length - 1 || str[i + 1] == '\0')
break;
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
}
return hash1 + (hash2 * 1566083941);
}
}
private string CleanName(string name)
{
name = name.Replace(" ", "_").Replace("-", "_");
name = new string(name.Where(c => char.IsLetterOrDigit(c) || c == '_').ToArray());
if (char.IsDigit(name[0]))
name = "_" + name;
return name;
}
#endif
#endregion
}
}