From 63cb3f1a8c483e4c38b664fcb5edecc1ad950bb5 Mon Sep 17 00:00:00 2001 From: tschesky Date: Wed, 24 Sep 2025 13:33:43 +0000 Subject: [PATCH] Revamp the settings system (#7) - A Settings Provider system to utilize addressables for loading settings at runtime - An editor UI for easy modifications of the settings objects - A split out developer settings functionality to keep gameplay and nitty-gritty details separately - Most settings migrated out of game objects and into the new system - An additional Editor utility for fetching the settings at editor runtime, for gizmos, visualization etc Co-authored-by: Michal Pikulski Co-authored-by: AlexanderT Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/7 --- .../AddressableAssetGroupSortSettings.asset | 19 + ...dressableAssetGroupSortSettings.asset.meta | 8 + .../AddressableAssetSettings.asset | 3 +- .../AssetGroups/Settings.asset | 41 ++ .../AssetGroups/Settings.asset.meta | 8 + .../Editor/DeveloperSettingsEditorWindow.cs | 207 ++++++++++ .../DeveloperSettingsEditorWindow.cs.meta | 3 + Assets/Editor/EditorSettingsProvider.cs | 97 +++++ Assets/Editor/EditorSettingsProvider.cs.meta | 3 + Assets/Editor/ItemPrefabEditor.cs | 2 +- Assets/Editor/LayerPropertyDrawer.cs | 54 +++ Assets/Editor/LayerPropertyDrawer.cs.meta | 3 + Assets/Editor/PrefabCreatorWindow.cs | 2 +- Assets/Editor/PuzzleChainEditorWindow.cs | 2 +- Assets/Editor/SceneObjectLocatorWindow.cs | 2 +- Assets/Editor/SettingsEditorWindow.cs | 195 +++++++++ Assets/Editor/SettingsEditorWindow.cs.meta | 3 + .../DivingForPictures/FloatingObstacle.prefab | 1 - .../Scenes/Levels/AppleHillsOverworld.unity | 4 + Assets/Scripts/Core/GameManager.cs | 197 +++++++-- Assets/Scripts/Core/GameSettings.cs | 73 ---- Assets/Scripts/Core/GameSettings.cs.meta | 3 - Assets/Scripts/Core/Settings.meta | 3 + .../Core/Settings/BaseDeveloperSettings.cs | 19 + .../Settings/BaseDeveloperSettings.cs.meta | 3 + Assets/Scripts/Core/Settings/BaseSettings.cs | 16 + .../Core/Settings/BaseSettings.cs.meta | 3 + .../Settings/DeveloperSettingsProvider.cs | 116 ++++++ .../DeveloperSettingsProvider.cs.meta | 3 + .../Core/Settings/DivingDeveloperSettings.cs | 246 ++++++++++++ .../Settings/DivingDeveloperSettings.cs.meta | 3 + .../Core/Settings/DivingMinigameSettings.cs | 234 +++++++++++ .../Settings/DivingMinigameSettings.cs.meta | 3 + .../Core/Settings/InteractionSettings.cs | 48 +++ .../Core/Settings/InteractionSettings.cs.meta | 3 + .../Scripts/Core/Settings/ItemConfigTypes.cs | 27 ++ .../Core/Settings/ItemConfigTypes.cs.meta | 3 + .../Scripts/Core/Settings/LayerAttributes.cs | 20 + .../Core/Settings/LayerAttributes.cs.meta | 3 + .../Core/Settings/MovementModeTypes.cs | 13 + .../Core/Settings/MovementModeTypes.cs.meta | 3 + .../Core/Settings/PlayerFollowerSettings.cs | 53 +++ .../Settings/PlayerFollowerSettings.cs.meta | 3 + .../Scripts/Core/Settings/ServiceLocator.cs | 51 +++ .../Core/Settings/ServiceLocator.cs.meta | 3 + .../Core/Settings/SettingsInterfaces.cs | 98 +++++ .../Core/Settings/SettingsInterfaces.cs.meta | 3 + .../Scripts/Core/Settings/SettingsProvider.cs | 100 +++++ .../Core/Settings/SettingsProvider.cs.meta | 3 + Assets/Scripts/Core/SettingsAccess.cs | 57 +++ Assets/Scripts/Core/SettingsAccess.cs.meta | 3 + Assets/Scripts/Input/PlayerTouchController.cs | 17 +- Assets/Scripts/Interactions/Interactable.cs | 22 +- .../Bubbles/BubbleSpawner.cs | 61 ++- .../DivingForPictures/DivingGameManager.cs | 72 ++-- .../Obstacles/ObstacleSpawner.cs | 121 +++--- .../ObstacleCollision.cs | 37 +- .../ObstacleCollision.cs.meta | 0 .../Player/PlayerBlinkBehavior.cs | 213 +++------- .../Player/PlayerCollisionBehavior.cs | 379 +++++++----------- .../Player/PlayerController.cs | 39 +- .../Player/TileBumpCollision.cs | 188 +-------- .../Player/WobbleBehavior.cs | 99 ++--- .../Tiles/TrenchTileSpawner.cs | 149 ++++--- Assets/Scripts/Utilities.meta | 3 + Assets/Scripts/Utilities/UnityExtensions.cs | 55 +++ .../Scripts/Utilities/UnityExtensions.cs.meta | 3 + Assets/Settings/Developer.meta | 8 + .../Developer/DivingDeveloperSettings.asset | 76 ++++ .../DivingDeveloperSettings.asset.meta | 8 + Assets/Settings/DivingMinigameSettings.asset | 48 +++ .../DivingMinigameSettings.asset.meta | 8 + Assets/Settings/InteractionSettings.asset | 50 +++ .../Settings/InteractionSettings.asset.meta | 8 + Assets/Settings/PlayerFollowerSettings.asset | 26 ++ .../PlayerFollowerSettings.asset.meta | 8 + ProjectSettings/TagManager.asset | 2 +- 77 files changed, 2795 insertions(+), 978 deletions(-) create mode 100644 Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset create mode 100644 Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset.meta create mode 100644 Assets/AddressableAssetsData/AssetGroups/Settings.asset create mode 100644 Assets/AddressableAssetsData/AssetGroups/Settings.asset.meta create mode 100644 Assets/Editor/DeveloperSettingsEditorWindow.cs create mode 100644 Assets/Editor/DeveloperSettingsEditorWindow.cs.meta create mode 100644 Assets/Editor/EditorSettingsProvider.cs create mode 100644 Assets/Editor/EditorSettingsProvider.cs.meta create mode 100644 Assets/Editor/LayerPropertyDrawer.cs create mode 100644 Assets/Editor/LayerPropertyDrawer.cs.meta create mode 100644 Assets/Editor/SettingsEditorWindow.cs create mode 100644 Assets/Editor/SettingsEditorWindow.cs.meta delete mode 100644 Assets/Scripts/Core/GameSettings.cs delete mode 100644 Assets/Scripts/Core/GameSettings.cs.meta create mode 100644 Assets/Scripts/Core/Settings.meta create mode 100644 Assets/Scripts/Core/Settings/BaseDeveloperSettings.cs create mode 100644 Assets/Scripts/Core/Settings/BaseDeveloperSettings.cs.meta create mode 100644 Assets/Scripts/Core/Settings/BaseSettings.cs create mode 100644 Assets/Scripts/Core/Settings/BaseSettings.cs.meta create mode 100644 Assets/Scripts/Core/Settings/DeveloperSettingsProvider.cs create mode 100644 Assets/Scripts/Core/Settings/DeveloperSettingsProvider.cs.meta create mode 100644 Assets/Scripts/Core/Settings/DivingDeveloperSettings.cs create mode 100644 Assets/Scripts/Core/Settings/DivingDeveloperSettings.cs.meta create mode 100644 Assets/Scripts/Core/Settings/DivingMinigameSettings.cs create mode 100644 Assets/Scripts/Core/Settings/DivingMinigameSettings.cs.meta create mode 100644 Assets/Scripts/Core/Settings/InteractionSettings.cs create mode 100644 Assets/Scripts/Core/Settings/InteractionSettings.cs.meta create mode 100644 Assets/Scripts/Core/Settings/ItemConfigTypes.cs create mode 100644 Assets/Scripts/Core/Settings/ItemConfigTypes.cs.meta create mode 100644 Assets/Scripts/Core/Settings/LayerAttributes.cs create mode 100644 Assets/Scripts/Core/Settings/LayerAttributes.cs.meta create mode 100644 Assets/Scripts/Core/Settings/MovementModeTypes.cs create mode 100644 Assets/Scripts/Core/Settings/MovementModeTypes.cs.meta create mode 100644 Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs create mode 100644 Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs.meta create mode 100644 Assets/Scripts/Core/Settings/ServiceLocator.cs create mode 100644 Assets/Scripts/Core/Settings/ServiceLocator.cs.meta create mode 100644 Assets/Scripts/Core/Settings/SettingsInterfaces.cs create mode 100644 Assets/Scripts/Core/Settings/SettingsInterfaces.cs.meta create mode 100644 Assets/Scripts/Core/Settings/SettingsProvider.cs create mode 100644 Assets/Scripts/Core/Settings/SettingsProvider.cs.meta create mode 100644 Assets/Scripts/Core/SettingsAccess.cs create mode 100644 Assets/Scripts/Core/SettingsAccess.cs.meta rename Assets/Scripts/Minigames/DivingForPictures/{Obstacles => Player}/ObstacleCollision.cs (51%) rename Assets/Scripts/Minigames/DivingForPictures/{Obstacles => Player}/ObstacleCollision.cs.meta (100%) create mode 100644 Assets/Scripts/Utilities.meta create mode 100644 Assets/Scripts/Utilities/UnityExtensions.cs create mode 100644 Assets/Scripts/Utilities/UnityExtensions.cs.meta create mode 100644 Assets/Settings/Developer.meta create mode 100644 Assets/Settings/Developer/DivingDeveloperSettings.asset create mode 100644 Assets/Settings/Developer/DivingDeveloperSettings.asset.meta create mode 100644 Assets/Settings/DivingMinigameSettings.asset create mode 100644 Assets/Settings/DivingMinigameSettings.asset.meta create mode 100644 Assets/Settings/InteractionSettings.asset create mode 100644 Assets/Settings/InteractionSettings.asset.meta create mode 100644 Assets/Settings/PlayerFollowerSettings.asset create mode 100644 Assets/Settings/PlayerFollowerSettings.asset.meta diff --git a/Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset b/Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset new file mode 100644 index 00000000..ad30b569 --- /dev/null +++ b/Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.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: dea69d41f90c6ea4fa55c27c1d60c145, type: 3} + m_Name: AddressableAssetGroupSortSettings + m_EditorClassIdentifier: + sortOrder: + - eb8a37153d819c44194f7ce97570a3d3 + - 2b3d7cefec0915e42be04aebf0400a56 + - 6f3207429a65b3e4b83935ac19791077 + - c62e6f02418e19949bca4cccdd5fa02e diff --git a/Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset.meta b/Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset.meta new file mode 100644 index 00000000..f0331bb7 --- /dev/null +++ b/Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 32c1a9c8651793e41848a173d66d98fd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: 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..81aa8648 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Settings.asset @@ -0,0 +1,41 @@ +%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: 328ce914b893df646be3ad3c62755453 + m_Address: Settings/Developer/DivingDeveloperSettings + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 + - 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/DivingMinigameSettings + 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/DeveloperSettingsEditorWindow.cs b/Assets/Editor/DeveloperSettingsEditorWindow.cs new file mode 100644 index 00000000..85935172 --- /dev/null +++ b/Assets/Editor/DeveloperSettingsEditorWindow.cs @@ -0,0 +1,207 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using System.Linq; +using System.IO; + +namespace AppleHills.Core.Settings.Editor +{ + public class DeveloperSettingsEditorWindow : EditorWindow + { + private Vector2 scrollPosition; + private List allDeveloperSettings = new List(); + private string[] tabNames = new string[] { "Diving", "Other Systems" }; // Add more tabs as needed + private int selectedTab = 0; + private Dictionary serializedSettingsObjects = new Dictionary(); + private GUIStyle headerStyle; + + [MenuItem("AppleHills/Developer Settings Editor")] + public static void ShowWindow() + { + GetWindow("Developer Settings"); + } + + private void OnEnable() + { + LoadAllSettings(); + } + + private void LoadAllSettings() + { + allDeveloperSettings.Clear(); + serializedSettingsObjects.Clear(); + + // Find all developer settings assets + string[] guids = AssetDatabase.FindAssets("t:BaseDeveloperSettings"); + foreach (string guid in guids) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + BaseDeveloperSettings settings = AssetDatabase.LoadAssetAtPath(path); + if (settings != null) + { + allDeveloperSettings.Add(settings); + serializedSettingsObjects[settings.GetType().Name] = new SerializedObject(settings); + } + } + + // If any settings are missing, create them + CreateSettingsIfMissing("DivingDeveloperSettings"); + + // Add more developer settings types here as needed + // CreateSettingsIfMissing("OtherDeveloperSettings"); + } + + private void CreateSettingsIfMissing(string fileName) where T : BaseDeveloperSettings + { + if (!allDeveloperSettings.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 + string settingsFolder = "Assets/Settings/Developer"; + if (!AssetDatabase.IsValidFolder("Assets/Settings")) + { + AssetDatabase.CreateFolder("Assets", "Settings"); + } + if (!AssetDatabase.IsValidFolder(settingsFolder)) + { + AssetDatabase.CreateFolder("Assets/Settings", "Developer"); + } + + // Create new settings asset + T settings = CreateInstance(); + string path = $"{settingsFolder}/{fileName}.asset"; + AssetDatabase.CreateAsset(settings, path); + AssetDatabase.SaveAssets(); + + allDeveloperSettings.Add(settings); + serializedSettingsObjects[typeof(T).Name] = new SerializedObject(settings); + Debug.Log($"Created missing developer settings asset: {path}"); + } + else + { + // Load existing asset + string path = AssetDatabase.GUIDToAssetPath(guids[0]); + T settings = AssetDatabase.LoadAssetAtPath(path); + allDeveloperSettings.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 Developer Settings", headerStyle); + EditorGUILayout.HelpBox("This editor is for technical settings intended for developers. For gameplay settings, use the Game Settings editor.", MessageType.Info); + + EditorGUILayout.Space(10); + selectedTab = GUILayout.Toolbar(selectedTab, tabNames); + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + + switch (selectedTab) + { + case 0: // Diving + DrawSettingsEditor(); + break; + case 1: // Other Systems + EditorGUILayout.HelpBox("Other developer settings will appear here as they are added.", MessageType.Info); + break; + // Add additional cases as more developer settings types are added + } + + 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 developer settings saved!"); + } + + EditorGUILayout.EndHorizontal(); + } + + private void DrawSettingsEditor() where T : BaseDeveloperSettings + { + BaseDeveloperSettings settings = allDeveloperSettings.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; + + // Group headers + if (property.isArray && property.propertyType == SerializedPropertyType.Generic) + { + EditorGUILayout.LabelField(property.displayName, EditorStyles.boldLabel); + } + + EditorGUILayout.PropertyField(property, true); + } + + // Apply changes + if (serializedObj.ApplyModifiedProperties()) + { + EditorUtility.SetDirty(settings); + + // Trigger OnValidate on the asset + if (settings != null) + { + settings.OnValidate(); + } + } + } + + // Helper method to highlight important fields + private void DrawHighlightedProperty(SerializedProperty property, string tooltip = null) + { + GUI.backgroundColor = new Color(0.8f, 0.9f, 1f); // Light blue for developer settings + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + GUI.backgroundColor = Color.white; + + EditorGUILayout.PropertyField(property, new GUIContent(property.displayName, tooltip)); + + EditorGUILayout.EndVertical(); + } + } +} diff --git a/Assets/Editor/DeveloperSettingsEditorWindow.cs.meta b/Assets/Editor/DeveloperSettingsEditorWindow.cs.meta new file mode 100644 index 00000000..c99b4494 --- /dev/null +++ b/Assets/Editor/DeveloperSettingsEditorWindow.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8ab6d9fee0924431b8e22edb500b35df +timeCreated: 1758710778 \ No newline at end of file diff --git a/Assets/Editor/EditorSettingsProvider.cs b/Assets/Editor/EditorSettingsProvider.cs new file mode 100644 index 00000000..6c102515 --- /dev/null +++ b/Assets/Editor/EditorSettingsProvider.cs @@ -0,0 +1,97 @@ +using UnityEditor; +using AppleHills.Core.Settings; +using UnityEngine; + +namespace AppleHills.Editor +{ + /// + /// Provides access to settings in editor (non-play) mode + /// + [InitializeOnLoad] + public static class EditorSettingsProvider + { + private static PlayerFollowerSettings _playerFollowerSettings; + private static InteractionSettings _interactionSettings; + private static DivingMinigameSettings _divingMinigameSettings; + + // Static constructor will be called when Unity loads/reloads scripts + static EditorSettingsProvider() + { + LoadAllSettings(); + + // Set up the delegates in SettingsAccess + AppleHills.SettingsAccess.SetupEditorProviders( + GetPlayerStopDistance, + GetPlayerStopDistanceDirectInteraction + ); + + // Subscribe to asset changes to auto-refresh when settings are modified + EditorApplication.delayCall += () => + { + EditorApplication.projectChanged += OnProjectChanged; + }; + } + + private static void OnProjectChanged() + { + // Check if any settings assets have changed + if (HasSettingsChanged()) + { + LoadAllSettings(); + RefreshSceneViews(); + } + } + + private static bool HasSettingsChanged() + { + // Simplified check - you might want to make this more efficient + // by checking timestamps or specific files + return true; + } + + public static void LoadAllSettings() + { + _playerFollowerSettings = AssetDatabase.LoadAssetAtPath("Assets/Settings/PlayerFollowerSettings.asset"); + _interactionSettings = AssetDatabase.LoadAssetAtPath("Assets/Settings/InteractionSettings.asset"); + _divingMinigameSettings = AssetDatabase.LoadAssetAtPath("Assets/Settings/MinigameSettings.asset"); + + // Re-register the delegates in case they were lost + AppleHills.SettingsAccess.SetupEditorProviders( + GetPlayerStopDistance, + GetPlayerStopDistanceDirectInteraction + ); + + Debug.Log("Editor settings loaded for Scene View use"); + } + + public static void RefreshSceneViews() + { + // Force scene views to repaint to refresh gizmos + SceneView.RepaintAll(); + } + + // Implementation of delegate methods + private static float GetPlayerStopDistance() + { + return _interactionSettings?.PlayerStopDistance ?? 6.0f; + } + + private static float GetPlayerStopDistanceDirectInteraction() + { + return _interactionSettings?.PlayerStopDistanceDirectInteraction ?? 2.0f; + } + + // Other utility methods + public static T GetSettings() where T : BaseSettings + { + if (typeof(T) == typeof(PlayerFollowerSettings)) + return _playerFollowerSettings as T; + else if (typeof(T) == typeof(InteractionSettings)) + return _interactionSettings as T; + else if (typeof(T) == typeof(DivingMinigameSettings)) + return _divingMinigameSettings as T; + + return null; + } + } +} diff --git a/Assets/Editor/EditorSettingsProvider.cs.meta b/Assets/Editor/EditorSettingsProvider.cs.meta new file mode 100644 index 00000000..1477ea64 --- /dev/null +++ b/Assets/Editor/EditorSettingsProvider.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1e8da573a8db4ea892fe476592276e0f +timeCreated: 1758634265 \ No newline at end of file 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/LayerPropertyDrawer.cs b/Assets/Editor/LayerPropertyDrawer.cs new file mode 100644 index 00000000..38c24e00 --- /dev/null +++ b/Assets/Editor/LayerPropertyDrawer.cs @@ -0,0 +1,54 @@ +using UnityEngine; +using UnityEditor; + +namespace AppleHills.Core.Settings.Editor +{ + /// + /// Custom property drawer for layer fields to display a dropdown with layer names + /// + [CustomPropertyDrawer(typeof(LayerAttribute))] + public class LayerPropertyDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(position, label, property); + + // Draw a nice layer selection dropdown like the one in Unity inspector + if (property.propertyType == SerializedPropertyType.Integer) + { + property.intValue = EditorGUI.LayerField(position, label, property.intValue); + } + else + { + EditorGUI.LabelField(position, label.text, "Use [Layer] with int fields only"); + } + + EditorGUI.EndProperty(); + } + } + + /// + /// Custom property drawer for LayerMask fields to display a mask dropdown with layer names + /// + [CustomPropertyDrawer(typeof(LayerMaskAttribute))] + public class LayerMaskPropertyDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(position, label, property); + + // Draw a nice layer mask selection like the one in Unity inspector + if (property.propertyType == SerializedPropertyType.LayerMask) + { + property.intValue = EditorGUI.MaskField(position, label, + property.intValue, UnityEditorInternal.InternalEditorUtility.layers); + } + else + { + EditorGUI.LabelField(position, label.text, "Use [LayerMask] with LayerMask fields only"); + } + + EditorGUI.EndProperty(); + } + } +} diff --git a/Assets/Editor/LayerPropertyDrawer.cs.meta b/Assets/Editor/LayerPropertyDrawer.cs.meta new file mode 100644 index 00000000..8cdac000 --- /dev/null +++ b/Assets/Editor/LayerPropertyDrawer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6c6b274ce7b14e91bf5a294a23ba0609 +timeCreated: 1758711663 \ No newline at end of file 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..70f9ebb3 --- /dev/null +++ b/Assets/Editor/SettingsEditorWindow.cs @@ -0,0 +1,195 @@ +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", "Diving Minigame" }; + 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("DivingMinigameSettings"); + } + + 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(); + + // Refresh editor settings after save + AppleHills.Editor.EditorSettingsProvider.LoadAllSettings(); + AppleHills.Editor.EditorSettingsProvider.RefreshSceneViews(); + + Debug.Log("All settings saved and editor views refreshed!"); + } + + 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/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab b/Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab index fb73fd83..bdc5d45b 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab @@ -47,7 +47,6 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: prefabIndex: 0 - damage: 1 moveSpeed: 2 enableMovement: 1 spawner: {fileID: 0} diff --git a/Assets/Scenes/Levels/AppleHillsOverworld.unity b/Assets/Scenes/Levels/AppleHillsOverworld.unity index 03f05fa3..44747a0a 100644 --- a/Assets/Scenes/Levels/AppleHillsOverworld.unity +++ b/Assets/Scenes/Levels/AppleHillsOverworld.unity @@ -1979,6 +1979,10 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 7852204877518954380, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3} + propertyPath: maxSpeed + value: 15 + objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] diff --git a/Assets/Scripts/Core/GameManager.cs b/Assets/Scripts/Core/GameManager.cs index bc92da72..0797b577 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; /// /// Singleton manager for global game state and settings. Provides accessors for various gameplay parameters. @@ -29,53 +31,172 @@ public class GameManager : MonoBehaviour } } - [Header("Game Settings")] - public GameSettings gameSettings; + [Header("Settings Status")] + [SerializeField] private bool _settingsLoaded = false; + [SerializeField] private bool _developerSettingsLoaded = false; + public bool SettingsLoaded => _settingsLoaded; + public bool DeveloperSettingsLoaded => _developerSettingsLoaded; 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"; + + // Create developer settings provider if it doesn't exist + DeveloperSettingsProvider.Instance.gameObject.name = "Developer Settings Provider"; + + // Load all settings synchronously during Awake + InitializeSettings(); + InitializeDeveloperSettings(); + // DontDestroyOnLoad(gameObject); } + private void InitializeSettings() + { + Debug.Log("Starting settings initialization..."); + + // Load settings synchronously + var playerSettings = SettingsProvider.Instance.LoadSettingsSynchronous(); + var interactionSettings = SettingsProvider.Instance.LoadSettingsSynchronous(); + var minigameSettings = SettingsProvider.Instance.LoadSettingsSynchronous(); + + // Register settings with service locator + if (playerSettings != null) + { + ServiceLocator.Register(playerSettings); + Debug.Log("PlayerFollowerSettings registered successfully"); + } + else + { + Debug.LogError("Failed to load PlayerFollowerSettings"); + } + + if (interactionSettings != null) + { + ServiceLocator.Register(interactionSettings); + Debug.Log("InteractionSettings registered successfully"); + } + else + { + Debug.LogError("Failed to load InteractionSettings"); + } + + if (minigameSettings != null) + { + ServiceLocator.Register(minigameSettings); + Debug.Log("MinigameSettings registered successfully"); + } + else + { + Debug.LogError("Failed to load MinigameSettings"); + } + + // Log success + _settingsLoaded = playerSettings != null && interactionSettings != null && minigameSettings != null; + if (_settingsLoaded) + { + Debug.Log("All settings loaded and registered with ServiceLocator"); + } + else + { + Debug.LogWarning("Some settings failed to load - check that all settings assets exist and are marked as Addressables"); + } + } + + /// + /// Check for and initialize developer settings. + /// + private void InitializeDeveloperSettings() + { + Debug.Log("Starting developer settings initialization..."); + + // Load developer settings + var divingDevSettings = DeveloperSettingsProvider.Instance.GetSettings(); + + _developerSettingsLoaded = divingDevSettings != null; + + if (_developerSettingsLoaded) + { + Debug.Log("All developer settings loaded successfully"); + } + else + { + Debug.LogWarning("Some developer settings failed to load"); + } + } + 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(); + } + + /// + /// Returns the entire settings object of specified type. + /// + /// Type of settings to retrieve + /// The settings object or null if not found + public static T GetSettingsObject() where T : class + { + return Instance?.GetSettings(); + } + + /// + /// Returns the developer settings object of specified type. + /// + /// Type of developer settings to retrieve + /// The developer settings object or null if not found + public static T GetDeveloperSettings() where T : BaseDeveloperSettings + { + return DeveloperSettingsProvider.Instance?.GetSettings(); + } + + // 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 HoldMovementMode DefaultHoldMovementMode => + GetSettings()?.DefaultHoldMovementMode ?? 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) + public 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))) @@ -89,22 +210,16 @@ public class GameManager : MonoBehaviour /// /// Returns the slot item config for a given slot item. /// - public GameSettings.SlotItemConfig GetSlotItemConfig(PickupItemData slotItem) + public 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; } diff --git a/Assets/Scripts/Core/GameSettings.cs b/Assets/Scripts/Core/GameSettings.cs deleted file mode 100644 index 3d4aa9a7..00000000 --- a/Assets/Scripts/Core/GameSettings.cs +++ /dev/null @@ -1,73 +0,0 @@ -using UnityEngine; - -/// -/// ScriptableObject for storing and configuring all game settings, including player, follower, and item configuration. -/// -[CreateAssetMenu(fileName = "GameSettings", menuName = "AppleHills/GameSettings", order = 1)] -public class GameSettings : ScriptableObject -{ - [Header("Interactions")] - public float playerStopDistance = 6.0f; - public float playerStopDistanceDirectInteraction = 2.0f; - public float followerPickupDelay = 0.2f; - - [Header("Follower Settings")] - public float followDistance = 1.5f; - public float manualMoveSmooth = 8f; - public float thresholdFar = 2.5f; - public float thresholdNear = 0.5f; - public float stopThreshold = 0.1f; - - [Header("Player Settings")] - public float moveSpeed = 5f; - public float stopDistance = 0.1f; - public bool useRigidbody = true; - public enum HoldMovementMode { Pathfinding, Direct } - public HoldMovementMode defaultHoldMovementMode = HoldMovementMode.Pathfinding; - - [Header("Backend Settings")] - [Tooltip("Technical parameters, not for design tuning")] - public float followUpdateInterval = 0.1f; - public float followerSpeedMultiplier = 1.2f; - public float heldIconDisplayHeight = 2.0f; - - [Header("Default Prefabs")] - public GameObject basePickupPrefab; - - [Header("Endless Descender Settings")] - [Tooltip("How quickly the character follows the finger horizontally (higher = more responsive)")] - public float endlessDescenderLerpSpeed = 12f; - [Tooltip("Maximum horizontal offset allowed between character and finger position")] - public float endlessDescenderMaxOffset = 3f; - [Tooltip("Minimum allowed X position for endless descender movement")] - public float endlessDescenderClampXMin = -3.5f; - [Tooltip("Maximum allowed X position for endless descender movement")] - public float endlessDescenderClampXMax = 3.5f; - [Tooltip("Exponent for speed drop-off curve (higher = sharper drop near target)")] - public float endlessDescenderSpeedExponent = 2.5f; - - [Header("InputManager Settings")] - [Tooltip("Layer(s) to use for interactable objects.")] - public LayerMask interactableLayerMask = -1; // Default to Everything - - [Header("UI Prefabs")] - public GameObject levelSwitchMenuPrefab; - - [System.Serializable] - public class CombinationRule { - public PickupItemData itemA; - public PickupItemData itemB; - public GameObject resultPrefab; // The prefab to spawn as the result - } - - [System.Serializable] - public class SlotItemConfig { - public PickupItemData slotItem; // The slot object (SO reference) - public System.Collections.Generic.List allowedItems; - public System.Collections.Generic.List forbiddenItems; - } - - [Header("Item Configuration")] - public System.Collections.Generic.List combinationRules; - public System.Collections.Generic.List slotItemConfigs; -} diff --git a/Assets/Scripts/Core/GameSettings.cs.meta b/Assets/Scripts/Core/GameSettings.cs.meta deleted file mode 100644 index aba7bd69..00000000 --- a/Assets/Scripts/Core/GameSettings.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: e4ec438b455a4044957501c2c66a6f4b -timeCreated: 1756933137 \ No newline at end of file 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/BaseDeveloperSettings.cs b/Assets/Scripts/Core/Settings/BaseDeveloperSettings.cs new file mode 100644 index 00000000..d75dc495 --- /dev/null +++ b/Assets/Scripts/Core/Settings/BaseDeveloperSettings.cs @@ -0,0 +1,19 @@ +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Base abstract class for all developer settings. + /// Developer settings are intended for technical configuration rather than gameplay/design values. + /// + public abstract class BaseDeveloperSettings : ScriptableObject + { + /// + /// Called to validate settings values when they are changed in the inspector. + /// + public virtual void OnValidate() + { + // Base implementation does nothing, override in derived classes + } + } +} diff --git a/Assets/Scripts/Core/Settings/BaseDeveloperSettings.cs.meta b/Assets/Scripts/Core/Settings/BaseDeveloperSettings.cs.meta new file mode 100644 index 00000000..ced7cb46 --- /dev/null +++ b/Assets/Scripts/Core/Settings/BaseDeveloperSettings.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 50def2e6e95a4830b57f3e1b76a4df51 +timeCreated: 1758707161 \ 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/DeveloperSettingsProvider.cs b/Assets/Scripts/Core/Settings/DeveloperSettingsProvider.cs new file mode 100644 index 00000000..d4608552 --- /dev/null +++ b/Assets/Scripts/Core/Settings/DeveloperSettingsProvider.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.AddressableAssets; +using System; + +namespace AppleHills.Core.Settings +{ + /// + /// Provides access to developer settings for technical configuration rather than gameplay parameters. + /// Follows the singleton pattern for global access. + /// + public class DeveloperSettingsProvider : MonoBehaviour + { + private static DeveloperSettingsProvider _instance; + + /// + /// Singleton instance of the provider. + /// + public static DeveloperSettingsProvider Instance + { + get + { + if (_instance == null && Application.isPlaying) + { + _instance = FindFirstObjectByType(); + + if (_instance == null) + { + GameObject go = new GameObject("DeveloperSettingsProvider"); + _instance = go.AddComponent(); + // Don't destroy between scenes + DontDestroyOnLoad(go); + } + } + + return _instance; + } + } + + // Dictionary to cache loaded settings + private Dictionary _settingsCache = new Dictionary(); + + // Path prefix for addressable developer settings + [SerializeField] private string _addressablePath = "Settings/Developer"; + + private void Awake() + { + if (_instance != null && _instance != this) + { + Destroy(gameObject); + return; + } + + _instance = this; + DontDestroyOnLoad(gameObject); + + // Initialize settings cache + _settingsCache = new Dictionary(); + } + + /// + /// Gets or loads developer settings of the specified type. + /// + /// Type of developer settings to retrieve + /// The settings instance or null if not found + public T GetSettings() where T : BaseDeveloperSettings + { + System.Type type = typeof(T); + + // Return from cache if available + if (_settingsCache.TryGetValue(type, out BaseDeveloperSettings cachedSettings)) + { + return cachedSettings as T; + } + + // Load from Addressables if not cached + string key = $"{_addressablePath}/{type.Name}"; + + try + { + T settings = Addressables.LoadAssetAsync(key).WaitForCompletion(); + + if (settings != null) + { + _settingsCache[type] = settings; + return settings; + } + } + catch (Exception e) + { + Debug.LogError($"Failed to load developer settings at '{key}': {e.Message}"); + } + + Debug.LogWarning($"Developer settings of type {type.Name} not found at addressable path '{key}'"); + + // Fallback to Resources for backward compatibility + T resourcesSettings = Resources.Load($"{_addressablePath}/{type.Name}"); + if (resourcesSettings != null) + { + Debug.Log($"Found developer settings in Resources instead of Addressables at '{_addressablePath}/{type.Name}'"); + _settingsCache[type] = resourcesSettings; + return resourcesSettings; + } + + return null; + } + + /// + /// Clears the settings cache, forcing settings to be reloaded. + /// + public void ClearCache() + { + _settingsCache.Clear(); + } + } +} diff --git a/Assets/Scripts/Core/Settings/DeveloperSettingsProvider.cs.meta b/Assets/Scripts/Core/Settings/DeveloperSettingsProvider.cs.meta new file mode 100644 index 00000000..f3f739f7 --- /dev/null +++ b/Assets/Scripts/Core/Settings/DeveloperSettingsProvider.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f9945aa4a563434e973ab49176259150 +timeCreated: 1758707186 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/DivingDeveloperSettings.cs b/Assets/Scripts/Core/Settings/DivingDeveloperSettings.cs new file mode 100644 index 00000000..69a233d7 --- /dev/null +++ b/Assets/Scripts/Core/Settings/DivingDeveloperSettings.cs @@ -0,0 +1,246 @@ +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Enum defining the type of bump response when player collides with obstacles + /// + public enum BumpMode + { + Impulse = 0, + SmoothToCenter = 1 + } + + /// + /// Developer settings for the diving minigame technical configuration. + /// These settings are separate from gameplay/design settings and focus on technical implementation details. + /// + [CreateAssetMenu(fileName = "DivingDeveloperSettings", menuName = "AppleHills/Developer Settings/Diving", order = 1)] + public class DivingDeveloperSettings : BaseDeveloperSettings + { + [Header("Bubble System")] + [Tooltip("Object pooling enabled for bubbles")] + [SerializeField] private bool bubbleUseObjectPooling = true; + + [Tooltip("Initial number of bubbles to pre-allocate in pool")] + [SerializeField] private int bubbleInitialPoolSize = 10; + + [Tooltip("Maximum number of bubbles allowed in pool")] + [SerializeField] private int bubbleMaxPoolSize = 30; + + [Tooltip("Default spawn interval for bubbles in seconds")] + [SerializeField] private float bubbleSpawnInterval = 0.3f; + + [Tooltip("Range of possible bubble movement speeds (min, max)")] + [SerializeField] private Vector2 bubbleSpeedRange = new Vector2(0.5f, 2f); + + [Tooltip("Range of possible bubble scale factors (min, max)")] + [SerializeField] private Vector2 bubbleScaleRange = new Vector2(0.3f, 0.7f); + + [Tooltip("Range of possible bubble wobble speeds (min, max)")] + [SerializeField] private Vector2 bubbleWobbleSpeedRange = new Vector2(1f, 3f); + + [Tooltip("Range of possible bubble wobble amounts (min, max)")] + [SerializeField] private Vector2 bubbleWobbleAmountRange = new Vector2(0.05f, 0.15f); + + [Tooltip("Minimum X position for bubble spawning")] + [SerializeField] private float bubbleSpawnXMin = -3.5f; + + [Tooltip("Maximum X position for bubble spawning")] + [SerializeField] private float bubbleSpawnXMax = 3.5f; + + [Tooltip("Y position for bubble spawning")] + [SerializeField] private float bubbleSpawnY = -5f; + + [Tooltip("Minimum scale factor during wobble animation")] + [SerializeField] private float bubbleWobbleMinScale = 0.2f; + + [Tooltip("Maximum scale factor during wobble animation")] + [SerializeField] private float bubbleWobbleMaxScale = 1.2f; + + [Tooltip("Factor to multiply bubble speed by when surfacing")] + [SerializeField] private float bubbleSurfacingSpeedFactor = 0.5f; + + [Header("Obstacle System")] + [Tooltip("Layer for obstacles to be placed on")] + [Layer] + [SerializeField] private int obstacleLayer = 9; + + [Tooltip("Whether to use object pooling for obstacles")] + [SerializeField] private bool obstacleUseObjectPooling = true; + + [Tooltip("Maximum objects per prefab type in obstacle pool")] + [SerializeField] private int obstacleMaxPerPrefabPoolSize = 3; + + [Tooltip("Total maximum size of obstacle pool across all prefab types")] + [SerializeField] private int obstacleTotalMaxPoolSize = 15; + + [Header("Trench Tile System")] + [Tooltip("Layer for trench tiles to be placed on")] + [Layer] + [SerializeField] private int trenchTileLayer = 13; // QuarryTrenchTile layer + + [Tooltip("Whether to use object pooling for trench tiles")] + [SerializeField] private bool trenchTileUseObjectPooling = true; + + [Tooltip("Maximum objects per prefab type in trench tile pool")] + [SerializeField] private int trenchTileMaxPerPrefabPoolSize = 2; + + [Tooltip("Total maximum size of trench tile pool across all prefab types")] + [SerializeField] private int trenchTileTotalMaxPoolSize = 10; + + [Header("Player Blink Behavior")] + [Tooltip("Color to blink to when taking damage (typically red for damage indication)")] + [SerializeField] private Color playerBlinkDamageColor = Color.red; + + [Tooltip("How fast to blink between normal and damage colors (seconds between color changes)")] + [SerializeField] private float playerBlinkRate = 0.15f; + + [Tooltip("Alpha value for the damage color (0 = transparent, 1 = opaque)")] + [Range(0f, 1f)] + [SerializeField] private float playerDamageColorAlpha = 0.7f; + + [Header("Player Wobble Behavior")] + [Tooltip("Frequency of wobble (higher = faster rocking)")] + [SerializeField] private float playerWobbleFrequency = 1.5f; + + [Tooltip("Base wobble amplitude in degrees from horizontal")] + [SerializeField] private float playerBaseWobbleAmplitude = 8f; + + [Tooltip("How much speed affects amplitude")] + [SerializeField] private float playerSpeedToAmplitude = 2f; + + [Tooltip("Maximum allowed rotation in degrees")] + [SerializeField] private float playerMaxRotationLimit = 45f; + + [Tooltip("Frequency of vertical bobbing")] + [SerializeField] private float playerVerticalFrequency = 0.5f; + + [Tooltip("How far the object moves up/down")] + [SerializeField] private float playerVerticalAmplitude = 0.5f; + + [Tooltip("How quickly velocity changes are smoothed")] + [SerializeField] private float playerVelocitySmoothing = 10f; + + [Tooltip("How quickly rotation is smoothed")] + [SerializeField] private float playerRotationSmoothing = 10f; + + [Header("Collision Settings")] + [Tooltip("Layer mask for obstacle detection - configure which layers contain obstacles")] + [LayerMask] + [SerializeField] private LayerMask playerObstacleLayerMask = -1; + + [Tooltip("Whether to block player input during damage immunity period")] + [SerializeField] private bool blockInputDuringImmunity = true; + + [Tooltip("Type of bump response: 0=Impulse, 1=SmoothToCenter")] + [SerializeField] private BumpMode bumpMode = BumpMode.Impulse; + + [Tooltip("Animation curve controlling bump movement over time")] + [SerializeField] private AnimationCurve bumpCurve = new AnimationCurve( + new Keyframe(0f, 0f, 0f, 2f), + new Keyframe(1f, 1f, 0f, 0f)); + + // Bubble properties access + public bool BubbleUseObjectPooling => bubbleUseObjectPooling; + public int BubbleInitialPoolSize => bubbleInitialPoolSize; + public int BubbleMaxPoolSize => bubbleMaxPoolSize; + public float BubbleSpawnInterval => bubbleSpawnInterval; + public Vector2 BubbleSpeedRange => bubbleSpeedRange; + public Vector2 BubbleScaleRange => bubbleScaleRange; + public Vector2 BubbleWobbleSpeedRange => bubbleWobbleSpeedRange; + public Vector2 BubbleWobbleAmountRange => bubbleWobbleAmountRange; + public float BubbleSpawnXMin => bubbleSpawnXMin; + public float BubbleSpawnXMax => bubbleSpawnXMax; + public float BubbleSpawnY => bubbleSpawnY; + public float BubbleWobbleMinScale => bubbleWobbleMinScale; + public float BubbleWobbleMaxScale => bubbleWobbleMaxScale; + public float BubbleSurfacingSpeedFactor => bubbleSurfacingSpeedFactor; + + // Obstacle properties access + public int ObstacleLayer => obstacleLayer; + public bool ObstacleUseObjectPooling => obstacleUseObjectPooling; + public int ObstacleMaxPerPrefabPoolSize => obstacleMaxPerPrefabPoolSize; + public int ObstacleTotalMaxPoolSize => obstacleTotalMaxPoolSize; + + // Trench Tile System properties + public int TrenchTileLayer => trenchTileLayer; + public bool TrenchTileUseObjectPooling => trenchTileUseObjectPooling; + public int TrenchTileMaxPerPrefabPoolSize => trenchTileMaxPerPrefabPoolSize; + public int TrenchTileTotalMaxPoolSize => trenchTileTotalMaxPoolSize; + + // Player Blink Behavior properties + public Color PlayerBlinkDamageColor => playerBlinkDamageColor; + public float PlayerBlinkRate => playerBlinkRate; + public float PlayerDamageColorAlpha => playerDamageColorAlpha; + + // Player Wobble Behavior properties + public float PlayerWobbleFrequency => playerWobbleFrequency; + public float PlayerBaseWobbleAmplitude => playerBaseWobbleAmplitude; + public float PlayerSpeedToAmplitude => playerSpeedToAmplitude; + public float PlayerMaxRotationLimit => playerMaxRotationLimit; + public float PlayerVerticalFrequency => playerVerticalFrequency; + public float PlayerVerticalAmplitude => playerVerticalAmplitude; + public float PlayerVelocitySmoothing => playerVelocitySmoothing; + public float PlayerRotationSmoothing => playerRotationSmoothing; + + // Collision Settings properties + public LayerMask PlayerObstacleLayerMask => playerObstacleLayerMask; + public bool BlockInputDuringImmunity => blockInputDuringImmunity; + public BumpMode BumpMode => bumpMode; + public AnimationCurve BumpCurve => bumpCurve; + + public override void OnValidate() + { + base.OnValidate(); + + // Validate bubble settings + bubbleInitialPoolSize = Mathf.Max(1, bubbleInitialPoolSize); + bubbleMaxPoolSize = Mathf.Max(bubbleInitialPoolSize, bubbleMaxPoolSize); + bubbleSpawnInterval = Mathf.Max(0.05f, bubbleSpawnInterval); + bubbleSpeedRange = new Vector2( + Mathf.Max(0.1f, bubbleSpeedRange.x), + Mathf.Max(bubbleSpeedRange.x, bubbleSpeedRange.y) + ); + bubbleScaleRange = new Vector2( + Mathf.Max(0.1f, bubbleScaleRange.x), + Mathf.Max(bubbleScaleRange.x, bubbleScaleRange.y) + ); + bubbleWobbleSpeedRange = new Vector2( + Mathf.Max(0.1f, bubbleWobbleSpeedRange.x), + Mathf.Max(bubbleWobbleSpeedRange.x, bubbleWobbleSpeedRange.y) + ); + bubbleWobbleAmountRange = new Vector2( + Mathf.Max(0.01f, bubbleWobbleAmountRange.x), + Mathf.Max(bubbleWobbleAmountRange.x, bubbleWobbleAmountRange.y) + ); + bubbleWobbleMinScale = Mathf.Max(0.01f, bubbleWobbleMinScale); + bubbleWobbleMaxScale = Mathf.Max(bubbleWobbleMinScale, bubbleWobbleMaxScale); + bubbleSurfacingSpeedFactor = Mathf.Max(0.01f, bubbleSurfacingSpeedFactor); + + // Validate obstacle settings + obstacleMaxPerPrefabPoolSize = Mathf.Max(1, obstacleMaxPerPrefabPoolSize); + obstacleTotalMaxPoolSize = Mathf.Max(obstacleMaxPerPrefabPoolSize, obstacleTotalMaxPoolSize); + + // Validate Trench Tile settings + trenchTileMaxPerPrefabPoolSize = Mathf.Max(1, trenchTileMaxPerPrefabPoolSize); + trenchTileTotalMaxPoolSize = Mathf.Max(trenchTileMaxPerPrefabPoolSize, trenchTileTotalMaxPoolSize); + + // Validate Player Blink settings + playerBlinkRate = Mathf.Max(0.01f, playerBlinkRate); + playerDamageColorAlpha = Mathf.Clamp01(playerDamageColorAlpha); + + // Validate Player Wobble settings + playerWobbleFrequency = Mathf.Max(0.01f, playerWobbleFrequency); + playerBaseWobbleAmplitude = Mathf.Max(0f, playerBaseWobbleAmplitude); + playerMaxRotationLimit = Mathf.Max(0f, playerMaxRotationLimit); + playerVerticalFrequency = Mathf.Max(0.01f, playerVerticalFrequency); + playerVerticalAmplitude = Mathf.Max(0f, playerVerticalAmplitude); + playerVelocitySmoothing = Mathf.Max(0.1f, playerVelocitySmoothing); + playerRotationSmoothing = Mathf.Max(0.1f, playerRotationSmoothing); + + // Validate Collision settings + bumpMode = (BumpMode)Mathf.Clamp((int)bumpMode, 0, 1); + } + } +} diff --git a/Assets/Scripts/Core/Settings/DivingDeveloperSettings.cs.meta b/Assets/Scripts/Core/Settings/DivingDeveloperSettings.cs.meta new file mode 100644 index 00000000..d3387e02 --- /dev/null +++ b/Assets/Scripts/Core/Settings/DivingDeveloperSettings.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 033961b12e7b4289838d554c2264bacd +timeCreated: 1758707215 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/DivingMinigameSettings.cs b/Assets/Scripts/Core/Settings/DivingMinigameSettings.cs new file mode 100644 index 00000000..f12da829 --- /dev/null +++ b/Assets/Scripts/Core/Settings/DivingMinigameSettings.cs @@ -0,0 +1,234 @@ +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Settings related to minigames + /// + [CreateAssetMenu(fileName = "MinigameSettings", menuName = "AppleHills/Settings/Minigames", order = 3)] + public class DivingMinigameSettings : BaseSettings, IDivingMinigameSettings + { + [Header("Basic Movement")] + [Tooltip("How quickly the character follows the finger horizontally (higher = more responsive)")] + [SerializeField] private float lerpSpeed = 12f; + + [Tooltip("Maximum horizontal offset allowed between character and finger position")] + [SerializeField] private float maxOffset = 3f; + + [Tooltip("Minimum allowed X position for movement")] + [SerializeField] private float clampXMin = -3.5f; + + [Tooltip("Maximum allowed X position for movement")] + [SerializeField] private float clampXMax = 3.5f; + + [Tooltip("Exponent for speed drop-off curve (higher = sharper drop near target)")] + [SerializeField] private float speedExponent = 2.5f; + + [Header("Player Movement")] + [Tooltip("Maximum distance the player can move from a single tap")] + [SerializeField] private float tapMaxDistance = 0.5f; + + [Tooltip("How quickly the tap impulse fades (higher = faster stop)")] + [SerializeField] private float tapDecelerationRate = 5.0f; + + [Header("Monster Spawning")] + [Tooltip("Base chance (0-1) of spawning a monster on each tile")] + [SerializeField] private float baseSpawnProbability = 0.2f; + + [Tooltip("Maximum chance (0-1) of spawning a monster")] + [SerializeField] private float maxSpawnProbability = 0.5f; + + [Tooltip("How fast the probability increases per second")] + [SerializeField] private float probabilityIncreaseRate = 0.01f; + + [Tooltip("Force a spawn after this many seconds without spawns")] + [SerializeField] private float guaranteedSpawnTime = 30f; + + [Tooltip("Minimum time between monster spawns")] + [SerializeField] private float spawnCooldown = 5f; + + [Header("Scoring")] + [Tooltip("Base points for taking a picture")] + [SerializeField] private int basePoints = 100; + + [Tooltip("Additional points per depth unit")] + [SerializeField] private int depthMultiplier = 10; + + [Header("Surfacing")] + [Tooltip("Duration in seconds for speed transition when surfacing")] + [SerializeField] private float speedTransitionDuration = 2.0f; + + [Tooltip("Factor to multiply speed by when surfacing (usually 1.0 for same speed)")] + [SerializeField] private float surfacingSpeedFactor = 3.0f; + + [Tooltip("How long to continue spawning tiles after surfacing begins (seconds)")] + [SerializeField] private float surfacingSpawnDelay = 5.0f; + + [Header("Tile Generation")] + [Tooltip("Initial number of tiles to create at start")] + [SerializeField] private int initialTileCount = 3; + + [Tooltip("Buffer distance for spawning new tiles")] + [SerializeField] private float tileSpawnBuffer = 1f; + + [Tooltip("Base movement speed for tiles")] + [SerializeField] private float moveSpeed = 3f; + + [Tooltip("Factor to increase speed by each interval")] + [SerializeField] private float speedUpFactor = 0.2f; + + [Tooltip("Time interval between speed increases (seconds)")] + [SerializeField] private float speedUpInterval = 10f; + + [Tooltip("Maximum movement speed allowed")] + [SerializeField] private float maxMoveSpeed = 12f; + + [Tooltip("Interval for velocity calculations (seconds)")] + [SerializeField] private float velocityCalculationInterval = 0.5f; + + [Header("Obstacles")] + [Tooltip("Time interval between obstacle spawn attempts (in seconds)")] + [SerializeField] private float obstacleSpawnInterval = 2f; + + [Tooltip("Random variation in obstacle spawn timing (+/- seconds)")] + [SerializeField] private float obstacleSpawnIntervalVariation = 0.5f; + + [Tooltip("Maximum number of obstacle spawn position attempts before skipping")] + [SerializeField] private int obstacleMaxSpawnAttempts = 10; + + [Tooltip("Radius around obstacle spawn point to check for tile collisions")] + [SerializeField] private float obstacleSpawnCollisionRadius = 1f; + + [Tooltip("Minimum movement speed for spawned obstacles")] + [SerializeField] private float obstacleMinMoveSpeed = 1f; + + [Tooltip("Maximum movement speed for spawned obstacles")] + [SerializeField] private float obstacleMaxMoveSpeed = 4f; + + [Header("Collision Handling")] + [Tooltip("Duration in seconds of damage immunity after being hit")] + [SerializeField] private float damageImmunityDuration = 1.0f; + + [Tooltip("Force strength for impulse bumps - higher values push further toward center")] + [SerializeField] private float bumpForce = 5.0f; + + [Tooltip("Speed for smooth movement to center (units per second)")] + [SerializeField] private float smoothMoveSpeed = 8.0f; + + [Tooltip("Whether to block player input during bump movement")] + [SerializeField] private bool blockInputDuringBump = true; + + // IDivingMinigameSettings implementation - Basic Movement + public float LerpSpeed => lerpSpeed; + public float MaxOffset => maxOffset; + public float ClampXMin => clampXMin; + public float ClampXMax => clampXMax; + public float SpeedExponent => speedExponent; + + // IDivingMinigameSettings implementation - Player Movement + public float TapMaxDistance => tapMaxDistance; + public float TapDecelerationRate => tapDecelerationRate; + + // IDivingMinigameSettings implementation - Monster Spawning + public float BaseSpawnProbability => baseSpawnProbability; + public float MaxSpawnProbability => maxSpawnProbability; + public float ProbabilityIncreaseRate => probabilityIncreaseRate; + public float GuaranteedSpawnTime => guaranteedSpawnTime; + public float SpawnCooldown => spawnCooldown; + + // IDivingMinigameSettings implementation - Scoring + public int BasePoints => basePoints; + public int DepthMultiplier => depthMultiplier; + + // IDivingMinigameSettings implementation - Surfacing + public float SpeedTransitionDuration => speedTransitionDuration; + public float SurfacingSpeedFactor => surfacingSpeedFactor; + public float SurfacingSpawnDelay => surfacingSpawnDelay; + + // IDivingMinigameSettings implementation - Tile Generation + public int InitialTileCount => initialTileCount; + public float TileSpawnBuffer => tileSpawnBuffer; + public float MoveSpeed => moveSpeed; + public float SpeedUpFactor => speedUpFactor; + public float SpeedUpInterval => speedUpInterval; + public float MaxMoveSpeed => maxMoveSpeed; + public float VelocityCalculationInterval => velocityCalculationInterval; + + // IDivingMinigameSettings implementation - Obstacles + public float ObstacleSpawnInterval => obstacleSpawnInterval; + public float ObstacleSpawnIntervalVariation => obstacleSpawnIntervalVariation; + public int ObstacleMaxSpawnAttempts => obstacleMaxSpawnAttempts; + public float ObstacleSpawnCollisionRadius => obstacleSpawnCollisionRadius; + public float ObstacleMinMoveSpeed => obstacleMinMoveSpeed; + public float ObstacleMaxMoveSpeed => obstacleMaxMoveSpeed; + + // IDivingMinigameSettings implementation - Collision Handling + public float DamageImmunityDuration => damageImmunityDuration; + public float BumpForce => bumpForce; + public float SmoothMoveSpeed => smoothMoveSpeed; + public bool BlockInputDuringBump => blockInputDuringBump; + + public override void OnValidate() + { + base.OnValidate(); + + // Validate basic movement values + lerpSpeed = Mathf.Max(0.1f, lerpSpeed); + maxOffset = Mathf.Max(0.1f, maxOffset); + speedExponent = Mathf.Max(0.1f, speedExponent); + + // Ensure min is less than max for clamping + if (clampXMin >= clampXMax) + { + clampXMin = clampXMax - 0.1f; + } + + // Validate player movement + tapMaxDistance = Mathf.Max(0.01f, tapMaxDistance); + tapDecelerationRate = Mathf.Max(0.1f, tapDecelerationRate); + + // Validate probability values + baseSpawnProbability = Mathf.Clamp01(baseSpawnProbability); + maxSpawnProbability = Mathf.Clamp01(maxSpawnProbability); + probabilityIncreaseRate = Mathf.Max(0f, probabilityIncreaseRate); + + // Ensure max probability is at least base probability + if (maxSpawnProbability < baseSpawnProbability) + { + maxSpawnProbability = baseSpawnProbability; + } + + // Validate time values + guaranteedSpawnTime = Mathf.Max(0.1f, guaranteedSpawnTime); + spawnCooldown = Mathf.Max(0.1f, spawnCooldown); + speedTransitionDuration = Mathf.Max(0.1f, speedTransitionDuration); + surfacingSpawnDelay = Mathf.Max(0f, surfacingSpawnDelay); + + // Validate scoring + basePoints = Mathf.Max(0, basePoints); + depthMultiplier = Mathf.Max(0, depthMultiplier); + + // Validate tile generation + initialTileCount = Mathf.Max(1, initialTileCount); + tileSpawnBuffer = Mathf.Max(0f, tileSpawnBuffer); + moveSpeed = Mathf.Max(0.1f, moveSpeed); + speedUpFactor = Mathf.Max(0f, speedUpFactor); + speedUpInterval = Mathf.Max(0.1f, speedUpInterval); + maxMoveSpeed = Mathf.Max(moveSpeed, maxMoveSpeed); + velocityCalculationInterval = Mathf.Max(0.01f, velocityCalculationInterval); + + // Validate obstacle values + obstacleSpawnInterval = Mathf.Max(0.1f, obstacleSpawnInterval); + obstacleSpawnIntervalVariation = Mathf.Max(0f, obstacleSpawnIntervalVariation); + obstacleMaxSpawnAttempts = Mathf.Max(1, obstacleMaxSpawnAttempts); + obstacleSpawnCollisionRadius = Mathf.Max(0.1f, obstacleSpawnCollisionRadius); + obstacleMinMoveSpeed = Mathf.Max(0.1f, obstacleMinMoveSpeed); + obstacleMaxMoveSpeed = Mathf.Max(obstacleMinMoveSpeed, obstacleMaxMoveSpeed); + + // Validate collision settings + damageImmunityDuration = Mathf.Max(0.1f, damageImmunityDuration); + bumpForce = Mathf.Max(0.1f, bumpForce); + smoothMoveSpeed = Mathf.Max(0.1f, smoothMoveSpeed); + } + } +} diff --git a/Assets/Scripts/Core/Settings/DivingMinigameSettings.cs.meta b/Assets/Scripts/Core/Settings/DivingMinigameSettings.cs.meta new file mode 100644 index 00000000..b83ebe5d --- /dev/null +++ b/Assets/Scripts/Core/Settings/DivingMinigameSettings.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/InteractionSettings.cs b/Assets/Scripts/Core/Settings/InteractionSettings.cs new file mode 100644 index 00000000..8ba9664f --- /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/ItemConfigTypes.cs b/Assets/Scripts/Core/Settings/ItemConfigTypes.cs new file mode 100644 index 00000000..a97c4269 --- /dev/null +++ b/Assets/Scripts/Core/Settings/ItemConfigTypes.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Defines a rule for combining two items + /// + [System.Serializable] + public class CombinationRule + { + public PickupItemData itemA; + public PickupItemData itemB; + public GameObject resultPrefab; // The prefab to spawn as the result + } + + /// + /// Configuration for items that can be placed in slots + /// + [System.Serializable] + public class SlotItemConfig + { + public PickupItemData slotItem; // The slot object (SO reference) + public List allowedItems; + public List forbiddenItems; // Items that cannot be placed in this slot + } +} diff --git a/Assets/Scripts/Core/Settings/ItemConfigTypes.cs.meta b/Assets/Scripts/Core/Settings/ItemConfigTypes.cs.meta new file mode 100644 index 00000000..f55d2a7c --- /dev/null +++ b/Assets/Scripts/Core/Settings/ItemConfigTypes.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9f9547445fd84c7db30533b7ee9d81dd +timeCreated: 1758699048 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/LayerAttributes.cs b/Assets/Scripts/Core/Settings/LayerAttributes.cs new file mode 100644 index 00000000..a4b5fafa --- /dev/null +++ b/Assets/Scripts/Core/Settings/LayerAttributes.cs @@ -0,0 +1,20 @@ +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Attribute to indicate a field should be drawn using the layer selector dropdown + /// + public class LayerAttribute : PropertyAttribute + { + // No properties needed - this is a marker attribute + } + + /// + /// Attribute to indicate a field should be drawn using the layer mask selector dropdown + /// + public class LayerMaskAttribute : PropertyAttribute + { + // No properties needed - this is a marker attribute + } +} diff --git a/Assets/Scripts/Core/Settings/LayerAttributes.cs.meta b/Assets/Scripts/Core/Settings/LayerAttributes.cs.meta new file mode 100644 index 00000000..b4edc908 --- /dev/null +++ b/Assets/Scripts/Core/Settings/LayerAttributes.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7c64dbd728524f23bda766b57a388207 +timeCreated: 1758711688 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/MovementModeTypes.cs b/Assets/Scripts/Core/Settings/MovementModeTypes.cs new file mode 100644 index 00000000..8d16fb21 --- /dev/null +++ b/Assets/Scripts/Core/Settings/MovementModeTypes.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +namespace AppleHills.Core.Settings +{ + /// + /// Enum defining different movement modes for player movement + /// + public enum HoldMovementMode + { + Pathfinding, + Direct + } +} diff --git a/Assets/Scripts/Core/Settings/MovementModeTypes.cs.meta b/Assets/Scripts/Core/Settings/MovementModeTypes.cs.meta new file mode 100644 index 00000000..c5cdd71c --- /dev/null +++ b/Assets/Scripts/Core/Settings/MovementModeTypes.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b6b1454235ab476dae09e99238d6c7ce +timeCreated: 1758699033 \ 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..8e1d59a2 --- /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 HoldMovementMode defaultHoldMovementMode = 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 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..33da8d20 --- /dev/null +++ b/Assets/Scripts/Core/Settings/SettingsInterfaces.cs @@ -0,0 +1,98 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace AppleHills.Core.Settings +{ + /// + /// Interface for player and follower settings + /// + public interface IPlayerFollowerSettings + { + // Player settings + float MoveSpeed { get; } + float StopDistance { get; } + bool UseRigidbody { get; } + 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; } + List CombinationRules { get; } + List SlotItemConfigs { get; } + } + + /// + /// Interface for minigame settings + /// + public interface IDivingMinigameSettings + { + // Basic Movement + float LerpSpeed { get; } + float MaxOffset { get; } + float ClampXMin { get; } + float ClampXMax { get; } + float SpeedExponent { get; } + + // Player Movement + float TapMaxDistance { get; } + float TapDecelerationRate { get; } + + // Monster Spawning + float BaseSpawnProbability { get; } + float MaxSpawnProbability { get; } + float ProbabilityIncreaseRate { get; } + float GuaranteedSpawnTime { get; } + float SpawnCooldown { get; } + + // Scoring + int BasePoints { get; } + int DepthMultiplier { get; } + + // Surfacing + float SpeedTransitionDuration { get; } + float SurfacingSpeedFactor { get; } + float SurfacingSpawnDelay { get; } + + // Tile Generation + int InitialTileCount { get; } + float TileSpawnBuffer { get; } + float MoveSpeed { get; } + float SpeedUpFactor { get; } + float SpeedUpInterval { get; } + float MaxMoveSpeed { get; } + float VelocityCalculationInterval { get; } + + // Obstacles + float ObstacleSpawnInterval { get; } + float ObstacleSpawnIntervalVariation { get; } + int ObstacleMaxSpawnAttempts { get; } + float ObstacleSpawnCollisionRadius { get; } + float ObstacleMinMoveSpeed { get; } + float ObstacleMaxMoveSpeed { get; } + + // Collision Handling + float DamageImmunityDuration { get; } + float BumpForce { get; } + float SmoothMoveSpeed { get; } + bool BlockInputDuringBump { 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..9f9ac284 --- /dev/null +++ b/Assets/Scripts/Core/Settings/SettingsProvider.cs @@ -0,0 +1,100 @@ +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. + /// Uses synchronous loading to ensure settings are available immediately. + /// + 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 synchronously using Addressables - blocks until complete + /// + public T LoadSettingsSynchronous() where T : BaseSettings + { + string key = typeof(T).Name; + + // Return from cache if already loaded + if (_settingsCache.TryGetValue(key, out BaseSettings cachedSettings)) + { + return cachedSettings as T; + } + + // Load using Addressables synchronously + try + { + // WaitForCompletion blocks until the asset is loaded + T settings = Addressables.LoadAssetAsync($"Settings/{key}").WaitForCompletion(); + + if (settings != null) + { + _settingsCache[key] = settings; + return settings; + } + else + { + Debug.LogError($"Failed to load settings: {key}"); + } + } + catch (Exception e) + { + Debug.LogError($"Error loading settings {key}: {e.Message}"); + } + + return null; + } + + /// + /// Get cached settings or load them synchronously if not cached + /// + public T GetSettings() where T : BaseSettings + { + string key = typeof(T).Name; + + // Return from cache if already loaded + if (_settingsCache.TryGetValue(key, out BaseSettings cachedSettings)) + { + return cachedSettings as T; + } + + // Load synchronously if not in cache + return LoadSettingsSynchronous(); + } + } +} 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/Scripts/Core/SettingsAccess.cs b/Assets/Scripts/Core/SettingsAccess.cs new file mode 100644 index 00000000..9bf875a1 --- /dev/null +++ b/Assets/Scripts/Core/SettingsAccess.cs @@ -0,0 +1,57 @@ +using UnityEngine; + +namespace AppleHills +{ + /// + /// Unified access to settings in both editor and play mode + /// + public static class SettingsAccess + { + // Delegate type for editor-only settings providers + public delegate float GetSettingsValueDelegate(); + + // Static delegates that will be set by editor code + private static GetSettingsValueDelegate getPlayerStopDistanceProvider; + private static GetSettingsValueDelegate getPlayerStopDistanceDirectInteractionProvider; + + // Editor-only method to set up providers - will be called from editor code + public static void SetupEditorProviders( + GetSettingsValueDelegate playerStopDistanceProvider, + GetSettingsValueDelegate playerStopDistanceDirectInteractionProvider) + { + #if UNITY_EDITOR + if (!Application.isPlaying) + { + getPlayerStopDistanceProvider = playerStopDistanceProvider; + getPlayerStopDistanceDirectInteractionProvider = playerStopDistanceDirectInteractionProvider; + } + #endif + } + + public static float GetPlayerStopDistance() + { + #if UNITY_EDITOR + if (!Application.isPlaying && getPlayerStopDistanceProvider != null) + { + return getPlayerStopDistanceProvider(); + } + #endif + + return GameManager.Instance.PlayerStopDistance; + } + + public static float GetPlayerStopDistanceDirectInteraction() + { + #if UNITY_EDITOR + if (!Application.isPlaying && getPlayerStopDistanceDirectInteractionProvider != null) + { + return getPlayerStopDistanceDirectInteractionProvider(); + } + #endif + + return GameManager.Instance.PlayerStopDistanceDirectInteraction; + } + + // Add more methods as needed for other settings + } +} diff --git a/Assets/Scripts/Core/SettingsAccess.cs.meta b/Assets/Scripts/Core/SettingsAccess.cs.meta new file mode 100644 index 00000000..f6336315 --- /dev/null +++ b/Assets/Scripts/Core/SettingsAccess.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a23d841c0e2047ff8dbe84820227bdea +timeCreated: 1758634274 \ No newline at end of file diff --git a/Assets/Scripts/Input/PlayerTouchController.cs b/Assets/Scripts/Input/PlayerTouchController.cs index f79f9768..7b441228 100644 --- a/Assets/Scripts/Input/PlayerTouchController.cs +++ b/Assets/Scripts/Input/PlayerTouchController.cs @@ -1,5 +1,6 @@ using UnityEngine; using Pathfinding; +using AppleHills.Core.Settings; namespace Input { @@ -94,7 +95,7 @@ namespace Input Debug.Log($"[PlayerTouchController] OnHoldStart at {worldPosition}"); lastHoldPosition = worldPosition; isHolding = true; - if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding && + if (GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Pathfinding && aiPath != null) { aiPath.enabled = true; @@ -110,12 +111,12 @@ namespace Input /// /// Handles hold move input. Updates the target position for direct or pathfinding movement. - /// + /// /// public void OnHoldMove(Vector2 worldPosition) { if (!isHolding) return; lastHoldPosition = worldPosition; - if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct) + if (GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Direct) { if (aiPath != null && aiPath.enabled) aiPath.enabled = false; MoveDirectlyTo(worldPosition); @@ -132,7 +133,7 @@ namespace Input isHolding = false; directMoveVelocity = Vector3.zero; if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == - GameSettings.HoldMovementMode.Pathfinding) + HoldMovementMode.Pathfinding) { if (pathfindingDragCoroutine != null) { @@ -141,7 +142,7 @@ namespace Input } } - if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct) + if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Direct) { aiPath.enabled = false; } @@ -237,7 +238,7 @@ namespace Input { float normalizedSpeed = 0f; Vector3 velocity = Vector3.zero; - if (isHolding && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct) + if (isHolding && GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Direct) { normalizedSpeed = directMoveVelocity.magnitude / aiPath.maxSpeed; velocity = directMoveVelocity; @@ -260,7 +261,7 @@ namespace Input bool isCurrentlyMoving = false; // Check direct movement - if (isHolding && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct) + if (isHolding && GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Direct) { isCurrentlyMoving = directMoveVelocity.sqrMagnitude > 0.001f; } @@ -339,7 +340,7 @@ namespace Input interruptMoveTo = true; isHolding = false; directMoveVelocity = Vector3.zero; - if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && aiPath != null) + if (GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Direct && aiPath != null) aiPath.enabled = false; OnMoveToCancelled?.Invoke(); } diff --git a/Assets/Scripts/Interactions/Interactable.cs b/Assets/Scripts/Interactions/Interactable.cs index 7578b894..f39baed3 100644 --- a/Assets/Scripts/Interactions/Interactable.cs +++ b/Assets/Scripts/Interactions/Interactable.cs @@ -194,25 +194,9 @@ namespace Interactions /// void OnDrawGizmos() { - float playerStopDistance; - if (Application.isPlaying) - { - playerStopDistance = characterToInteract == CharacterToInteract.Trafalgar - ? GameManager.Instance.PlayerStopDistanceDirectInteraction - : GameManager.Instance.PlayerStopDistance; - } - else - { - // Load settings directly from asset path in editor - var settings = - UnityEditor.AssetDatabase.LoadAssetAtPath( - "Assets/Data/Settings/DefaultSettings.asset"); - playerStopDistance = settings != null - ? (characterToInteract == CharacterToInteract.Trafalgar - ? settings.playerStopDistanceDirectInteraction - : settings.playerStopDistance) - : 1.0f; - } + float playerStopDistance = characterToInteract == CharacterToInteract.Trafalgar + ? AppleHills.SettingsAccess.GetPlayerStopDistanceDirectInteraction() + : AppleHills.SettingsAccess.GetPlayerStopDistance(); Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, playerStopDistance); diff --git a/Assets/Scripts/Minigames/DivingForPictures/Bubbles/BubbleSpawner.cs b/Assets/Scripts/Minigames/DivingForPictures/Bubbles/BubbleSpawner.cs index 65823896..66aae15b 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Bubbles/BubbleSpawner.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Bubbles/BubbleSpawner.cs @@ -1,5 +1,6 @@ using UnityEngine; using Pooling; +using AppleHills.Core.Settings; namespace Minigames.DivingForPictures { @@ -10,25 +11,9 @@ namespace Minigames.DivingForPictures { public Bubble bubblePrefab; public Sprite[] bubbleSprites; // Assign in inspector - public float spawnInterval = 0.3f; - public Vector2 speedRange = new Vector2(0.5f, 2f); - public Vector2 scaleRange = new Vector2(0.3f, 0.7f); - public Vector2 wobbleSpeedRange = new Vector2(1f, 3f); - public Vector2 wobbleAmountRange = new Vector2(0.05f, 0.15f); - public float spawnXMin = -3.5f; - public float spawnXMax = 3.5f; - public float spawnY = -5f; - public float wobbleMinScale = 0.2f; - public float wobbleMaxScale = 1.2f; - [Header("Object Pooling")] - public bool useObjectPooling = true; - public int initialPoolSize = 10; - public int maxPoolSize = 30; - - [Header("Surfacing Settings")] - [Tooltip("Factor to multiply bubble speed by when surfacing (0.5 = half speed)")] - [SerializeField] private float surfacingSpeedFactor = 0.5f; + private DivingDeveloperSettings _devSettings; + private IDivingMinigameSettings _gameSettings; private float _timer; private float _nextSpawnInterval; @@ -40,14 +25,24 @@ namespace Minigames.DivingForPictures { _mainCamera = Camera.main; - if (useObjectPooling) + // Get developer settings and game settings + _devSettings = GameManager.GetDeveloperSettings(); + _gameSettings = GameManager.GetSettingsObject(); + + if (_devSettings == null) + { + Debug.LogError("[BubbleSpawner] Failed to load developer settings!"); + return; + } + + if (_devSettings.BubbleUseObjectPooling) { // Create the bubble pool GameObject poolGO = new GameObject("BubblePool"); poolGO.transform.SetParent(transform); _bubblePool = poolGO.AddComponent(); - _bubblePool.initialPoolSize = initialPoolSize; - _bubblePool.maxPoolSize = maxPoolSize; + _bubblePool.initialPoolSize = _devSettings.BubbleInitialPoolSize; + _bubblePool.maxPoolSize = _devSettings.BubbleMaxPoolSize; _bubblePool.Initialize(bubblePrefab); // Periodically check for pool statistics in debug builds @@ -80,7 +75,7 @@ namespace Minigames.DivingForPictures /// Randomized interval in seconds. float GetRandomizedInterval() { - return spawnInterval * Random.Range(0.8f, 1.2f); + return _devSettings.BubbleSpawnInterval * Random.Range(0.8f, 1.2f); } /// @@ -88,11 +83,11 @@ namespace Minigames.DivingForPictures /// void SpawnBubble() { - float x = Random.Range(spawnXMin, spawnXMax); - Vector3 spawnPos = new Vector3(x, spawnY, 0f); + float x = Random.Range(_devSettings.BubbleSpawnXMin, _devSettings.BubbleSpawnXMax); + Vector3 spawnPos = new Vector3(x, _devSettings.BubbleSpawnY, 0f); Bubble bubble; - if (useObjectPooling && _bubblePool != null) + if (_devSettings.BubbleUseObjectPooling && _bubblePool != null) { bubble = _bubblePool.GetBubble(); bubble.transform.position = spawnPos; @@ -103,25 +98,25 @@ namespace Minigames.DivingForPictures } // Randomize bubble properties - float baseSpeed = Random.Range(speedRange.x, speedRange.y); + float baseSpeed = Random.Range(_devSettings.BubbleSpeedRange.x, _devSettings.BubbleSpeedRange.y); // Apply surfacing speed reduction if needed if (_isSurfacing) { - bubble.speed = baseSpeed * surfacingSpeedFactor; + bubble.speed = baseSpeed * _devSettings.BubbleSurfacingSpeedFactor; } else { bubble.speed = baseSpeed; } - bubble.wobbleSpeed = Random.Range(wobbleSpeedRange.x, wobbleSpeedRange.y); + bubble.wobbleSpeed = Random.Range(_devSettings.BubbleWobbleSpeedRange.x, _devSettings.BubbleWobbleSpeedRange.y); // Set base scale (initial size) for the bubble - float baseScale = Random.Range(scaleRange.x, scaleRange.y); + float baseScale = Random.Range(_devSettings.BubbleScaleRange.x, _devSettings.BubbleScaleRange.y); bubble.SetBaseScale(baseScale); - // Assign random sprite to BubbleSprite (fixed naming from BottleSprite) + // Assign random sprite to BubbleSprite if (bubbleSprites != null && bubbleSprites.Length > 0) { Sprite randomSprite = bubbleSprites[Random.Range(0, bubbleSprites.Length)]; @@ -132,7 +127,7 @@ namespace Minigames.DivingForPictures bubble.transform.rotation = Quaternion.Euler(0f, 0f, Random.Range(0f, 360f)); // Pass min/max scale for wobble clamping - bubble.SetWobbleScaleLimits(wobbleMinScale, wobbleMaxScale); + bubble.SetWobbleScaleLimits(_devSettings.BubbleWobbleMinScale, _devSettings.BubbleWobbleMaxScale); } /// @@ -148,10 +143,10 @@ namespace Minigames.DivingForPictures Bubble[] activeBubbles = FindObjectsByType(FindObjectsSortMode.None); foreach (Bubble bubble in activeBubbles) { - bubble.speed *= surfacingSpeedFactor; + bubble.speed *= _devSettings.BubbleSurfacingSpeedFactor; } - Debug.Log($"[BubbleSpawner] Started surfacing mode. Bubbles slowed to {surfacingSpeedFactor * 100}% speed."); + Debug.Log($"[BubbleSpawner] Started surfacing mode. Bubbles slowed to {_devSettings.BubbleSurfacingSpeedFactor * 100}% speed."); } /// diff --git a/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs b/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs index d97285b4..9e7dce64 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using UnityEngine.Events; using UnityEngine.Playables; +using AppleHills.Core.Settings; namespace Minigames.DivingForPictures { @@ -13,40 +14,17 @@ namespace Minigames.DivingForPictures [Tooltip("Array of monster prefabs to spawn randomly")] [SerializeField] private GameObject[] monsterPrefabs; - [Header("Spawn Probability")] - [Tooltip("Base chance (0-1) of spawning a monster on each tile")] - [SerializeField] private float baseSpawnProbability = 0.2f; - [Tooltip("Maximum chance (0-1) of spawning a monster")] - [SerializeField] private float maxSpawnProbability = 0.5f; - [Tooltip("How fast the probability increases per second")] - [SerializeField] private float probabilityIncreaseRate = 0.01f; - - [Header("Spawn Timing")] - [Tooltip("Force a spawn after this many seconds without spawns")] - [SerializeField] private float guaranteedSpawnTime = 30f; - [Tooltip("Minimum time between monster spawns")] - [SerializeField] private float spawnCooldown = 5f; - - [Header("Scoring")] - [Tooltip("Base points for taking a picture")] - [SerializeField] private int basePoints = 100; - [Tooltip("Additional points per depth unit")] - [SerializeField] private int depthMultiplier = 10; - [Header("Rope Damage System")] [Tooltip("Ropes that will break one by one as player takes damage")] [SerializeField] private RopeBreaker[] playerRopes; [Header("Surfacing Settings")] - [Tooltip("Duration in seconds for speed transition when surfacing")] - [SerializeField] private float speedTransitionDuration = 2.0f; - [Tooltip("Factor to multiply speed by when surfacing (usually 1.0 for same speed)")] - [SerializeField] private float surfacingSpeedFactor = 3.0f; - [Tooltip("How long to continue spawning tiles after surfacing begins (seconds)")] - [SerializeField] private float surfacingSpawnDelay = 5.0f; [Tooltip("Reference to the PlayableDirector that will play the surfacing timeline")] [SerializeField] private PlayableDirector surfacingTimeline; + // Settings reference + private IDivingMinigameSettings _settings; + // Private state variables private int playerScore = 0; private float currentSpawnProbability; @@ -83,7 +61,15 @@ namespace Minigames.DivingForPictures private void Awake() { - currentSpawnProbability = baseSpawnProbability; + // Get settings from GameManager + _settings = GameManager.GetSettingsObject(); + if (_settings == null) + { + Debug.LogError("[DivingGameManager] Failed to load diving minigame settings!"); + } + + // Initialize with base probability from settings + currentSpawnProbability = _settings?.BaseSpawnProbability ?? 0.2f; } private void Start() @@ -118,10 +104,10 @@ namespace Minigames.DivingForPictures // Gradually increase spawn probability over time float previousProbability = currentSpawnProbability; - if (currentSpawnProbability < maxSpawnProbability) + if (currentSpawnProbability < _settings.MaxSpawnProbability) { - currentSpawnProbability += probabilityIncreaseRate * Time.deltaTime; - currentSpawnProbability = Mathf.Min(currentSpawnProbability, maxSpawnProbability); + currentSpawnProbability += _settings.ProbabilityIncreaseRate * Time.deltaTime; + currentSpawnProbability = Mathf.Min(currentSpawnProbability, _settings.MaxSpawnProbability); // Only fire event if probability changed significantly if (Mathf.Abs(currentSpawnProbability - previousProbability) > 0.01f) @@ -141,8 +127,8 @@ namespace Minigames.DivingForPictures // If we're surfacing, don't spawn new monsters if (_isSurfacing) return; - bool forceSpawn = timeSinceLastSpawn >= guaranteedSpawnTime; - bool onCooldown = timeSinceLastSpawn < spawnCooldown; + bool forceSpawn = timeSinceLastSpawn >= _settings.GuaranteedSpawnTime; + bool onCooldown = timeSinceLastSpawn < _settings.SpawnCooldown; // Don't spawn if on cooldown, unless forced if (onCooldown && !forceSpawn) return; @@ -159,7 +145,7 @@ namespace Minigames.DivingForPictures // Reset timer and adjust probability lastSpawnTime = Time.time; timeSinceLastSpawn = 0f; - currentSpawnProbability = baseSpawnProbability; + currentSpawnProbability = _settings.BaseSpawnProbability; OnSpawnProbabilityChanged?.Invoke(currentSpawnProbability); } } @@ -204,8 +190,8 @@ namespace Minigames.DivingForPictures private void OnMonsterPictureTaken(Monster monster) { // Calculate points based on depth - int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * depthMultiplier); - int pointsAwarded = basePoints + depthBonus; + int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * _settings.DepthMultiplier); + int pointsAwarded = _settings.BasePoints + depthBonus; // Add score playerScore += pointsAwarded; @@ -356,7 +342,7 @@ namespace Minigames.DivingForPictures _isSurfacing = true; // 1. Initiate smooth velocity transition to surfacing speed - float targetVelocityFactor = -1.0f * surfacingSpeedFactor; + float targetVelocityFactor = -1.0f * _settings.SurfacingSpeedFactor; SetVelocityFactor(targetVelocityFactor); // 2. Find and notify trench tile spawner about direction change (for spawning/despawning logic) @@ -404,10 +390,12 @@ namespace Minigames.DivingForPictures GameObject playerObject = GameObject.FindGameObjectWithTag("Player"); if (playerObject != null) { - // Disable all components except Transform and Animator on the player object (not its children) + // Disable all components except Transform, Animator, and PlayerBlinkBehavior on the player object foreach (Component component in playerObject.GetComponents()) { - if (!(component is Transform) && !(component is Animator)) + if (!(component is Transform) && + !(component is Animator) && + !(component is PlayerBlinkBehavior)) { if (component is Behaviour behaviour) { @@ -419,7 +407,7 @@ namespace Minigames.DivingForPictures // Start coroutine to reset X position to 0 over 1 second StartCoroutine(ResetPlayerPosition(playerObject.transform)); - Debug.Log("[DivingGameManager] Disabled player components (keeping Animator) and resetting position"); + Debug.Log("[DivingGameManager] Disabled player components (keeping Animator and PlayerBlinkBehavior) and resetting position"); } // 3. Find bubble spawner and slow down existing bubbles (no velocity management needed) @@ -525,7 +513,7 @@ namespace Minigames.DivingForPictures private IEnumerator SurfacingSequence() { // Wait for the configured delay - yield return new WaitForSeconds(surfacingSpawnDelay); + yield return new WaitForSeconds(_settings.SurfacingSpawnDelay); // Find tile spawner and tell it to stop spawning TrenchTileSpawner tileSpawner = FindFirstObjectByType(); @@ -594,10 +582,10 @@ namespace Minigames.DivingForPictures float startFactor = _currentVelocityFactor; float elapsed = 0f; - while (elapsed < speedTransitionDuration) + while (elapsed < _settings.SpeedTransitionDuration) { elapsed += Time.deltaTime; - float t = Mathf.Clamp01(elapsed / speedTransitionDuration); + float t = Mathf.Clamp01(elapsed / _settings.SpeedTransitionDuration); // Smooth step interpolation float smoothStep = t * t * (3f - 2f * t); diff --git a/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleSpawner.cs b/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleSpawner.cs index d34adc04..311c2e94 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleSpawner.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleSpawner.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using Pooling; +using AppleHills.Core.Settings; namespace Minigames.DivingForPictures { @@ -16,50 +17,17 @@ namespace Minigames.DivingForPictures [Tooltip("List of possible obstacle prefabs to spawn")] [SerializeField] private List obstaclePrefabs; - [Header("Spawn Settings")] - [Tooltip("Time interval between spawn attempts (in seconds)")] - [SerializeField] private float spawnInterval = 2f; - - [Tooltip("Random variation in spawn timing (+/- seconds)")] - [SerializeField] private float spawnIntervalVariation = 0.5f; - - [Tooltip("Maximum number of spawn position attempts before skipping")] - [SerializeField] private int maxSpawnAttempts = 10; - - [Tooltip("Radius around spawn point to check for tile collisions")] - [SerializeField] private float spawnCollisionRadius = 1f; - - [Header("Obstacle Properties Randomization")] - [Tooltip("Minimum movement speed for spawned obstacles")] - [SerializeField] private float minMoveSpeed = 1f; - - [Tooltip("Maximum movement speed for spawned obstacles")] - [SerializeField] private float maxMoveSpeed = 4f; - - [Header("Object Pooling")] - [Tooltip("Whether to use object pooling for obstacles")] - [SerializeField] private bool useObjectPooling = true; - - [Tooltip("Maximum objects per prefab type in pool")] - [SerializeField] private int maxPerPrefabPoolSize = 3; - - [Tooltip("Total maximum pool size across all prefab types")] - [SerializeField] private int totalMaxPoolSize = 15; - - [Header("Layer Settings")] - [Tooltip("Layer mask for tile collision detection during spawn position validation")] - [SerializeField] private LayerMask tileLayerMask = -1; // Let user configure which layers to avoid - - [Tooltip("Target layer for spawned obstacles - obstacles will be placed on this layer")] - [SerializeField] private int obstacleLayer = 11; // Default to layer 11, but configurable - [Header("Events")] - [Tooltip("Called when an obstacle is spawned")] + [Tooltip("Invoked when a new obstacle is spawned")] public UnityEvent onObstacleSpawned; - [Tooltip("Called when an obstacle is returned to pool")] + [Tooltip("Invoked when an obstacle is destroyed or returned to pool")] public UnityEvent onObstacleDestroyed; + // Settings references + private IDivingMinigameSettings _settings; + private DivingDeveloperSettings _devSettings; + // Private fields private ObstaclePool _obstaclePool; private Camera _mainCamera; @@ -75,13 +43,34 @@ namespace Minigames.DivingForPictures { _mainCamera = Camera.main; + // Get settings from GameManager + _settings = GameManager.GetSettingsObject(); + _devSettings = GameManager.GetDeveloperSettings(); + + if (_settings == null) + { + Debug.LogError("[ObstacleSpawner] Failed to load diving minigame settings!"); + } + + if (_devSettings == null) + { + Debug.LogError("[ObstacleSpawner] Failed to load diving developer settings!"); + } + // Validate obstacle prefabs ValidateObstaclePrefabs(); - if (useObjectPooling) + if (_devSettings?.ObstacleUseObjectPooling ?? false) { InitializeObjectPool(); } + + // Initialize events if null + if (onObstacleSpawned == null) + onObstacleSpawned = new UnityEvent(); + + if (onObstacleDestroyed == null) + onObstacleDestroyed = new UnityEvent(); } private void Start() @@ -100,6 +89,8 @@ namespace Minigames.DivingForPictures /// private void ValidateObstaclePrefabs() { + if (_devSettings == null) return; + for (int i = 0; i < obstaclePrefabs.Count; i++) { if (obstaclePrefabs[i] == null) continue; @@ -112,10 +103,10 @@ namespace Minigames.DivingForPictures } // Ensure the prefab is on the correct layer (using configurable obstacleLayer) - if (obstaclePrefabs[i].layer != obstacleLayer) + if (obstaclePrefabs[i].layer != _devSettings.ObstacleLayer) { - Debug.LogWarning($"Obstacle prefab {obstaclePrefabs[i].name} is not on the configured obstacle layer ({obstacleLayer}). Setting layer automatically."); - SetLayerRecursively(obstaclePrefabs[i], obstacleLayer); + Debug.LogWarning($"Obstacle prefab {obstaclePrefabs[i].name} is not on the configured obstacle layer ({_devSettings.ObstacleLayer}). Setting layer automatically."); + SetLayerRecursively(obstaclePrefabs[i], _devSettings.ObstacleLayer); } } } @@ -142,8 +133,8 @@ namespace Minigames.DivingForPictures _obstaclePool = poolGO.AddComponent(); // Set up pool configuration - _obstaclePool.maxPerPrefabPoolSize = maxPerPrefabPoolSize; - _obstaclePool.totalMaxPoolSize = totalMaxPoolSize; + _obstaclePool.maxPerPrefabPoolSize = _devSettings.ObstacleMaxPerPrefabPoolSize; + _obstaclePool.totalMaxPoolSize = _devSettings.ObstacleTotalMaxPoolSize; // Convert GameObject list to FloatingObstacle list List prefabObstacles = new List(obstaclePrefabs.Count); @@ -230,7 +221,9 @@ namespace Minigames.DivingForPictures while (true) { // Calculate next spawn time with variation - float nextSpawnTime = spawnInterval + Random.Range(-spawnIntervalVariation, spawnIntervalVariation); + float nextSpawnTime = _settings.ObstacleSpawnInterval + + Random.Range(-_settings.ObstacleSpawnIntervalVariation, + _settings.ObstacleSpawnIntervalVariation); nextSpawnTime = Mathf.Max(0.1f, nextSpawnTime); // Ensure minimum interval yield return new WaitForSeconds(nextSpawnTime); @@ -264,7 +257,7 @@ namespace Minigames.DivingForPictures bool foundValidPosition = false; // Try to find a valid spawn position - for (int attempts = 0; attempts < maxSpawnAttempts; attempts++) + for (int attempts = 0; attempts < _settings.ObstacleMaxSpawnAttempts; attempts++) { spawnPosition = GetRandomSpawnPosition(); @@ -277,13 +270,13 @@ namespace Minigames.DivingForPictures } else { - Debug.Log($"[ObstacleSpawner] Position {spawnPosition} invalid (attempt {attempts + 1}/{maxSpawnAttempts})"); + Debug.Log($"[ObstacleSpawner] Position {spawnPosition} invalid (attempt {attempts + 1}/{_settings.ObstacleMaxSpawnAttempts})"); } } if (!foundValidPosition) { - Debug.LogWarning($"[ObstacleSpawner] SPAWN MISSED: Could not find valid spawn position after {maxSpawnAttempts} attempts at {Time.time:F2}"); + Debug.LogWarning($"[ObstacleSpawner] SPAWN MISSED: Could not find valid spawn position after {_settings.ObstacleMaxSpawnAttempts} attempts at {Time.time:F2}"); } } @@ -305,8 +298,9 @@ namespace Minigames.DivingForPictures /// private bool IsValidSpawnPosition(Vector3 position) { - // Use OverlapCircle to check for collisions with tiles - Collider2D collision = Physics2D.OverlapCircle(position, spawnCollisionRadius, tileLayerMask); + // Use OverlapCircle to check for collisions with tiles using just the layer + // Convert the single layer to a layer mask inline (1 << layerNumber) + Collider2D collision = Physics2D.OverlapCircle(position, _settings.ObstacleSpawnCollisionRadius, 1 << _devSettings.TrenchTileLayer); return collision == null; } @@ -330,7 +324,7 @@ namespace Minigames.DivingForPictures GameObject obstacle; // Spawn using pool or instantiate directly - if (useObjectPooling && _obstaclePool != null) + if (_devSettings.ObstacleUseObjectPooling && _obstaclePool != null) { Debug.Log($"[ObstacleSpawner] Requesting obstacle from pool (prefab index {prefabIndex})"); obstacle = _obstaclePool.GetObstacle(prefabIndex); @@ -416,8 +410,10 @@ namespace Minigames.DivingForPictures // Set prefab index obstacleComponent.PrefabIndex = prefabIndex; - // Randomize properties - obstacleComponent.MoveSpeed = Random.Range(minMoveSpeed, maxMoveSpeed); + // Randomize properties using settings + obstacleComponent.MoveSpeed = Random.Range( + _settings.ObstacleMinMoveSpeed, + _settings.ObstacleMaxMoveSpeed); // Set spawner reference (since FloatingObstacle has this built-in now) obstacleComponent.SetSpawner(this); @@ -440,7 +436,7 @@ namespace Minigames.DivingForPictures onObstacleDestroyed?.Invoke(obstacle); // Return to pool or destroy - if (useObjectPooling && _obstaclePool != null) + if (_devSettings.ObstacleUseObjectPooling && _obstaclePool != null) { Debug.Log($"[ObstacleSpawner] Returning {obstacle.name} to pool"); _obstaclePool.ReturnObstacle(obstacle, prefabIndex); @@ -457,7 +453,9 @@ namespace Minigames.DivingForPictures /// public void SetSpawnInterval(float interval) { - spawnInterval = interval; + // This method can no longer directly modify the settings + // Consider implementing a runtime settings override system if needed + Debug.LogWarning("[ObstacleSpawner] SetSpawnInterval no longer modifies settings directly. Settings are now centralized."); } /// @@ -465,8 +463,9 @@ namespace Minigames.DivingForPictures /// public void SetSpeedRange(float min, float max) { - minMoveSpeed = min; - maxMoveSpeed = max; + // This method can no longer directly modify the settings + // Consider implementing a runtime settings override system if needed + Debug.LogWarning("[ObstacleSpawner] SetSpeedRange no longer modifies settings directly. Settings are now centralized."); } /// @@ -534,8 +533,8 @@ namespace Minigames.DivingForPictures #if UNITY_EDITOR private void OnDrawGizmosSelected() { - // Only draw if screen bounds have been calculated - if (_spawnRangeX > 0f) + // Only draw if screen bounds have been calculated and settings are available + if (_spawnRangeX > 0f && _settings != null) { // Draw spawn area using dynamic calculations Gizmos.color = Color.yellow; @@ -545,7 +544,7 @@ namespace Minigames.DivingForPictures // Draw collision radius at spawn point Gizmos.color = Color.red; - Gizmos.DrawWireSphere(center, spawnCollisionRadius); + Gizmos.DrawWireSphere(center, _settings.ObstacleSpawnCollisionRadius); } } #endif diff --git a/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleCollision.cs b/Assets/Scripts/Minigames/DivingForPictures/Player/ObstacleCollision.cs similarity index 51% rename from Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleCollision.cs rename to Assets/Scripts/Minigames/DivingForPictures/Player/ObstacleCollision.cs index 30eaf7ee..fb333584 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleCollision.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Player/ObstacleCollision.cs @@ -8,8 +8,34 @@ namespace Minigames.DivingForPictures /// public class ObstacleCollision : PlayerCollisionBehavior { + protected override void OnEnable() + { + base.OnEnable(); + + // Subscribe to immunity events + OnImmunityStarted += HandleImmunityStarted; + OnImmunityEnded += HandleImmunityEnded; + } + + protected override void OnDisable() + { + // Unsubscribe from immunity events + OnImmunityStarted -= HandleImmunityStarted; + OnImmunityEnded -= HandleImmunityEnded; + + base.OnDisable(); + } + protected override void HandleCollisionResponse(Collider2D obstacle) { + // Check if the obstacle is on the ObstacleLayer + if (obstacle.gameObject.layer != _devSettings.ObstacleLayer) + { + // If not on the obstacle layer, don't process the collision + Debug.Log($"[ObstacleCollision] Ignored collision with object on layer {obstacle.gameObject.layer} (expected {_devSettings.ObstacleLayer})"); + return; + } + // Mark the obstacle as having dealt damage to prevent multiple hits FloatingObstacle obstacleComponent = obstacle.GetComponent(); if (obstacleComponent != null) @@ -21,21 +47,20 @@ namespace Minigames.DivingForPictures } /// - /// Override to prevent input blocking during damage immunity - /// Since obstacles pass through the player, we don't want to block input + /// Handler for immunity started event - replaces OnImmunityStart method /// - protected override void OnImmunityStart() + private void HandleImmunityStarted() { - Debug.Log($"[ObstacleCollision] Damage immunity started for {damageImmunityDuration} seconds"); + Debug.Log($"[ObstacleCollision] Damage immunity started for {_gameSettings.DamageImmunityDuration} seconds"); // Don't block input for obstacle damage - let player keep moving // The shared immunity system will handle the collision prevention } /// - /// Override to handle immunity end + /// Handler for immunity ended event - replaces OnImmunityEnd method /// - protected override void OnImmunityEnd() + private void HandleImmunityEnded() { Debug.Log($"[ObstacleCollision] Damage immunity ended"); // No special handling needed - shared immunity system handles collider re-enabling diff --git a/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleCollision.cs.meta b/Assets/Scripts/Minigames/DivingForPictures/Player/ObstacleCollision.cs.meta similarity index 100% rename from Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleCollision.cs.meta rename to Assets/Scripts/Minigames/DivingForPictures/Player/ObstacleCollision.cs.meta diff --git a/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerBlinkBehavior.cs b/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerBlinkBehavior.cs index 022a779b..ca62c174 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerBlinkBehavior.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerBlinkBehavior.cs @@ -1,5 +1,6 @@ using UnityEngine; using System.Collections; +using AppleHills.Core.Settings; namespace Minigames.DivingForPictures { @@ -9,28 +10,27 @@ namespace Minigames.DivingForPictures /// public class PlayerBlinkBehavior : MonoBehaviour { - [Header("Blink Settings")] - [Tooltip("Color to blink to when taking damage (typically red for damage indication)")] - [SerializeField] private Color damageBlinkColor = Color.red; - - [Tooltip("How fast to blink between normal and damage colors (seconds between color changes)")] - [SerializeField] private float blinkRate = 0.15f; - - [Tooltip("Alpha value for the damage color (0 = transparent, 1 = opaque)")] - [Range(0f, 1f)] - [SerializeField] private float damageColorAlpha = 0.7f; - [Header("References")] [Tooltip("The SpriteRenderer to apply blink effects to (auto-assigned if empty)")] [SerializeField] private SpriteRenderer targetSpriteRenderer; + // Developer settings reference + private DivingDeveloperSettings _devSettings; + private bool _isBlinking; private bool _isShowingDamageColor; private Coroutine _blinkCoroutine; - private Color _originalColor; // Missing field declaration + private Color _originalColor; private void Awake() { + // Get developer settings + _devSettings = GameManager.GetDeveloperSettings(); + if (_devSettings == null) + { + Debug.LogError("[PlayerBlinkBehavior] Failed to load developer settings!"); + } + // Auto-assign sprite renderer if not set if (targetSpriteRenderer == null) { @@ -51,192 +51,101 @@ namespace Minigames.DivingForPictures return; } - // Store original color + // Store the original color _originalColor = targetSpriteRenderer.color; - - // Ensure damage color has the correct alpha - damageBlinkColor.a = damageColorAlpha; } - + private void OnEnable() { - // Subscribe to immunity events (renamed from damage events) - PlayerCollisionBehavior.OnImmunityStarted += StartBlinking; - PlayerCollisionBehavior.OnImmunityEnded += StopBlinking; + // Subscribe to damage events + PlayerCollisionBehavior.OnDamageTaken += StartBlinkEffect; } - + private void OnDisable() { - // Unsubscribe from immunity events - PlayerCollisionBehavior.OnImmunityStarted -= StartBlinking; - PlayerCollisionBehavior.OnImmunityEnded -= StopBlinking; + // Unsubscribe to prevent memory leaks + PlayerCollisionBehavior.OnDamageTaken -= StartBlinkEffect; - // Stop any ongoing blink effect if (_blinkCoroutine != null) { StopCoroutine(_blinkCoroutine); _blinkCoroutine = null; } - // Restore original color - RestoreOriginalColor(); - } - - /// - /// Starts the red blinking effect when damage begins - /// - private void StartBlinking() - { - if (targetSpriteRenderer == null) return; - - Debug.Log("[PlayerBlinkBehavior] Starting damage blink effect"); - - // Stop any existing blink coroutine - if (_blinkCoroutine != null) - { - StopCoroutine(_blinkCoroutine); - } - - _isBlinking = true; - _isShowingDamageColor = false; - - // Start the blink coroutine - _blinkCoroutine = StartCoroutine(BlinkCoroutine()); - } - - /// - /// Stops the blinking effect when damage ends - /// - private void StopBlinking() - { - Debug.Log("[PlayerBlinkBehavior] Stopping damage blink effect"); - - _isBlinking = false; - - // Stop the blink coroutine - if (_blinkCoroutine != null) - { - StopCoroutine(_blinkCoroutine); - _blinkCoroutine = null; - } - - // Restore original color - RestoreOriginalColor(); - } - - /// - /// Coroutine that handles the blinking animation - /// - private IEnumerator BlinkCoroutine() - { - while (_isBlinking && targetSpriteRenderer != null) - { - // Toggle between original and damage colors - if (_isShowingDamageColor) - { - targetSpriteRenderer.color = _originalColor; - _isShowingDamageColor = false; - } - else - { - targetSpriteRenderer.color = damageBlinkColor; - _isShowingDamageColor = true; - } - - // Wait for blink interval - yield return new WaitForSeconds(blinkRate); - } - } - - /// - /// Restores the sprite renderer to its original color - /// - private void RestoreOriginalColor() - { + // Restore original color if disabled during blinking if (targetSpriteRenderer != null) { targetSpriteRenderer.color = _originalColor; - _isShowingDamageColor = false; } } - + /// - /// Updates the original color reference (useful if sprite color changes during gameplay) + /// Start the blinking effect coroutine /// - public void UpdateOriginalColor() + private void StartBlinkEffect() { - if (targetSpriteRenderer != null && !_isBlinking) - { - _originalColor = targetSpriteRenderer.color; - } - } - - /// - /// Public method to change blink color at runtime - /// - public void SetDamageBlinkColor(Color newColor) - { - damageBlinkColor = newColor; - damageBlinkColor.a = damageColorAlpha; - } - - /// - /// Public method to change blink rate at runtime - /// - public void SetBlinkRate(float rate) - { - blinkRate = rate; - } - - /// - /// Check if currently blinking - /// - public bool IsBlinking => _isBlinking; - - /// - /// Manually trigger blink effect (useful for testing or other damage sources) - /// - public void TriggerBlink(float duration) - { - if (_blinkCoroutine != null) + if (targetSpriteRenderer == null || _devSettings == null) return; + + // If already blinking, stop the current coroutine + if (_isBlinking && _blinkCoroutine != null) { StopCoroutine(_blinkCoroutine); } - StartCoroutine(ManualBlinkCoroutine(duration)); + // Start a new blink coroutine + _blinkCoroutine = StartCoroutine(BlinkCoroutine()); } - + /// - /// Coroutine for manually triggered blink effects + /// Coroutine that handles the blink effect timing /// - private IEnumerator ManualBlinkCoroutine(float duration) + private IEnumerator BlinkCoroutine() { _isBlinking = true; _isShowingDamageColor = false; - float elapsed = 0f; + // Create damage color with configured alpha + Color damageColor = _devSettings.PlayerBlinkDamageColor; + damageColor.a = _devSettings.PlayerDamageColorAlpha; - while (elapsed < duration && targetSpriteRenderer != null) + // Wait for immunity to end + PlayerCollisionBehavior.OnImmunityEnded += StopBlinking; + + // Blink while immunity is active + while (_isBlinking) { - // Toggle between original and damage colors + // Toggle between original and damage color if (_isShowingDamageColor) { targetSpriteRenderer.color = _originalColor; - _isShowingDamageColor = false; } else { - targetSpriteRenderer.color = damageBlinkColor; - _isShowingDamageColor = true; + targetSpriteRenderer.color = damageColor; } - yield return new WaitForSeconds(blinkRate); - elapsed += blinkRate; + _isShowingDamageColor = !_isShowingDamageColor; + + // Wait for next blink + yield return new WaitForSeconds(_devSettings.PlayerBlinkRate); } - // Ensure we end with original color - RestoreOriginalColor(); + // Restore original color when done blinking + targetSpriteRenderer.color = _originalColor; + } + + /// + /// Called when immunity ends to stop the blinking effect + /// + private void StopBlinking() + { + // Unsubscribe from the event to avoid memory leaks + PlayerCollisionBehavior.OnImmunityEnded -= StopBlinking; + _isBlinking = false; + + // No need to stop the coroutine, it will exit naturally + // This avoids race conditions if immunity ends during a blink cycle } } } diff --git a/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerCollisionBehavior.cs b/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerCollisionBehavior.cs index d7c7a3be..02e2793c 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerCollisionBehavior.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerCollisionBehavior.cs @@ -1,6 +1,8 @@ using UnityEngine; using System; using System.Collections; +using AppleHills.Core.Settings; +using AppleHills.Utilities; namespace Minigames.DivingForPictures { @@ -10,17 +12,6 @@ namespace Minigames.DivingForPictures /// public abstract class PlayerCollisionBehavior : MonoBehaviour { - [Header("Collision Settings")] - [Tooltip("Duration in seconds of damage immunity after being hit")] - [SerializeField] protected float damageImmunityDuration = 1.0f; - - [Tooltip("Layer mask for obstacle detection - configure which layers contain obstacles")] - [SerializeField] protected LayerMask obstacleLayerMask = -1; - - [Header("Input Blocking")] - [Tooltip("Whether to block player input during damage immunity period")] - [SerializeField] protected bool blockInputDuringImmunity; - [Header("References")] [Tooltip("The player character GameObject (auto-assigned if empty)")] [SerializeField] protected GameObject playerCharacter; @@ -28,11 +19,16 @@ namespace Minigames.DivingForPictures [Tooltip("Reference to the PlayerController component (auto-assigned if empty)")] [SerializeField] protected PlayerController playerController; + // Settings references + protected IDivingMinigameSettings _gameSettings; + protected DivingDeveloperSettings _devSettings; + // Static shared immunity state across all collision behaviors private static bool _isGloballyImmune; private static Coroutine _globalImmunityCoroutine; private static MonoBehaviour _coroutineRunner; private static Collider2D _sharedPlayerCollider; + private static bool wasInputBlocked = false; // Track if input was blocked // Events for immunity and damage state changes public static event Action OnImmunityStarted; @@ -67,214 +63,175 @@ namespace Minigames.DivingForPictures OnDamageTaken?.Invoke(); } - protected bool wasInputBlocked; - - protected virtual void Awake() + /// + /// Called when the component is enabled + /// + protected virtual void OnEnable() { - // Auto-assign if not set in inspector + _allInstances.Add(this); + + // Auto-assign references if needed if (playerCharacter == null) playerCharacter = gameObject; if (playerController == null) playerController = GetComponent(); - // Set up shared collider reference (only once) + // Initialize the shared player collider if not already set if (_sharedPlayerCollider == null) { _sharedPlayerCollider = GetComponent(); if (_sharedPlayerCollider == null) { - _sharedPlayerCollider = GetComponentInChildren(); - if (_sharedPlayerCollider != null) - { - Debug.Log($"[PlayerCollisionBehavior] Found collider on child object: {_sharedPlayerCollider.gameObject.name}"); - } - } - - if (_sharedPlayerCollider == null) - { - Debug.LogError($"[PlayerCollisionBehavior] No Collider2D found on this GameObject or its children!"); + Debug.LogError("[PlayerCollisionBehavior] No Collider2D found on this GameObject!"); } } + + // Load settings + _gameSettings = GameManager.GetSettingsObject(); + _devSettings = GameManager.GetDeveloperSettings(); + + if (_gameSettings == null) + { + Debug.LogError("[PlayerCollisionBehavior] Failed to load game settings!"); + } + + if (_devSettings == null) + { + Debug.LogError("[PlayerCollisionBehavior] Failed to load developer settings!"); + } + } - // Set up coroutine runner (use first instance) + /// + /// Called when the component is disabled + /// + protected virtual void OnDisable() + { + _allInstances.Remove(this); + } + + /// + /// Called when a Collider enters the trigger + /// + protected virtual void OnTriggerEnter2D(Collider2D other) + { + // Don't process collisions if already immune + if (_isGloballyImmune) + return; + + // Use our extension method to check if the collider's layer is in the obstacle layer mask + if (_devSettings.PlayerObstacleLayerMask.Contains(other.gameObject)) + { + HandleObstacleCollision(other); + } + } + + /// + /// Process collision with an obstacle + /// + protected virtual void HandleObstacleCollision(Collider2D obstacle) + { + // Trigger global damage and start immunity + TriggerDamageAndImmunity(); + + // Call the specific collision response for the derived class + HandleCollisionResponse(obstacle); + } + + /// + /// Abstract method for derived classes to implement specific collision responses + /// + protected abstract void HandleCollisionResponse(Collider2D obstacle); + + /// + /// Trigger damage event and start immunity period + /// + protected virtual void TriggerDamageAndImmunity() + { + // Make sure we're not already in immunity period + if (_isGloballyImmune) + return; + + // Trigger damage event for all listeners (like visual effects) + OnDamageTaken?.Invoke(); + + // Start immunity period + StartImmunity(); + } + + /// + /// Start the immunity period for all collision behaviors + /// + protected virtual void StartImmunity() + { + // Don't start if already immune + if (_isGloballyImmune) + return; + + // Set global immune state + _isGloballyImmune = true; + + // Store this instance to run the coroutine if needed if (_coroutineRunner == null) { _coroutineRunner = this; } - // Register this instance - _allInstances.Add(this); - } - - private void OnDestroy() - { - // Unregister this instance - _allInstances.Remove(this); - - // Clean up static references if this was the coroutine runner - if (_coroutineRunner == this) + // Block input if configured + if (_devSettings.BlockInputDuringImmunity && playerController != null) { - if (_globalImmunityCoroutine != null) - { - StopCoroutine(_globalImmunityCoroutine); - _globalImmunityCoroutine = null; - } - _coroutineRunner = null; - - // Find a new coroutine runner if there are other instances - foreach (var instance in _allInstances) - { - if (instance != null) - { - _coroutineRunner = instance; - break; - } - } - } - } - - /// - /// Called when another collider enters this trigger collider - /// - /// The other collider that entered the trigger - private void OnTriggerEnter2D(Collider2D other) - { - Debug.Log($"[{GetType().Name}] OnTriggerEnter2D called with collider: {other.gameObject.name} on layer: {other.gameObject.layer}"); - - // Check if the other collider is on one of our obstacle layers and we're not immune - if (IsObstacleLayer(other.gameObject.layer) && !_isGloballyImmune) - { - OnCollisionDetected(other); - } - } - - /// - /// Called when a collision with an obstacle is detected - /// - /// The obstacle collider that was hit - protected virtual void OnCollisionDetected(Collider2D obstacle) - { - if (_isGloballyImmune) return; - - // Trigger damage taken event first - OnDamageTaken?.Invoke(); - - // Start shared immunity period - StartGlobalImmunity(); - - // Call the specific collision response - HandleCollisionResponse(obstacle); - } - - /// - /// Starts the shared immunity period across all collision behaviors - /// - private void StartGlobalImmunity() - { - if (_isGloballyImmune) return; // Already immune - - _isGloballyImmune = true; - - // Disable the shared collider to prevent further collisions - if (_sharedPlayerCollider != null) - { - _sharedPlayerCollider.enabled = false; + // Notify player controller to block input + BlockPlayerInput(); + wasInputBlocked = true; } - // Stop any existing immunity coroutine + // Trigger event for all listeners + OnImmunityStarted?.Invoke(); + + // Stop existing coroutine if one is running if (_globalImmunityCoroutine != null && _coroutineRunner != null) { _coroutineRunner.StopCoroutine(_globalImmunityCoroutine); } - // Start new immunity coroutine - if (_coroutineRunner != null) - { - _globalImmunityCoroutine = _coroutineRunner.StartCoroutine(ImmunityCoroutine()); - } - - // Notify all instances about immunity start - foreach (var instance in _allInstances) - { - if (instance != null) - { - instance.OnImmunityStart(); - } - } - - // Broadcast immunity start event - OnImmunityStarted?.Invoke(); + // Start immunity timer coroutine on this instance + _globalImmunityCoroutine = StartCoroutine(ImmunityTimerCoroutine()); } /// - /// Coroutine that handles the immunity timer + /// Coroutine to handle the immunity duration timer /// - private IEnumerator ImmunityCoroutine() + private IEnumerator ImmunityTimerCoroutine() { - Debug.Log($"[PlayerCollisionBehavior] Starting immunity coroutine for {damageImmunityDuration} seconds"); - - yield return new WaitForSeconds(damageImmunityDuration); - - Debug.Log($"[PlayerCollisionBehavior] Immunity period ended"); - - // End immunity + // Wait for the immunity duration + yield return new WaitForSeconds(_gameSettings.DamageImmunityDuration); + + // Reset immunity state _isGloballyImmune = false; - _globalImmunityCoroutine = null; - // Re-enable the shared collider - if (_sharedPlayerCollider != null) - { - _sharedPlayerCollider.enabled = true; - } - - // Notify all instances about immunity end - foreach (var instance in _allInstances) - { - if (instance != null) - { - instance.OnImmunityEnd(); - } - } - - // Broadcast immunity end event - OnImmunityEnded?.Invoke(); - } - - /// - /// Override this method to implement specific collision response behavior - /// - /// The obstacle that was collided with - protected abstract void HandleCollisionResponse(Collider2D obstacle); - - /// - /// Called when damage immunity starts (called on all instances) - /// - protected virtual void OnImmunityStart() - { - Debug.Log($"[{GetType().Name}] Damage immunity started for {damageImmunityDuration} seconds"); - - // Block input if specified - if (blockInputDuringImmunity) - { - BlockPlayerInput(); - } - } - - /// - /// Called when damage immunity ends (called on all instances) - /// - protected virtual void OnImmunityEnd() - { - Debug.Log($"[{GetType().Name}] Damage immunity ended"); - - // Restore input if it was blocked - if (wasInputBlocked) + // Restore player input if it was blocked + if (_devSettings.BlockInputDuringImmunity) { RestorePlayerInput(); } - } + // Trigger event for all listeners + OnImmunityEnded?.Invoke(); + } + + /// + /// Blocks player input during immunity + /// + protected virtual void BlockPlayerInput() + { + if (playerController != null && playerController.enabled) + { + playerController.enabled = false; + wasInputBlocked = true; + Debug.Log($"[{GetType().Name}] Player input blocked during immunity"); + } + } + /// /// Restores player input after immunity /// @@ -291,22 +248,9 @@ namespace Minigames.DivingForPictures Debug.Log($"[{GetType().Name}] Player input restored after immunity"); } } - + /// - /// Blocks player input during immunity - /// - protected virtual void BlockPlayerInput() - { - if (playerController != null && playerController.enabled) - { - playerController.enabled = false; - wasInputBlocked = true; - Debug.Log($"[{GetType().Name}] Player input blocked during immunity"); - } - } - - /// - /// Updates the PlayerController's internal target to match current position + /// Updates the player controller's target position to the current position to prevent snapping /// protected virtual void UpdateControllerTarget() { @@ -322,50 +266,5 @@ namespace Minigames.DivingForPictures } } } - - /// - /// Checks if the given layer is included in our obstacle layer mask - /// - /// The layer to check - /// True if the layer is included in the obstacle layer mask - private bool IsObstacleLayer(int layer) - { - return (obstacleLayerMask.value & (1 << layer)) != 0; - } - - /// - /// Public property to check if player is currently immune (shared across all instances) - /// - public static bool IsImmune => _isGloballyImmune; - - /// - /// Public method to manually end immunity (affects all collision behaviors) - /// - public static void EndImmunity() - { - if (_isGloballyImmune && _globalImmunityCoroutine != null && _coroutineRunner != null) - { - _coroutineRunner.StopCoroutine(_globalImmunityCoroutine); - _globalImmunityCoroutine = null; - _isGloballyImmune = false; - - // Re-enable the shared collider - if (_sharedPlayerCollider != null) - { - _sharedPlayerCollider.enabled = true; - } - - // Notify all instances - foreach (var instance in _allInstances) - { - if (instance != null) - { - instance.OnImmunityEnd(); - } - } - - OnImmunityEnded?.Invoke(); - } - } } } diff --git a/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerController.cs b/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerController.cs index fd247d7a..9c3fa3cf 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerController.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerController.cs @@ -1,4 +1,5 @@ using UnityEngine; +using AppleHills.Core.Settings; namespace Minigames.DivingForPictures { @@ -8,11 +9,8 @@ namespace Minigames.DivingForPictures /// public class PlayerController : MonoBehaviour, ITouchInputConsumer { - [Header("Tap Movement")] - [Tooltip("Maximum distance the player can move from a single tap")] - [SerializeField] private float tapMaxDistance = 0.5f; - [Tooltip("How quickly the tap impulse fades (higher = faster stop)")] - [SerializeField] private float tapDecelerationRate = 5.0f; + // Settings reference + private IDivingMinigameSettings _settings; private float _targetFingerX; private bool _isTouchActive; @@ -25,6 +23,13 @@ namespace Minigames.DivingForPictures void Awake() { _originY = transform.position.y; + + // Get settings from GameManager + _settings = GameManager.GetSettingsObject(); + if (_settings == null) + { + Debug.LogError("[PlayerController] Failed to load diving minigame settings!"); + } } void Start() @@ -42,7 +47,7 @@ namespace Minigames.DivingForPictures public void OnTap(Vector2 worldPosition) { // Debug.Log($"[EndlessDescenderController] OnTap at {worldPosition}"); - float targetX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); + float targetX = Mathf.Clamp(worldPosition.x, _settings.ClampXMin, _settings.ClampXMax); // Calculate tap direction (+1 for right, -1 for left) _tapDirection = Mathf.Sign(targetX - transform.position.x); @@ -63,7 +68,7 @@ namespace Minigames.DivingForPictures public void OnHoldStart(Vector2 worldPosition) { // Debug.Log($"[EndlessDescenderController] OnHoldStart at {worldPosition}"); - _targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); + _targetFingerX = Mathf.Clamp(worldPosition.x, _settings.ClampXMin, _settings.ClampXMax); _isTouchActive = true; } @@ -73,7 +78,7 @@ namespace Minigames.DivingForPictures public void OnHoldMove(Vector2 worldPosition) { // Debug.Log($"[EndlessDescenderController] OnHoldMove at {worldPosition}"); - _targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); + _targetFingerX = Mathf.Clamp(worldPosition.x, _settings.ClampXMin, _settings.ClampXMax); } /// @@ -91,9 +96,9 @@ namespace Minigames.DivingForPictures if (_isTouchActive) { float currentX = transform.position.x; - float lerpSpeed = GameManager.Instance.EndlessDescenderLerpSpeed; - float maxOffset = GameManager.Instance.EndlessDescenderMaxOffset; - float exponent = GameManager.Instance.EndlessDescenderSpeedExponent; + float lerpSpeed = _settings.LerpSpeed; + float maxOffset = _settings.MaxOffset; + float exponent = _settings.SpeedExponent; float targetX = _targetFingerX; float offset = targetX - currentX; offset = Mathf.Clamp(offset, -maxOffset, maxOffset); @@ -103,7 +108,7 @@ namespace Minigames.DivingForPictures // Prevent overshooting moveStep = Mathf.Clamp(moveStep, -absOffset, absOffset); float newX = currentX + moveStep; - newX = Mathf.Clamp(newX, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); + newX = Mathf.Clamp(newX, _settings.ClampXMin, _settings.ClampXMax); UpdatePosition(newX); } @@ -111,21 +116,21 @@ namespace Minigames.DivingForPictures else if (_tapImpulseStrength > 0) { float currentX = transform.position.x; - float maxOffset = GameManager.Instance.EndlessDescenderMaxOffset; - float lerpSpeed = GameManager.Instance.EndlessDescenderLerpSpeed; + float maxOffset = _settings.MaxOffset; + float lerpSpeed = _settings.LerpSpeed; // Calculate move distance based on impulse strength float moveDistance = maxOffset * _tapImpulseStrength * Time.deltaTime * lerpSpeed; // Limit total movement from single tap - moveDistance = Mathf.Min(moveDistance, tapMaxDistance * _tapImpulseStrength); + moveDistance = Mathf.Min(moveDistance, _settings.TapMaxDistance * _tapImpulseStrength); // Apply movement in tap direction float newX = currentX + (moveDistance * _tapDirection); - newX = Mathf.Clamp(newX, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); + newX = Mathf.Clamp(newX, _settings.ClampXMin, _settings.ClampXMax); // Reduce impulse strength over time - _tapImpulseStrength -= Time.deltaTime * tapDecelerationRate; + _tapImpulseStrength -= Time.deltaTime * _settings.TapDecelerationRate; if (_tapImpulseStrength < 0.01f) { _tapImpulseStrength = 0f; diff --git a/Assets/Scripts/Minigames/DivingForPictures/Player/TileBumpCollision.cs b/Assets/Scripts/Minigames/DivingForPictures/Player/TileBumpCollision.cs index ab9efb77..3cb5d32d 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Player/TileBumpCollision.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Player/TileBumpCollision.cs @@ -1,5 +1,6 @@ using UnityEngine; using System.Collections; +using AppleHills.Core.Settings; namespace Minigames.DivingForPictures { @@ -9,35 +10,21 @@ namespace Minigames.DivingForPictures /// public class TileBumpCollision : PlayerCollisionBehavior { - [Header("Bump Settings")] - [Tooltip("Type of bump response: Impulse pushes with force, SmoothToCenter moves directly to center")] - [SerializeField] private BumpMode bumpMode = BumpMode.Impulse; - - [Tooltip("Force strength for impulse bumps - higher values push further toward center")] - [SerializeField] private float bumpForce = 5.0f; - - [Tooltip("Speed for smooth movement to center (units per second)")] - [SerializeField] private float smoothMoveSpeed = 8.0f; - - [Tooltip("Animation curve controlling bump movement over time")] - [SerializeField] private AnimationCurve bumpCurve = new AnimationCurve(new Keyframe(0f, 0f, 0f, 2f), new Keyframe(1f, 1f, 0f, 0f)); - - [Tooltip("Whether to block player input during bump movement")] - [SerializeField] private bool blockInputDuringBump = true; - - public enum BumpMode - { - Impulse, // Force-based push toward center (distance depends on force) - SmoothToCenter // Smooth movement to center with configurable speed - } - private bool _isBumping; private Coroutine _bumpCoroutine; - private bool _bumpInputBlocked; // Tracks bump-specific input blocking - + protected override void HandleCollisionResponse(Collider2D obstacle) { - switch (bumpMode) + // Check if the obstacle is on the TrenchTileLayer + if (obstacle.gameObject.layer != _devSettings.TrenchTileLayer) + { + // If not on the trench tile layer, don't process the collision + Debug.Log($"[TileBumpCollision] Ignored collision with object on layer {obstacle.gameObject.layer} (expected {_devSettings.TrenchTileLayer})"); + return; + } + + // Use bump mode from developer settings + switch (_devSettings.BumpMode) { case BumpMode.Impulse: StartImpulseBump(); @@ -48,7 +35,7 @@ namespace Minigames.DivingForPictures break; } - Debug.Log($"[TileBumpCollision] Collision handled with {bumpMode} mode"); + Debug.Log($"[TileBumpCollision] Collision handled with {_devSettings.BumpMode} mode"); } /// @@ -64,7 +51,7 @@ namespace Minigames.DivingForPictures float directionToCenter = currentX > 0 ? -1f : 1f; // Direction toward center // Calculate target position - closer to center based on bump force - float bumpDistance = bumpForce * 0.2f; // Scale factor for distance + float bumpDistance = _gameSettings.BumpForce * 0.2f; // Scale factor for distance float targetX = currentX + (directionToCenter * bumpDistance); // Clamp to center if we would overshoot @@ -77,7 +64,7 @@ namespace Minigames.DivingForPictures StartBump(currentX, targetX, bumpDuration); - Debug.Log($"[TileBumpCollision] Starting impulse bump from X={currentX} to X={targetX} (force={bumpForce})"); + Debug.Log($"[TileBumpCollision] Starting impulse bump from X={currentX} to X={targetX} (force={_gameSettings.BumpForce})"); } /// @@ -91,11 +78,11 @@ namespace Minigames.DivingForPictures float distanceToCenter = Mathf.Abs(currentX); float targetX = 0f; // Always move to center - float bumpDuration = distanceToCenter / smoothMoveSpeed; // Duration based on distance and speed + float bumpDuration = distanceToCenter / _gameSettings.SmoothMoveSpeed; // Duration based on distance and speed StartBump(currentX, targetX, bumpDuration); - Debug.Log($"[TileBumpCollision] Starting smooth move to center from X={currentX} (speed={smoothMoveSpeed}, duration={bumpDuration:F2}s)"); + Debug.Log($"[TileBumpCollision] Starting smooth move to center from X={currentX} (speed={_gameSettings.SmoothMoveSpeed}, duration={bumpDuration:F2}s)"); } /// @@ -112,14 +99,6 @@ namespace Minigames.DivingForPictures _isBumping = true; - // Block player input if enabled (use bump-specific blocking) - if (blockInputDuringBump && playerController != null && playerController.enabled) - { - playerController.enabled = false; - _bumpInputBlocked = true; - Debug.Log("[TileBumpCollision] Player input blocked during bump"); - } - // Start bump coroutine _bumpCoroutine = StartCoroutine(BumpCoroutine(startX, targetX, duration)); } @@ -137,7 +116,7 @@ namespace Minigames.DivingForPictures // Calculate progress and apply curve float progress = elapsedTime / duration; - float curveValue = bumpCurve.Evaluate(progress); + float curveValue = _devSettings.BumpCurve.Evaluate(progress); // Interpolate position float currentX = Mathf.Lerp(startX, targetX, curveValue); @@ -146,7 +125,8 @@ namespace Minigames.DivingForPictures if (playerCharacter != null) { Vector3 currentPos = playerCharacter.transform.position; - playerCharacter.transform.position = new Vector3(currentX, currentPos.y, currentPos.z); + currentPos.x = Mathf.Clamp(currentX, _gameSettings.ClampXMin, _gameSettings.ClampXMax); + playerCharacter.transform.position = currentPos; } yield return null; @@ -156,139 +136,15 @@ namespace Minigames.DivingForPictures if (playerCharacter != null) { Vector3 currentPos = playerCharacter.transform.position; - playerCharacter.transform.position = new Vector3(targetX, currentPos.y, currentPos.z); + float clampedTargetX = Mathf.Clamp(targetX, _gameSettings.ClampXMin, _gameSettings.ClampXMax); + playerCharacter.transform.position = new Vector3(clampedTargetX, currentPos.y, currentPos.z); } // Bump finished _isBumping = false; _bumpCoroutine = null; - if (_bumpInputBlocked) - { - RestoreBumpInput(); - } - Debug.Log("[TileBumpCollision] Bump movement completed"); } - - /// - /// Restores player input after bump movement - /// - private void RestoreBumpInput() - { - if (_bumpInputBlocked && playerController != null) - { - playerController.enabled = true; - _bumpInputBlocked = false; - - // Update the controller's target position to current position to prevent snapping - UpdateControllerTarget(); - - Debug.Log("[TileBumpCollision] Player input restored after bump"); - } - } - - /// - /// Override to handle bump-specific input blocking during immunity - /// - protected override void OnImmunityStart() - { - Debug.Log($"[TileBumpCollision] Damage immunity started for {damageImmunityDuration} seconds"); - - // Block input if specified (in addition to any bump input blocking) - if (blockInputDuringImmunity && !_bumpInputBlocked) - { - BlockPlayerInput(); - } - } - - /// - /// Override to handle immunity end and bump cleanup - /// - protected override void OnImmunityEnd() - { - base.OnImmunityEnd(); - - // Stop any ongoing bump if immunity ends - if (_isBumping && _bumpCoroutine != null) - { - StopCoroutine(_bumpCoroutine); - _bumpCoroutine = null; - _isBumping = false; - - if (_bumpInputBlocked) - { - RestoreBumpInput(); - } - - Debug.Log("[TileBumpCollision] Bump interrupted by immunity end"); - } - } - - /// - /// Called when component is destroyed - cleanup coroutines - /// - private void OnDestroy() - { - if (_bumpCoroutine != null) - { - StopCoroutine(_bumpCoroutine); - _bumpCoroutine = null; - } - } - - /// - /// Public method to change bump mode at runtime - /// - public void SetBumpMode(BumpMode mode) - { - bumpMode = mode; - } - - /// - /// Public method to change bump force at runtime - /// - public void SetBumpForce(float force) - { - bumpForce = force; - } - - /// - /// Public method to change smooth move speed at runtime - /// - public void SetSmoothMoveSpeed(float speed) - { - smoothMoveSpeed = speed; - } - - /// - /// Check if player is currently being bumped - /// - public bool IsBumping => _isBumping; - - /// - /// Check if input is currently blocked by bump - /// - public bool IsBumpInputBlocked => _bumpInputBlocked; - - /// - /// Public method to manually stop bump movement - /// - public void StopBump() - { - if (_isBumping && _bumpCoroutine != null) - { - StopCoroutine(_bumpCoroutine); - _bumpCoroutine = null; - _isBumping = false; - - if (_bumpInputBlocked) - { - RestoreBumpInput(); - } - - Debug.Log("[TileBumpCollision] Bump manually stopped"); - } - } } } diff --git a/Assets/Scripts/Minigames/DivingForPictures/Player/WobbleBehavior.cs b/Assets/Scripts/Minigames/DivingForPictures/Player/WobbleBehavior.cs index 8d55e8de..469d38e3 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Player/WobbleBehavior.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Player/WobbleBehavior.cs @@ -1,38 +1,13 @@ using UnityEngine; +using AppleHills.Core.Settings; /// /// Adds a wobble (rocking and vertical movement) effect to the object, based on speed and time. /// public class WobbleBehavior : MonoBehaviour { - [Header("Wobble Settings")] - public float wobbleFrequency = 1.5f; - /// - /// Max degrees from horizontal. - /// - public float baseWobbleAmplitude = 8f; - /// - /// How much speed affects amplitude. - /// - public float speedToAmplitude = 2f; - /// - /// Maximum allowed rotation in degrees. - /// - public float maxRotationLimit = 45f; - - [Header("Vertical Movement Settings")] - public float verticalFrequency = 0.5f; - /// - /// How far the object moves up/down. - /// - public float verticalAmplitude = 0.5f; - - [Header("Smoothing Settings")] - public float velocitySmoothing = 10f; - /// - /// How quickly rotation is smoothed. - /// - public float rotationSmoothing = 10f; + // Developer settings reference + private DivingDeveloperSettings _devSettings; private Vector3 lastPosition; private float wobbleTime; @@ -46,47 +21,61 @@ public class WobbleBehavior : MonoBehaviour /// The current velocity of the object (horizontal only). /// public float Velocity => velocity; + /// /// The current vertical offset due to wobble. /// public float VerticalOffset => verticalOffset; - void Start() + private void Awake() { + // Get developer settings + _devSettings = GameManager.GetDeveloperSettings(); + if (_devSettings == null) + { + Debug.LogError("[WobbleBehavior] Failed to load developer settings!"); + } + + // Initialize lastPosition = transform.position; - smoothedVelocity = 0f; - smoothedAngle = 0f; + basePosition = transform.position; } - void Update() + private void Update() { - // Calculate movement speed (exclude vertical wobble from velocity calculation) - Vector3 horizontalPosition = transform.position; - horizontalPosition.y = 0f; // Ignore Y for velocity if only horizontal movement matters - Vector3 horizontalLastPosition = lastPosition; - horizontalLastPosition.y = 0f; - velocity = (horizontalPosition - horizontalLastPosition).magnitude / Time.deltaTime; + if (_devSettings == null) return; + + // Calculate velocity based on position change + Vector3 positionDelta = transform.position - lastPosition; + velocity = positionDelta.x / Time.deltaTime; lastPosition = transform.position; + basePosition = transform.position; - // Smooth velocity to prevent jitter - smoothedVelocity = Mathf.Lerp(smoothedVelocity, velocity, velocitySmoothing * Time.deltaTime); + // Smooth velocity changes + smoothedVelocity = Mathf.Lerp(smoothedVelocity, velocity, Time.deltaTime * _devSettings.PlayerVelocitySmoothing); - // Wobble amplitude scales with smoothed speed, but always has a base value - float amplitude = baseWobbleAmplitude + smoothedVelocity * speedToAmplitude; - amplitude = Mathf.Min(amplitude, maxRotationLimit); // Prevent amplitude from exceeding limit + // Calculate wobble rotation based on velocity and time + wobbleTime += Time.deltaTime * _devSettings.PlayerWobbleFrequency; + float rawWobble = Mathf.Sin(wobbleTime); - // Oscillate around horizontal (0 degrees) - wobbleTime += Time.deltaTime * wobbleFrequency; - float targetAngle = Mathf.Sin(wobbleTime) * amplitude; - targetAngle = Mathf.Clamp(targetAngle, -maxRotationLimit, maxRotationLimit); + // Calculate wobble amplitude based on velocity + float velocityFactor = Mathf.Abs(smoothedVelocity) * _devSettings.PlayerSpeedToAmplitude; + float wobbleAmplitude = _devSettings.PlayerBaseWobbleAmplitude + velocityFactor; + + // Clamp to maximum rotation limit + wobbleAmplitude = Mathf.Min(wobbleAmplitude, _devSettings.PlayerMaxRotationLimit); + + // Calculate target angle + float targetAngle = rawWobble * wobbleAmplitude; + + // Smooth angle changes + smoothedAngle = Mathf.Lerp(smoothedAngle, targetAngle, Time.deltaTime * _devSettings.PlayerRotationSmoothing); + + // Apply rotation + transform.rotation = Quaternion.Euler(0f, 0f, smoothedAngle); - // Smooth the rotation angle - smoothedAngle = Mathf.Lerp(smoothedAngle, targetAngle, rotationSmoothing * Time.deltaTime); - - // Apply rotation (Z axis for 2D) - transform.localRotation = Quaternion.Euler(0f, 0f, smoothedAngle); - - // Calculate vertical up/down movement (wave riding) only once - verticalOffset = Mathf.Sin(wobbleTime * verticalFrequency) * verticalAmplitude; + // Calculate vertical bobbing + float time = Time.time * _devSettings.PlayerVerticalFrequency; + verticalOffset = Mathf.Sin(time) * _devSettings.PlayerVerticalAmplitude; } } diff --git a/Assets/Scripts/Minigames/DivingForPictures/Tiles/TrenchTileSpawner.cs b/Assets/Scripts/Minigames/DivingForPictures/Tiles/TrenchTileSpawner.cs index 064944dc..50f93f69 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Tiles/TrenchTileSpawner.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Tiles/TrenchTileSpawner.cs @@ -4,6 +4,7 @@ using UnityEngine; using UnityEngine.Events; using UnityEngine.Serialization; using Pooling; +using AppleHills.Core.Settings; namespace Minigames.DivingForPictures { @@ -16,21 +17,6 @@ namespace Minigames.DivingForPictures [Tooltip("List of possible trench tile prefabs.")] [SerializeField] private List tilePrefabs; - [Header("Tile Settings")] - [SerializeField] private int initialTileCount = 3; - [SerializeField] private float tileSpawnBuffer = 1f; - - [Header("Movement Settings")] - [SerializeField] private float moveSpeed = 3f; - [SerializeField] private float speedUpFactor = 0.2f; - [SerializeField] private float speedUpInterval = 10f; - [SerializeField] private float maxMoveSpeed = 12f; - - [Header("Object Pooling")] - [SerializeField] private bool useObjectPooling = true; - [SerializeField] private int maxPerPrefabPoolSize = 2; - [SerializeField] private int totalMaxPoolSize = 10; - [Header("Events")] [FormerlySerializedAs("OnTileSpawned")] public UnityEvent onTileSpawned; @@ -38,6 +24,10 @@ namespace Minigames.DivingForPictures [FormerlySerializedAs("OnTileDestroyed")] public UnityEvent onTileDestroyed; + // Settings references + private IDivingMinigameSettings _settings; + private DivingDeveloperSettings _devSettings; + // Private fields private readonly Dictionary _tileHeights = new Dictionary(); private readonly List _activeTiles = new List(); @@ -52,9 +42,6 @@ namespace Minigames.DivingForPictures // Current velocity for tile movement private float _currentVelocity; - - // Interval for velocity calculations (seconds) - [SerializeField] private float velocityCalculationInterval = 0.5f; private const float TileSpawnZ = -1f; private const float DefaultTileHeight = 5f; @@ -73,7 +60,22 @@ namespace Minigames.DivingForPictures private void Awake() { _mainCamera = Camera.main; - _baseMoveSpeed = moveSpeed; // Store the original base speed + + // Get settings from GameManager + _settings = GameManager.GetSettingsObject(); + _devSettings = GameManager.GetDeveloperSettings(); + + if (_settings == null) + { + Debug.LogError("[TrenchTileSpawner] Failed to load diving minigame settings!"); + } + + if (_devSettings == null) + { + Debug.LogError("[TrenchTileSpawner] Failed to load diving developer settings!"); + } + + _baseMoveSpeed = _settings?.MoveSpeed ?? 3f; // Store the original base speed // Calculate tile heights for each prefab CalculateTileHeights(); @@ -81,7 +83,7 @@ namespace Minigames.DivingForPictures // Ensure all prefabs have Tile components ValidateTilePrefabs(); - if (useObjectPooling) + if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling) { InitializeObjectPool(); } @@ -110,7 +112,7 @@ namespace Minigames.DivingForPictures SpawnInitialTiles(); // Initialize velocity and start the velocity calculation coroutine - _currentVelocity = moveSpeed * Time.fixedDeltaTime; + _currentVelocity = _baseMoveSpeed * Time.fixedDeltaTime; StartCoroutine(VelocityCalculationRoutine()); } @@ -148,7 +150,7 @@ namespace Minigames.DivingForPictures while (true) { CalculateVelocity(); - yield return new WaitForSeconds(velocityCalculationInterval); + yield return new WaitForSeconds(_settings.VelocityCalculationInterval); } } @@ -157,7 +159,7 @@ namespace Minigames.DivingForPictures /// private void CalculateVelocity() { - _currentVelocity = moveSpeed * Time.fixedDeltaTime; + _currentVelocity = _baseMoveSpeed * Time.fixedDeltaTime; } /// @@ -197,9 +199,9 @@ namespace Minigames.DivingForPictures poolGO.transform.SetParent(transform); _tilePool = poolGO.AddComponent(); - // Set up the pool configuration - _tilePool.maxPerPrefabPoolSize = maxPerPrefabPoolSize; - _tilePool.totalMaxPoolSize = totalMaxPoolSize; + // Set up the pool configuration using developer settings + _tilePool.maxPerPrefabPoolSize = _devSettings.TrenchTileMaxPerPrefabPoolSize; + _tilePool.totalMaxPoolSize = _devSettings.TrenchTileTotalMaxPoolSize; // Convert the GameObject list to a Tile list List prefabTiles = new List(tilePrefabs.Count); @@ -247,7 +249,7 @@ namespace Minigames.DivingForPictures // Move starting position up by 2 tile heights startingY += tileHeightEstimate * 2; - for (int i = 0; i < initialTileCount; i++) + for (int i = 0; i < _settings.InitialTileCount; i++) { float y = startingY; // Calculate proper Y position based on previous tiles @@ -291,12 +293,12 @@ namespace Minigames.DivingForPictures // Update the actual move speed based on the velocity factor // This keeps the original move speed intact for game logic - moveSpeed = _baseMoveSpeed * Mathf.Abs(_velocityFactor); + _baseMoveSpeed = _settings.MoveSpeed * Mathf.Abs(_velocityFactor); // Recalculate velocity immediately CalculateVelocity(); - Debug.Log($"[TrenchTileSpawner] Velocity factor updated to {_velocityFactor:F2}, moveSpeed: {moveSpeed:F2}"); + Debug.Log($"[TrenchTileSpawner] Velocity factor updated to {_velocityFactor:F2}, moveSpeed: {_baseMoveSpeed:F2}"); } /// @@ -362,12 +364,12 @@ namespace Minigames.DivingForPictures if (_isSurfacing) { // When surfacing, destroy tiles at the bottom - shouldDestroy = topTile.transform.position.y + tileHeight / 2 < _screenBottom - tileSpawnBuffer; + shouldDestroy = topTile.transform.position.y + tileHeight / 2 < _screenBottom - _settings.TileSpawnBuffer; } else { // When descending, destroy tiles at the top - shouldDestroy = topTile.transform.position.y - tileHeight / 2 > _screenTop + tileSpawnBuffer; + shouldDestroy = topTile.transform.position.y - tileHeight / 2 > _screenTop + _settings.TileSpawnBuffer; } if (shouldDestroy) @@ -375,7 +377,7 @@ namespace Minigames.DivingForPictures _activeTiles.RemoveAt(0); onTileDestroyed?.Invoke(topTile); - if (useObjectPooling && _tilePool != null) + if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _tilePool != null) { // Find the prefab index for this tile int prefabIndex = GetPrefabIndex(topTile); @@ -431,12 +433,12 @@ namespace Minigames.DivingForPictures if (_isSurfacing) { // When surfacing, check if the bottom of the tile is above the top of the screen - isLastTileLeaving = bottomTile.transform.position.y - tileHeight / 2 > _screenTop + tileSpawnBuffer; + isLastTileLeaving = bottomTile.transform.position.y - tileHeight / 2 > _screenTop + _settings.TileSpawnBuffer; } else { // When descending, check if the top of the tile is below the bottom of the screen - isLastTileLeaving = bottomTile.transform.position.y + tileHeight / 2 < _screenBottom - tileSpawnBuffer; + isLastTileLeaving = bottomTile.transform.position.y + tileHeight / 2 < _screenBottom - _settings.TileSpawnBuffer; } if (isLastTileLeaving) @@ -454,14 +456,14 @@ namespace Minigames.DivingForPictures { // When surfacing, spawn new tiles at the top float topEdge = bottomTile.transform.position.y + tileHeight / 2; - shouldSpawn = topEdge < _screenTop + tileSpawnBuffer; + shouldSpawn = topEdge < _screenTop + _settings.TileSpawnBuffer; newY = bottomTile.transform.position.y + tileHeight; } else { // When descending, spawn new tiles at the bottom float bottomEdge = bottomTile.transform.position.y - tileHeight / 2; - shouldSpawn = bottomEdge > _screenBottom - tileSpawnBuffer; + shouldSpawn = bottomEdge > _screenBottom - _settings.TileSpawnBuffer; newY = bottomTile.transform.position.y - tileHeight; } @@ -477,9 +479,9 @@ namespace Minigames.DivingForPictures private void HandleSpeedRamping() { _speedUpTimer += Time.deltaTime; - if (_speedUpTimer >= speedUpInterval) + if (_speedUpTimer >= _settings.SpeedUpInterval) { - moveSpeed = Mathf.Min(moveSpeed + speedUpFactor, maxMoveSpeed); + _baseMoveSpeed = Mathf.Min(_baseMoveSpeed + _settings.SpeedUpFactor, _settings.MaxMoveSpeed); _speedUpTimer = 0f; } } @@ -506,7 +508,7 @@ namespace Minigames.DivingForPictures GameObject tile; - if (useObjectPooling && _tilePool != null) + if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _tilePool != null) { tile = _tilePool.GetTile(prefabIndex); if (tile == null) @@ -518,11 +520,25 @@ namespace Minigames.DivingForPictures tile.transform.position = new Vector3(0f, y, TileSpawnZ); tile.transform.rotation = prefab.transform.rotation; tile.transform.SetParent(transform); + + // Set the layer to the configured trench tile layer + if (_devSettings != null) + { + tile.layer = _devSettings.TrenchTileLayer; + SetLayerRecursively(tile, _devSettings.TrenchTileLayer); + } } else { // Use the prefab's original rotation tile = Instantiate(prefab, new Vector3(0f, y, TileSpawnZ), prefab.transform.rotation, transform); + + // Set the layer to the configured trench tile layer + if (_devSettings != null) + { + tile.layer = _devSettings.TrenchTileLayer; + SetLayerRecursively(tile, _devSettings.TrenchTileLayer); + } } _activeTiles.Add(tile); @@ -538,7 +554,7 @@ namespace Minigames.DivingForPictures { int prefabCount = tilePrefabs.Count; List weights = new List(prefabCount); - + for (int i = 0; i < prefabCount; i++) { int lastUsed = _tileLastUsed.TryGetValue(i, out var value) ? value : -prefabCount; @@ -546,13 +562,13 @@ namespace Minigames.DivingForPictures float weight = Mathf.Clamp(age, 1, prefabCount * 2); // More unused = higher weight weights.Add(weight); } - + float totalWeight = 0f; foreach (var weight in weights) { totalWeight += weight; } - + float randomValue = Random.value * totalWeight; for (int i = 0; i < prefabCount; i++) { @@ -562,10 +578,10 @@ namespace Minigames.DivingForPictures } randomValue -= weights[i]; } - + return Random.Range(0, prefabCount); // fallback } - + /// /// Gets the height of a tile based on its prefab or renderer bounds /// @@ -593,18 +609,18 @@ namespace Minigames.DivingForPictures } } } - + // If not found, calculate it from the renderer Renderer renderer = tile.GetComponentInChildren(); if (renderer != null) { return renderer.bounds.size.y; } - + // Fallback return DefaultTileHeight; } - + /// /// Gets the index of the prefab that was used to create this tile /// @@ -620,7 +636,7 @@ namespace Minigames.DivingForPictures for (int i = 0; i < tilePrefabs.Count; i++) { if (tilePrefabs[i] == null) continue; - + if (tile.name.StartsWith(tilePrefabs[i].name)) { return i; @@ -656,18 +672,41 @@ namespace Minigames.DivingForPictures // Draw tile bounds for debugging Gizmos.color = Color.cyan; - for (int i = 0; i < initialTileCount; i++) + if (_settings != null) { - float height = DefaultTileHeight; - if (tilePrefabs != null && tilePrefabs.Count > 0 && tilePrefabs[0] != null && - _tileHeights.TryGetValue(tilePrefabs[0], out float h)) + for (int i = 0; i < _settings.InitialTileCount; i++) { - height = h; + float height = DefaultTileHeight; + if (tilePrefabs != null && tilePrefabs.Count > 0 && tilePrefabs[0] != null && + _tileHeights.TryGetValue(tilePrefabs[0], out float h)) + { + height = h; + } + Vector3 center = new Vector3(0f, _screenBottom + i * height, 0f); + Gizmos.DrawWireCube(center, new Vector3(10f, height, 1f)); } - Vector3 center = new Vector3(0f, _screenBottom + i * height, 0f); - Gizmos.DrawWireCube(center, new Vector3(10f, height, 1f)); } } #endif + + /// + /// Set the layer of a GameObject and all its children recursively + /// + /// The GameObject to set the layer for + /// The layer index to set + private void SetLayerRecursively(GameObject obj, int layer) + { + if (obj == null) return; + + obj.layer = layer; + + foreach (Transform child in obj.transform) + { + if (child != null) + { + SetLayerRecursively(child.gameObject, layer); + } + } + } } } diff --git a/Assets/Scripts/Utilities.meta b/Assets/Scripts/Utilities.meta new file mode 100644 index 00000000..55c0c5ae --- /dev/null +++ b/Assets/Scripts/Utilities.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d54ca063d685467f9cb2c05507ac833f +timeCreated: 1758717247 \ No newline at end of file diff --git a/Assets/Scripts/Utilities/UnityExtensions.cs b/Assets/Scripts/Utilities/UnityExtensions.cs new file mode 100644 index 00000000..5d75c001 --- /dev/null +++ b/Assets/Scripts/Utilities/UnityExtensions.cs @@ -0,0 +1,55 @@ +using UnityEngine; + +namespace AppleHills.Utilities +{ + /// + /// Collection of useful extension methods for Unity built-in classes + /// + public static class UnityExtensions + { + /// + /// Extension method to check if a layer is in a layermask. + /// Accounts for Unity's 0-indexed layer system when comparing with editor layer values. + /// + /// The layer mask to check + /// The layer value to check for + /// True if the layer is in the mask, false otherwise + public static bool Contains(this LayerMask mask, int layer) + { + // Adjust for the -1 offset as specifically requested + int adjustedLayer = layer - 1; + return mask.value == (mask.value | (1 << adjustedLayer)); + } + + /// + /// Extension method to check if a GameObject's layer is in a layermask. + /// Automatically gets the layer from the GameObject and handles the offset. + /// + /// The layer mask to check + /// The GameObject whose layer to check + /// True if the GameObject's layer is in the mask, false otherwise + public static bool Contains(this LayerMask mask, GameObject gameObject) + { + if (gameObject == null) return false; + + int layer = gameObject.layer; + // Adjust for the -1 offset as specifically requested + int adjustedLayer = layer - 1; + return mask.value == (mask.value | (1 << adjustedLayer)); + } + + /// + /// Bitwise check if a layer is in a layermask. + /// This is an alternative implementation that uses bitwise AND operation. + /// + /// The layer mask to check + /// The layer value to check for + /// True if the layer is in the mask, false otherwise + public static bool ContainsBitwise(this LayerMask mask, int layer) + { + // Adjust for the -1 offset as specifically requested + int adjustedLayer = layer - 1; + return (mask.value & (1 << adjustedLayer)) != 0; + } + } +} diff --git a/Assets/Scripts/Utilities/UnityExtensions.cs.meta b/Assets/Scripts/Utilities/UnityExtensions.cs.meta new file mode 100644 index 00000000..a0a114b7 --- /dev/null +++ b/Assets/Scripts/Utilities/UnityExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3e92a12c248d4b9ab06fcc0ba9c63ef3 +timeCreated: 1758717247 \ No newline at end of file diff --git a/Assets/Settings/Developer.meta b/Assets/Settings/Developer.meta new file mode 100644 index 00000000..683ed765 --- /dev/null +++ b/Assets/Settings/Developer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dfde9ecdb3e084d47b97f511323c4a77 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Developer/DivingDeveloperSettings.asset b/Assets/Settings/Developer/DivingDeveloperSettings.asset new file mode 100644 index 00000000..528ea282 --- /dev/null +++ b/Assets/Settings/Developer/DivingDeveloperSettings.asset @@ -0,0 +1,76 @@ +%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: 033961b12e7b4289838d554c2264bacd, type: 3} + m_Name: DivingDeveloperSettings + m_EditorClassIdentifier: + bubbleUseObjectPooling: 1 + bubbleInitialPoolSize: 10 + bubbleMaxPoolSize: 30 + bubbleSpawnInterval: 0.5 + bubbleSpeedRange: {x: 0.5, y: 2} + bubbleScaleRange: {x: 0.3, y: 0.7} + bubbleWobbleSpeedRange: {x: 1, y: 3} + bubbleWobbleAmountRange: {x: 0.05, y: 0.15} + bubbleSpawnXMin: -3.5 + bubbleSpawnXMax: 3.5 + bubbleSpawnY: -5 + bubbleWobbleMinScale: 0.2 + bubbleWobbleMaxScale: 1.2 + bubbleSurfacingSpeedFactor: 0.5 + obstacleLayer: 11 + obstacleUseObjectPooling: 1 + obstacleMaxPerPrefabPoolSize: 3 + obstacleTotalMaxPoolSize: 15 + trenchTileLayer: 13 + trenchTileUseObjectPooling: 1 + trenchTileMaxPerPrefabPoolSize: 2 + trenchTileTotalMaxPoolSize: 10 + playerBlinkDamageColor: {r: 1, g: 0, b: 0, a: 1} + playerBlinkRate: 0.15 + playerDamageColorAlpha: 0.7 + playerWobbleFrequency: 1.5 + playerBaseWobbleAmplitude: 8 + playerSpeedToAmplitude: 2 + playerMaxRotationLimit: 45 + playerVerticalFrequency: 0.5 + playerVerticalAmplitude: 0.2 + playerVelocitySmoothing: 10 + playerRotationSmoothing: 10 + playerObstacleLayerMask: + serializedVersion: 2 + m_Bits: 5120 + blockInputDuringImmunity: 1 + bumpMode: 0 + bumpCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 2 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 diff --git a/Assets/Settings/Developer/DivingDeveloperSettings.asset.meta b/Assets/Settings/Developer/DivingDeveloperSettings.asset.meta new file mode 100644 index 00000000..3e80e618 --- /dev/null +++ b/Assets/Settings/Developer/DivingDeveloperSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 328ce914b893df646be3ad3c62755453 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/DivingMinigameSettings.asset b/Assets/Settings/DivingMinigameSettings.asset new file mode 100644 index 00000000..766926f6 --- /dev/null +++ b/Assets/Settings/DivingMinigameSettings.asset @@ -0,0 +1,48 @@ +%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: DivingMinigameSettings + m_EditorClassIdentifier: + lerpSpeed: 2 + maxOffset: 10 + clampXMin: -3.5 + clampXMax: 3.5 + speedExponent: 0.97 + tapMaxDistance: 0.2 + tapDecelerationRate: 15 + baseSpawnProbability: 0.2 + maxSpawnProbability: 0.5 + probabilityIncreaseRate: 0.01 + guaranteedSpawnTime: 30 + spawnCooldown: 5 + basePoints: 100 + depthMultiplier: 10 + speedTransitionDuration: 2 + surfacingSpeedFactor: 3 + surfacingSpawnDelay: 5 + initialTileCount: 3 + tileSpawnBuffer: 1 + moveSpeed: 2 + speedUpFactor: 0 + speedUpInterval: 10 + maxMoveSpeed: 12 + velocityCalculationInterval: 0.5 + obstacleSpawnInterval: 2 + obstacleSpawnIntervalVariation: 0.5 + obstacleMaxSpawnAttempts: 10 + obstacleSpawnCollisionRadius: 1 + obstacleMinMoveSpeed: 1 + obstacleMaxMoveSpeed: 4 + damageImmunityDuration: 1 + bumpForce: 5 + smoothMoveSpeed: 8 + blockInputDuringBump: 1 diff --git a/Assets/Settings/DivingMinigameSettings.asset.meta b/Assets/Settings/DivingMinigameSettings.asset.meta new file mode 100644 index 00000000..da608929 --- /dev/null +++ b/Assets/Settings/DivingMinigameSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a9569848f604a6540827d4d4bb0a35c2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: 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/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: diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset index 3436af9a..992d7402 100644 --- a/ProjectSettings/TagManager.asset +++ b/ProjectSettings/TagManager.asset @@ -21,7 +21,7 @@ TagManager: - Interactable - QuarryObstacle - QuarryMonster - - + - QuarryTrenchTile - - -