Compare commits
7 Commits
e338e28673
...
settings_r
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ea7e7ca84 | |||
| aeaa977294 | |||
| 783541a776 | |||
| 8b96a5d0c3 | |||
|
|
f07ce88331 | ||
|
|
404f6f4e5c | ||
|
|
197aedddb0 |
@@ -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_DefaultGroup: 6f3207429a65b3e4b83935ac19791077
|
||||||
m_currentHash:
|
m_currentHash:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
Hash: c0cf00979528ae95d3583c572e4eb343
|
Hash: 00000000000000000000000000000000
|
||||||
m_OptimizeCatalogSize: 0
|
m_OptimizeCatalogSize: 0
|
||||||
m_BuildRemoteCatalog: 0
|
m_BuildRemoteCatalog: 0
|
||||||
m_CatalogRequestsTimeout: 0
|
m_CatalogRequestsTimeout: 0
|
||||||
@@ -61,6 +61,7 @@ MonoBehaviour:
|
|||||||
m_GroupAssets:
|
m_GroupAssets:
|
||||||
- {fileID: 11400000, guid: efe7e1728e73e9546ac5dfee2eff524f, type: 2}
|
- {fileID: 11400000, guid: efe7e1728e73e9546ac5dfee2eff524f, type: 2}
|
||||||
- {fileID: 11400000, guid: 6e4927e7e19eef34b93dc2baa9e9e8e2, type: 2}
|
- {fileID: 11400000, guid: 6e4927e7e19eef34b93dc2baa9e9e8e2, type: 2}
|
||||||
|
- {fileID: 11400000, guid: e25c7672a65b5974bb354fcfb2a8400c, type: 2}
|
||||||
- {fileID: 11400000, guid: 7fcc03e584505ed4381983b6ebb1179d, type: 2}
|
- {fileID: 11400000, guid: 7fcc03e584505ed4381983b6ebb1179d, type: 2}
|
||||||
m_BuildSettings:
|
m_BuildSettings:
|
||||||
m_LogResourceManagerExceptions: 1
|
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 enum ItemType { None, Pickup, ItemSlot }
|
||||||
private ItemType _itemType = ItemType.None;
|
private ItemType _itemType = ItemType.None;
|
||||||
|
|
||||||
[MenuItem("Tools/Item Prefab Editor")]
|
[MenuItem("AppleHills/Item Prefab Editor")]
|
||||||
public static void ShowWindow()
|
public static void ShowWindow()
|
||||||
{
|
{
|
||||||
var window = GetWindow<ItemPrefabEditorWindow>("Item Prefab Editor");
|
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;
|
private bool _createNext = false;
|
||||||
|
|
||||||
[MenuItem("Tools/Prefab Creator")]
|
[MenuItem("AppleHills/Item Prefab Creator")]
|
||||||
public static void ShowWindow()
|
public static void ShowWindow()
|
||||||
{
|
{
|
||||||
var window = GetWindow<PrefabCreatorWindow>("Prefab Creator");
|
var window = GetWindow<PrefabCreatorWindow>("Prefab Creator");
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public class PuzzleChainEditorWindow : EditorWindow
|
|||||||
private Vector2 scrollPos;
|
private Vector2 scrollPos;
|
||||||
private const int INDENT_SIZE = 24;
|
private const int INDENT_SIZE = 24;
|
||||||
|
|
||||||
[MenuItem("Tools/Puzzle Chain Editor")]
|
[MenuItem("AppleHills/Puzzle Chain Editor")]
|
||||||
public static void ShowWindow()
|
public static void ShowWindow()
|
||||||
{
|
{
|
||||||
var window = GetWindow<PuzzleChainEditorWindow>("Puzzle Chain Editor");
|
var window = GetWindow<PuzzleChainEditorWindow>("Puzzle Chain Editor");
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class SceneObjectLocatorWindow : EditorWindow
|
|||||||
private List<PickupInfo> pickupInfos = new List<PickupInfo>();
|
private List<PickupInfo> pickupInfos = new List<PickupInfo>();
|
||||||
private Vector2 scrollPos;
|
private Vector2 scrollPos;
|
||||||
|
|
||||||
[MenuItem("Tools/Scene Object Locator")]
|
[MenuItem("AppleHills/Scene Object Locator")]
|
||||||
public static void ShowWindow()
|
public static void ShowWindow()
|
||||||
{
|
{
|
||||||
var window = GetWindow<SceneObjectLocatorWindow>("Scene Object Locator");
|
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_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
prefabIndex: 0
|
prefabIndex: 0
|
||||||
damage: 1
|
|
||||||
moveSpeed: 2
|
moveSpeed: 2
|
||||||
enableMovement: 1
|
enableMovement: 1
|
||||||
spawner: {fileID: 0}
|
spawner: {fileID: 0}
|
||||||
|
|||||||
@@ -1979,6 +1979,10 @@ PrefabInstance:
|
|||||||
propertyPath: m_LocalEulerAnglesHint.z
|
propertyPath: m_LocalEulerAnglesHint.z
|
||||||
value: 0
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 7852204877518954380, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
|
||||||
|
propertyPath: maxSpeed
|
||||||
|
value: 15
|
||||||
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents: []
|
m_RemovedComponents: []
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
m_AddedGameObjects: []
|
m_AddedGameObjects: []
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
using System;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Singleton manager for global game state and settings. Provides accessors for various gameplay parameters.
|
/// 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")]
|
[Header("Settings Status")]
|
||||||
public GameSettings gameSettings;
|
[SerializeField] private bool _settingsLoaded = false;
|
||||||
|
[SerializeField] private bool _developerSettingsLoaded = false;
|
||||||
|
public bool SettingsLoaded => _settingsLoaded;
|
||||||
|
public bool DeveloperSettingsLoaded => _developerSettingsLoaded;
|
||||||
|
|
||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
_instance = this;
|
_instance = this;
|
||||||
if (gameSettings == null)
|
|
||||||
{
|
// Create settings provider if it doesn't exist
|
||||||
gameSettings = Resources.Load<GameSettings>("DefaultSettings");
|
SettingsProvider.Instance.gameObject.name = "Settings Provider";
|
||||||
if (gameSettings == null)
|
|
||||||
{
|
// Create developer settings provider if it doesn't exist
|
||||||
Debug.LogError("GameSettings asset not found in Resources!");
|
DeveloperSettingsProvider.Instance.gameObject.name = "Developer Settings Provider";
|
||||||
}
|
|
||||||
}
|
// Load all settings synchronously during Awake
|
||||||
|
InitializeSettings();
|
||||||
|
InitializeDeveloperSettings();
|
||||||
|
|
||||||
// DontDestroyOnLoad(gameObject);
|
// 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()
|
void OnApplicationQuit()
|
||||||
{
|
{
|
||||||
_isQuitting = true;
|
_isQuitting = true;
|
||||||
|
ServiceLocator.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accessors for game settings
|
// Helper method to get settings
|
||||||
public float PlayerStopDistance => gameSettings != null ? gameSettings.playerStopDistance : 1.0f;
|
private T GetSettings<T>() where T : class
|
||||||
public float FollowerPickupDelay => gameSettings != null ? gameSettings.followerPickupDelay : 0.2f;
|
{
|
||||||
public float FollowDistance => gameSettings != null ? gameSettings.followDistance : 1.5f;
|
return ServiceLocator.Get<T>();
|
||||||
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;
|
/// <summary>
|
||||||
public float StopThreshold => gameSettings != null ? gameSettings.stopThreshold : 0.5f;
|
/// Returns the entire settings object of specified type.
|
||||||
public float MoveSpeed => gameSettings != null ? gameSettings.moveSpeed : 5f;
|
/// </summary>
|
||||||
public float StopDistance => gameSettings != null ? gameSettings.stopDistance : 0.1f;
|
/// <typeparam name="T">Type of settings to retrieve</typeparam>
|
||||||
public bool UseRigidbody => gameSettings != null ? gameSettings.useRigidbody : true;
|
/// <returns>The settings object or null if not found</returns>
|
||||||
public float FollowUpdateInterval => gameSettings != null ? gameSettings.followUpdateInterval : 0.1f;
|
public static T GetSettingsObject<T>() where T : class
|
||||||
public float FollowerSpeedMultiplier => gameSettings != null ? gameSettings.followerSpeedMultiplier : 1.2f;
|
{
|
||||||
public float HeldIconDisplayHeight => gameSettings != null ? gameSettings.heldIconDisplayHeight : 2.0f;
|
return Instance?.GetSettings<T>();
|
||||||
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;
|
/// <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>
|
/// <summary>
|
||||||
/// Returns the combination rule for two items, if any.
|
/// Returns the combination rule for two items, if any.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameSettings.CombinationRule GetCombinationRule(PickupItemData item1, PickupItemData item2)
|
public CombinationRule GetCombinationRule(PickupItemData item1, PickupItemData item2)
|
||||||
{
|
{
|
||||||
if (gameSettings == null || gameSettings.combinationRules == null) return null;
|
var settings = GetSettings<IInteractionSettings>();
|
||||||
foreach (var rule in gameSettings.combinationRules)
|
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)) ||
|
if ((PickupItemData.AreEquivalent(rule.itemA, item1) && PickupItemData.AreEquivalent(rule.itemB, item2)) ||
|
||||||
(PickupItemData.AreEquivalent(rule.itemA, item2) && PickupItemData.AreEquivalent(rule.itemB, item1)))
|
(PickupItemData.AreEquivalent(rule.itemA, item2) && PickupItemData.AreEquivalent(rule.itemB, item1)))
|
||||||
@@ -89,22 +210,16 @@ public class GameManager : MonoBehaviour
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the slot item config for a given slot item.
|
/// Returns the slot item config for a given slot item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameSettings.SlotItemConfig GetSlotItemConfig(PickupItemData slotItem)
|
public SlotItemConfig GetSlotItemConfig(PickupItemData slotItem)
|
||||||
{
|
{
|
||||||
if (gameSettings == null || gameSettings.slotItemConfigs == null || slotItem == null) return null;
|
var settings = GetSettings<IInteractionSettings>();
|
||||||
foreach (var config in gameSettings.slotItemConfigs)
|
if (settings == null || settings.SlotItemConfigs == null || slotItem == null) return null;
|
||||||
|
|
||||||
|
foreach (var config in settings.SlotItemConfigs)
|
||||||
{
|
{
|
||||||
if (PickupItemData.AreEquivalent(slotItem, config.slotItem))
|
if (PickupItemData.AreEquivalent(slotItem, config.slotItem))
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
return null;
|
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 UnityEngine;
|
||||||
using Pathfinding;
|
using Pathfinding;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace Input
|
namespace Input
|
||||||
{
|
{
|
||||||
@@ -94,7 +95,7 @@ namespace Input
|
|||||||
Debug.Log($"[PlayerTouchController] OnHoldStart at {worldPosition}");
|
Debug.Log($"[PlayerTouchController] OnHoldStart at {worldPosition}");
|
||||||
lastHoldPosition = worldPosition;
|
lastHoldPosition = worldPosition;
|
||||||
isHolding = true;
|
isHolding = true;
|
||||||
if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding &&
|
if (GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Pathfinding &&
|
||||||
aiPath != null)
|
aiPath != null)
|
||||||
{
|
{
|
||||||
aiPath.enabled = true;
|
aiPath.enabled = true;
|
||||||
@@ -110,12 +111,12 @@ namespace Input
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles hold move input. Updates the target position for direct or pathfinding movement.
|
/// Handles hold move input. Updates the target position for direct or pathfinding movement.
|
||||||
/// </summary>
|
/// /// </summary>
|
||||||
public void OnHoldMove(Vector2 worldPosition)
|
public void OnHoldMove(Vector2 worldPosition)
|
||||||
{
|
{
|
||||||
if (!isHolding) return;
|
if (!isHolding) return;
|
||||||
lastHoldPosition = worldPosition;
|
lastHoldPosition = worldPosition;
|
||||||
if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct)
|
if (GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Direct)
|
||||||
{
|
{
|
||||||
if (aiPath != null && aiPath.enabled) aiPath.enabled = false;
|
if (aiPath != null && aiPath.enabled) aiPath.enabled = false;
|
||||||
MoveDirectlyTo(worldPosition);
|
MoveDirectlyTo(worldPosition);
|
||||||
@@ -132,7 +133,7 @@ namespace Input
|
|||||||
isHolding = false;
|
isHolding = false;
|
||||||
directMoveVelocity = Vector3.zero;
|
directMoveVelocity = Vector3.zero;
|
||||||
if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode ==
|
if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode ==
|
||||||
GameSettings.HoldMovementMode.Pathfinding)
|
HoldMovementMode.Pathfinding)
|
||||||
{
|
{
|
||||||
if (pathfindingDragCoroutine != null)
|
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;
|
aiPath.enabled = false;
|
||||||
}
|
}
|
||||||
@@ -237,7 +238,7 @@ namespace Input
|
|||||||
{
|
{
|
||||||
float normalizedSpeed = 0f;
|
float normalizedSpeed = 0f;
|
||||||
Vector3 velocity = Vector3.zero;
|
Vector3 velocity = Vector3.zero;
|
||||||
if (isHolding && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct)
|
if (isHolding && GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Direct)
|
||||||
{
|
{
|
||||||
normalizedSpeed = directMoveVelocity.magnitude / aiPath.maxSpeed;
|
normalizedSpeed = directMoveVelocity.magnitude / aiPath.maxSpeed;
|
||||||
velocity = directMoveVelocity;
|
velocity = directMoveVelocity;
|
||||||
@@ -260,7 +261,7 @@ namespace Input
|
|||||||
bool isCurrentlyMoving = false;
|
bool isCurrentlyMoving = false;
|
||||||
|
|
||||||
// Check direct movement
|
// Check direct movement
|
||||||
if (isHolding && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct)
|
if (isHolding && GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Direct)
|
||||||
{
|
{
|
||||||
isCurrentlyMoving = directMoveVelocity.sqrMagnitude > 0.001f;
|
isCurrentlyMoving = directMoveVelocity.sqrMagnitude > 0.001f;
|
||||||
}
|
}
|
||||||
@@ -339,7 +340,7 @@ namespace Input
|
|||||||
interruptMoveTo = true;
|
interruptMoveTo = true;
|
||||||
isHolding = false;
|
isHolding = false;
|
||||||
directMoveVelocity = Vector3.zero;
|
directMoveVelocity = Vector3.zero;
|
||||||
if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && aiPath != null)
|
if (GameManager.Instance.DefaultHoldMovementMode == HoldMovementMode.Direct && aiPath != null)
|
||||||
aiPath.enabled = false;
|
aiPath.enabled = false;
|
||||||
OnMoveToCancelled?.Invoke();
|
OnMoveToCancelled?.Invoke();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,25 +194,9 @@ namespace Interactions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void OnDrawGizmos()
|
void OnDrawGizmos()
|
||||||
{
|
{
|
||||||
float playerStopDistance;
|
float playerStopDistance = characterToInteract == CharacterToInteract.Trafalgar
|
||||||
if (Application.isPlaying)
|
? AppleHills.SettingsAccess.GetPlayerStopDistanceDirectInteraction()
|
||||||
{
|
: AppleHills.SettingsAccess.GetPlayerStopDistance();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gizmos.color = Color.yellow;
|
Gizmos.color = Color.yellow;
|
||||||
Gizmos.DrawWireSphere(transform.position, playerStopDistance);
|
Gizmos.DrawWireSphere(transform.position, playerStopDistance);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Pooling;
|
using Pooling;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -10,25 +11,9 @@ namespace Minigames.DivingForPictures
|
|||||||
{
|
{
|
||||||
public Bubble bubblePrefab;
|
public Bubble bubblePrefab;
|
||||||
public Sprite[] bubbleSprites; // Assign in inspector
|
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")]
|
private DivingDeveloperSettings _devSettings;
|
||||||
public bool useObjectPooling = true;
|
private IDivingMinigameSettings _gameSettings;
|
||||||
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 float _timer;
|
private float _timer;
|
||||||
private float _nextSpawnInterval;
|
private float _nextSpawnInterval;
|
||||||
@@ -40,14 +25,24 @@ namespace Minigames.DivingForPictures
|
|||||||
{
|
{
|
||||||
_mainCamera = Camera.main;
|
_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
|
// Create the bubble pool
|
||||||
GameObject poolGO = new GameObject("BubblePool");
|
GameObject poolGO = new GameObject("BubblePool");
|
||||||
poolGO.transform.SetParent(transform);
|
poolGO.transform.SetParent(transform);
|
||||||
_bubblePool = poolGO.AddComponent<BubblePool>();
|
_bubblePool = poolGO.AddComponent<BubblePool>();
|
||||||
_bubblePool.initialPoolSize = initialPoolSize;
|
_bubblePool.initialPoolSize = _devSettings.BubbleInitialPoolSize;
|
||||||
_bubblePool.maxPoolSize = maxPoolSize;
|
_bubblePool.maxPoolSize = _devSettings.BubbleMaxPoolSize;
|
||||||
_bubblePool.Initialize(bubblePrefab);
|
_bubblePool.Initialize(bubblePrefab);
|
||||||
|
|
||||||
// Periodically check for pool statistics in debug builds
|
// Periodically check for pool statistics in debug builds
|
||||||
@@ -80,7 +75,7 @@ namespace Minigames.DivingForPictures
|
|||||||
/// <returns>Randomized interval in seconds.</returns>
|
/// <returns>Randomized interval in seconds.</returns>
|
||||||
float GetRandomizedInterval()
|
float GetRandomizedInterval()
|
||||||
{
|
{
|
||||||
return spawnInterval * Random.Range(0.8f, 1.2f);
|
return _devSettings.BubbleSpawnInterval * Random.Range(0.8f, 1.2f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -88,11 +83,11 @@ namespace Minigames.DivingForPictures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void SpawnBubble()
|
void SpawnBubble()
|
||||||
{
|
{
|
||||||
float x = Random.Range(spawnXMin, spawnXMax);
|
float x = Random.Range(_devSettings.BubbleSpawnXMin, _devSettings.BubbleSpawnXMax);
|
||||||
Vector3 spawnPos = new Vector3(x, spawnY, 0f);
|
Vector3 spawnPos = new Vector3(x, _devSettings.BubbleSpawnY, 0f);
|
||||||
|
|
||||||
Bubble bubble;
|
Bubble bubble;
|
||||||
if (useObjectPooling && _bubblePool != null)
|
if (_devSettings.BubbleUseObjectPooling && _bubblePool != null)
|
||||||
{
|
{
|
||||||
bubble = _bubblePool.GetBubble();
|
bubble = _bubblePool.GetBubble();
|
||||||
bubble.transform.position = spawnPos;
|
bubble.transform.position = spawnPos;
|
||||||
@@ -103,25 +98,25 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Randomize bubble properties
|
// 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
|
// Apply surfacing speed reduction if needed
|
||||||
if (_isSurfacing)
|
if (_isSurfacing)
|
||||||
{
|
{
|
||||||
bubble.speed = baseSpeed * surfacingSpeedFactor;
|
bubble.speed = baseSpeed * _devSettings.BubbleSurfacingSpeedFactor;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bubble.speed = baseSpeed;
|
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
|
// 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);
|
bubble.SetBaseScale(baseScale);
|
||||||
|
|
||||||
// Assign random sprite to BubbleSprite (fixed naming from BottleSprite)
|
// Assign random sprite to BubbleSprite
|
||||||
if (bubbleSprites != null && bubbleSprites.Length > 0)
|
if (bubbleSprites != null && bubbleSprites.Length > 0)
|
||||||
{
|
{
|
||||||
Sprite randomSprite = bubbleSprites[Random.Range(0, bubbleSprites.Length)];
|
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));
|
bubble.transform.rotation = Quaternion.Euler(0f, 0f, Random.Range(0f, 360f));
|
||||||
|
|
||||||
// Pass min/max scale for wobble clamping
|
// Pass min/max scale for wobble clamping
|
||||||
bubble.SetWobbleScaleLimits(wobbleMinScale, wobbleMaxScale);
|
bubble.SetWobbleScaleLimits(_devSettings.BubbleWobbleMinScale, _devSettings.BubbleWobbleMaxScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -148,10 +143,10 @@ namespace Minigames.DivingForPictures
|
|||||||
Bubble[] activeBubbles = FindObjectsByType<Bubble>(FindObjectsSortMode.None);
|
Bubble[] activeBubbles = FindObjectsByType<Bubble>(FindObjectsSortMode.None);
|
||||||
foreach (Bubble bubble in activeBubbles)
|
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>
|
/// <summary>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
using UnityEngine.Playables;
|
using UnityEngine.Playables;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -13,40 +14,17 @@ namespace Minigames.DivingForPictures
|
|||||||
[Tooltip("Array of monster prefabs to spawn randomly")]
|
[Tooltip("Array of monster prefabs to spawn randomly")]
|
||||||
[SerializeField] private GameObject[] monsterPrefabs;
|
[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")]
|
[Header("Rope Damage System")]
|
||||||
[Tooltip("Ropes that will break one by one as player takes damage")]
|
[Tooltip("Ropes that will break one by one as player takes damage")]
|
||||||
[SerializeField] private RopeBreaker[] playerRopes;
|
[SerializeField] private RopeBreaker[] playerRopes;
|
||||||
|
|
||||||
[Header("Surfacing Settings")]
|
[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")]
|
[Tooltip("Reference to the PlayableDirector that will play the surfacing timeline")]
|
||||||
[SerializeField] private PlayableDirector surfacingTimeline;
|
[SerializeField] private PlayableDirector surfacingTimeline;
|
||||||
|
|
||||||
|
// Settings reference
|
||||||
|
private IDivingMinigameSettings _settings;
|
||||||
|
|
||||||
// Private state variables
|
// Private state variables
|
||||||
private int playerScore = 0;
|
private int playerScore = 0;
|
||||||
private float currentSpawnProbability;
|
private float currentSpawnProbability;
|
||||||
@@ -83,7 +61,15 @@ namespace Minigames.DivingForPictures
|
|||||||
|
|
||||||
private void Awake()
|
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()
|
private void Start()
|
||||||
@@ -118,10 +104,10 @@ namespace Minigames.DivingForPictures
|
|||||||
|
|
||||||
// Gradually increase spawn probability over time
|
// Gradually increase spawn probability over time
|
||||||
float previousProbability = currentSpawnProbability;
|
float previousProbability = currentSpawnProbability;
|
||||||
if (currentSpawnProbability < maxSpawnProbability)
|
if (currentSpawnProbability < _settings.MaxSpawnProbability)
|
||||||
{
|
{
|
||||||
currentSpawnProbability += probabilityIncreaseRate * Time.deltaTime;
|
currentSpawnProbability += _settings.ProbabilityIncreaseRate * Time.deltaTime;
|
||||||
currentSpawnProbability = Mathf.Min(currentSpawnProbability, maxSpawnProbability);
|
currentSpawnProbability = Mathf.Min(currentSpawnProbability, _settings.MaxSpawnProbability);
|
||||||
|
|
||||||
// Only fire event if probability changed significantly
|
// Only fire event if probability changed significantly
|
||||||
if (Mathf.Abs(currentSpawnProbability - previousProbability) > 0.01f)
|
if (Mathf.Abs(currentSpawnProbability - previousProbability) > 0.01f)
|
||||||
@@ -141,8 +127,8 @@ namespace Minigames.DivingForPictures
|
|||||||
// If we're surfacing, don't spawn new monsters
|
// If we're surfacing, don't spawn new monsters
|
||||||
if (_isSurfacing) return;
|
if (_isSurfacing) return;
|
||||||
|
|
||||||
bool forceSpawn = timeSinceLastSpawn >= guaranteedSpawnTime;
|
bool forceSpawn = timeSinceLastSpawn >= _settings.GuaranteedSpawnTime;
|
||||||
bool onCooldown = timeSinceLastSpawn < spawnCooldown;
|
bool onCooldown = timeSinceLastSpawn < _settings.SpawnCooldown;
|
||||||
|
|
||||||
// Don't spawn if on cooldown, unless forced
|
// Don't spawn if on cooldown, unless forced
|
||||||
if (onCooldown && !forceSpawn) return;
|
if (onCooldown && !forceSpawn) return;
|
||||||
@@ -159,7 +145,7 @@ namespace Minigames.DivingForPictures
|
|||||||
// Reset timer and adjust probability
|
// Reset timer and adjust probability
|
||||||
lastSpawnTime = Time.time;
|
lastSpawnTime = Time.time;
|
||||||
timeSinceLastSpawn = 0f;
|
timeSinceLastSpawn = 0f;
|
||||||
currentSpawnProbability = baseSpawnProbability;
|
currentSpawnProbability = _settings.BaseSpawnProbability;
|
||||||
OnSpawnProbabilityChanged?.Invoke(currentSpawnProbability);
|
OnSpawnProbabilityChanged?.Invoke(currentSpawnProbability);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,8 +190,8 @@ namespace Minigames.DivingForPictures
|
|||||||
private void OnMonsterPictureTaken(Monster monster)
|
private void OnMonsterPictureTaken(Monster monster)
|
||||||
{
|
{
|
||||||
// Calculate points based on depth
|
// Calculate points based on depth
|
||||||
int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * depthMultiplier);
|
int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * _settings.DepthMultiplier);
|
||||||
int pointsAwarded = basePoints + depthBonus;
|
int pointsAwarded = _settings.BasePoints + depthBonus;
|
||||||
|
|
||||||
// Add score
|
// Add score
|
||||||
playerScore += pointsAwarded;
|
playerScore += pointsAwarded;
|
||||||
@@ -356,7 +342,7 @@ namespace Minigames.DivingForPictures
|
|||||||
_isSurfacing = true;
|
_isSurfacing = true;
|
||||||
|
|
||||||
// 1. Initiate smooth velocity transition to surfacing speed
|
// 1. Initiate smooth velocity transition to surfacing speed
|
||||||
float targetVelocityFactor = -1.0f * surfacingSpeedFactor;
|
float targetVelocityFactor = -1.0f * _settings.SurfacingSpeedFactor;
|
||||||
SetVelocityFactor(targetVelocityFactor);
|
SetVelocityFactor(targetVelocityFactor);
|
||||||
|
|
||||||
// 2. Find and notify trench tile spawner about direction change (for spawning/despawning logic)
|
// 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");
|
GameObject playerObject = GameObject.FindGameObjectWithTag("Player");
|
||||||
if (playerObject != null)
|
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>())
|
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)
|
if (component is Behaviour behaviour)
|
||||||
{
|
{
|
||||||
@@ -419,7 +407,7 @@ namespace Minigames.DivingForPictures
|
|||||||
// Start coroutine to reset X position to 0 over 1 second
|
// Start coroutine to reset X position to 0 over 1 second
|
||||||
StartCoroutine(ResetPlayerPosition(playerObject.transform));
|
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)
|
// 3. Find bubble spawner and slow down existing bubbles (no velocity management needed)
|
||||||
@@ -525,7 +513,7 @@ namespace Minigames.DivingForPictures
|
|||||||
private IEnumerator SurfacingSequence()
|
private IEnumerator SurfacingSequence()
|
||||||
{
|
{
|
||||||
// Wait for the configured delay
|
// 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
|
// Find tile spawner and tell it to stop spawning
|
||||||
TrenchTileSpawner tileSpawner = FindFirstObjectByType<TrenchTileSpawner>();
|
TrenchTileSpawner tileSpawner = FindFirstObjectByType<TrenchTileSpawner>();
|
||||||
@@ -594,10 +582,10 @@ namespace Minigames.DivingForPictures
|
|||||||
float startFactor = _currentVelocityFactor;
|
float startFactor = _currentVelocityFactor;
|
||||||
float elapsed = 0f;
|
float elapsed = 0f;
|
||||||
|
|
||||||
while (elapsed < speedTransitionDuration)
|
while (elapsed < _settings.SpeedTransitionDuration)
|
||||||
{
|
{
|
||||||
elapsed += Time.deltaTime;
|
elapsed += Time.deltaTime;
|
||||||
float t = Mathf.Clamp01(elapsed / speedTransitionDuration);
|
float t = Mathf.Clamp01(elapsed / _settings.SpeedTransitionDuration);
|
||||||
|
|
||||||
// Smooth step interpolation
|
// Smooth step interpolation
|
||||||
float smoothStep = t * t * (3f - 2f * t);
|
float smoothStep = t * t * (3f - 2f * t);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
using Pooling;
|
using Pooling;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -16,50 +17,17 @@ namespace Minigames.DivingForPictures
|
|||||||
[Tooltip("List of possible obstacle prefabs to spawn")]
|
[Tooltip("List of possible obstacle prefabs to spawn")]
|
||||||
[SerializeField] private List<GameObject> obstaclePrefabs;
|
[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")]
|
[Header("Events")]
|
||||||
[Tooltip("Called when an obstacle is spawned")]
|
[Tooltip("Invoked when a new obstacle is spawned")]
|
||||||
public UnityEvent<GameObject> onObstacleSpawned;
|
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;
|
public UnityEvent<GameObject> onObstacleDestroyed;
|
||||||
|
|
||||||
|
// Settings references
|
||||||
|
private IDivingMinigameSettings _settings;
|
||||||
|
private DivingDeveloperSettings _devSettings;
|
||||||
|
|
||||||
// Private fields
|
// Private fields
|
||||||
private ObstaclePool _obstaclePool;
|
private ObstaclePool _obstaclePool;
|
||||||
private Camera _mainCamera;
|
private Camera _mainCamera;
|
||||||
@@ -75,13 +43,34 @@ namespace Minigames.DivingForPictures
|
|||||||
{
|
{
|
||||||
_mainCamera = Camera.main;
|
_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
|
// Validate obstacle prefabs
|
||||||
ValidateObstaclePrefabs();
|
ValidateObstaclePrefabs();
|
||||||
|
|
||||||
if (useObjectPooling)
|
if (_devSettings?.ObstacleUseObjectPooling ?? false)
|
||||||
{
|
{
|
||||||
InitializeObjectPool();
|
InitializeObjectPool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize events if null
|
||||||
|
if (onObstacleSpawned == null)
|
||||||
|
onObstacleSpawned = new UnityEvent<GameObject>();
|
||||||
|
|
||||||
|
if (onObstacleDestroyed == null)
|
||||||
|
onObstacleDestroyed = new UnityEvent<GameObject>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
@@ -100,6 +89,8 @@ namespace Minigames.DivingForPictures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void ValidateObstaclePrefabs()
|
private void ValidateObstaclePrefabs()
|
||||||
{
|
{
|
||||||
|
if (_devSettings == null) return;
|
||||||
|
|
||||||
for (int i = 0; i < obstaclePrefabs.Count; i++)
|
for (int i = 0; i < obstaclePrefabs.Count; i++)
|
||||||
{
|
{
|
||||||
if (obstaclePrefabs[i] == null) continue;
|
if (obstaclePrefabs[i] == null) continue;
|
||||||
@@ -112,10 +103,10 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the prefab is on the correct layer (using configurable obstacleLayer)
|
// 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.");
|
Debug.LogWarning($"Obstacle prefab {obstaclePrefabs[i].name} is not on the configured obstacle layer ({_devSettings.ObstacleLayer}). Setting layer automatically.");
|
||||||
SetLayerRecursively(obstaclePrefabs[i], obstacleLayer);
|
SetLayerRecursively(obstaclePrefabs[i], _devSettings.ObstacleLayer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,8 +133,8 @@ namespace Minigames.DivingForPictures
|
|||||||
_obstaclePool = poolGO.AddComponent<ObstaclePool>();
|
_obstaclePool = poolGO.AddComponent<ObstaclePool>();
|
||||||
|
|
||||||
// Set up pool configuration
|
// Set up pool configuration
|
||||||
_obstaclePool.maxPerPrefabPoolSize = maxPerPrefabPoolSize;
|
_obstaclePool.maxPerPrefabPoolSize = _devSettings.ObstacleMaxPerPrefabPoolSize;
|
||||||
_obstaclePool.totalMaxPoolSize = totalMaxPoolSize;
|
_obstaclePool.totalMaxPoolSize = _devSettings.ObstacleTotalMaxPoolSize;
|
||||||
|
|
||||||
// Convert GameObject list to FloatingObstacle list
|
// Convert GameObject list to FloatingObstacle list
|
||||||
List<FloatingObstacle> prefabObstacles = new List<FloatingObstacle>(obstaclePrefabs.Count);
|
List<FloatingObstacle> prefabObstacles = new List<FloatingObstacle>(obstaclePrefabs.Count);
|
||||||
@@ -230,7 +221,9 @@ namespace Minigames.DivingForPictures
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// Calculate next spawn time with variation
|
// 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
|
nextSpawnTime = Mathf.Max(0.1f, nextSpawnTime); // Ensure minimum interval
|
||||||
|
|
||||||
yield return new WaitForSeconds(nextSpawnTime);
|
yield return new WaitForSeconds(nextSpawnTime);
|
||||||
@@ -264,7 +257,7 @@ namespace Minigames.DivingForPictures
|
|||||||
bool foundValidPosition = false;
|
bool foundValidPosition = false;
|
||||||
|
|
||||||
// Try to find a valid spawn position
|
// Try to find a valid spawn position
|
||||||
for (int attempts = 0; attempts < maxSpawnAttempts; attempts++)
|
for (int attempts = 0; attempts < _settings.ObstacleMaxSpawnAttempts; attempts++)
|
||||||
{
|
{
|
||||||
spawnPosition = GetRandomSpawnPosition();
|
spawnPosition = GetRandomSpawnPosition();
|
||||||
|
|
||||||
@@ -277,13 +270,13 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Log($"[ObstacleSpawner] Position {spawnPosition} invalid (attempt {attempts + 1}/{maxSpawnAttempts})");
|
Debug.Log($"[ObstacleSpawner] Position {spawnPosition} invalid (attempt {attempts + 1}/{_settings.ObstacleMaxSpawnAttempts})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foundValidPosition)
|
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>
|
/// </summary>
|
||||||
private bool IsValidSpawnPosition(Vector3 position)
|
private bool IsValidSpawnPosition(Vector3 position)
|
||||||
{
|
{
|
||||||
// Use OverlapCircle to check for collisions with tiles
|
// Use OverlapCircle to check for collisions with tiles using just the layer
|
||||||
Collider2D collision = Physics2D.OverlapCircle(position, spawnCollisionRadius, tileLayerMask);
|
// Convert the single layer to a layer mask inline (1 << layerNumber)
|
||||||
|
Collider2D collision = Physics2D.OverlapCircle(position, _settings.ObstacleSpawnCollisionRadius, 1 << _devSettings.TrenchTileLayer);
|
||||||
return collision == null;
|
return collision == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +324,7 @@ namespace Minigames.DivingForPictures
|
|||||||
GameObject obstacle;
|
GameObject obstacle;
|
||||||
|
|
||||||
// Spawn using pool or instantiate directly
|
// 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})");
|
Debug.Log($"[ObstacleSpawner] Requesting obstacle from pool (prefab index {prefabIndex})");
|
||||||
obstacle = _obstaclePool.GetObstacle(prefabIndex);
|
obstacle = _obstaclePool.GetObstacle(prefabIndex);
|
||||||
@@ -416,8 +410,10 @@ namespace Minigames.DivingForPictures
|
|||||||
// Set prefab index
|
// Set prefab index
|
||||||
obstacleComponent.PrefabIndex = prefabIndex;
|
obstacleComponent.PrefabIndex = prefabIndex;
|
||||||
|
|
||||||
// Randomize properties
|
// Randomize properties using settings
|
||||||
obstacleComponent.MoveSpeed = Random.Range(minMoveSpeed, maxMoveSpeed);
|
obstacleComponent.MoveSpeed = Random.Range(
|
||||||
|
_settings.ObstacleMinMoveSpeed,
|
||||||
|
_settings.ObstacleMaxMoveSpeed);
|
||||||
|
|
||||||
// Set spawner reference (since FloatingObstacle has this built-in now)
|
// Set spawner reference (since FloatingObstacle has this built-in now)
|
||||||
obstacleComponent.SetSpawner(this);
|
obstacleComponent.SetSpawner(this);
|
||||||
@@ -440,7 +436,7 @@ namespace Minigames.DivingForPictures
|
|||||||
onObstacleDestroyed?.Invoke(obstacle);
|
onObstacleDestroyed?.Invoke(obstacle);
|
||||||
|
|
||||||
// Return to pool or destroy
|
// Return to pool or destroy
|
||||||
if (useObjectPooling && _obstaclePool != null)
|
if (_devSettings.ObstacleUseObjectPooling && _obstaclePool != null)
|
||||||
{
|
{
|
||||||
Debug.Log($"[ObstacleSpawner] Returning {obstacle.name} to pool");
|
Debug.Log($"[ObstacleSpawner] Returning {obstacle.name} to pool");
|
||||||
_obstaclePool.ReturnObstacle(obstacle, prefabIndex);
|
_obstaclePool.ReturnObstacle(obstacle, prefabIndex);
|
||||||
@@ -457,7 +453,9 @@ namespace Minigames.DivingForPictures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetSpawnInterval(float interval)
|
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>
|
/// <summary>
|
||||||
@@ -465,8 +463,9 @@ namespace Minigames.DivingForPictures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetSpeedRange(float min, float max)
|
public void SetSpeedRange(float min, float max)
|
||||||
{
|
{
|
||||||
minMoveSpeed = min;
|
// This method can no longer directly modify the settings
|
||||||
maxMoveSpeed = max;
|
// Consider implementing a runtime settings override system if needed
|
||||||
|
Debug.LogWarning("[ObstacleSpawner] SetSpeedRange no longer modifies settings directly. Settings are now centralized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -534,8 +533,8 @@ namespace Minigames.DivingForPictures
|
|||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
private void OnDrawGizmosSelected()
|
private void OnDrawGizmosSelected()
|
||||||
{
|
{
|
||||||
// Only draw if screen bounds have been calculated
|
// Only draw if screen bounds have been calculated and settings are available
|
||||||
if (_spawnRangeX > 0f)
|
if (_spawnRangeX > 0f && _settings != null)
|
||||||
{
|
{
|
||||||
// Draw spawn area using dynamic calculations
|
// Draw spawn area using dynamic calculations
|
||||||
Gizmos.color = Color.yellow;
|
Gizmos.color = Color.yellow;
|
||||||
@@ -545,7 +544,7 @@ namespace Minigames.DivingForPictures
|
|||||||
|
|
||||||
// Draw collision radius at spawn point
|
// Draw collision radius at spawn point
|
||||||
Gizmos.color = Color.red;
|
Gizmos.color = Color.red;
|
||||||
Gizmos.DrawWireSphere(center, spawnCollisionRadius);
|
Gizmos.DrawWireSphere(center, _settings.ObstacleSpawnCollisionRadius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -8,8 +8,34 @@ namespace Minigames.DivingForPictures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ObstacleCollision : PlayerCollisionBehavior
|
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)
|
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
|
// Mark the obstacle as having dealt damage to prevent multiple hits
|
||||||
FloatingObstacle obstacleComponent = obstacle.GetComponent<FloatingObstacle>();
|
FloatingObstacle obstacleComponent = obstacle.GetComponent<FloatingObstacle>();
|
||||||
if (obstacleComponent != null)
|
if (obstacleComponent != null)
|
||||||
@@ -21,21 +47,20 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Override to prevent input blocking during damage immunity
|
/// Handler for immunity started event - replaces OnImmunityStart method
|
||||||
/// Since obstacles pass through the player, we don't want to block input
|
|
||||||
/// </summary>
|
/// </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
|
// Don't block input for obstacle damage - let player keep moving
|
||||||
// The shared immunity system will handle the collision prevention
|
// The shared immunity system will handle the collision prevention
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Override to handle immunity end
|
/// Handler for immunity ended event - replaces OnImmunityEnd method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void OnImmunityEnd()
|
private void HandleImmunityEnded()
|
||||||
{
|
{
|
||||||
Debug.Log($"[ObstacleCollision] Damage immunity ended");
|
Debug.Log($"[ObstacleCollision] Damage immunity ended");
|
||||||
// No special handling needed - shared immunity system handles collider re-enabling
|
// No special handling needed - shared immunity system handles collider re-enabling
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -9,28 +10,27 @@ namespace Minigames.DivingForPictures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PlayerBlinkBehavior : MonoBehaviour
|
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")]
|
[Header("References")]
|
||||||
[Tooltip("The SpriteRenderer to apply blink effects to (auto-assigned if empty)")]
|
[Tooltip("The SpriteRenderer to apply blink effects to (auto-assigned if empty)")]
|
||||||
[SerializeField] private SpriteRenderer targetSpriteRenderer;
|
[SerializeField] private SpriteRenderer targetSpriteRenderer;
|
||||||
|
|
||||||
|
// Developer settings reference
|
||||||
|
private DivingDeveloperSettings _devSettings;
|
||||||
|
|
||||||
private bool _isBlinking;
|
private bool _isBlinking;
|
||||||
private bool _isShowingDamageColor;
|
private bool _isShowingDamageColor;
|
||||||
private Coroutine _blinkCoroutine;
|
private Coroutine _blinkCoroutine;
|
||||||
private Color _originalColor; // Missing field declaration
|
private Color _originalColor;
|
||||||
|
|
||||||
private void Awake()
|
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
|
// Auto-assign sprite renderer if not set
|
||||||
if (targetSpriteRenderer == null)
|
if (targetSpriteRenderer == null)
|
||||||
{
|
{
|
||||||
@@ -51,192 +51,101 @@ namespace Minigames.DivingForPictures
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store original color
|
// Store the original color
|
||||||
_originalColor = targetSpriteRenderer.color;
|
_originalColor = targetSpriteRenderer.color;
|
||||||
|
|
||||||
// Ensure damage color has the correct alpha
|
|
||||||
damageBlinkColor.a = damageColorAlpha;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
// Subscribe to immunity events (renamed from damage events)
|
// Subscribe to damage events
|
||||||
PlayerCollisionBehavior.OnImmunityStarted += StartBlinking;
|
PlayerCollisionBehavior.OnDamageTaken += StartBlinkEffect;
|
||||||
PlayerCollisionBehavior.OnImmunityEnded += StopBlinking;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
// Unsubscribe from immunity events
|
// Unsubscribe to prevent memory leaks
|
||||||
PlayerCollisionBehavior.OnImmunityStarted -= StartBlinking;
|
PlayerCollisionBehavior.OnDamageTaken -= StartBlinkEffect;
|
||||||
PlayerCollisionBehavior.OnImmunityEnded -= StopBlinking;
|
|
||||||
|
|
||||||
// Stop any ongoing blink effect
|
|
||||||
if (_blinkCoroutine != null)
|
if (_blinkCoroutine != null)
|
||||||
{
|
{
|
||||||
StopCoroutine(_blinkCoroutine);
|
StopCoroutine(_blinkCoroutine);
|
||||||
_blinkCoroutine = null;
|
_blinkCoroutine = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore original color
|
// Restore original color if disabled during blinking
|
||||||
RestoreOriginalColor();
|
if (targetSpriteRenderer != null)
|
||||||
|
{
|
||||||
|
targetSpriteRenderer.color = _originalColor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts the red blinking effect when damage begins
|
/// Start the blinking effect coroutine
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void StartBlinking()
|
private void StartBlinkEffect()
|
||||||
{
|
{
|
||||||
if (targetSpriteRenderer == null) return;
|
if (targetSpriteRenderer == null || _devSettings == null) return;
|
||||||
|
|
||||||
Debug.Log("[PlayerBlinkBehavior] Starting damage blink effect");
|
// If already blinking, stop the current coroutine
|
||||||
|
if (_isBlinking && _blinkCoroutine != null)
|
||||||
// Stop any existing blink coroutine
|
|
||||||
if (_blinkCoroutine != null)
|
|
||||||
{
|
{
|
||||||
StopCoroutine(_blinkCoroutine);
|
StopCoroutine(_blinkCoroutine);
|
||||||
}
|
}
|
||||||
|
|
||||||
_isBlinking = true;
|
// Start a new blink coroutine
|
||||||
_isShowingDamageColor = false;
|
|
||||||
|
|
||||||
// Start the blink coroutine
|
|
||||||
_blinkCoroutine = StartCoroutine(BlinkCoroutine());
|
_blinkCoroutine = StartCoroutine(BlinkCoroutine());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops the blinking effect when damage ends
|
/// Coroutine that handles the blink effect timing
|
||||||
/// </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
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IEnumerator BlinkCoroutine()
|
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;
|
_isBlinking = true;
|
||||||
_isShowingDamageColor = false;
|
_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)
|
if (_isShowingDamageColor)
|
||||||
{
|
{
|
||||||
targetSpriteRenderer.color = _originalColor;
|
targetSpriteRenderer.color = _originalColor;
|
||||||
_isShowingDamageColor = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
targetSpriteRenderer.color = damageBlinkColor;
|
targetSpriteRenderer.color = damageColor;
|
||||||
_isShowingDamageColor = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new WaitForSeconds(blinkRate);
|
_isShowingDamageColor = !_isShowingDamageColor;
|
||||||
elapsed += blinkRate;
|
|
||||||
|
// Wait for next blink
|
||||||
|
yield return new WaitForSeconds(_devSettings.PlayerBlinkRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we end with original color
|
// Restore original color when done blinking
|
||||||
RestoreOriginalColor();
|
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;
|
_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 UnityEngine;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
using AppleHills.Utilities;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -10,17 +12,6 @@ namespace Minigames.DivingForPictures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class PlayerCollisionBehavior : MonoBehaviour
|
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")]
|
[Header("References")]
|
||||||
[Tooltip("The player character GameObject (auto-assigned if empty)")]
|
[Tooltip("The player character GameObject (auto-assigned if empty)")]
|
||||||
[SerializeField] protected GameObject playerCharacter;
|
[SerializeField] protected GameObject playerCharacter;
|
||||||
@@ -28,11 +19,16 @@ namespace Minigames.DivingForPictures
|
|||||||
[Tooltip("Reference to the PlayerController component (auto-assigned if empty)")]
|
[Tooltip("Reference to the PlayerController component (auto-assigned if empty)")]
|
||||||
[SerializeField] protected PlayerController playerController;
|
[SerializeField] protected PlayerController playerController;
|
||||||
|
|
||||||
|
// Settings references
|
||||||
|
protected IDivingMinigameSettings _gameSettings;
|
||||||
|
protected DivingDeveloperSettings _devSettings;
|
||||||
|
|
||||||
// Static shared immunity state across all collision behaviors
|
// Static shared immunity state across all collision behaviors
|
||||||
private static bool _isGloballyImmune;
|
private static bool _isGloballyImmune;
|
||||||
private static Coroutine _globalImmunityCoroutine;
|
private static Coroutine _globalImmunityCoroutine;
|
||||||
private static MonoBehaviour _coroutineRunner;
|
private static MonoBehaviour _coroutineRunner;
|
||||||
private static Collider2D _sharedPlayerCollider;
|
private static Collider2D _sharedPlayerCollider;
|
||||||
|
private static bool wasInputBlocked = false; // Track if input was blocked
|
||||||
|
|
||||||
// Events for immunity and damage state changes
|
// Events for immunity and damage state changes
|
||||||
public static event Action OnImmunityStarted;
|
public static event Action OnImmunityStarted;
|
||||||
@@ -67,211 +63,172 @@ namespace Minigames.DivingForPictures
|
|||||||
OnDamageTaken?.Invoke();
|
OnDamageTaken?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool wasInputBlocked;
|
/// <summary>
|
||||||
|
/// Called when the component is enabled
|
||||||
protected virtual void Awake()
|
/// </summary>
|
||||||
|
protected virtual void OnEnable()
|
||||||
{
|
{
|
||||||
// Auto-assign if not set in inspector
|
_allInstances.Add(this);
|
||||||
|
|
||||||
|
// Auto-assign references if needed
|
||||||
if (playerCharacter == null)
|
if (playerCharacter == null)
|
||||||
playerCharacter = gameObject;
|
playerCharacter = gameObject;
|
||||||
|
|
||||||
if (playerController == null)
|
if (playerController == null)
|
||||||
playerController = GetComponent<PlayerController>();
|
playerController = GetComponent<PlayerController>();
|
||||||
|
|
||||||
// Set up shared collider reference (only once)
|
// Initialize the shared player collider if not already set
|
||||||
if (_sharedPlayerCollider == null)
|
if (_sharedPlayerCollider == null)
|
||||||
{
|
{
|
||||||
_sharedPlayerCollider = GetComponent<Collider2D>();
|
_sharedPlayerCollider = GetComponent<Collider2D>();
|
||||||
if (_sharedPlayerCollider == null)
|
if (_sharedPlayerCollider == null)
|
||||||
{
|
{
|
||||||
_sharedPlayerCollider = GetComponentInChildren<Collider2D>();
|
Debug.LogError("[PlayerCollisionBehavior] No Collider2D found on this GameObject!");
|
||||||
if (_sharedPlayerCollider != null)
|
|
||||||
{
|
|
||||||
Debug.Log($"[PlayerCollisionBehavior] Found collider on child object: {_sharedPlayerCollider.gameObject.name}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
if (_coroutineRunner == null)
|
||||||
{
|
{
|
||||||
_coroutineRunner = this;
|
_coroutineRunner = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register this instance
|
// Block input if configured
|
||||||
_allInstances.Add(this);
|
if (_devSettings.BlockInputDuringImmunity && playerController != null)
|
||||||
|
{
|
||||||
|
// Notify player controller to block input
|
||||||
|
BlockPlayerInput();
|
||||||
|
wasInputBlocked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
// Trigger event for all listeners
|
||||||
{
|
OnImmunityStarted?.Invoke();
|
||||||
// Unregister this instance
|
|
||||||
_allInstances.Remove(this);
|
|
||||||
|
|
||||||
// Clean up static references if this was the coroutine runner
|
// Stop existing coroutine if one is running
|
||||||
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
|
|
||||||
if (_globalImmunityCoroutine != null && _coroutineRunner != null)
|
if (_globalImmunityCoroutine != null && _coroutineRunner != null)
|
||||||
{
|
{
|
||||||
_coroutineRunner.StopCoroutine(_globalImmunityCoroutine);
|
_coroutineRunner.StopCoroutine(_globalImmunityCoroutine);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start new immunity coroutine
|
// Start immunity timer coroutine on this instance
|
||||||
if (_coroutineRunner != null)
|
_globalImmunityCoroutine = StartCoroutine(ImmunityTimerCoroutine());
|
||||||
{
|
|
||||||
_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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Coroutine that handles the immunity timer
|
/// Coroutine to handle the immunity duration timer
|
||||||
/// </summary>
|
/// </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);
|
// Reset immunity state
|
||||||
|
|
||||||
Debug.Log($"[PlayerCollisionBehavior] Immunity period ended");
|
|
||||||
|
|
||||||
// End immunity
|
|
||||||
_isGloballyImmune = false;
|
_isGloballyImmune = false;
|
||||||
_globalImmunityCoroutine = null;
|
|
||||||
|
|
||||||
// Re-enable the shared collider
|
// Restore player input if it was blocked
|
||||||
if (_sharedPlayerCollider != null)
|
if (_devSettings.BlockInputDuringImmunity)
|
||||||
{
|
{
|
||||||
_sharedPlayerCollider.enabled = true;
|
RestorePlayerInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify all instances about immunity end
|
// Trigger event for all listeners
|
||||||
foreach (var instance in _allInstances)
|
|
||||||
{
|
|
||||||
if (instance != null)
|
|
||||||
{
|
|
||||||
instance.OnImmunityEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast immunity end event
|
|
||||||
OnImmunityEnded?.Invoke();
|
OnImmunityEnded?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Override this method to implement specific collision response behavior
|
/// Blocks player input during immunity
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obstacle">The obstacle that was collided with</param>
|
protected virtual void BlockPlayerInput()
|
||||||
protected abstract void HandleCollisionResponse(Collider2D obstacle);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when damage immunity starts (called on all instances)
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnImmunityStart()
|
|
||||||
{
|
{
|
||||||
Debug.Log($"[{GetType().Name}] Damage immunity started for {damageImmunityDuration} seconds");
|
if (playerController != null && playerController.enabled)
|
||||||
|
|
||||||
// Block input if specified
|
|
||||||
if (blockInputDuringImmunity)
|
|
||||||
{
|
{
|
||||||
BlockPlayerInput();
|
playerController.enabled = false;
|
||||||
}
|
wasInputBlocked = true;
|
||||||
}
|
Debug.Log($"[{GetType().Name}] Player input blocked during immunity");
|
||||||
|
|
||||||
/// <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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,20 +250,7 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Blocks player input during immunity
|
/// Updates the player controller's target position to the current position to prevent snapping
|
||||||
/// </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
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void UpdateControllerTarget()
|
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 UnityEngine;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -8,11 +9,8 @@ namespace Minigames.DivingForPictures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PlayerController : MonoBehaviour, ITouchInputConsumer
|
public class PlayerController : MonoBehaviour, ITouchInputConsumer
|
||||||
{
|
{
|
||||||
[Header("Tap Movement")]
|
// Settings reference
|
||||||
[Tooltip("Maximum distance the player can move from a single tap")]
|
private IDivingMinigameSettings _settings;
|
||||||
[SerializeField] private float tapMaxDistance = 0.5f;
|
|
||||||
[Tooltip("How quickly the tap impulse fades (higher = faster stop)")]
|
|
||||||
[SerializeField] private float tapDecelerationRate = 5.0f;
|
|
||||||
|
|
||||||
private float _targetFingerX;
|
private float _targetFingerX;
|
||||||
private bool _isTouchActive;
|
private bool _isTouchActive;
|
||||||
@@ -25,6 +23,13 @@ namespace Minigames.DivingForPictures
|
|||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
_originY = transform.position.y;
|
_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()
|
void Start()
|
||||||
@@ -42,7 +47,7 @@ namespace Minigames.DivingForPictures
|
|||||||
public void OnTap(Vector2 worldPosition)
|
public void OnTap(Vector2 worldPosition)
|
||||||
{
|
{
|
||||||
// Debug.Log($"[EndlessDescenderController] OnTap at {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)
|
// Calculate tap direction (+1 for right, -1 for left)
|
||||||
_tapDirection = Mathf.Sign(targetX - transform.position.x);
|
_tapDirection = Mathf.Sign(targetX - transform.position.x);
|
||||||
@@ -63,7 +68,7 @@ namespace Minigames.DivingForPictures
|
|||||||
public void OnHoldStart(Vector2 worldPosition)
|
public void OnHoldStart(Vector2 worldPosition)
|
||||||
{
|
{
|
||||||
// Debug.Log($"[EndlessDescenderController] OnHoldStart at {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;
|
_isTouchActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +78,7 @@ namespace Minigames.DivingForPictures
|
|||||||
public void OnHoldMove(Vector2 worldPosition)
|
public void OnHoldMove(Vector2 worldPosition)
|
||||||
{
|
{
|
||||||
// Debug.Log($"[EndlessDescenderController] OnHoldMove at {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>
|
/// <summary>
|
||||||
@@ -91,9 +96,9 @@ namespace Minigames.DivingForPictures
|
|||||||
if (_isTouchActive)
|
if (_isTouchActive)
|
||||||
{
|
{
|
||||||
float currentX = transform.position.x;
|
float currentX = transform.position.x;
|
||||||
float lerpSpeed = GameManager.Instance.EndlessDescenderLerpSpeed;
|
float lerpSpeed = _settings.LerpSpeed;
|
||||||
float maxOffset = GameManager.Instance.EndlessDescenderMaxOffset;
|
float maxOffset = _settings.MaxOffset;
|
||||||
float exponent = GameManager.Instance.EndlessDescenderSpeedExponent;
|
float exponent = _settings.SpeedExponent;
|
||||||
float targetX = _targetFingerX;
|
float targetX = _targetFingerX;
|
||||||
float offset = targetX - currentX;
|
float offset = targetX - currentX;
|
||||||
offset = Mathf.Clamp(offset, -maxOffset, maxOffset);
|
offset = Mathf.Clamp(offset, -maxOffset, maxOffset);
|
||||||
@@ -103,7 +108,7 @@ namespace Minigames.DivingForPictures
|
|||||||
// Prevent overshooting
|
// Prevent overshooting
|
||||||
moveStep = Mathf.Clamp(moveStep, -absOffset, absOffset);
|
moveStep = Mathf.Clamp(moveStep, -absOffset, absOffset);
|
||||||
float newX = currentX + moveStep;
|
float newX = currentX + moveStep;
|
||||||
newX = Mathf.Clamp(newX, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
|
newX = Mathf.Clamp(newX, _settings.ClampXMin, _settings.ClampXMax);
|
||||||
|
|
||||||
UpdatePosition(newX);
|
UpdatePosition(newX);
|
||||||
}
|
}
|
||||||
@@ -111,21 +116,21 @@ namespace Minigames.DivingForPictures
|
|||||||
else if (_tapImpulseStrength > 0)
|
else if (_tapImpulseStrength > 0)
|
||||||
{
|
{
|
||||||
float currentX = transform.position.x;
|
float currentX = transform.position.x;
|
||||||
float maxOffset = GameManager.Instance.EndlessDescenderMaxOffset;
|
float maxOffset = _settings.MaxOffset;
|
||||||
float lerpSpeed = GameManager.Instance.EndlessDescenderLerpSpeed;
|
float lerpSpeed = _settings.LerpSpeed;
|
||||||
|
|
||||||
// Calculate move distance based on impulse strength
|
// Calculate move distance based on impulse strength
|
||||||
float moveDistance = maxOffset * _tapImpulseStrength * Time.deltaTime * lerpSpeed;
|
float moveDistance = maxOffset * _tapImpulseStrength * Time.deltaTime * lerpSpeed;
|
||||||
|
|
||||||
// Limit total movement from single tap
|
// Limit total movement from single tap
|
||||||
moveDistance = Mathf.Min(moveDistance, tapMaxDistance * _tapImpulseStrength);
|
moveDistance = Mathf.Min(moveDistance, _settings.TapMaxDistance * _tapImpulseStrength);
|
||||||
|
|
||||||
// Apply movement in tap direction
|
// Apply movement in tap direction
|
||||||
float newX = currentX + (moveDistance * _tapDirection);
|
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
|
// Reduce impulse strength over time
|
||||||
_tapImpulseStrength -= Time.deltaTime * tapDecelerationRate;
|
_tapImpulseStrength -= Time.deltaTime * _settings.TapDecelerationRate;
|
||||||
if (_tapImpulseStrength < 0.01f)
|
if (_tapImpulseStrength < 0.01f)
|
||||||
{
|
{
|
||||||
_tapImpulseStrength = 0f;
|
_tapImpulseStrength = 0f;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -9,35 +10,21 @@ namespace Minigames.DivingForPictures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TileBumpCollision : PlayerCollisionBehavior
|
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 bool _isBumping;
|
||||||
private Coroutine _bumpCoroutine;
|
private Coroutine _bumpCoroutine;
|
||||||
private bool _bumpInputBlocked; // Tracks bump-specific input blocking
|
|
||||||
|
|
||||||
protected override void HandleCollisionResponse(Collider2D obstacle)
|
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:
|
case BumpMode.Impulse:
|
||||||
StartImpulseBump();
|
StartImpulseBump();
|
||||||
@@ -48,7 +35,7 @@ namespace Minigames.DivingForPictures
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Log($"[TileBumpCollision] Collision handled with {bumpMode} mode");
|
Debug.Log($"[TileBumpCollision] Collision handled with {_devSettings.BumpMode} mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -64,7 +51,7 @@ namespace Minigames.DivingForPictures
|
|||||||
float directionToCenter = currentX > 0 ? -1f : 1f; // Direction toward center
|
float directionToCenter = currentX > 0 ? -1f : 1f; // Direction toward center
|
||||||
|
|
||||||
// Calculate target position - closer to center based on bump force
|
// 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);
|
float targetX = currentX + (directionToCenter * bumpDistance);
|
||||||
|
|
||||||
// Clamp to center if we would overshoot
|
// Clamp to center if we would overshoot
|
||||||
@@ -77,7 +64,7 @@ namespace Minigames.DivingForPictures
|
|||||||
|
|
||||||
StartBump(currentX, targetX, bumpDuration);
|
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>
|
/// <summary>
|
||||||
@@ -91,11 +78,11 @@ namespace Minigames.DivingForPictures
|
|||||||
float distanceToCenter = Mathf.Abs(currentX);
|
float distanceToCenter = Mathf.Abs(currentX);
|
||||||
|
|
||||||
float targetX = 0f; // Always move to center
|
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);
|
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>
|
/// <summary>
|
||||||
@@ -112,14 +99,6 @@ namespace Minigames.DivingForPictures
|
|||||||
|
|
||||||
_isBumping = true;
|
_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
|
// Start bump coroutine
|
||||||
_bumpCoroutine = StartCoroutine(BumpCoroutine(startX, targetX, duration));
|
_bumpCoroutine = StartCoroutine(BumpCoroutine(startX, targetX, duration));
|
||||||
}
|
}
|
||||||
@@ -137,7 +116,7 @@ namespace Minigames.DivingForPictures
|
|||||||
|
|
||||||
// Calculate progress and apply curve
|
// Calculate progress and apply curve
|
||||||
float progress = elapsedTime / duration;
|
float progress = elapsedTime / duration;
|
||||||
float curveValue = bumpCurve.Evaluate(progress);
|
float curveValue = _devSettings.BumpCurve.Evaluate(progress);
|
||||||
|
|
||||||
// Interpolate position
|
// Interpolate position
|
||||||
float currentX = Mathf.Lerp(startX, targetX, curveValue);
|
float currentX = Mathf.Lerp(startX, targetX, curveValue);
|
||||||
@@ -146,7 +125,8 @@ namespace Minigames.DivingForPictures
|
|||||||
if (playerCharacter != null)
|
if (playerCharacter != null)
|
||||||
{
|
{
|
||||||
Vector3 currentPos = playerCharacter.transform.position;
|
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;
|
yield return null;
|
||||||
@@ -156,139 +136,15 @@ namespace Minigames.DivingForPictures
|
|||||||
if (playerCharacter != null)
|
if (playerCharacter != null)
|
||||||
{
|
{
|
||||||
Vector3 currentPos = playerCharacter.transform.position;
|
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
|
// Bump finished
|
||||||
_isBumping = false;
|
_isBumping = false;
|
||||||
_bumpCoroutine = null;
|
_bumpCoroutine = null;
|
||||||
|
|
||||||
if (_bumpInputBlocked)
|
|
||||||
{
|
|
||||||
RestoreBumpInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Log("[TileBumpCollision] Bump movement completed");
|
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 UnityEngine;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a wobble (rocking and vertical movement) effect to the object, based on speed and time.
|
/// Adds a wobble (rocking and vertical movement) effect to the object, based on speed and time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WobbleBehavior : MonoBehaviour
|
public class WobbleBehavior : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Header("Wobble Settings")]
|
// Developer settings reference
|
||||||
public float wobbleFrequency = 1.5f;
|
private DivingDeveloperSettings _devSettings;
|
||||||
/// <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;
|
|
||||||
|
|
||||||
private Vector3 lastPosition;
|
private Vector3 lastPosition;
|
||||||
private float wobbleTime;
|
private float wobbleTime;
|
||||||
@@ -46,47 +21,61 @@ public class WobbleBehavior : MonoBehaviour
|
|||||||
/// The current velocity of the object (horizontal only).
|
/// The current velocity of the object (horizontal only).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float Velocity => velocity;
|
public float Velocity => velocity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current vertical offset due to wobble.
|
/// The current vertical offset due to wobble.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float VerticalOffset => verticalOffset;
|
public float VerticalOffset => verticalOffset;
|
||||||
|
|
||||||
void Start()
|
private void Awake()
|
||||||
{
|
{
|
||||||
lastPosition = transform.position;
|
// Get developer settings
|
||||||
smoothedVelocity = 0f;
|
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||||
smoothedAngle = 0f;
|
if (_devSettings == null)
|
||||||
}
|
|
||||||
|
|
||||||
void Update()
|
|
||||||
{
|
{
|
||||||
// Calculate movement speed (exclude vertical wobble from velocity calculation)
|
Debug.LogError("[WobbleBehavior] Failed to load developer settings!");
|
||||||
Vector3 horizontalPosition = transform.position;
|
}
|
||||||
horizontalPosition.y = 0f; // Ignore Y for velocity if only horizontal movement matters
|
|
||||||
Vector3 horizontalLastPosition = lastPosition;
|
// Initialize
|
||||||
horizontalLastPosition.y = 0f;
|
|
||||||
velocity = (horizontalPosition - horizontalLastPosition).magnitude / Time.deltaTime;
|
|
||||||
lastPosition = transform.position;
|
lastPosition = transform.position;
|
||||||
|
basePosition = transform.position;
|
||||||
|
}
|
||||||
|
|
||||||
// Smooth velocity to prevent jitter
|
private void Update()
|
||||||
smoothedVelocity = Mathf.Lerp(smoothedVelocity, velocity, velocitySmoothing * Time.deltaTime);
|
{
|
||||||
|
if (_devSettings == null) return;
|
||||||
|
|
||||||
// Wobble amplitude scales with smoothed speed, but always has a base value
|
// Calculate velocity based on position change
|
||||||
float amplitude = baseWobbleAmplitude + smoothedVelocity * speedToAmplitude;
|
Vector3 positionDelta = transform.position - lastPosition;
|
||||||
amplitude = Mathf.Min(amplitude, maxRotationLimit); // Prevent amplitude from exceeding limit
|
velocity = positionDelta.x / Time.deltaTime;
|
||||||
|
lastPosition = transform.position;
|
||||||
|
basePosition = transform.position;
|
||||||
|
|
||||||
// Oscillate around horizontal (0 degrees)
|
// Smooth velocity changes
|
||||||
wobbleTime += Time.deltaTime * wobbleFrequency;
|
smoothedVelocity = Mathf.Lerp(smoothedVelocity, velocity, Time.deltaTime * _devSettings.PlayerVelocitySmoothing);
|
||||||
float targetAngle = Mathf.Sin(wobbleTime) * amplitude;
|
|
||||||
targetAngle = Mathf.Clamp(targetAngle, -maxRotationLimit, maxRotationLimit);
|
|
||||||
|
|
||||||
// Smooth the rotation angle
|
// Calculate wobble rotation based on velocity and time
|
||||||
smoothedAngle = Mathf.Lerp(smoothedAngle, targetAngle, rotationSmoothing * Time.deltaTime);
|
wobbleTime += Time.deltaTime * _devSettings.PlayerWobbleFrequency;
|
||||||
|
float rawWobble = Mathf.Sin(wobbleTime);
|
||||||
|
|
||||||
// Apply rotation (Z axis for 2D)
|
// Calculate wobble amplitude based on velocity
|
||||||
transform.localRotation = Quaternion.Euler(0f, 0f, smoothedAngle);
|
float velocityFactor = Mathf.Abs(smoothedVelocity) * _devSettings.PlayerSpeedToAmplitude;
|
||||||
|
float wobbleAmplitude = _devSettings.PlayerBaseWobbleAmplitude + velocityFactor;
|
||||||
|
|
||||||
// Calculate vertical up/down movement (wave riding) only once
|
// Clamp to maximum rotation limit
|
||||||
verticalOffset = Mathf.Sin(wobbleTime * verticalFrequency) * verticalAmplitude;
|
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.Events;
|
||||||
using UnityEngine.Serialization;
|
using UnityEngine.Serialization;
|
||||||
using Pooling;
|
using Pooling;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -16,21 +17,6 @@ namespace Minigames.DivingForPictures
|
|||||||
[Tooltip("List of possible trench tile prefabs.")]
|
[Tooltip("List of possible trench tile prefabs.")]
|
||||||
[SerializeField] private List<GameObject> tilePrefabs;
|
[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")]
|
[Header("Events")]
|
||||||
[FormerlySerializedAs("OnTileSpawned")]
|
[FormerlySerializedAs("OnTileSpawned")]
|
||||||
public UnityEvent<GameObject> onTileSpawned;
|
public UnityEvent<GameObject> onTileSpawned;
|
||||||
@@ -38,6 +24,10 @@ namespace Minigames.DivingForPictures
|
|||||||
[FormerlySerializedAs("OnTileDestroyed")]
|
[FormerlySerializedAs("OnTileDestroyed")]
|
||||||
public UnityEvent<GameObject> onTileDestroyed;
|
public UnityEvent<GameObject> onTileDestroyed;
|
||||||
|
|
||||||
|
// Settings references
|
||||||
|
private IDivingMinigameSettings _settings;
|
||||||
|
private DivingDeveloperSettings _devSettings;
|
||||||
|
|
||||||
// Private fields
|
// Private fields
|
||||||
private readonly Dictionary<GameObject, float> _tileHeights = new Dictionary<GameObject, float>();
|
private readonly Dictionary<GameObject, float> _tileHeights = new Dictionary<GameObject, float>();
|
||||||
private readonly List<GameObject> _activeTiles = new List<GameObject>();
|
private readonly List<GameObject> _activeTiles = new List<GameObject>();
|
||||||
@@ -53,9 +43,6 @@ namespace Minigames.DivingForPictures
|
|||||||
// Current velocity for tile movement
|
// Current velocity for tile movement
|
||||||
private float _currentVelocity;
|
private float _currentVelocity;
|
||||||
|
|
||||||
// Interval for velocity calculations (seconds)
|
|
||||||
[SerializeField] private float velocityCalculationInterval = 0.5f;
|
|
||||||
|
|
||||||
private const float TileSpawnZ = -1f;
|
private const float TileSpawnZ = -1f;
|
||||||
private const float DefaultTileHeight = 5f;
|
private const float DefaultTileHeight = 5f;
|
||||||
|
|
||||||
@@ -73,7 +60,22 @@ namespace Minigames.DivingForPictures
|
|||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_mainCamera = Camera.main;
|
_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
|
// Calculate tile heights for each prefab
|
||||||
CalculateTileHeights();
|
CalculateTileHeights();
|
||||||
@@ -81,7 +83,7 @@ namespace Minigames.DivingForPictures
|
|||||||
// Ensure all prefabs have Tile components
|
// Ensure all prefabs have Tile components
|
||||||
ValidateTilePrefabs();
|
ValidateTilePrefabs();
|
||||||
|
|
||||||
if (useObjectPooling)
|
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling)
|
||||||
{
|
{
|
||||||
InitializeObjectPool();
|
InitializeObjectPool();
|
||||||
}
|
}
|
||||||
@@ -110,7 +112,7 @@ namespace Minigames.DivingForPictures
|
|||||||
SpawnInitialTiles();
|
SpawnInitialTiles();
|
||||||
|
|
||||||
// Initialize velocity and start the velocity calculation coroutine
|
// Initialize velocity and start the velocity calculation coroutine
|
||||||
_currentVelocity = moveSpeed * Time.fixedDeltaTime;
|
_currentVelocity = _baseMoveSpeed * Time.fixedDeltaTime;
|
||||||
StartCoroutine(VelocityCalculationRoutine());
|
StartCoroutine(VelocityCalculationRoutine());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +150,7 @@ namespace Minigames.DivingForPictures
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
CalculateVelocity();
|
CalculateVelocity();
|
||||||
yield return new WaitForSeconds(velocityCalculationInterval);
|
yield return new WaitForSeconds(_settings.VelocityCalculationInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +159,7 @@ namespace Minigames.DivingForPictures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void CalculateVelocity()
|
private void CalculateVelocity()
|
||||||
{
|
{
|
||||||
_currentVelocity = moveSpeed * Time.fixedDeltaTime;
|
_currentVelocity = _baseMoveSpeed * Time.fixedDeltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -197,9 +199,9 @@ namespace Minigames.DivingForPictures
|
|||||||
poolGO.transform.SetParent(transform);
|
poolGO.transform.SetParent(transform);
|
||||||
_tilePool = poolGO.AddComponent<TrenchTilePool>();
|
_tilePool = poolGO.AddComponent<TrenchTilePool>();
|
||||||
|
|
||||||
// Set up the pool configuration
|
// Set up the pool configuration using developer settings
|
||||||
_tilePool.maxPerPrefabPoolSize = maxPerPrefabPoolSize;
|
_tilePool.maxPerPrefabPoolSize = _devSettings.TrenchTileMaxPerPrefabPoolSize;
|
||||||
_tilePool.totalMaxPoolSize = totalMaxPoolSize;
|
_tilePool.totalMaxPoolSize = _devSettings.TrenchTileTotalMaxPoolSize;
|
||||||
|
|
||||||
// Convert the GameObject list to a Tile list
|
// Convert the GameObject list to a Tile list
|
||||||
List<Tile> prefabTiles = new List<Tile>(tilePrefabs.Count);
|
List<Tile> prefabTiles = new List<Tile>(tilePrefabs.Count);
|
||||||
@@ -247,7 +249,7 @@ namespace Minigames.DivingForPictures
|
|||||||
// Move starting position up by 2 tile heights
|
// Move starting position up by 2 tile heights
|
||||||
startingY += tileHeightEstimate * 2;
|
startingY += tileHeightEstimate * 2;
|
||||||
|
|
||||||
for (int i = 0; i < initialTileCount; i++)
|
for (int i = 0; i < _settings.InitialTileCount; i++)
|
||||||
{
|
{
|
||||||
float y = startingY;
|
float y = startingY;
|
||||||
// Calculate proper Y position based on previous tiles
|
// 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
|
// Update the actual move speed based on the velocity factor
|
||||||
// This keeps the original move speed intact for game logic
|
// This keeps the original move speed intact for game logic
|
||||||
moveSpeed = _baseMoveSpeed * Mathf.Abs(_velocityFactor);
|
_baseMoveSpeed = _settings.MoveSpeed * Mathf.Abs(_velocityFactor);
|
||||||
|
|
||||||
// Recalculate velocity immediately
|
// Recalculate velocity immediately
|
||||||
CalculateVelocity();
|
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>
|
/// <summary>
|
||||||
@@ -362,12 +364,12 @@ namespace Minigames.DivingForPictures
|
|||||||
if (_isSurfacing)
|
if (_isSurfacing)
|
||||||
{
|
{
|
||||||
// When surfacing, destroy tiles at the bottom
|
// 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
|
else
|
||||||
{
|
{
|
||||||
// When descending, destroy tiles at the top
|
// 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)
|
if (shouldDestroy)
|
||||||
@@ -375,7 +377,7 @@ namespace Minigames.DivingForPictures
|
|||||||
_activeTiles.RemoveAt(0);
|
_activeTiles.RemoveAt(0);
|
||||||
onTileDestroyed?.Invoke(topTile);
|
onTileDestroyed?.Invoke(topTile);
|
||||||
|
|
||||||
if (useObjectPooling && _tilePool != null)
|
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _tilePool != null)
|
||||||
{
|
{
|
||||||
// Find the prefab index for this tile
|
// Find the prefab index for this tile
|
||||||
int prefabIndex = GetPrefabIndex(topTile);
|
int prefabIndex = GetPrefabIndex(topTile);
|
||||||
@@ -431,12 +433,12 @@ namespace Minigames.DivingForPictures
|
|||||||
if (_isSurfacing)
|
if (_isSurfacing)
|
||||||
{
|
{
|
||||||
// When surfacing, check if the bottom of the tile is above the top of the screen
|
// 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
|
else
|
||||||
{
|
{
|
||||||
// When descending, check if the top of the tile is below the bottom of the screen
|
// 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)
|
if (isLastTileLeaving)
|
||||||
@@ -454,14 +456,14 @@ namespace Minigames.DivingForPictures
|
|||||||
{
|
{
|
||||||
// When surfacing, spawn new tiles at the top
|
// When surfacing, spawn new tiles at the top
|
||||||
float topEdge = bottomTile.transform.position.y + tileHeight / 2;
|
float topEdge = bottomTile.transform.position.y + tileHeight / 2;
|
||||||
shouldSpawn = topEdge < _screenTop + tileSpawnBuffer;
|
shouldSpawn = topEdge < _screenTop + _settings.TileSpawnBuffer;
|
||||||
newY = bottomTile.transform.position.y + tileHeight;
|
newY = bottomTile.transform.position.y + tileHeight;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// When descending, spawn new tiles at the bottom
|
// When descending, spawn new tiles at the bottom
|
||||||
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
|
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
|
||||||
shouldSpawn = bottomEdge > _screenBottom - tileSpawnBuffer;
|
shouldSpawn = bottomEdge > _screenBottom - _settings.TileSpawnBuffer;
|
||||||
newY = bottomTile.transform.position.y - tileHeight;
|
newY = bottomTile.transform.position.y - tileHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,9 +479,9 @@ namespace Minigames.DivingForPictures
|
|||||||
private void HandleSpeedRamping()
|
private void HandleSpeedRamping()
|
||||||
{
|
{
|
||||||
_speedUpTimer += Time.deltaTime;
|
_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;
|
_speedUpTimer = 0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -506,7 +508,7 @@ namespace Minigames.DivingForPictures
|
|||||||
|
|
||||||
GameObject tile;
|
GameObject tile;
|
||||||
|
|
||||||
if (useObjectPooling && _tilePool != null)
|
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _tilePool != null)
|
||||||
{
|
{
|
||||||
tile = _tilePool.GetTile(prefabIndex);
|
tile = _tilePool.GetTile(prefabIndex);
|
||||||
if (tile == null)
|
if (tile == null)
|
||||||
@@ -518,11 +520,25 @@ namespace Minigames.DivingForPictures
|
|||||||
tile.transform.position = new Vector3(0f, y, TileSpawnZ);
|
tile.transform.position = new Vector3(0f, y, TileSpawnZ);
|
||||||
tile.transform.rotation = prefab.transform.rotation;
|
tile.transform.rotation = prefab.transform.rotation;
|
||||||
tile.transform.SetParent(transform);
|
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
|
else
|
||||||
{
|
{
|
||||||
// Use the prefab's original rotation
|
// Use the prefab's original rotation
|
||||||
tile = Instantiate(prefab, new Vector3(0f, y, TileSpawnZ), prefab.transform.rotation, transform);
|
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);
|
_activeTiles.Add(tile);
|
||||||
@@ -656,7 +672,9 @@ namespace Minigames.DivingForPictures
|
|||||||
|
|
||||||
// Draw tile bounds for debugging
|
// Draw tile bounds for debugging
|
||||||
Gizmos.color = Color.cyan;
|
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;
|
float height = DefaultTileHeight;
|
||||||
if (tilePrefabs != null && tilePrefabs.Count > 0 && tilePrefabs[0] != null &&
|
if (tilePrefabs != null && tilePrefabs.Count > 0 && tilePrefabs[0] != null &&
|
||||||
@@ -668,6 +686,27 @@ namespace Minigames.DivingForPictures
|
|||||||
Gizmos.DrawWireCube(center, new Vector3(10f, height, 1f));
|
Gizmos.DrawWireCube(center, new Vector3(10f, height, 1f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif
|
#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
|
- Interactable
|
||||||
- QuarryObstacle
|
- QuarryObstacle
|
||||||
- QuarryMonster
|
- QuarryMonster
|
||||||
-
|
- QuarryTrenchTile
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
|
|||||||
Reference in New Issue
Block a user