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 <michal.a.pikulski@gmail.com> Co-authored-by: AlexanderT <alexander@foolhardyhorizons.com> Reviewed-on: #7
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32c1a9c8651793e41848a173d66d98fd
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
|
||||
41
Assets/AddressableAssetsData/AssetGroups/Settings.asset
Normal file
41
Assets/AddressableAssetsData/AssetGroups/Settings.asset
Normal file
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e25c7672a65b5974bb354fcfb2a8400c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
207
Assets/Editor/DeveloperSettingsEditorWindow.cs
Normal file
207
Assets/Editor/DeveloperSettingsEditorWindow.cs
Normal file
@@ -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<BaseDeveloperSettings> allDeveloperSettings = new List<BaseDeveloperSettings>();
|
||||
private string[] tabNames = new string[] { "Diving", "Other Systems" }; // Add more tabs as needed
|
||||
private int selectedTab = 0;
|
||||
private Dictionary<string, SerializedObject> serializedSettingsObjects = new Dictionary<string, SerializedObject>();
|
||||
private GUIStyle headerStyle;
|
||||
|
||||
[MenuItem("AppleHills/Developer Settings Editor")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
GetWindow<DeveloperSettingsEditorWindow>("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<BaseDeveloperSettings>(path);
|
||||
if (settings != null)
|
||||
{
|
||||
allDeveloperSettings.Add(settings);
|
||||
serializedSettingsObjects[settings.GetType().Name] = new SerializedObject(settings);
|
||||
}
|
||||
}
|
||||
|
||||
// If any settings are missing, create them
|
||||
CreateSettingsIfMissing<DivingDeveloperSettings>("DivingDeveloperSettings");
|
||||
|
||||
// Add more developer settings types here as needed
|
||||
// CreateSettingsIfMissing<OtherDeveloperSettings>("OtherDeveloperSettings");
|
||||
}
|
||||
|
||||
private void CreateSettingsIfMissing<T>(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<T>();
|
||||
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<T>(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<DivingDeveloperSettings>();
|
||||
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<T>() 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Editor/DeveloperSettingsEditorWindow.cs.meta
Normal file
3
Assets/Editor/DeveloperSettingsEditorWindow.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ab6d9fee0924431b8e22edb500b35df
|
||||
timeCreated: 1758710778
|
||||
97
Assets/Editor/EditorSettingsProvider.cs
Normal file
97
Assets/Editor/EditorSettingsProvider.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using UnityEditor;
|
||||
using AppleHills.Core.Settings;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to settings in editor (non-play) mode
|
||||
/// </summary>
|
||||
[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<PlayerFollowerSettings>("Assets/Settings/PlayerFollowerSettings.asset");
|
||||
_interactionSettings = AssetDatabase.LoadAssetAtPath<InteractionSettings>("Assets/Settings/InteractionSettings.asset");
|
||||
_divingMinigameSettings = AssetDatabase.LoadAssetAtPath<DivingMinigameSettings>("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<T>() 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Editor/EditorSettingsProvider.cs.meta
Normal file
3
Assets/Editor/EditorSettingsProvider.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e8da573a8db4ea892fe476592276e0f
|
||||
timeCreated: 1758634265
|
||||
@@ -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<ItemPrefabEditorWindow>("Item Prefab Editor");
|
||||
|
||||
54
Assets/Editor/LayerPropertyDrawer.cs
Normal file
54
Assets/Editor/LayerPropertyDrawer.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace AppleHills.Core.Settings.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom property drawer for layer fields to display a dropdown with layer names
|
||||
/// </summary>
|
||||
[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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom property drawer for LayerMask fields to display a mask dropdown with layer names
|
||||
/// </summary>
|
||||
[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();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Editor/LayerPropertyDrawer.cs.meta
Normal file
3
Assets/Editor/LayerPropertyDrawer.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c6b274ce7b14e91bf5a294a23ba0609
|
||||
timeCreated: 1758711663
|
||||
@@ -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<PrefabCreatorWindow>("Prefab Creator");
|
||||
|
||||
@@ -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<PuzzleChainEditorWindow>("Puzzle Chain Editor");
|
||||
|
||||
@@ -15,7 +15,7 @@ public class SceneObjectLocatorWindow : EditorWindow
|
||||
private List<PickupInfo> pickupInfos = new List<PickupInfo>();
|
||||
private Vector2 scrollPos;
|
||||
|
||||
[MenuItem("Tools/Scene Object Locator")]
|
||||
[MenuItem("AppleHills/Scene Object Locator")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<SceneObjectLocatorWindow>("Scene Object Locator");
|
||||
|
||||
195
Assets/Editor/SettingsEditorWindow.cs
Normal file
195
Assets/Editor/SettingsEditorWindow.cs
Normal file
@@ -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<BaseSettings> allSettings = new List<BaseSettings>();
|
||||
private string[] tabNames = new string[] { "Player & Follower", "Interaction & Items", "Diving Minigame" };
|
||||
private int selectedTab = 0;
|
||||
private Dictionary<string, SerializedObject> serializedSettingsObjects = new Dictionary<string, SerializedObject>();
|
||||
private GUIStyle headerStyle;
|
||||
|
||||
[MenuItem("AppleHills/Settings Editor")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
GetWindow<SettingsEditorWindow>("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<BaseSettings>(path);
|
||||
if (settings != null)
|
||||
{
|
||||
allSettings.Add(settings);
|
||||
serializedSettingsObjects[settings.GetType().Name] = new SerializedObject(settings);
|
||||
}
|
||||
}
|
||||
|
||||
// If any settings are missing, create them
|
||||
CreateSettingsIfMissing<PlayerFollowerSettings>("PlayerFollowerSettings");
|
||||
CreateSettingsIfMissing<InteractionSettings>("InteractionSettings");
|
||||
CreateSettingsIfMissing<DivingMinigameSettings>("DivingMinigameSettings");
|
||||
}
|
||||
|
||||
private void CreateSettingsIfMissing<T>(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<T>();
|
||||
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<T>(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<PlayerFollowerSettings>();
|
||||
break;
|
||||
case 1: // Interaction & Items
|
||||
DrawSettingsEditor<InteractionSettings>();
|
||||
break;
|
||||
case 2: // Minigames
|
||||
DrawSettingsEditor<DivingMinigameSettings>();
|
||||
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<T>() 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Editor/SettingsEditorWindow.cs.meta
Normal file
3
Assets/Editor/SettingsEditorWindow.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfb9e77c746e41a2903603a39df3d424
|
||||
timeCreated: 1758619952
|
||||
@@ -47,7 +47,6 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
prefabIndex: 0
|
||||
damage: 1
|
||||
moveSpeed: 2
|
||||
enableMovement: 1
|
||||
spawner: {fileID: 0}
|
||||
|
||||
@@ -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: []
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using UnityEngine;
|
||||
using AppleHills.Core.Settings;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// 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<GameSettings>("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<PlayerFollowerSettings>();
|
||||
var interactionSettings = SettingsProvider.Instance.LoadSettingsSynchronous<InteractionSettings>();
|
||||
var minigameSettings = SettingsProvider.Instance.LoadSettingsSynchronous<DivingMinigameSettings>();
|
||||
|
||||
// Register settings with service locator
|
||||
if (playerSettings != null)
|
||||
{
|
||||
ServiceLocator.Register<IPlayerFollowerSettings>(playerSettings);
|
||||
Debug.Log("PlayerFollowerSettings registered successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to load PlayerFollowerSettings");
|
||||
}
|
||||
|
||||
if (interactionSettings != null)
|
||||
{
|
||||
ServiceLocator.Register<IInteractionSettings>(interactionSettings);
|
||||
Debug.Log("InteractionSettings registered successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to load InteractionSettings");
|
||||
}
|
||||
|
||||
if (minigameSettings != null)
|
||||
{
|
||||
ServiceLocator.Register<IDivingMinigameSettings>(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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for and initialize developer settings.
|
||||
/// </summary>
|
||||
private void InitializeDeveloperSettings()
|
||||
{
|
||||
Debug.Log("Starting developer settings initialization...");
|
||||
|
||||
// Load developer settings
|
||||
var divingDevSettings = DeveloperSettingsProvider.Instance.GetSettings<DivingDeveloperSettings>();
|
||||
|
||||
_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<T>() where T : class
|
||||
{
|
||||
return ServiceLocator.Get<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the entire settings object of specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of settings to retrieve</typeparam>
|
||||
/// <returns>The settings object or null if not found</returns>
|
||||
public static T GetSettingsObject<T>() where T : class
|
||||
{
|
||||
return Instance?.GetSettings<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the developer settings object of specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of developer settings to retrieve</typeparam>
|
||||
/// <returns>The developer settings object or null if not found</returns>
|
||||
public static T GetDeveloperSettings<T>() where T : BaseDeveloperSettings
|
||||
{
|
||||
return DeveloperSettingsProvider.Instance?.GetSettings<T>();
|
||||
}
|
||||
|
||||
// PLAYER & FOLLOWER SETTINGS
|
||||
|
||||
// Player settings
|
||||
public float MoveSpeed => GetSettings<IPlayerFollowerSettings>()?.MoveSpeed ?? 5f;
|
||||
public float StopDistance => GetSettings<IPlayerFollowerSettings>()?.StopDistance ?? 0.1f;
|
||||
public bool UseRigidbody => GetSettings<IPlayerFollowerSettings>()?.UseRigidbody ?? true;
|
||||
public HoldMovementMode DefaultHoldMovementMode =>
|
||||
GetSettings<IPlayerFollowerSettings>()?.DefaultHoldMovementMode ?? HoldMovementMode.Pathfinding;
|
||||
|
||||
// Follower settings
|
||||
public float FollowDistance => GetSettings<IPlayerFollowerSettings>()?.FollowDistance ?? 1.5f;
|
||||
public float ManualMoveSmooth => GetSettings<IPlayerFollowerSettings>()?.ManualMoveSmooth ?? 8f;
|
||||
public float ThresholdFar => GetSettings<IPlayerFollowerSettings>()?.ThresholdFar ?? 2.5f;
|
||||
public float ThresholdNear => GetSettings<IPlayerFollowerSettings>()?.ThresholdNear ?? 0.5f;
|
||||
public float StopThreshold => GetSettings<IPlayerFollowerSettings>()?.StopThreshold ?? 0.1f;
|
||||
public float FollowUpdateInterval => GetSettings<IPlayerFollowerSettings>()?.FollowUpdateInterval ?? 0.1f;
|
||||
public float FollowerSpeedMultiplier => GetSettings<IPlayerFollowerSettings>()?.FollowerSpeedMultiplier ?? 1.2f;
|
||||
public float HeldIconDisplayHeight => GetSettings<IPlayerFollowerSettings>()?.HeldIconDisplayHeight ?? 2.0f;
|
||||
|
||||
// INTERACTION SETTINGS
|
||||
|
||||
public float PlayerStopDistance => GetSettings<IInteractionSettings>()?.PlayerStopDistance ?? 6.0f;
|
||||
public float PlayerStopDistanceDirectInteraction => GetSettings<IInteractionSettings>()?.PlayerStopDistanceDirectInteraction ?? 2.0f;
|
||||
public float FollowerPickupDelay => GetSettings<IInteractionSettings>()?.FollowerPickupDelay ?? 0.2f;
|
||||
public LayerMask InteractableLayerMask => GetSettings<IInteractionSettings>()?.InteractableLayerMask ?? -1;
|
||||
public GameObject BasePickupPrefab => GetSettings<IInteractionSettings>()?.BasePickupPrefab;
|
||||
public GameObject LevelSwitchMenuPrefab => GetSettings<IInteractionSettings>()?.LevelSwitchMenuPrefab;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the combination rule for two items, if any.
|
||||
/// </summary>
|
||||
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<IInteractionSettings>();
|
||||
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
|
||||
/// <summary>
|
||||
/// Returns the slot item config for a given slot item.
|
||||
/// </summary>
|
||||
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<IInteractionSettings>();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// ScriptableObject for storing and configuring all game settings, including player, follower, and item configuration.
|
||||
/// </summary>
|
||||
[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<PickupItemData> allowedItems;
|
||||
public System.Collections.Generic.List<PickupItemData> forbiddenItems;
|
||||
}
|
||||
|
||||
[Header("Item Configuration")]
|
||||
public System.Collections.Generic.List<CombinationRule> combinationRules;
|
||||
public System.Collections.Generic.List<SlotItemConfig> slotItemConfigs;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4ec438b455a4044957501c2c66a6f4b
|
||||
timeCreated: 1756933137
|
||||
3
Assets/Scripts/Core/Settings.meta
Normal file
3
Assets/Scripts/Core/Settings.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e380783135324fcd925048783e01d691
|
||||
timeCreated: 1758619858
|
||||
19
Assets/Scripts/Core/Settings/BaseDeveloperSettings.cs
Normal file
19
Assets/Scripts/Core/Settings/BaseDeveloperSettings.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Base abstract class for all developer settings.
|
||||
/// Developer settings are intended for technical configuration rather than gameplay/design values.
|
||||
/// </summary>
|
||||
public abstract class BaseDeveloperSettings : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Called to validate settings values when they are changed in the inspector.
|
||||
/// </summary>
|
||||
public virtual void OnValidate()
|
||||
{
|
||||
// Base implementation does nothing, override in derived classes
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50def2e6e95a4830b57f3e1b76a4df51
|
||||
timeCreated: 1758707161
|
||||
16
Assets/Scripts/Core/Settings/BaseSettings.cs
Normal file
16
Assets/Scripts/Core/Settings/BaseSettings.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all settings ScriptableObjects.
|
||||
/// Provides common functionality for all settings types.
|
||||
/// </summary>
|
||||
public abstract class BaseSettings : ScriptableObject
|
||||
{
|
||||
public virtual void OnValidate()
|
||||
{
|
||||
// Override in derived classes to add validation
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Settings/BaseSettings.cs.meta
Normal file
3
Assets/Scripts/Core/Settings/BaseSettings.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd33ef6036eb49358acbbd50dfd9bb13
|
||||
timeCreated: 1758619858
|
||||
116
Assets/Scripts/Core/Settings/DeveloperSettingsProvider.cs
Normal file
116
Assets/Scripts/Core/Settings/DeveloperSettingsProvider.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using System;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to developer settings for technical configuration rather than gameplay parameters.
|
||||
/// Follows the singleton pattern for global access.
|
||||
/// </summary>
|
||||
public class DeveloperSettingsProvider : MonoBehaviour
|
||||
{
|
||||
private static DeveloperSettingsProvider _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton instance of the provider.
|
||||
/// </summary>
|
||||
public static DeveloperSettingsProvider Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null && Application.isPlaying)
|
||||
{
|
||||
_instance = FindFirstObjectByType<DeveloperSettingsProvider>();
|
||||
|
||||
if (_instance == null)
|
||||
{
|
||||
GameObject go = new GameObject("DeveloperSettingsProvider");
|
||||
_instance = go.AddComponent<DeveloperSettingsProvider>();
|
||||
// Don't destroy between scenes
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Dictionary to cache loaded settings
|
||||
private Dictionary<System.Type, BaseDeveloperSettings> _settingsCache = new Dictionary<System.Type, BaseDeveloperSettings>();
|
||||
|
||||
// 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<System.Type, BaseDeveloperSettings>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or loads developer settings of the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of developer settings to retrieve</typeparam>
|
||||
/// <returns>The settings instance or null if not found</returns>
|
||||
public T GetSettings<T>() 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<T>(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<T>($"{_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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the settings cache, forcing settings to be reloaded.
|
||||
/// </summary>
|
||||
public void ClearCache()
|
||||
{
|
||||
_settingsCache.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9945aa4a563434e973ab49176259150
|
||||
timeCreated: 1758707186
|
||||
246
Assets/Scripts/Core/Settings/DivingDeveloperSettings.cs
Normal file
246
Assets/Scripts/Core/Settings/DivingDeveloperSettings.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum defining the type of bump response when player collides with obstacles
|
||||
/// </summary>
|
||||
public enum BumpMode
|
||||
{
|
||||
Impulse = 0,
|
||||
SmoothToCenter = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Developer settings for the diving minigame technical configuration.
|
||||
/// These settings are separate from gameplay/design settings and focus on technical implementation details.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 033961b12e7b4289838d554c2264bacd
|
||||
timeCreated: 1758707215
|
||||
234
Assets/Scripts/Core/Settings/DivingMinigameSettings.cs
Normal file
234
Assets/Scripts/Core/Settings/DivingMinigameSettings.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings related to minigames
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ce4dba7a1c54e73b1b3d7131a1c0570
|
||||
timeCreated: 1758619927
|
||||
48
Assets/Scripts/Core/Settings/InteractionSettings.cs
Normal file
48
Assets/Scripts/Core/Settings/InteractionSettings.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings related to interactions and items
|
||||
/// </summary>
|
||||
[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<CombinationRule> combinationRules = new List<CombinationRule>();
|
||||
[SerializeField] private List<SlotItemConfig> slotItemConfigs = new List<SlotItemConfig>();
|
||||
|
||||
// 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<CombinationRule> CombinationRules => combinationRules;
|
||||
public List<SlotItemConfig> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Settings/InteractionSettings.cs.meta
Normal file
3
Assets/Scripts/Core/Settings/InteractionSettings.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac22b092dc6f4db5b3dad35172b6e4c4
|
||||
timeCreated: 1758619914
|
||||
27
Assets/Scripts/Core/Settings/ItemConfigTypes.cs
Normal file
27
Assets/Scripts/Core/Settings/ItemConfigTypes.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a rule for combining two items
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class CombinationRule
|
||||
{
|
||||
public PickupItemData itemA;
|
||||
public PickupItemData itemB;
|
||||
public GameObject resultPrefab; // The prefab to spawn as the result
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for items that can be placed in slots
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class SlotItemConfig
|
||||
{
|
||||
public PickupItemData slotItem; // The slot object (SO reference)
|
||||
public List<PickupItemData> allowedItems;
|
||||
public List<PickupItemData> forbiddenItems; // Items that cannot be placed in this slot
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Settings/ItemConfigTypes.cs.meta
Normal file
3
Assets/Scripts/Core/Settings/ItemConfigTypes.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f9547445fd84c7db30533b7ee9d81dd
|
||||
timeCreated: 1758699048
|
||||
20
Assets/Scripts/Core/Settings/LayerAttributes.cs
Normal file
20
Assets/Scripts/Core/Settings/LayerAttributes.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute to indicate a field should be drawn using the layer selector dropdown
|
||||
/// </summary>
|
||||
public class LayerAttribute : PropertyAttribute
|
||||
{
|
||||
// No properties needed - this is a marker attribute
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to indicate a field should be drawn using the layer mask selector dropdown
|
||||
/// </summary>
|
||||
public class LayerMaskAttribute : PropertyAttribute
|
||||
{
|
||||
// No properties needed - this is a marker attribute
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Settings/LayerAttributes.cs.meta
Normal file
3
Assets/Scripts/Core/Settings/LayerAttributes.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c64dbd728524f23bda766b57a388207
|
||||
timeCreated: 1758711688
|
||||
13
Assets/Scripts/Core/Settings/MovementModeTypes.cs
Normal file
13
Assets/Scripts/Core/Settings/MovementModeTypes.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum defining different movement modes for player movement
|
||||
/// </summary>
|
||||
public enum HoldMovementMode
|
||||
{
|
||||
Pathfinding,
|
||||
Direct
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Settings/MovementModeTypes.cs.meta
Normal file
3
Assets/Scripts/Core/Settings/MovementModeTypes.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6b1454235ab476dae09e99238d6c7ce
|
||||
timeCreated: 1758699033
|
||||
53
Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs
Normal file
53
Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings related to player and follower behavior
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32cd6d14d9304d5ba0fd590da1346654
|
||||
timeCreated: 1758619904
|
||||
51
Assets/Scripts/Core/Settings/ServiceLocator.cs
Normal file
51
Assets/Scripts/Core/Settings/ServiceLocator.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Service Locator implementation for managing settings services.
|
||||
/// Provides a central registry for all settings services.
|
||||
/// </summary>
|
||||
public static class ServiceLocator
|
||||
{
|
||||
private static readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Register a service with the service locator.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The interface type for the service</typeparam>
|
||||
/// <param name="service">The service implementation</param>
|
||||
public static void Register<T>(T service) where T : class
|
||||
{
|
||||
_services[typeof(T)] = service;
|
||||
Debug.Log($"Service registered: {typeof(T).Name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a service from the service locator.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The interface type for the service</typeparam>
|
||||
/// <returns>The service implementation, or null if not found</returns>
|
||||
public static T Get<T>() 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all registered services.
|
||||
/// </summary>
|
||||
public static void Clear()
|
||||
{
|
||||
_services.Clear();
|
||||
Debug.Log("All services cleared");
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Settings/ServiceLocator.cs.meta
Normal file
3
Assets/Scripts/Core/Settings/ServiceLocator.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16cc39d2f99b4e7fa65c4a8b39f3e87c
|
||||
timeCreated: 1758619866
|
||||
98
Assets/Scripts/Core/Settings/SettingsInterfaces.cs
Normal file
98
Assets/Scripts/Core/Settings/SettingsInterfaces.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for player and follower settings
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for interaction and item settings
|
||||
/// </summary>
|
||||
public interface IInteractionSettings
|
||||
{
|
||||
float PlayerStopDistance { get; }
|
||||
float PlayerStopDistanceDirectInteraction { get; }
|
||||
float FollowerPickupDelay { get; }
|
||||
LayerMask InteractableLayerMask { get; }
|
||||
GameObject BasePickupPrefab { get; }
|
||||
GameObject LevelSwitchMenuPrefab { get; }
|
||||
List<CombinationRule> CombinationRules { get; }
|
||||
List<SlotItemConfig> SlotItemConfigs { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for minigame settings
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Settings/SettingsInterfaces.cs.meta
Normal file
3
Assets/Scripts/Core/Settings/SettingsInterfaces.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54611ae012ab4455a53bd60961d9e7ea
|
||||
timeCreated: 1758619892
|
||||
100
Assets/Scripts/Core/Settings/SettingsProvider.cs
Normal file
100
Assets/Scripts/Core/Settings/SettingsProvider.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for loading and caching settings from Addressables.
|
||||
/// Uses synchronous loading to ensure settings are available immediately.
|
||||
/// </summary>
|
||||
public class SettingsProvider : MonoBehaviour
|
||||
{
|
||||
private static SettingsProvider _instance;
|
||||
private Dictionary<string, BaseSettings> _settingsCache = new Dictionary<string, BaseSettings>();
|
||||
|
||||
// Singleton instance
|
||||
public static SettingsProvider Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
GameObject go = new GameObject("Settings Provider");
|
||||
_instance = go.AddComponent<SettingsProvider>();
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
else if (_instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load settings synchronously using Addressables - blocks until complete
|
||||
/// </summary>
|
||||
public T LoadSettingsSynchronous<T>() 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<T>($"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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get cached settings or load them synchronously if not cached
|
||||
/// </summary>
|
||||
public T GetSettings<T>() 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<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Settings/SettingsProvider.cs.meta
Normal file
3
Assets/Scripts/Core/Settings/SettingsProvider.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d212b25192045d198f2bf42ef74f278
|
||||
timeCreated: 1758619879
|
||||
57
Assets/Scripts/Core/SettingsAccess.cs
Normal file
57
Assets/Scripts/Core/SettingsAccess.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills
|
||||
{
|
||||
/// <summary>
|
||||
/// Unified access to settings in both editor and play mode
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/SettingsAccess.cs.meta
Normal file
3
Assets/Scripts/Core/SettingsAccess.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a23d841c0e2047ff8dbe84820227bdea
|
||||
timeCreated: 1758634274
|
||||
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Handles hold move input. Updates the target position for direct or pathfinding movement.
|
||||
/// </summary>
|
||||
/// /// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -194,25 +194,9 @@ namespace Interactions
|
||||
/// </summary>
|
||||
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<GameSettings>(
|
||||
"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);
|
||||
|
||||
@@ -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<DivingDeveloperSettings>();
|
||||
_gameSettings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
|
||||
|
||||
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>();
|
||||
_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
|
||||
/// <returns>Randomized interval in seconds.</returns>
|
||||
float GetRandomizedInterval()
|
||||
{
|
||||
return spawnInterval * Random.Range(0.8f, 1.2f);
|
||||
return _devSettings.BubbleSpawnInterval * Random.Range(0.8f, 1.2f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -88,11 +83,11 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -148,10 +143,10 @@ namespace Minigames.DivingForPictures
|
||||
Bubble[] activeBubbles = FindObjectsByType<Bubble>(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.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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<IDivingMinigameSettings>();
|
||||
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<Component>())
|
||||
{
|
||||
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<TrenchTileSpawner>();
|
||||
@@ -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);
|
||||
|
||||
@@ -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<GameObject> 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<GameObject> onObstacleSpawned;
|
||||
|
||||
[Tooltip("Called when an obstacle is returned to pool")]
|
||||
[Tooltip("Invoked when an obstacle is destroyed or returned to pool")]
|
||||
public UnityEvent<GameObject> 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<IDivingMinigameSettings>();
|
||||
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||
|
||||
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<GameObject>();
|
||||
|
||||
if (onObstacleDestroyed == null)
|
||||
onObstacleDestroyed = new UnityEvent<GameObject>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
@@ -100,6 +89,8 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
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<ObstaclePool>();
|
||||
|
||||
// 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<FloatingObstacle> prefabObstacles = new List<FloatingObstacle>(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
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
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.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -465,8 +463,9 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
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.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
|
||||
@@ -8,8 +8,34 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
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<FloatingObstacle>();
|
||||
if (obstacleComponent != null)
|
||||
@@ -21,21 +47,20 @@ namespace Minigames.DivingForPictures
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to handle immunity end
|
||||
/// Handler for immunity ended event - replaces OnImmunityEnd method
|
||||
/// </summary>
|
||||
protected override void OnImmunityEnd()
|
||||
private void HandleImmunityEnded()
|
||||
{
|
||||
Debug.Log($"[ObstacleCollision] Damage immunity ended");
|
||||
// No special handling needed - shared immunity system handles collider re-enabling
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace Minigames.DivingForPictures
|
||||
{
|
||||
@@ -9,28 +10,27 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
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<DivingDeveloperSettings>();
|
||||
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();
|
||||
// Restore original color if disabled during blinking
|
||||
if (targetSpriteRenderer != null)
|
||||
{
|
||||
targetSpriteRenderer.color = _originalColor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the red blinking effect when damage begins
|
||||
/// Start the blinking effect coroutine
|
||||
/// </summary>
|
||||
private void StartBlinking()
|
||||
private void StartBlinkEffect()
|
||||
{
|
||||
if (targetSpriteRenderer == null) return;
|
||||
if (targetSpriteRenderer == null || _devSettings == null) return;
|
||||
|
||||
Debug.Log("[PlayerBlinkBehavior] Starting damage blink effect");
|
||||
|
||||
// Stop any existing blink coroutine
|
||||
if (_blinkCoroutine != null)
|
||||
// If already blinking, stop the current coroutine
|
||||
if (_isBlinking && _blinkCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_blinkCoroutine);
|
||||
}
|
||||
|
||||
_isBlinking = true;
|
||||
_isShowingDamageColor = false;
|
||||
|
||||
// Start the blink coroutine
|
||||
// Start a new blink coroutine
|
||||
_blinkCoroutine = StartCoroutine(BlinkCoroutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the blinking effect when damage ends
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that handles the blinking animation
|
||||
/// Coroutine that handles the blink effect timing
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the sprite renderer to its original color
|
||||
/// </summary>
|
||||
private void RestoreOriginalColor()
|
||||
{
|
||||
if (targetSpriteRenderer != null)
|
||||
{
|
||||
targetSpriteRenderer.color = _originalColor;
|
||||
_isShowingDamageColor = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the original color reference (useful if sprite color changes during gameplay)
|
||||
/// </summary>
|
||||
public void UpdateOriginalColor()
|
||||
{
|
||||
if (targetSpriteRenderer != null && !_isBlinking)
|
||||
{
|
||||
_originalColor = targetSpriteRenderer.color;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to change blink color at runtime
|
||||
/// </summary>
|
||||
public void SetDamageBlinkColor(Color newColor)
|
||||
{
|
||||
damageBlinkColor = newColor;
|
||||
damageBlinkColor.a = damageColorAlpha;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to change blink rate at runtime
|
||||
/// </summary>
|
||||
public void SetBlinkRate(float rate)
|
||||
{
|
||||
blinkRate = rate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if currently blinking
|
||||
/// </summary>
|
||||
public bool IsBlinking => _isBlinking;
|
||||
|
||||
/// <summary>
|
||||
/// Manually trigger blink effect (useful for testing or other damage sources)
|
||||
/// </summary>
|
||||
public void TriggerBlink(float duration)
|
||||
{
|
||||
if (_blinkCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_blinkCoroutine);
|
||||
}
|
||||
|
||||
StartCoroutine(ManualBlinkCoroutine(duration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine for manually triggered blink effects
|
||||
/// </summary>
|
||||
private IEnumerator ManualBlinkCoroutine(float duration)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when immunity ends to stop the blinking effect
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
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,211 +63,172 @@ namespace Minigames.DivingForPictures
|
||||
OnDamageTaken?.Invoke();
|
||||
}
|
||||
|
||||
protected bool wasInputBlocked;
|
||||
|
||||
protected virtual void Awake()
|
||||
/// <summary>
|
||||
/// Called when the component is enabled
|
||||
/// </summary>
|
||||
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<PlayerController>();
|
||||
|
||||
// Set up shared collider reference (only once)
|
||||
// Initialize the shared player collider if not already set
|
||||
if (_sharedPlayerCollider == null)
|
||||
{
|
||||
_sharedPlayerCollider = GetComponent<Collider2D>();
|
||||
if (_sharedPlayerCollider == null)
|
||||
{
|
||||
_sharedPlayerCollider = GetComponentInChildren<Collider2D>();
|
||||
if (_sharedPlayerCollider != null)
|
||||
{
|
||||
Debug.Log($"[PlayerCollisionBehavior] Found collider on child object: {_sharedPlayerCollider.gameObject.name}");
|
||||
Debug.LogError("[PlayerCollisionBehavior] No Collider2D found on this GameObject!");
|
||||
}
|
||||
}
|
||||
|
||||
if (_sharedPlayerCollider == null)
|
||||
// Load settings
|
||||
_gameSettings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
|
||||
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||
|
||||
if (_gameSettings == null)
|
||||
{
|
||||
Debug.LogError($"[PlayerCollisionBehavior] No Collider2D found on this GameObject or its children!");
|
||||
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)
|
||||
/// <summary>
|
||||
/// Called when the component is disabled
|
||||
/// </summary>
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
_allInstances.Remove(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a Collider enters the trigger
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process collision with an obstacle
|
||||
/// </summary>
|
||||
protected virtual void HandleObstacleCollision(Collider2D obstacle)
|
||||
{
|
||||
// Trigger global damage and start immunity
|
||||
TriggerDamageAndImmunity();
|
||||
|
||||
// Call the specific collision response for the derived class
|
||||
HandleCollisionResponse(obstacle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract method for derived classes to implement specific collision responses
|
||||
/// </summary>
|
||||
protected abstract void HandleCollisionResponse(Collider2D obstacle);
|
||||
|
||||
/// <summary>
|
||||
/// Trigger damage event and start immunity period
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the immunity period for all collision behaviors
|
||||
/// </summary>
|
||||
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);
|
||||
// Block input if configured
|
||||
if (_devSettings.BlockInputDuringImmunity && playerController != null)
|
||||
{
|
||||
// Notify player controller to block input
|
||||
BlockPlayerInput();
|
||||
wasInputBlocked = true;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Unregister this instance
|
||||
_allInstances.Remove(this);
|
||||
// Trigger event for all listeners
|
||||
OnImmunityStarted?.Invoke();
|
||||
|
||||
// Clean up static references if this was the coroutine runner
|
||||
if (_coroutineRunner == this)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another collider enters this trigger collider
|
||||
/// </summary>
|
||||
/// <param name="other">The other collider that entered the trigger</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a collision with an obstacle is detected
|
||||
/// </summary>
|
||||
/// <param name="obstacle">The obstacle collider that was hit</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the shared immunity period across all collision behaviors
|
||||
/// </summary>
|
||||
private void StartGlobalImmunity()
|
||||
{
|
||||
if (_isGloballyImmune) return; // Already immune
|
||||
|
||||
_isGloballyImmune = true;
|
||||
|
||||
// Disable the shared collider to prevent further collisions
|
||||
if (_sharedPlayerCollider != null)
|
||||
{
|
||||
_sharedPlayerCollider.enabled = false;
|
||||
}
|
||||
|
||||
// Stop any existing immunity coroutine
|
||||
// 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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that handles the immunity timer
|
||||
/// Coroutine to handle the immunity duration timer
|
||||
/// </summary>
|
||||
private IEnumerator ImmunityCoroutine()
|
||||
private IEnumerator ImmunityTimerCoroutine()
|
||||
{
|
||||
Debug.Log($"[PlayerCollisionBehavior] Starting immunity coroutine for {damageImmunityDuration} seconds");
|
||||
// Wait for the immunity duration
|
||||
yield return new WaitForSeconds(_gameSettings.DamageImmunityDuration);
|
||||
|
||||
yield return new WaitForSeconds(damageImmunityDuration);
|
||||
|
||||
Debug.Log($"[PlayerCollisionBehavior] Immunity period ended");
|
||||
|
||||
// End immunity
|
||||
// Reset immunity state
|
||||
_isGloballyImmune = false;
|
||||
_globalImmunityCoroutine = null;
|
||||
|
||||
// Re-enable the shared collider
|
||||
if (_sharedPlayerCollider != null)
|
||||
// Restore player input if it was blocked
|
||||
if (_devSettings.BlockInputDuringImmunity)
|
||||
{
|
||||
_sharedPlayerCollider.enabled = true;
|
||||
RestorePlayerInput();
|
||||
}
|
||||
|
||||
// Notify all instances about immunity end
|
||||
foreach (var instance in _allInstances)
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
instance.OnImmunityEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast immunity end event
|
||||
// Trigger event for all listeners
|
||||
OnImmunityEnded?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to implement specific collision response behavior
|
||||
/// Blocks player input during immunity
|
||||
/// </summary>
|
||||
/// <param name="obstacle">The obstacle that was collided with</param>
|
||||
protected abstract void HandleCollisionResponse(Collider2D obstacle);
|
||||
|
||||
/// <summary>
|
||||
/// Called when damage immunity starts (called on all instances)
|
||||
/// </summary>
|
||||
protected virtual void OnImmunityStart()
|
||||
protected virtual void BlockPlayerInput()
|
||||
{
|
||||
Debug.Log($"[{GetType().Name}] Damage immunity started for {damageImmunityDuration} seconds");
|
||||
|
||||
// Block input if specified
|
||||
if (blockInputDuringImmunity)
|
||||
if (playerController != null && playerController.enabled)
|
||||
{
|
||||
BlockPlayerInput();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when damage immunity ends (called on all instances)
|
||||
/// </summary>
|
||||
protected virtual void OnImmunityEnd()
|
||||
{
|
||||
Debug.Log($"[{GetType().Name}] Damage immunity ended");
|
||||
|
||||
// Restore input if it was blocked
|
||||
if (wasInputBlocked)
|
||||
{
|
||||
RestorePlayerInput();
|
||||
playerController.enabled = false;
|
||||
wasInputBlocked = true;
|
||||
Debug.Log($"[{GetType().Name}] Player input blocked during immunity");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,20 +250,7 @@ namespace Minigames.DivingForPictures
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blocks player input during immunity
|
||||
/// </summary>
|
||||
protected virtual void BlockPlayerInput()
|
||||
{
|
||||
if (playerController != null && playerController.enabled)
|
||||
{
|
||||
playerController.enabled = false;
|
||||
wasInputBlocked = true;
|
||||
Debug.Log($"[{GetType().Name}] Player input blocked during immunity");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the PlayerController's internal target to match current position
|
||||
/// Updates the player controller's target position to the current position to prevent snapping
|
||||
/// </summary>
|
||||
protected virtual void UpdateControllerTarget()
|
||||
{
|
||||
@@ -322,50 +266,5 @@ namespace Minigames.DivingForPictures
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given layer is included in our obstacle layer mask
|
||||
/// </summary>
|
||||
/// <param name="layer">The layer to check</param>
|
||||
/// <returns>True if the layer is included in the obstacle layer mask</returns>
|
||||
private bool IsObstacleLayer(int layer)
|
||||
{
|
||||
return (obstacleLayerMask.value & (1 << layer)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public property to check if player is currently immune (shared across all instances)
|
||||
/// </summary>
|
||||
public static bool IsImmune => _isGloballyImmune;
|
||||
|
||||
/// <summary>
|
||||
/// Public method to manually end immunity (affects all collision behaviors)
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using UnityEngine;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace Minigames.DivingForPictures
|
||||
{
|
||||
@@ -8,11 +9,8 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
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<IDivingMinigameSettings>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace Minigames.DivingForPictures
|
||||
{
|
||||
@@ -9,35 +10,21 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores player input after bump movement
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to handle bump-specific input blocking during immunity
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to handle immunity end and bump cleanup
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when component is destroyed - cleanup coroutines
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_bumpCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_bumpCoroutine);
|
||||
_bumpCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to change bump mode at runtime
|
||||
/// </summary>
|
||||
public void SetBumpMode(BumpMode mode)
|
||||
{
|
||||
bumpMode = mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to change bump force at runtime
|
||||
/// </summary>
|
||||
public void SetBumpForce(float force)
|
||||
{
|
||||
bumpForce = force;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to change smooth move speed at runtime
|
||||
/// </summary>
|
||||
public void SetSmoothMoveSpeed(float speed)
|
||||
{
|
||||
smoothMoveSpeed = speed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if player is currently being bumped
|
||||
/// </summary>
|
||||
public bool IsBumping => _isBumping;
|
||||
|
||||
/// <summary>
|
||||
/// Check if input is currently blocked by bump
|
||||
/// </summary>
|
||||
public bool IsBumpInputBlocked => _bumpInputBlocked;
|
||||
|
||||
/// <summary>
|
||||
/// Public method to manually stop bump movement
|
||||
/// </summary>
|
||||
public void StopBump()
|
||||
{
|
||||
if (_isBumping && _bumpCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_bumpCoroutine);
|
||||
_bumpCoroutine = null;
|
||||
_isBumping = false;
|
||||
|
||||
if (_bumpInputBlocked)
|
||||
{
|
||||
RestoreBumpInput();
|
||||
}
|
||||
|
||||
Debug.Log("[TileBumpCollision] Bump manually stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,13 @@
|
||||
using UnityEngine;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a wobble (rocking and vertical movement) effect to the object, based on speed and time.
|
||||
/// </summary>
|
||||
public class WobbleBehavior : MonoBehaviour
|
||||
{
|
||||
[Header("Wobble Settings")]
|
||||
public float wobbleFrequency = 1.5f;
|
||||
/// <summary>
|
||||
/// Max degrees from horizontal.
|
||||
/// </summary>
|
||||
public float baseWobbleAmplitude = 8f;
|
||||
/// <summary>
|
||||
/// How much speed affects amplitude.
|
||||
/// </summary>
|
||||
public float speedToAmplitude = 2f;
|
||||
/// <summary>
|
||||
/// Maximum allowed rotation in degrees.
|
||||
/// </summary>
|
||||
public float maxRotationLimit = 45f;
|
||||
|
||||
[Header("Vertical Movement Settings")]
|
||||
public float verticalFrequency = 0.5f;
|
||||
/// <summary>
|
||||
/// How far the object moves up/down.
|
||||
/// </summary>
|
||||
public float verticalAmplitude = 0.5f;
|
||||
|
||||
[Header("Smoothing Settings")]
|
||||
public float velocitySmoothing = 10f;
|
||||
/// <summary>
|
||||
/// How quickly rotation is smoothed.
|
||||
/// </summary>
|
||||
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).
|
||||
/// </summary>
|
||||
public float Velocity => velocity;
|
||||
|
||||
/// <summary>
|
||||
/// The current vertical offset due to wobble.
|
||||
/// </summary>
|
||||
public float VerticalOffset => verticalOffset;
|
||||
|
||||
void Start()
|
||||
private void Awake()
|
||||
{
|
||||
lastPosition = transform.position;
|
||||
smoothedVelocity = 0f;
|
||||
smoothedAngle = 0f;
|
||||
// Get developer settings
|
||||
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||
if (_devSettings == null)
|
||||
{
|
||||
Debug.LogError("[WobbleBehavior] Failed to load developer settings!");
|
||||
}
|
||||
|
||||
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;
|
||||
// Initialize
|
||||
lastPosition = transform.position;
|
||||
basePosition = transform.position;
|
||||
}
|
||||
|
||||
// Smooth velocity to prevent jitter
|
||||
smoothedVelocity = Mathf.Lerp(smoothedVelocity, velocity, velocitySmoothing * Time.deltaTime);
|
||||
private void Update()
|
||||
{
|
||||
if (_devSettings == null) return;
|
||||
|
||||
// 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 velocity based on position change
|
||||
Vector3 positionDelta = transform.position - lastPosition;
|
||||
velocity = positionDelta.x / Time.deltaTime;
|
||||
lastPosition = transform.position;
|
||||
basePosition = transform.position;
|
||||
|
||||
// Oscillate around horizontal (0 degrees)
|
||||
wobbleTime += Time.deltaTime * wobbleFrequency;
|
||||
float targetAngle = Mathf.Sin(wobbleTime) * amplitude;
|
||||
targetAngle = Mathf.Clamp(targetAngle, -maxRotationLimit, maxRotationLimit);
|
||||
// Smooth velocity changes
|
||||
smoothedVelocity = Mathf.Lerp(smoothedVelocity, velocity, Time.deltaTime * _devSettings.PlayerVelocitySmoothing);
|
||||
|
||||
// Smooth the rotation angle
|
||||
smoothedAngle = Mathf.Lerp(smoothedAngle, targetAngle, rotationSmoothing * Time.deltaTime);
|
||||
// Calculate wobble rotation based on velocity and time
|
||||
wobbleTime += Time.deltaTime * _devSettings.PlayerWobbleFrequency;
|
||||
float rawWobble = Mathf.Sin(wobbleTime);
|
||||
|
||||
// Apply rotation (Z axis for 2D)
|
||||
transform.localRotation = Quaternion.Euler(0f, 0f, smoothedAngle);
|
||||
// Calculate wobble amplitude based on velocity
|
||||
float velocityFactor = Mathf.Abs(smoothedVelocity) * _devSettings.PlayerSpeedToAmplitude;
|
||||
float wobbleAmplitude = _devSettings.PlayerBaseWobbleAmplitude + velocityFactor;
|
||||
|
||||
// Calculate vertical up/down movement (wave riding) only once
|
||||
verticalOffset = Mathf.Sin(wobbleTime * verticalFrequency) * verticalAmplitude;
|
||||
// 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);
|
||||
|
||||
// Calculate vertical bobbing
|
||||
float time = Time.time * _devSettings.PlayerVerticalFrequency;
|
||||
verticalOffset = Mathf.Sin(time) * _devSettings.PlayerVerticalAmplitude;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<GameObject> 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<GameObject> onTileSpawned;
|
||||
@@ -38,6 +24,10 @@ namespace Minigames.DivingForPictures
|
||||
[FormerlySerializedAs("OnTileDestroyed")]
|
||||
public UnityEvent<GameObject> onTileDestroyed;
|
||||
|
||||
// Settings references
|
||||
private IDivingMinigameSettings _settings;
|
||||
private DivingDeveloperSettings _devSettings;
|
||||
|
||||
// Private fields
|
||||
private readonly Dictionary<GameObject, float> _tileHeights = new Dictionary<GameObject, float>();
|
||||
private readonly List<GameObject> _activeTiles = new List<GameObject>();
|
||||
@@ -53,9 +43,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<IDivingMinigameSettings>();
|
||||
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||
|
||||
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
|
||||
/// </summary>
|
||||
private void CalculateVelocity()
|
||||
{
|
||||
_currentVelocity = moveSpeed * Time.fixedDeltaTime;
|
||||
_currentVelocity = _baseMoveSpeed * Time.fixedDeltaTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -197,9 +199,9 @@ namespace Minigames.DivingForPictures
|
||||
poolGO.transform.SetParent(transform);
|
||||
_tilePool = poolGO.AddComponent<TrenchTilePool>();
|
||||
|
||||
// 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<Tile> prefabTiles = new List<Tile>(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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
@@ -656,7 +672,9 @@ namespace Minigames.DivingForPictures
|
||||
|
||||
// Draw tile bounds for debugging
|
||||
Gizmos.color = Color.cyan;
|
||||
for (int i = 0; i < initialTileCount; i++)
|
||||
if (_settings != null)
|
||||
{
|
||||
for (int i = 0; i < _settings.InitialTileCount; i++)
|
||||
{
|
||||
float height = DefaultTileHeight;
|
||||
if (tilePrefabs != null && tilePrefabs.Count > 0 && tilePrefabs[0] != null &&
|
||||
@@ -668,6 +686,27 @@ namespace Minigames.DivingForPictures
|
||||
Gizmos.DrawWireCube(center, new Vector3(10f, height, 1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Set the layer of a GameObject and all its children recursively
|
||||
/// </summary>
|
||||
/// <param name="obj">The GameObject to set the layer for</param>
|
||||
/// <param name="layer">The layer index to set</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Utilities.meta
Normal file
3
Assets/Scripts/Utilities.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d54ca063d685467f9cb2c05507ac833f
|
||||
timeCreated: 1758717247
|
||||
55
Assets/Scripts/Utilities/UnityExtensions.cs
Normal file
55
Assets/Scripts/Utilities/UnityExtensions.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Collection of useful extension methods for Unity built-in classes
|
||||
/// </summary>
|
||||
public static class UnityExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="mask">The layer mask to check</param>
|
||||
/// <param name="layer">The layer value to check for</param>
|
||||
/// <returns>True if the layer is in the mask, false otherwise</returns>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method to check if a GameObject's layer is in a layermask.
|
||||
/// Automatically gets the layer from the GameObject and handles the offset.
|
||||
/// </summary>
|
||||
/// <param name="mask">The layer mask to check</param>
|
||||
/// <param name="gameObject">The GameObject whose layer to check</param>
|
||||
/// <returns>True if the GameObject's layer is in the mask, false otherwise</returns>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bitwise check if a layer is in a layermask.
|
||||
/// This is an alternative implementation that uses bitwise AND operation.
|
||||
/// </summary>
|
||||
/// <param name="mask">The layer mask to check</param>
|
||||
/// <param name="layer">The layer value to check for</param>
|
||||
/// <returns>True if the layer is in the mask, false otherwise</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Utilities/UnityExtensions.cs.meta
Normal file
3
Assets/Scripts/Utilities/UnityExtensions.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e92a12c248d4b9ab06fcc0ba9c63ef3
|
||||
timeCreated: 1758717247
|
||||
8
Assets/Settings/Developer.meta
Normal file
8
Assets/Settings/Developer.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfde9ecdb3e084d47b97f511323c4a77
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
76
Assets/Settings/Developer/DivingDeveloperSettings.asset
Normal file
76
Assets/Settings/Developer/DivingDeveloperSettings.asset
Normal file
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 328ce914b893df646be3ad3c62755453
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
48
Assets/Settings/DivingMinigameSettings.asset
Normal file
48
Assets/Settings/DivingMinigameSettings.asset
Normal file
@@ -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
|
||||
8
Assets/Settings/DivingMinigameSettings.asset.meta
Normal file
8
Assets/Settings/DivingMinigameSettings.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9569848f604a6540827d4d4bb0a35c2
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
50
Assets/Settings/InteractionSettings.asset
Normal file
50
Assets/Settings/InteractionSettings.asset
Normal file
@@ -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: []
|
||||
8
Assets/Settings/InteractionSettings.asset.meta
Normal file
8
Assets/Settings/InteractionSettings.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f5195fb013895049a19488fd4d8f2a1
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
26
Assets/Settings/PlayerFollowerSettings.asset
Normal file
26
Assets/Settings/PlayerFollowerSettings.asset
Normal file
@@ -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
|
||||
8
Assets/Settings/PlayerFollowerSettings.asset.meta
Normal file
8
Assets/Settings/PlayerFollowerSettings.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35bfcff00faa72c4eb272a9e8288f965
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -21,7 +21,7 @@ TagManager:
|
||||
- Interactable
|
||||
- QuarryObstacle
|
||||
- QuarryMonster
|
||||
-
|
||||
- QuarryTrenchTile
|
||||
-
|
||||
-
|
||||
-
|
||||
|
||||
Reference in New Issue
Block a user