diff --git a/Assets/AddressableAssetsData/AddressableAssetSettings.asset b/Assets/AddressableAssetsData/AddressableAssetSettings.asset index 690bbee6..d626f909 100644 --- a/Assets/AddressableAssetsData/AddressableAssetSettings.asset +++ b/Assets/AddressableAssetsData/AddressableAssetSettings.asset @@ -15,7 +15,7 @@ MonoBehaviour: m_DefaultGroup: 6f3207429a65b3e4b83935ac19791077 m_currentHash: serializedVersion: 2 - Hash: c0cf00979528ae95d3583c572e4eb343 + Hash: 00000000000000000000000000000000 m_OptimizeCatalogSize: 0 m_BuildRemoteCatalog: 0 m_CatalogRequestsTimeout: 0 @@ -61,6 +61,7 @@ MonoBehaviour: m_GroupAssets: - {fileID: 11400000, guid: efe7e1728e73e9546ac5dfee2eff524f, type: 2} - {fileID: 11400000, guid: 6e4927e7e19eef34b93dc2baa9e9e8e2, type: 2} + - {fileID: 11400000, guid: e25c7672a65b5974bb354fcfb2a8400c, type: 2} - {fileID: 11400000, guid: 7fcc03e584505ed4381983b6ebb1179d, type: 2} m_BuildSettings: m_LogResourceManagerExceptions: 1 diff --git a/Assets/AddressableAssetsData/AssetGroups/Settings.asset b/Assets/AddressableAssetsData/AssetGroups/Settings.asset new file mode 100644 index 00000000..43153491 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Settings.asset @@ -0,0 +1,36 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3} + m_Name: Settings + m_EditorClassIdentifier: + m_GroupName: Settings + m_GUID: c62e6f02418e19949bca4cccdd5fa02e + m_SerializeEntries: + - m_GUID: 35bfcff00faa72c4eb272a9e8288f965 + m_Address: Settings/PlayerFollowerSettings + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 8f5195fb013895049a19488fd4d8f2a1 + m_Address: Settings/InteractionSettings + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: a9569848f604a6540827d4d4bb0a35c2 + m_Address: Settings/MinigameSettings + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 + m_ReadOnly: 0 + m_Settings: {fileID: 11400000, guid: 11da9bb90d9dd5848b4f7629415a6937, type: 2} + m_SchemaSet: + m_Schemas: [] diff --git a/Assets/AddressableAssetsData/AssetGroups/Settings.asset.meta b/Assets/AddressableAssetsData/AssetGroups/Settings.asset.meta new file mode 100644 index 00000000..bd8e86af --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Settings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e25c7672a65b5974bb354fcfb2a8400c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/ItemPrefabEditor.cs b/Assets/Editor/ItemPrefabEditor.cs index 9ea59a25..470b1e36 100644 --- a/Assets/Editor/ItemPrefabEditor.cs +++ b/Assets/Editor/ItemPrefabEditor.cs @@ -18,7 +18,7 @@ namespace Editor private enum ItemType { None, Pickup, ItemSlot } private ItemType _itemType = ItemType.None; - [MenuItem("Tools/Item Prefab Editor")] + [MenuItem("AppleHills/Item Prefab Editor")] public static void ShowWindow() { var window = GetWindow("Item Prefab Editor"); diff --git a/Assets/Editor/PrefabCreatorWindow.cs b/Assets/Editor/PrefabCreatorWindow.cs index 5c8cf87e..b7114b90 100644 --- a/Assets/Editor/PrefabCreatorWindow.cs +++ b/Assets/Editor/PrefabCreatorWindow.cs @@ -23,7 +23,7 @@ namespace Editor private bool _createNext = false; - [MenuItem("Tools/Prefab Creator")] + [MenuItem("AppleHills/Item Prefab Creator")] public static void ShowWindow() { var window = GetWindow("Prefab Creator"); diff --git a/Assets/Editor/PuzzleChainEditorWindow.cs b/Assets/Editor/PuzzleChainEditorWindow.cs index 708dc14b..27c140ac 100644 --- a/Assets/Editor/PuzzleChainEditorWindow.cs +++ b/Assets/Editor/PuzzleChainEditorWindow.cs @@ -9,7 +9,7 @@ public class PuzzleChainEditorWindow : EditorWindow private Vector2 scrollPos; private const int INDENT_SIZE = 24; - [MenuItem("Tools/Puzzle Chain Editor")] + [MenuItem("AppleHills/Puzzle Chain Editor")] public static void ShowWindow() { var window = GetWindow("Puzzle Chain Editor"); diff --git a/Assets/Editor/SceneObjectLocatorWindow.cs b/Assets/Editor/SceneObjectLocatorWindow.cs index cac362a1..591616cc 100644 --- a/Assets/Editor/SceneObjectLocatorWindow.cs +++ b/Assets/Editor/SceneObjectLocatorWindow.cs @@ -15,7 +15,7 @@ public class SceneObjectLocatorWindow : EditorWindow private List pickupInfos = new List(); private Vector2 scrollPos; - [MenuItem("Tools/Scene Object Locator")] + [MenuItem("AppleHills/Scene Object Locator")] public static void ShowWindow() { var window = GetWindow("Scene Object Locator"); diff --git a/Assets/Editor/SettingsEditorWindow.cs b/Assets/Editor/SettingsEditorWindow.cs new file mode 100644 index 00000000..915cbb28 --- /dev/null +++ b/Assets/Editor/SettingsEditorWindow.cs @@ -0,0 +1,190 @@ + using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using System.Linq; +using System.IO; + +namespace AppleHills.Core.Settings.Editor +{ + public class SettingsEditorWindow : EditorWindow + { + private Vector2 scrollPosition; + private List allSettings = new List(); + private string[] tabNames = new string[] { "Player & Follower", "Interaction & Items", "Minigames" }; + private int selectedTab = 0; + private Dictionary serializedSettingsObjects = new Dictionary(); + private GUIStyle headerStyle; + + [MenuItem("AppleHills/Settings Editor")] + public static void ShowWindow() + { + GetWindow("Game Settings"); + } + + private void OnEnable() + { + LoadAllSettings(); + } + + private void LoadAllSettings() + { + allSettings.Clear(); + serializedSettingsObjects.Clear(); + + // Find all settings assets + string[] guids = AssetDatabase.FindAssets("t:BaseSettings"); + foreach (string guid in guids) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + BaseSettings settings = AssetDatabase.LoadAssetAtPath(path); + if (settings != null) + { + allSettings.Add(settings); + serializedSettingsObjects[settings.GetType().Name] = new SerializedObject(settings); + } + } + + // If any settings are missing, create them + CreateSettingsIfMissing("PlayerFollowerSettings"); + CreateSettingsIfMissing("InteractionSettings"); + CreateSettingsIfMissing("MinigameSettings"); + } + + private void CreateSettingsIfMissing(string fileName) where T : BaseSettings + { + if (!allSettings.Any(s => s is T)) + { + // Check if the asset already exists + string[] guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}"); + if (guids.Length == 0) + { + // Create the settings folder if it doesn't exist + if (!AssetDatabase.IsValidFolder("Assets/Settings")) + { + AssetDatabase.CreateFolder("Assets", "Settings"); + } + + // Create new settings asset + T settings = CreateInstance(); + string path = $"Assets/Settings/{fileName}.asset"; + AssetDatabase.CreateAsset(settings, path); + AssetDatabase.SaveAssets(); + + allSettings.Add(settings); + serializedSettingsObjects[typeof(T).Name] = new SerializedObject(settings); + Debug.Log($"Created missing settings asset: {path}"); + } + else + { + // Load existing asset + string path = AssetDatabase.GUIDToAssetPath(guids[0]); + T settings = AssetDatabase.LoadAssetAtPath(path); + allSettings.Add(settings); + serializedSettingsObjects[typeof(T).Name] = new SerializedObject(settings); + } + } + } + + private void OnGUI() + { + if (headerStyle == null) + { + headerStyle = new GUIStyle(EditorStyles.boldLabel); + headerStyle.fontSize = 14; + headerStyle.margin = new RectOffset(0, 0, 10, 10); + } + + EditorGUILayout.Space(10); + EditorGUILayout.LabelField("Apple Hills Game Settings", headerStyle); + EditorGUILayout.HelpBox("Use this window to modify game settings. Changes are saved automatically.", MessageType.Info); + + EditorGUILayout.Space(10); + selectedTab = GUILayout.Toolbar(selectedTab, tabNames); + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + + switch (selectedTab) + { + case 0: // Player & Follower + DrawSettingsEditor(); + break; + case 1: // Interaction & Items + DrawSettingsEditor(); + break; + case 2: // Minigames + DrawSettingsEditor(); + break; + } + + EditorGUILayout.EndScrollView(); + + EditorGUILayout.Space(10); + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Refresh", GUILayout.Width(100))) + { + LoadAllSettings(); + } + + if (GUILayout.Button("Save All", GUILayout.Width(100))) + { + foreach (var serializedObj in serializedSettingsObjects.Values) + { + serializedObj.ApplyModifiedProperties(); + EditorUtility.SetDirty(serializedObj.targetObject); + } + AssetDatabase.SaveAssets(); + Debug.Log("All settings saved!"); + } + + EditorGUILayout.EndHorizontal(); + } + + private void DrawSettingsEditor() where T : BaseSettings + { + BaseSettings settings = allSettings.Find(s => s is T); + if (settings == null) + { + EditorGUILayout.HelpBox($"No {typeof(T).Name} found. Click Refresh to create one.", MessageType.Warning); + return; + } + + SerializedObject serializedObj = serializedSettingsObjects[typeof(T).Name]; + serializedObj.Update(); + + EditorGUILayout.Space(10); + + // Draw all properties + SerializedProperty property = serializedObj.GetIterator(); + bool enterChildren = true; + while (property.NextVisible(enterChildren)) + { + enterChildren = false; + + // Skip the script field + if (property.name == "m_Script") continue; + + EditorGUILayout.PropertyField(property, true); + } + + // Apply changes + if (serializedObj.ApplyModifiedProperties()) + { + EditorUtility.SetDirty(settings); + } + } + + // Helper method to highlight important fields + private void DrawHighlightedProperty(SerializedProperty property, string tooltip = null) + { + GUI.backgroundColor = new Color(1f, 1f, 0.8f); + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + GUI.backgroundColor = Color.white; + + EditorGUILayout.PropertyField(property, new GUIContent(property.displayName, tooltip)); + + EditorGUILayout.EndVertical(); + } + } +} diff --git a/Assets/Editor/SettingsEditorWindow.cs.meta b/Assets/Editor/SettingsEditorWindow.cs.meta new file mode 100644 index 00000000..81c689ca --- /dev/null +++ b/Assets/Editor/SettingsEditorWindow.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bfb9e77c746e41a2903603a39df3d424 +timeCreated: 1758619952 \ No newline at end of file diff --git a/Assets/Editor/SettingsMigrationWindow.cs b/Assets/Editor/SettingsMigrationWindow.cs new file mode 100644 index 00000000..e87124bf --- /dev/null +++ b/Assets/Editor/SettingsMigrationWindow.cs @@ -0,0 +1,307 @@ +using UnityEngine; +using UnityEditor; +using UnityEditor.AddressableAssets; +using UnityEditor.AddressableAssets.Settings; +using System.IO; +using AppleHills.Core.Settings; + +namespace AppleHills.Editor +{ + public class SettingsMigrationWindow : EditorWindow + { + private GameSettings legacySettings; + private bool settingsFolderExists; + private bool addressablesInstalled; + private AddressableAssetSettings addressableSettings; + private AddressableAssetGroup settingsGroup; + private GUIStyle headerStyle; + private GUIStyle successStyle; + private Vector2 scrollPosition; + private bool migrationCompleted = false; + + [MenuItem("AppleHills/Migrate Legacy Settings")] + public static void ShowWindow() + { + var window = GetWindow("Settings Migration"); + window.minSize = new Vector2(450, 400); + } + + private void OnEnable() + { + // Check if Settings folder exists + settingsFolderExists = AssetDatabase.IsValidFolder("Assets/Settings"); + + // Check if Addressables package is installed + addressablesInstalled = AddressableAssetSettingsDefaultObject.SettingsExists; + + if (addressablesInstalled) + { + addressableSettings = AddressableAssetSettingsDefaultObject.Settings; + } + } + + private void OnGUI() + { + if (headerStyle == null) + { + headerStyle = new GUIStyle(EditorStyles.boldLabel); + headerStyle.fontSize = 14; + headerStyle.margin = new RectOffset(0, 0, 10, 10); + + successStyle = new GUIStyle(EditorStyles.label); + successStyle.normal.textColor = Color.green; + successStyle.fontSize = 12; + } + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + + EditorGUILayout.LabelField("Migrate Legacy Settings", headerStyle); + EditorGUILayout.Space(10); + + EditorGUILayout.HelpBox( + "This tool will migrate your legacy GameSettings to the new modular settings system. " + + "It will create new settings assets in the Assets/Settings folder, mark them as Addressables, " + + "and copy values from your legacy settings.", + MessageType.Info); + + EditorGUILayout.Space(10); + + // Prerequisites section + EditorGUILayout.LabelField("Prerequisites", EditorStyles.boldLabel); + + // Check Addressables + GUI.enabled = false; + EditorGUILayout.Toggle("Addressables Package Installed", addressablesInstalled); + GUI.enabled = true; + + if (!addressablesInstalled) + { + EditorGUILayout.HelpBox( + "The Addressables package is not installed. Please install it via Window > Package Manager.", + MessageType.Error); + } + + EditorGUILayout.Space(5); + + // Legacy settings field + legacySettings = EditorGUILayout.ObjectField("Legacy GameSettings", legacySettings, typeof(GameSettings), false) as GameSettings; + + if (legacySettings == null) + { + EditorGUILayout.HelpBox( + "Please assign your legacy GameSettings asset to migrate from.", + MessageType.Warning); + } + + EditorGUILayout.Space(15); + + // Migration button + GUI.enabled = legacySettings != null && addressablesInstalled; + + if (GUILayout.Button("Migrate Settings", GUILayout.Height(30))) + { + MigrateSettings(); + } + + GUI.enabled = true; + + // Success message + if (migrationCompleted) + { + EditorGUILayout.Space(10); + EditorGUILayout.LabelField("Migration completed successfully!", successStyle); + EditorGUILayout.HelpBox( + "The legacy settings have been migrated to the new system. " + + "You can now access these settings through the AppleHills > Settings Editor menu.", + MessageType.Info); + } + + EditorGUILayout.EndScrollView(); + } + + private void MigrateSettings() + { + // Create Settings folder if it doesn't exist + if (!settingsFolderExists) + { + AssetDatabase.CreateFolder("Assets", "Settings"); + settingsFolderExists = true; + } + + // Setup Addressables group for settings + SetupAddressablesGroup(); + + // Create and populate the new settings assets + CreatePlayerFollowerSettings(); + CreateInteractionSettings(); + CreateMinigameSettings(); + + // Save all assets + AssetDatabase.SaveAssets(); + + migrationCompleted = true; + } + + private void SetupAddressablesGroup() + { + // Find or create a settings group + settingsGroup = addressableSettings.FindGroup("Settings"); + + if (settingsGroup == null) + { + settingsGroup = addressableSettings.CreateGroup("Settings", false, false, true, null); + } + } + + private void AddAssetToAddressables(string assetPath, string address) + { + // Create entry in addressables + var guid = AssetDatabase.AssetPathToGUID(assetPath); + var entry = addressableSettings.CreateOrMoveEntry(guid, settingsGroup); + + // Set the address + entry.address = address; + + Debug.Log($"Added {assetPath} to Addressables with address {address}"); + } + + private void CreatePlayerFollowerSettings() + { + // Create the settings asset + var settings = ScriptableObject.CreateInstance(); + + // Copy values from legacy settings + if (legacySettings != null) + { + // Player settings + var playerSettings = typeof(GameSettings).GetField("moveSpeed"); + if (playerSettings != null) settings.GetType().GetField("moveSpeed", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.moveSpeed); + + var stopDistanceField = typeof(GameSettings).GetField("stopDistance"); + if (stopDistanceField != null) settings.GetType().GetField("stopDistance", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.stopDistance); + + var useRigidbodyField = typeof(GameSettings).GetField("useRigidbody"); + if (useRigidbodyField != null) settings.GetType().GetField("useRigidbody", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.useRigidbody); + + var defaultHoldMovementModeField = typeof(GameSettings).GetField("defaultHoldMovementMode"); + if (defaultHoldMovementModeField != null) settings.GetType().GetField("defaultHoldMovementMode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.defaultHoldMovementMode); + + // Follower settings + var followDistanceField = typeof(GameSettings).GetField("followDistance"); + if (followDistanceField != null) settings.GetType().GetField("followDistance", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.followDistance); + + var manualMoveSmoothField = typeof(GameSettings).GetField("manualMoveSmooth"); + if (manualMoveSmoothField != null) settings.GetType().GetField("manualMoveSmooth", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.manualMoveSmooth); + + var thresholdFarField = typeof(GameSettings).GetField("thresholdFar"); + if (thresholdFarField != null) settings.GetType().GetField("thresholdFar", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.thresholdFar); + + var thresholdNearField = typeof(GameSettings).GetField("thresholdNear"); + if (thresholdNearField != null) settings.GetType().GetField("thresholdNear", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.thresholdNear); + + var stopThresholdField = typeof(GameSettings).GetField("stopThreshold"); + if (stopThresholdField != null) settings.GetType().GetField("stopThreshold", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.stopThreshold); + + // Backend settings + var followUpdateIntervalField = typeof(GameSettings).GetField("followUpdateInterval"); + if (followUpdateIntervalField != null) settings.GetType().GetField("followUpdateInterval", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.followUpdateInterval); + + var followerSpeedMultiplierField = typeof(GameSettings).GetField("followerSpeedMultiplier"); + if (followerSpeedMultiplierField != null) settings.GetType().GetField("followerSpeedMultiplier", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.followerSpeedMultiplier); + + var heldIconDisplayHeightField = typeof(GameSettings).GetField("heldIconDisplayHeight"); + if (heldIconDisplayHeightField != null) settings.GetType().GetField("heldIconDisplayHeight", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.heldIconDisplayHeight); + } + + // Save the asset + string assetPath = "Assets/Settings/PlayerFollowerSettings.asset"; + AssetDatabase.CreateAsset(settings, assetPath); + + // Add to addressables + AddAssetToAddressables(assetPath, "Settings/PlayerFollowerSettings"); + + Debug.Log("Created PlayerFollowerSettings asset"); + } + + private void CreateInteractionSettings() + { + // Create the settings asset + var settings = ScriptableObject.CreateInstance(); + + // Copy values from legacy settings + if (legacySettings != null) + { + // Interaction settings + var playerStopDistanceField = typeof(GameSettings).GetField("playerStopDistance"); + if (playerStopDistanceField != null) settings.GetType().GetField("playerStopDistance", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.playerStopDistance); + + var playerStopDistanceDirectInteractionField = typeof(GameSettings).GetField("playerStopDistanceDirectInteraction"); + if (playerStopDistanceDirectInteractionField != null) settings.GetType().GetField("playerStopDistanceDirectInteraction", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.playerStopDistanceDirectInteraction); + + var followerPickupDelayField = typeof(GameSettings).GetField("followerPickupDelay"); + if (followerPickupDelayField != null) settings.GetType().GetField("followerPickupDelay", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.followerPickupDelay); + + var interactableLayerMaskField = typeof(GameSettings).GetField("interactableLayerMask"); + if (interactableLayerMaskField != null) settings.GetType().GetField("interactableLayerMask", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.interactableLayerMask); + + // Prefabs + var basePickupPrefabField = typeof(GameSettings).GetField("basePickupPrefab"); + if (basePickupPrefabField != null) settings.GetType().GetField("basePickupPrefab", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.basePickupPrefab); + + var levelSwitchMenuPrefabField = typeof(GameSettings).GetField("levelSwitchMenuPrefab"); + if (levelSwitchMenuPrefabField != null) settings.GetType().GetField("levelSwitchMenuPrefab", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.levelSwitchMenuPrefab); + + // Item configuration + var combinationRulesField = typeof(GameSettings).GetField("combinationRules"); + if (combinationRulesField != null) settings.GetType().GetField("combinationRules", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.combinationRules); + + var slotItemConfigsField = typeof(GameSettings).GetField("slotItemConfigs"); + if (slotItemConfigsField != null) settings.GetType().GetField("slotItemConfigs", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.slotItemConfigs); + } + + // Save the asset + string assetPath = "Assets/Settings/InteractionSettings.asset"; + AssetDatabase.CreateAsset(settings, assetPath); + + // Add to addressables + AddAssetToAddressables(assetPath, "Settings/InteractionSettings"); + + Debug.Log("Created InteractionSettings asset"); + } + + private void CreateMinigameSettings() + { + // Create the settings asset + var settings = ScriptableObject.CreateInstance(); + + // Copy values from legacy settings + if (legacySettings != null) + { + // Endless descender settings + var endlessDescenderLerpSpeedField = typeof(GameSettings).GetField("endlessDescenderLerpSpeed"); + if (endlessDescenderLerpSpeedField != null) settings.GetType().GetField("endlessDescenderLerpSpeed", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.endlessDescenderLerpSpeed); + + var endlessDescenderMaxOffsetField = typeof(GameSettings).GetField("endlessDescenderMaxOffset"); + if (endlessDescenderMaxOffsetField != null) settings.GetType().GetField("endlessDescenderMaxOffset", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.endlessDescenderMaxOffset); + + var endlessDescenderClampXMinField = typeof(GameSettings).GetField("endlessDescenderClampXMin"); + if (endlessDescenderClampXMinField != null) settings.GetType().GetField("endlessDescenderClampXMin", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.endlessDescenderClampXMin); + + var endlessDescenderClampXMaxField = typeof(GameSettings).GetField("endlessDescenderClampXMax"); + if (endlessDescenderClampXMaxField != null) settings.GetType().GetField("endlessDescenderClampXMax", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.endlessDescenderClampXMax); + + var endlessDescenderSpeedExponentField = typeof(GameSettings).GetField("endlessDescenderSpeedExponent"); + if (endlessDescenderSpeedExponentField != null) settings.GetType().GetField("endlessDescenderSpeedExponent", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(settings, legacySettings.endlessDescenderSpeedExponent); + } + + // Save the asset + string assetPath = "Assets/Settings/MinigameSettings.asset"; + AssetDatabase.CreateAsset(settings, assetPath); + + // Add to addressables + AddAssetToAddressables(assetPath, "Settings/MinigameSettings"); + + Debug.Log("Created MinigameSettings asset"); + } + } +} diff --git a/Assets/Editor/SettingsMigrationWindow.cs.meta b/Assets/Editor/SettingsMigrationWindow.cs.meta new file mode 100644 index 00000000..fa76f701 --- /dev/null +++ b/Assets/Editor/SettingsMigrationWindow.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b16caebbe9934df3a34f0b75879e65f2 +timeCreated: 1758630926 \ No newline at end of file diff --git a/Assets/Scripts/Core/GameManager.cs b/Assets/Scripts/Core/GameManager.cs index bc92da72..40ce344d 100644 --- a/Assets/Scripts/Core/GameManager.cs +++ b/Assets/Scripts/Core/GameManager.cs @@ -1,4 +1,6 @@ using UnityEngine; +using AppleHills.Core.Settings; +using System.Collections; /// /// Singleton manager for global game state and settings. Provides accessors for various gameplay parameters. @@ -29,53 +31,115 @@ public class GameManager : MonoBehaviour } } - [Header("Game Settings")] - public GameSettings gameSettings; + [Header("Legacy Game Settings (Deprecated)")] + [Tooltip("This is only used for migration to the new settings system")] + public GameSettings legacyGameSettings; + + [Header("Settings Status")] + [SerializeField] private bool _settingsLoaded = false; void Awake() { _instance = this; - if (gameSettings == null) - { - gameSettings = Resources.Load("DefaultSettings"); - if (gameSettings == null) - { - Debug.LogError("GameSettings asset not found in Resources!"); - } - } + + // Create settings provider if it doesn't exist + SettingsProvider.Instance.gameObject.name = "Settings Provider"; + + // Load all settings + StartCoroutine(InitializeSettings()); + // DontDestroyOnLoad(gameObject); } + private IEnumerator InitializeSettings() + { + // Initialize the settings provider + var initComplete = false; + SettingsProvider.Instance.PreloadAllSettings(() => initComplete = true); + + // Wait for settings to be loaded + while (!initComplete) + { + yield return null; + } + + // Register settings with service locator + ServiceLocator.Register( + SettingsProvider.Instance.GetSettings()); + + ServiceLocator.Register( + SettingsProvider.Instance.GetSettings()); + + ServiceLocator.Register( + SettingsProvider.Instance.GetSettings()); + + // Log success + Debug.Log("All settings loaded and registered with ServiceLocator"); + _settingsLoaded = true; + + // Migrate settings if needed + if (legacyGameSettings != null) + { + MigrateFromLegacySettings(); + } + } + + private void MigrateFromLegacySettings() + { + // This method can be used to copy settings from the old GameSettings to the new system + // Implement if needed for your production environment + Debug.Log("Legacy settings migration available but not implemented."); + } + void OnApplicationQuit() { _isQuitting = true; + ServiceLocator.Clear(); } - // Accessors for game settings - public float PlayerStopDistance => gameSettings != null ? gameSettings.playerStopDistance : 1.0f; - public float FollowerPickupDelay => gameSettings != null ? gameSettings.followerPickupDelay : 0.2f; - public float FollowDistance => gameSettings != null ? gameSettings.followDistance : 1.5f; - public float ManualMoveSmooth => gameSettings != null ? gameSettings.manualMoveSmooth : 8f; - public float ThresholdFar => gameSettings != null ? gameSettings.thresholdFar : 2.5f; - public float ThresholdNear => gameSettings != null ? gameSettings.thresholdNear : 0.5f; - public float StopThreshold => gameSettings != null ? gameSettings.stopThreshold : 0.5f; - public float MoveSpeed => gameSettings != null ? gameSettings.moveSpeed : 5f; - public float StopDistance => gameSettings != null ? gameSettings.stopDistance : 0.1f; - public bool UseRigidbody => gameSettings != null ? gameSettings.useRigidbody : true; - public float FollowUpdateInterval => gameSettings != null ? gameSettings.followUpdateInterval : 0.1f; - public float FollowerSpeedMultiplier => gameSettings != null ? gameSettings.followerSpeedMultiplier : 1.2f; - public float HeldIconDisplayHeight => gameSettings != null ? gameSettings.heldIconDisplayHeight : 2.0f; - public GameObject BasePickupPrefab => gameSettings != null ? gameSettings.basePickupPrefab : null; - public LayerMask InteractableLayerMask => gameSettings != null ? gameSettings.interactableLayerMask : -1; - public float PlayerStopDistanceDirectInteraction => gameSettings != null ? gameSettings.playerStopDistanceDirectInteraction : 2.0f; + // Helper method to get settings + private T GetSettings() where T : class + { + return ServiceLocator.Get(); + } + + // PLAYER & FOLLOWER SETTINGS + + // Player settings + public float MoveSpeed => GetSettings()?.MoveSpeed ?? 5f; + public float StopDistance => GetSettings()?.StopDistance ?? 0.1f; + public bool UseRigidbody => GetSettings()?.UseRigidbody ?? true; + public GameSettings.HoldMovementMode DefaultHoldMovementMode => + GetSettings()?.DefaultHoldMovementMode ?? GameSettings.HoldMovementMode.Pathfinding; + + // Follower settings + public float FollowDistance => GetSettings()?.FollowDistance ?? 1.5f; + public float ManualMoveSmooth => GetSettings()?.ManualMoveSmooth ?? 8f; + public float ThresholdFar => GetSettings()?.ThresholdFar ?? 2.5f; + public float ThresholdNear => GetSettings()?.ThresholdNear ?? 0.5f; + public float StopThreshold => GetSettings()?.StopThreshold ?? 0.1f; + public float FollowUpdateInterval => GetSettings()?.FollowUpdateInterval ?? 0.1f; + public float FollowerSpeedMultiplier => GetSettings()?.FollowerSpeedMultiplier ?? 1.2f; + public float HeldIconDisplayHeight => GetSettings()?.HeldIconDisplayHeight ?? 2.0f; + + // INTERACTION SETTINGS + + public float PlayerStopDistance => GetSettings()?.PlayerStopDistance ?? 6.0f; + public float PlayerStopDistanceDirectInteraction => GetSettings()?.PlayerStopDistanceDirectInteraction ?? 2.0f; + public float FollowerPickupDelay => GetSettings()?.FollowerPickupDelay ?? 0.2f; + public LayerMask InteractableLayerMask => GetSettings()?.InteractableLayerMask ?? -1; + public GameObject BasePickupPrefab => GetSettings()?.BasePickupPrefab; + public GameObject LevelSwitchMenuPrefab => GetSettings()?.LevelSwitchMenuPrefab; /// /// Returns the combination rule for two items, if any. /// public GameSettings.CombinationRule GetCombinationRule(PickupItemData item1, PickupItemData item2) { - if (gameSettings == null || gameSettings.combinationRules == null) return null; - foreach (var rule in gameSettings.combinationRules) + var settings = GetSettings(); + if (settings == null || settings.CombinationRules == null) return null; + + foreach (var rule in settings.CombinationRules) { if ((PickupItemData.AreEquivalent(rule.itemA, item1) && PickupItemData.AreEquivalent(rule.itemB, item2)) || (PickupItemData.AreEquivalent(rule.itemA, item2) && PickupItemData.AreEquivalent(rule.itemB, item1))) @@ -91,20 +155,23 @@ public class GameManager : MonoBehaviour /// public GameSettings.SlotItemConfig GetSlotItemConfig(PickupItemData slotItem) { - if (gameSettings == null || gameSettings.slotItemConfigs == null || slotItem == null) return null; - foreach (var config in gameSettings.slotItemConfigs) + var settings = GetSettings(); + if (settings == null || settings.SlotItemConfigs == null || slotItem == null) return null; + + foreach (var config in settings.SlotItemConfigs) { if (PickupItemData.AreEquivalent(slotItem, config.slotItem)) return config; } return null; } - // Add more accessors as needed - public float EndlessDescenderLerpSpeed => gameSettings != null ? gameSettings.endlessDescenderLerpSpeed : 12f; - public float EndlessDescenderMaxOffset => gameSettings != null ? gameSettings.endlessDescenderMaxOffset : 3f; - public float EndlessDescenderClampXMin => gameSettings != null ? gameSettings.endlessDescenderClampXMin : -5f; - public float EndlessDescenderClampXMax => gameSettings != null ? gameSettings.endlessDescenderClampXMax : 5f; - public float EndlessDescenderSpeedExponent => gameSettings != null ? gameSettings.endlessDescenderSpeedExponent : 2.5f; - public GameSettings.HoldMovementMode DefaultHoldMovementMode => gameSettings != null ? gameSettings.defaultHoldMovementMode : GameSettings.HoldMovementMode.Pathfinding; - public GameObject LevelSwitchMenuPrefab => gameSettings != null ? gameSettings.levelSwitchMenuPrefab : null; + + // MINIGAME SETTINGS + + // Endless Descender settings + public float EndlessDescenderLerpSpeed => GetSettings()?.EndlessDescenderLerpSpeed ?? 12f; + public float EndlessDescenderMaxOffset => GetSettings()?.EndlessDescenderMaxOffset ?? 3f; + public float EndlessDescenderClampXMin => GetSettings()?.EndlessDescenderClampXMin ?? -3.5f; + public float EndlessDescenderClampXMax => GetSettings()?.EndlessDescenderClampXMax ?? 3.5f; + public float EndlessDescenderSpeedExponent => GetSettings()?.EndlessDescenderSpeedExponent ?? 2.5f; } diff --git a/Assets/Scripts/Core/Settings.meta b/Assets/Scripts/Core/Settings.meta new file mode 100644 index 00000000..ecc9a2bf --- /dev/null +++ b/Assets/Scripts/Core/Settings.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e380783135324fcd925048783e01d691 +timeCreated: 1758619858 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/BaseSettings.cs b/Assets/Scripts/Core/Settings/BaseSettings.cs new file mode 100644 index 00000000..b7114843 --- /dev/null +++ b/Assets/Scripts/Core/Settings/BaseSettings.cs @@ -0,0 +1,16 @@ +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Base class for all settings ScriptableObjects. + /// Provides common functionality for all settings types. + /// + public abstract class BaseSettings : ScriptableObject + { + public virtual void OnValidate() + { + // Override in derived classes to add validation + } + } +} diff --git a/Assets/Scripts/Core/Settings/BaseSettings.cs.meta b/Assets/Scripts/Core/Settings/BaseSettings.cs.meta new file mode 100644 index 00000000..4c483ce6 --- /dev/null +++ b/Assets/Scripts/Core/Settings/BaseSettings.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cd33ef6036eb49358acbbd50dfd9bb13 +timeCreated: 1758619858 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/InteractionSettings.cs b/Assets/Scripts/Core/Settings/InteractionSettings.cs new file mode 100644 index 00000000..c3bb83b0 --- /dev/null +++ b/Assets/Scripts/Core/Settings/InteractionSettings.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Settings related to interactions and items + /// + [CreateAssetMenu(fileName = "InteractionSettings", menuName = "AppleHills/Settings/Interaction & Items", order = 2)] + public class InteractionSettings : BaseSettings, IInteractionSettings + { + [Header("Interactions")] + [SerializeField] private float playerStopDistance = 6.0f; + [SerializeField] private float playerStopDistanceDirectInteraction = 2.0f; + [SerializeField] private float followerPickupDelay = 0.2f; + + [Header("InputManager Settings")] + [Tooltip("Layer(s) to use for interactable objects.")] + [SerializeField] private LayerMask interactableLayerMask = -1; // Default to Everything + + [Header("Default Prefabs")] + [SerializeField] private GameObject basePickupPrefab; + [SerializeField] private GameObject levelSwitchMenuPrefab; + + [Header("Item Configuration")] + [SerializeField] private List combinationRules = new List(); + [SerializeField] private List slotItemConfigs = new List(); + + // IInteractionSettings implementation + public float PlayerStopDistance => playerStopDistance; + public float PlayerStopDistanceDirectInteraction => playerStopDistanceDirectInteraction; + public float FollowerPickupDelay => followerPickupDelay; + public LayerMask InteractableLayerMask => interactableLayerMask; + public GameObject BasePickupPrefab => basePickupPrefab; + public GameObject LevelSwitchMenuPrefab => levelSwitchMenuPrefab; + public List CombinationRules => combinationRules; + public List SlotItemConfigs => slotItemConfigs; + + public override void OnValidate() + { + base.OnValidate(); + // Validate values + playerStopDistance = Mathf.Max(0.1f, playerStopDistance); + playerStopDistanceDirectInteraction = Mathf.Max(0.1f, playerStopDistanceDirectInteraction); + followerPickupDelay = Mathf.Max(0f, followerPickupDelay); + } + } +} diff --git a/Assets/Scripts/Core/Settings/InteractionSettings.cs.meta b/Assets/Scripts/Core/Settings/InteractionSettings.cs.meta new file mode 100644 index 00000000..d4665565 --- /dev/null +++ b/Assets/Scripts/Core/Settings/InteractionSettings.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ac22b092dc6f4db5b3dad35172b6e4c4 +timeCreated: 1758619914 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/MinigameSettings.cs b/Assets/Scripts/Core/Settings/MinigameSettings.cs new file mode 100644 index 00000000..c37a7945 --- /dev/null +++ b/Assets/Scripts/Core/Settings/MinigameSettings.cs @@ -0,0 +1,49 @@ +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Settings related to minigames + /// + [CreateAssetMenu(fileName = "MinigameSettings", menuName = "AppleHills/Settings/Minigames", order = 3)] + public class MinigameSettings : BaseSettings, IMinigameSettings + { + [Header("Endless Descender Settings")] + [Tooltip("How quickly the character follows the finger horizontally (higher = more responsive)")] + [SerializeField] private float endlessDescenderLerpSpeed = 12f; + + [Tooltip("Maximum horizontal offset allowed between character and finger position")] + [SerializeField] private float endlessDescenderMaxOffset = 3f; + + [Tooltip("Minimum allowed X position for endless descender movement")] + [SerializeField] private float endlessDescenderClampXMin = -3.5f; + + [Tooltip("Maximum allowed X position for endless descender movement")] + [SerializeField] private float endlessDescenderClampXMax = 3.5f; + + [Tooltip("Exponent for speed drop-off curve (higher = sharper drop near target)")] + [SerializeField] private float endlessDescenderSpeedExponent = 2.5f; + + // IMinigameSettings implementation + public float EndlessDescenderLerpSpeed => endlessDescenderLerpSpeed; + public float EndlessDescenderMaxOffset => endlessDescenderMaxOffset; + public float EndlessDescenderClampXMin => endlessDescenderClampXMin; + public float EndlessDescenderClampXMax => endlessDescenderClampXMax; + public float EndlessDescenderSpeedExponent => endlessDescenderSpeedExponent; + + public override void OnValidate() + { + base.OnValidate(); + // Validate values + endlessDescenderLerpSpeed = Mathf.Max(0.1f, endlessDescenderLerpSpeed); + endlessDescenderMaxOffset = Mathf.Max(0.1f, endlessDescenderMaxOffset); + endlessDescenderSpeedExponent = Mathf.Max(0.1f, endlessDescenderSpeedExponent); + + // Ensure min is less than max + if (endlessDescenderClampXMin >= endlessDescenderClampXMax) + { + endlessDescenderClampXMin = endlessDescenderClampXMax - 0.1f; + } + } + } +} diff --git a/Assets/Scripts/Core/Settings/MinigameSettings.cs.meta b/Assets/Scripts/Core/Settings/MinigameSettings.cs.meta new file mode 100644 index 00000000..b83ebe5d --- /dev/null +++ b/Assets/Scripts/Core/Settings/MinigameSettings.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0ce4dba7a1c54e73b1b3d7131a1c0570 +timeCreated: 1758619927 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs b/Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs new file mode 100644 index 00000000..2ea57c05 --- /dev/null +++ b/Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs @@ -0,0 +1,53 @@ +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Settings related to player and follower behavior + /// + [CreateAssetMenu(fileName = "PlayerFollowerSettings", menuName = "AppleHills/Settings/Player & Follower", order = 1)] + public class PlayerFollowerSettings : BaseSettings, IPlayerFollowerSettings + { + [Header("Player Settings")] + [SerializeField] private float moveSpeed = 5f; + [SerializeField] private float stopDistance = 0.1f; + [SerializeField] private bool useRigidbody = true; + [SerializeField] private GameSettings.HoldMovementMode defaultHoldMovementMode = GameSettings.HoldMovementMode.Pathfinding; + + [Header("Follower Settings")] + [SerializeField] private float followDistance = 1.5f; + [SerializeField] private float manualMoveSmooth = 8f; + [SerializeField] private float thresholdFar = 2.5f; + [SerializeField] private float thresholdNear = 0.5f; + [SerializeField] private float stopThreshold = 0.1f; + + [Header("Backend Settings")] + [Tooltip("Technical parameters, not for design tuning")] + [SerializeField] private float followUpdateInterval = 0.1f; + [SerializeField] private float followerSpeedMultiplier = 1.2f; + [SerializeField] private float heldIconDisplayHeight = 2.0f; + + // IPlayerFollowerSettings implementation + public float MoveSpeed => moveSpeed; + public float StopDistance => stopDistance; + public bool UseRigidbody => useRigidbody; + public GameSettings.HoldMovementMode DefaultHoldMovementMode => defaultHoldMovementMode; + public float FollowDistance => followDistance; + public float ManualMoveSmooth => manualMoveSmooth; + public float ThresholdFar => thresholdFar; + public float ThresholdNear => thresholdNear; + public float StopThreshold => stopThreshold; + public float FollowUpdateInterval => followUpdateInterval; + public float FollowerSpeedMultiplier => followerSpeedMultiplier; + public float HeldIconDisplayHeight => heldIconDisplayHeight; + + public override void OnValidate() + { + base.OnValidate(); + // Validate values + moveSpeed = Mathf.Max(0.1f, moveSpeed); + followDistance = Mathf.Max(0.1f, followDistance); + followerSpeedMultiplier = Mathf.Max(0.1f, followerSpeedMultiplier); + } + } +} diff --git a/Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs.meta b/Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs.meta new file mode 100644 index 00000000..434c694e --- /dev/null +++ b/Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 32cd6d14d9304d5ba0fd590da1346654 +timeCreated: 1758619904 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/ServiceLocator.cs b/Assets/Scripts/Core/Settings/ServiceLocator.cs new file mode 100644 index 00000000..4b1b4e75 --- /dev/null +++ b/Assets/Scripts/Core/Settings/ServiceLocator.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Service Locator implementation for managing settings services. + /// Provides a central registry for all settings services. + /// + public static class ServiceLocator + { + private static readonly Dictionary _services = new Dictionary(); + + /// + /// Register a service with the service locator. + /// + /// The interface type for the service + /// The service implementation + public static void Register(T service) where T : class + { + _services[typeof(T)] = service; + Debug.Log($"Service registered: {typeof(T).Name}"); + } + + /// + /// Get a service from the service locator. + /// + /// The interface type for the service + /// The service implementation, or null if not found + public static T Get() where T : class + { + if (_services.TryGetValue(typeof(T), out object service)) + { + return service as T; + } + + Debug.LogWarning($"Service of type {typeof(T).Name} not found!"); + return null; + } + + /// + /// Clear all registered services. + /// + public static void Clear() + { + _services.Clear(); + Debug.Log("All services cleared"); + } + } +} diff --git a/Assets/Scripts/Core/Settings/ServiceLocator.cs.meta b/Assets/Scripts/Core/Settings/ServiceLocator.cs.meta new file mode 100644 index 00000000..efcda40a --- /dev/null +++ b/Assets/Scripts/Core/Settings/ServiceLocator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 16cc39d2f99b4e7fa65c4a8b39f3e87c +timeCreated: 1758619866 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/SettingsInterfaces.cs b/Assets/Scripts/Core/Settings/SettingsInterfaces.cs new file mode 100644 index 00000000..98a813d6 --- /dev/null +++ b/Assets/Scripts/Core/Settings/SettingsInterfaces.cs @@ -0,0 +1,54 @@ +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Interface for player and follower settings + /// + public interface IPlayerFollowerSettings + { + // Player settings + float MoveSpeed { get; } + float StopDistance { get; } + bool UseRigidbody { get; } + GameSettings.HoldMovementMode DefaultHoldMovementMode { get; } + + // Follower settings + float FollowDistance { get; } + float ManualMoveSmooth { get; } + float ThresholdFar { get; } + float ThresholdNear { get; } + float StopThreshold { get; } + float FollowUpdateInterval { get; } + float FollowerSpeedMultiplier { get; } + float HeldIconDisplayHeight { get; } + } + + /// + /// Interface for interaction and item settings + /// + public interface IInteractionSettings + { + float PlayerStopDistance { get; } + float PlayerStopDistanceDirectInteraction { get; } + float FollowerPickupDelay { get; } + LayerMask InteractableLayerMask { get; } + GameObject BasePickupPrefab { get; } + GameObject LevelSwitchMenuPrefab { get; } + System.Collections.Generic.List CombinationRules { get; } + System.Collections.Generic.List SlotItemConfigs { get; } + } + + /// + /// Interface for minigame settings + /// + public interface IMinigameSettings + { + // Endless Descender settings + float EndlessDescenderLerpSpeed { get; } + float EndlessDescenderMaxOffset { get; } + float EndlessDescenderClampXMin { get; } + float EndlessDescenderClampXMax { get; } + float EndlessDescenderSpeedExponent { get; } + } +} diff --git a/Assets/Scripts/Core/Settings/SettingsInterfaces.cs.meta b/Assets/Scripts/Core/Settings/SettingsInterfaces.cs.meta new file mode 100644 index 00000000..ec383f64 --- /dev/null +++ b/Assets/Scripts/Core/Settings/SettingsInterfaces.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 54611ae012ab4455a53bd60961d9e7ea +timeCreated: 1758619892 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/SettingsProvider.cs b/Assets/Scripts/Core/Settings/SettingsProvider.cs new file mode 100644 index 00000000..a0d44d76 --- /dev/null +++ b/Assets/Scripts/Core/Settings/SettingsProvider.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; + +namespace AppleHills.Core.Settings +{ + /// + /// Responsible for loading and caching settings from Addressables. + /// + public class SettingsProvider : MonoBehaviour + { + private static SettingsProvider _instance; + private Dictionary _settingsCache = new Dictionary(); + + // Singleton instance + public static SettingsProvider Instance + { + get + { + if (_instance == null) + { + GameObject go = new GameObject("Settings Provider"); + _instance = go.AddComponent(); + DontDestroyOnLoad(go); + } + return _instance; + } + } + + private void Awake() + { + if (_instance == null) + { + _instance = this; + DontDestroyOnLoad(gameObject); + } + else if (_instance != this) + { + Destroy(gameObject); + } + } + + /// + /// Load settings asynchronously using Addressables + /// + public void LoadSettings(Action onLoaded) where T : BaseSettings + { + string key = typeof(T).Name; + + // Return from cache if already loaded + if (_settingsCache.TryGetValue(key, out BaseSettings cachedSettings)) + { + onLoaded?.Invoke(cachedSettings as T); + return; + } + + // Load using Addressables + Addressables.LoadAssetAsync($"Settings/{key}.asset").Completed += handle => + { + if (handle.Status == AsyncOperationStatus.Succeeded) + { + _settingsCache[key] = handle.Result; + onLoaded?.Invoke(handle.Result); + } + else + { + Debug.LogError($"Failed to load settings: {key}"); + onLoaded?.Invoke(null); + } + }; + } + + /// + /// Get cached settings + /// + public T GetSettings() where T : BaseSettings + { + string key = typeof(T).Name; + if (_settingsCache.TryGetValue(key, out BaseSettings settings)) + { + return settings as T; + } + return null; + } + + /// + /// Preload all settings - call this at game startup + /// + public void PreloadAllSettings(Action onComplete) + { + // Load all necessary settings types + int pendingLoads = 3; // Number of settings types + Action decrementCounter = () => { + pendingLoads--; + if (pendingLoads <= 0) + onComplete?.Invoke(); + }; + + LoadSettings(settings => decrementCounter()); + LoadSettings(settings => decrementCounter()); + LoadSettings(settings => decrementCounter()); + } + } +} diff --git a/Assets/Scripts/Core/Settings/SettingsProvider.cs.meta b/Assets/Scripts/Core/Settings/SettingsProvider.cs.meta new file mode 100644 index 00000000..7c19dd7d --- /dev/null +++ b/Assets/Scripts/Core/Settings/SettingsProvider.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4d212b25192045d198f2bf42ef74f278 +timeCreated: 1758619879 \ No newline at end of file diff --git a/Assets/Settings/InteractionSettings.asset b/Assets/Settings/InteractionSettings.asset new file mode 100644 index 00000000..eb6a2465 --- /dev/null +++ b/Assets/Settings/InteractionSettings.asset @@ -0,0 +1,50 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ac22b092dc6f4db5b3dad35172b6e4c4, type: 3} + m_Name: InteractionSettings + m_EditorClassIdentifier: + playerStopDistance: 10 + playerStopDistanceDirectInteraction: 2 + followerPickupDelay: 0.2 + interactableLayerMask: + serializedVersion: 2 + m_Bits: 1024 + basePickupPrefab: {fileID: 7447346505753002421, guid: bf4b9d7045397f946b2125b1ad4a3fbd, type: 3} + levelSwitchMenuPrefab: {fileID: 4062459998181038721, guid: de2ed28e4200a4340a5af4086c98a0dc, type: 3} + combinationRules: + - itemA: {fileID: 11400000, guid: 33e7ca06b22108d4e802486e08bcdfd1, type: 2} + itemB: {fileID: 11400000, guid: 8b2616beb14825a46b9b1ed85ad3cb25, type: 2} + resultPrefab: {fileID: 1610562450228973293, guid: 58654125374567147839eb382fcde422, type: 3} + - itemA: {fileID: 11400000, guid: 983414276ae3f004c854e9c450f27f88, type: 2} + itemB: {fileID: 11400000, guid: 0c6986639ca176a419c92f5a327d95ce, type: 2} + resultPrefab: {fileID: 5562196803416682102, guid: bb505cdbd2463b64790deffc6244c55c, type: 3} + slotItemConfigs: + - slotItem: {fileID: 11400000, guid: e0fad48a84a6b6346ac17c84bc512500, type: 2} + allowedItems: + - {fileID: 11400000, guid: ecae2d83a5ab2a047a2733ebff607380, type: 2} + forbiddenItems: [] + - slotItem: {fileID: 11400000, guid: f97b9e24d6dceb145b56426c1152ebeb, type: 2} + allowedItems: + - {fileID: 11400000, guid: ff4bbba87722023468a0f6395d1f96c7, type: 2} + forbiddenItems: [] + - slotItem: {fileID: 11400000, guid: d28f5774afad9d14f823601707150700, type: 2} + allowedItems: + - {fileID: 11400000, guid: 6934dcb56c610c44da228f7f24ca13c9, type: 2} + forbiddenItems: [] + - slotItem: {fileID: 11400000, guid: aaf36cd26cf74334e9c7db6c1b03b3fb, type: 2} + allowedItems: + - {fileID: 11400000, guid: c50330645a2b9d549aae3639bdfcea19, type: 2} + forbiddenItems: [] + - slotItem: {fileID: 11400000, guid: c68dea945fecbf44094359769db04f31, type: 2} + allowedItems: + - {fileID: 11400000, guid: ab57c8237aac144439a18d69f56d36c6, type: 2} + forbiddenItems: [] diff --git a/Assets/Settings/InteractionSettings.asset.meta b/Assets/Settings/InteractionSettings.asset.meta new file mode 100644 index 00000000..00a4f2fa --- /dev/null +++ b/Assets/Settings/InteractionSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8f5195fb013895049a19488fd4d8f2a1 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/MinigameSettings.asset b/Assets/Settings/MinigameSettings.asset new file mode 100644 index 00000000..d2ac2a18 --- /dev/null +++ b/Assets/Settings/MinigameSettings.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0ce4dba7a1c54e73b1b3d7131a1c0570, type: 3} + m_Name: MinigameSettings + m_EditorClassIdentifier: + endlessDescenderLerpSpeed: 2.03 + endlessDescenderMaxOffset: 10 + endlessDescenderClampXMin: -3.5 + endlessDescenderClampXMax: 3.5 + endlessDescenderSpeedExponent: 0.97 diff --git a/Assets/Settings/MinigameSettings.asset.meta b/Assets/Settings/MinigameSettings.asset.meta new file mode 100644 index 00000000..da608929 --- /dev/null +++ b/Assets/Settings/MinigameSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a9569848f604a6540827d4d4bb0a35c2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/PlayerFollowerSettings.asset b/Assets/Settings/PlayerFollowerSettings.asset new file mode 100644 index 00000000..95991414 --- /dev/null +++ b/Assets/Settings/PlayerFollowerSettings.asset @@ -0,0 +1,26 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 32cd6d14d9304d5ba0fd590da1346654, type: 3} + m_Name: PlayerFollowerSettings + m_EditorClassIdentifier: + moveSpeed: 25 + stopDistance: 2 + useRigidbody: 1 + defaultHoldMovementMode: 1 + followDistance: 5 + manualMoveSmooth: 2 + thresholdFar: 10 + thresholdNear: 7 + stopThreshold: 0.5 + followUpdateInterval: 0.1 + followerSpeedMultiplier: 1.2 + heldIconDisplayHeight: 2 diff --git a/Assets/Settings/PlayerFollowerSettings.asset.meta b/Assets/Settings/PlayerFollowerSettings.asset.meta new file mode 100644 index 00000000..f7789d63 --- /dev/null +++ b/Assets/Settings/PlayerFollowerSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 35bfcff00faa72c4eb272a9e8288f965 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: