Working Developer Settings
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: dea69d41f90c6ea4fa55c27c1d60c145, type: 3}
|
||||||
|
m_Name: AddressableAssetGroupSortSettings
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
sortOrder:
|
||||||
|
- eb8a37153d819c44194f7ce97570a3d3
|
||||||
|
- 2b3d7cefec0915e42be04aebf0400a56
|
||||||
|
- 6f3207429a65b3e4b83935ac19791077
|
||||||
|
- c62e6f02418e19949bca4cccdd5fa02e
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 32c1a9c8651793e41848a173d66d98fd
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -15,6 +15,11 @@ MonoBehaviour:
|
|||||||
m_GroupName: Settings
|
m_GroupName: Settings
|
||||||
m_GUID: c62e6f02418e19949bca4cccdd5fa02e
|
m_GUID: c62e6f02418e19949bca4cccdd5fa02e
|
||||||
m_SerializeEntries:
|
m_SerializeEntries:
|
||||||
|
- m_GUID: 328ce914b893df646be3ad3c62755453
|
||||||
|
m_Address: Settings/Developer/DivingDeveloperSettings
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels: []
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
- m_GUID: 35bfcff00faa72c4eb272a9e8288f965
|
- m_GUID: 35bfcff00faa72c4eb272a9e8288f965
|
||||||
m_Address: Settings/PlayerFollowerSettings
|
m_Address: Settings/PlayerFollowerSettings
|
||||||
m_ReadOnly: 0
|
m_ReadOnly: 0
|
||||||
|
|||||||
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
|
||||||
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
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.AddressableAssets;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace AppleHills.Core.Settings
|
namespace AppleHills.Core.Settings
|
||||||
{
|
{
|
||||||
@@ -38,8 +40,8 @@ namespace AppleHills.Core.Settings
|
|||||||
// Dictionary to cache loaded settings
|
// Dictionary to cache loaded settings
|
||||||
private Dictionary<System.Type, BaseDeveloperSettings> _settingsCache = new Dictionary<System.Type, BaseDeveloperSettings>();
|
private Dictionary<System.Type, BaseDeveloperSettings> _settingsCache = new Dictionary<System.Type, BaseDeveloperSettings>();
|
||||||
|
|
||||||
// Default developer settings stored in the Resources folder
|
// Path prefix for addressable developer settings
|
||||||
[SerializeField] private string _resourcesPath = "Settings/Developer";
|
[SerializeField] private string _addressablePath = "Settings/Developer";
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
@@ -71,16 +73,35 @@ namespace AppleHills.Core.Settings
|
|||||||
return cachedSettings as T;
|
return cachedSettings as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load from Resources if not cached
|
// Load from Addressables if not cached
|
||||||
T settings = Resources.Load<T>($"{_resourcesPath}/{type.Name}");
|
string key = $"{_addressablePath}/{type.Name}";
|
||||||
|
|
||||||
if (settings != null)
|
try
|
||||||
{
|
{
|
||||||
_settingsCache[type] = settings;
|
T settings = Addressables.LoadAssetAsync<T>(key).WaitForCompletion();
|
||||||
return settings;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.LogWarning($"Developer settings of type {type.Name} not found in Resources/{_resourcesPath}");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,11 +54,9 @@ namespace AppleHills.Core.Settings
|
|||||||
|
|
||||||
[Header("Obstacle System")]
|
[Header("Obstacle System")]
|
||||||
[Tooltip("Layer for obstacles to be placed on")]
|
[Tooltip("Layer for obstacles to be placed on")]
|
||||||
|
[Layer]
|
||||||
[SerializeField] private int obstacleLayer = 9;
|
[SerializeField] private int obstacleLayer = 9;
|
||||||
|
|
||||||
[Tooltip("Layer mask for tile collision detection during obstacle spawn validation")]
|
|
||||||
[SerializeField] private LayerMask obstacleTileLayerMask = -1;
|
|
||||||
|
|
||||||
[Tooltip("Whether to use object pooling for obstacles")]
|
[Tooltip("Whether to use object pooling for obstacles")]
|
||||||
[SerializeField] private bool obstacleUseObjectPooling = true;
|
[SerializeField] private bool obstacleUseObjectPooling = true;
|
||||||
|
|
||||||
@@ -67,7 +65,73 @@ namespace AppleHills.Core.Settings
|
|||||||
|
|
||||||
[Tooltip("Total maximum size of obstacle pool across all prefab types")]
|
[Tooltip("Total maximum size of obstacle pool across all prefab types")]
|
||||||
[SerializeField] private int obstacleTotalMaxPoolSize = 15;
|
[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 int bumpMode = 0;
|
||||||
|
|
||||||
|
[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
|
// Bubble properties access
|
||||||
public bool BubbleUseObjectPooling => bubbleUseObjectPooling;
|
public bool BubbleUseObjectPooling => bubbleUseObjectPooling;
|
||||||
public int BubbleInitialPoolSize => bubbleInitialPoolSize;
|
public int BubbleInitialPoolSize => bubbleInitialPoolSize;
|
||||||
@@ -86,10 +150,36 @@ namespace AppleHills.Core.Settings
|
|||||||
|
|
||||||
// Obstacle properties access
|
// Obstacle properties access
|
||||||
public int ObstacleLayer => obstacleLayer;
|
public int ObstacleLayer => obstacleLayer;
|
||||||
public LayerMask ObstacleTileLayerMask => obstacleTileLayerMask;
|
|
||||||
public bool ObstacleUseObjectPooling => obstacleUseObjectPooling;
|
public bool ObstacleUseObjectPooling => obstacleUseObjectPooling;
|
||||||
public int ObstacleMaxPerPrefabPoolSize => obstacleMaxPerPrefabPoolSize;
|
public int ObstacleMaxPerPrefabPoolSize => obstacleMaxPerPrefabPoolSize;
|
||||||
public int ObstacleTotalMaxPoolSize => obstacleTotalMaxPoolSize;
|
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 int BumpMode => bumpMode;
|
||||||
|
public AnimationCurve BumpCurve => bumpCurve;
|
||||||
|
|
||||||
public override void OnValidate()
|
public override void OnValidate()
|
||||||
{
|
{
|
||||||
@@ -122,6 +212,26 @@ namespace AppleHills.Core.Settings
|
|||||||
// Validate obstacle settings
|
// Validate obstacle settings
|
||||||
obstacleMaxPerPrefabPoolSize = Mathf.Max(1, obstacleMaxPerPrefabPoolSize);
|
obstacleMaxPerPrefabPoolSize = Mathf.Max(1, obstacleMaxPerPrefabPoolSize);
|
||||||
obstacleTotalMaxPoolSize = Mathf.Max(obstacleMaxPerPrefabPoolSize, obstacleTotalMaxPoolSize);
|
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 = Mathf.Clamp(bumpMode, 0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,19 @@ namespace AppleHills.Core.Settings
|
|||||||
[Tooltip("Maximum movement speed for spawned obstacles")]
|
[Tooltip("Maximum movement speed for spawned obstacles")]
|
||||||
[SerializeField] private float endlessDescenderObstacleMaxMoveSpeed = 4f;
|
[SerializeField] private float endlessDescenderObstacleMaxMoveSpeed = 4f;
|
||||||
|
|
||||||
|
[Header("Endless Descender - Collision Handling")]
|
||||||
|
[Tooltip("Duration in seconds of damage immunity after being hit")]
|
||||||
|
[SerializeField] private float endlessDescenderDamageImmunityDuration = 1.0f;
|
||||||
|
|
||||||
|
[Tooltip("Force strength for impulse bumps - higher values push further toward center")]
|
||||||
|
[SerializeField] private float endlessDescenderBumpForce = 5.0f;
|
||||||
|
|
||||||
|
[Tooltip("Speed for smooth movement to center (units per second)")]
|
||||||
|
[SerializeField] private float endlessDescenderSmoothMoveSpeed = 8.0f;
|
||||||
|
|
||||||
|
[Tooltip("Whether to block player input during bump movement")]
|
||||||
|
[SerializeField] private bool endlessDescenderBlockInputDuringBump = true;
|
||||||
|
|
||||||
// IMinigameSettings implementation - Basic Movement
|
// IMinigameSettings implementation - Basic Movement
|
||||||
public float EndlessDescenderLerpSpeed => endlessDescenderLerpSpeed;
|
public float EndlessDescenderLerpSpeed => endlessDescenderLerpSpeed;
|
||||||
public float EndlessDescenderMaxOffset => endlessDescenderMaxOffset;
|
public float EndlessDescenderMaxOffset => endlessDescenderMaxOffset;
|
||||||
@@ -149,6 +162,12 @@ namespace AppleHills.Core.Settings
|
|||||||
public float EndlessDescenderObstacleMinMoveSpeed => endlessDescenderObstacleMinMoveSpeed;
|
public float EndlessDescenderObstacleMinMoveSpeed => endlessDescenderObstacleMinMoveSpeed;
|
||||||
public float EndlessDescenderObstacleMaxMoveSpeed => endlessDescenderObstacleMaxMoveSpeed;
|
public float EndlessDescenderObstacleMaxMoveSpeed => endlessDescenderObstacleMaxMoveSpeed;
|
||||||
|
|
||||||
|
// IMinigameSettings implementation - Collision Handling
|
||||||
|
public float EndlessDescenderDamageImmunityDuration => endlessDescenderDamageImmunityDuration;
|
||||||
|
public float EndlessDescenderBumpForce => endlessDescenderBumpForce;
|
||||||
|
public float EndlessDescenderSmoothMoveSpeed => endlessDescenderSmoothMoveSpeed;
|
||||||
|
public bool EndlessDescenderBlockInputDuringBump => endlessDescenderBlockInputDuringBump;
|
||||||
|
|
||||||
public override void OnValidate()
|
public override void OnValidate()
|
||||||
{
|
{
|
||||||
base.OnValidate();
|
base.OnValidate();
|
||||||
@@ -205,6 +224,11 @@ namespace AppleHills.Core.Settings
|
|||||||
endlessDescenderObstacleSpawnCollisionRadius = Mathf.Max(0.1f, endlessDescenderObstacleSpawnCollisionRadius);
|
endlessDescenderObstacleSpawnCollisionRadius = Mathf.Max(0.1f, endlessDescenderObstacleSpawnCollisionRadius);
|
||||||
endlessDescenderObstacleMinMoveSpeed = Mathf.Max(0.1f, endlessDescenderObstacleMinMoveSpeed);
|
endlessDescenderObstacleMinMoveSpeed = Mathf.Max(0.1f, endlessDescenderObstacleMinMoveSpeed);
|
||||||
endlessDescenderObstacleMaxMoveSpeed = Mathf.Max(endlessDescenderObstacleMinMoveSpeed, endlessDescenderObstacleMaxMoveSpeed);
|
endlessDescenderObstacleMaxMoveSpeed = Mathf.Max(endlessDescenderObstacleMinMoveSpeed, endlessDescenderObstacleMaxMoveSpeed);
|
||||||
|
|
||||||
|
// Validate collision settings
|
||||||
|
endlessDescenderDamageImmunityDuration = Mathf.Max(0.1f, endlessDescenderDamageImmunityDuration);
|
||||||
|
endlessDescenderBumpForce = Mathf.Max(0.1f, endlessDescenderBumpForce);
|
||||||
|
endlessDescenderSmoothMoveSpeed = Mathf.Max(0.1f, endlessDescenderSmoothMoveSpeed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
@@ -88,5 +88,11 @@ namespace AppleHills.Core.Settings
|
|||||||
float EndlessDescenderObstacleSpawnCollisionRadius { get; }
|
float EndlessDescenderObstacleSpawnCollisionRadius { get; }
|
||||||
float EndlessDescenderObstacleMinMoveSpeed { get; }
|
float EndlessDescenderObstacleMinMoveSpeed { get; }
|
||||||
float EndlessDescenderObstacleMaxMoveSpeed { get; }
|
float EndlessDescenderObstacleMaxMoveSpeed { get; }
|
||||||
|
|
||||||
|
// Endless Descender - Collision Handling
|
||||||
|
float EndlessDescenderDamageImmunityDuration { get; }
|
||||||
|
float EndlessDescenderBumpForce { get; }
|
||||||
|
float EndlessDescenderSmoothMoveSpeed { get; }
|
||||||
|
bool EndlessDescenderBlockInputDuringBump { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,24 @@ 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)
|
||||||
{
|
{
|
||||||
// Mark the obstacle as having dealt damage to prevent multiple hits
|
// Mark the obstacle as having dealt damage to prevent multiple hits
|
||||||
@@ -21,21 +39,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.EndlessDescenderDamageImmunityDuration} 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
|
||||||
|
|||||||
@@ -298,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, _settings.EndlessDescenderObstacleSpawnCollisionRadius, _devSettings.ObstacleTileLayerMask);
|
// Convert the single layer to a layer mask inline (1 << layerNumber)
|
||||||
|
Collider2D collision = Physics2D.OverlapCircle(position, _settings.EndlessDescenderObstacleSpawnCollisionRadius, 1 << _devSettings.TrenchTileLayer);
|
||||||
return collision == null;
|
return collision == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts the red blinking effect when damage begins
|
|
||||||
/// </summary>
|
|
||||||
private void StartBlinking()
|
|
||||||
{
|
|
||||||
if (targetSpriteRenderer == null) return;
|
|
||||||
|
|
||||||
Debug.Log("[PlayerBlinkBehavior] Starting damage blink effect");
|
|
||||||
|
|
||||||
// Stop any existing blink coroutine
|
|
||||||
if (_blinkCoroutine != null)
|
|
||||||
{
|
|
||||||
StopCoroutine(_blinkCoroutine);
|
|
||||||
}
|
|
||||||
|
|
||||||
_isBlinking = true;
|
|
||||||
_isShowingDamageColor = false;
|
|
||||||
|
|
||||||
// Start the blink coroutine
|
|
||||||
_blinkCoroutine = StartCoroutine(BlinkCoroutine());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops the blinking effect when damage ends
|
|
||||||
/// </summary>
|
|
||||||
private void StopBlinking()
|
|
||||||
{
|
|
||||||
Debug.Log("[PlayerBlinkBehavior] Stopping damage blink effect");
|
|
||||||
|
|
||||||
_isBlinking = false;
|
|
||||||
|
|
||||||
// Stop the blink coroutine
|
|
||||||
if (_blinkCoroutine != null)
|
|
||||||
{
|
|
||||||
StopCoroutine(_blinkCoroutine);
|
|
||||||
_blinkCoroutine = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore original color
|
|
||||||
RestoreOriginalColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Coroutine that handles the blinking animation
|
|
||||||
/// </summary>
|
|
||||||
private IEnumerator BlinkCoroutine()
|
|
||||||
{
|
|
||||||
while (_isBlinking && targetSpriteRenderer != null)
|
|
||||||
{
|
|
||||||
// Toggle between original and damage colors
|
|
||||||
if (_isShowingDamageColor)
|
|
||||||
{
|
|
||||||
targetSpriteRenderer.color = _originalColor;
|
|
||||||
_isShowingDamageColor = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
targetSpriteRenderer.color = damageBlinkColor;
|
|
||||||
_isShowingDamageColor = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for blink interval
|
|
||||||
yield return new WaitForSeconds(blinkRate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Restores the sprite renderer to its original color
|
|
||||||
/// </summary>
|
|
||||||
private void RestoreOriginalColor()
|
|
||||||
{
|
|
||||||
if (targetSpriteRenderer != null)
|
if (targetSpriteRenderer != null)
|
||||||
{
|
{
|
||||||
targetSpriteRenderer.color = _originalColor;
|
targetSpriteRenderer.color = _originalColor;
|
||||||
_isShowingDamageColor = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the original color reference (useful if sprite color changes during gameplay)
|
/// Start the blinking effect coroutine
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateOriginalColor()
|
private void StartBlinkEffect()
|
||||||
{
|
{
|
||||||
if (targetSpriteRenderer != null && !_isBlinking)
|
if (targetSpriteRenderer == null || _devSettings == null) return;
|
||||||
{
|
|
||||||
_originalColor = targetSpriteRenderer.color;
|
// If already blinking, stop the current coroutine
|
||||||
}
|
if (_isBlinking && _blinkCoroutine != null)
|
||||||
}
|
|
||||||
|
|
||||||
/// <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);
|
StopCoroutine(_blinkCoroutine);
|
||||||
}
|
}
|
||||||
|
|
||||||
StartCoroutine(ManualBlinkCoroutine(duration));
|
// Start a new blink coroutine
|
||||||
|
_blinkCoroutine = StartCoroutine(BlinkCoroutine());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Coroutine for manually triggered blink effects
|
/// Coroutine that handles the blink effect timing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IEnumerator ManualBlinkCoroutine(float duration)
|
private IEnumerator BlinkCoroutine()
|
||||||
{
|
{
|
||||||
_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,7 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -10,17 +11,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,6 +18,10 @@ 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;
|
||||||
@@ -67,305 +61,152 @@ 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)
|
|
||||||
{
|
|
||||||
Debug.LogError($"[PlayerCollisionBehavior] No Collider2D found on this GameObject or its children!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load settings
|
||||||
|
_gameSettings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
|
||||||
|
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||||
|
|
||||||
|
if (_gameSettings == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[PlayerCollisionBehavior] Failed to load game settings!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_devSettings == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[PlayerCollisionBehavior] Failed to load developer settings!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set up coroutine runner (use first instance)
|
/// <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;
|
||||||
|
|
||||||
|
// Check if the collider is an obstacle
|
||||||
|
if ((_devSettings.PlayerObstacleLayerMask.value & (1 << other.gameObject.layer)) != 0)
|
||||||
|
{
|
||||||
|
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)
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDestroy()
|
|
||||||
{
|
|
||||||
// Unregister this instance
|
|
||||||
_allInstances.Remove(this);
|
|
||||||
|
|
||||||
// Clean up static references if this was the coroutine runner
|
|
||||||
if (_coroutineRunner == this)
|
|
||||||
{
|
{
|
||||||
if (_globalImmunityCoroutine != null)
|
// Notify player controller to block input
|
||||||
{
|
|
||||||
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
|
// Trigger event for all listeners
|
||||||
|
OnImmunityStarted?.Invoke();
|
||||||
|
|
||||||
|
// Stop existing coroutine if one is running
|
||||||
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.EndlessDescenderDamageImmunityDuration);
|
||||||
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
|
// Trigger event for all listeners
|
||||||
if (_sharedPlayerCollider != null)
|
|
||||||
{
|
|
||||||
_sharedPlayerCollider.enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify all instances about immunity end
|
|
||||||
foreach (var instance in _allInstances)
|
|
||||||
{
|
|
||||||
if (instance != null)
|
|
||||||
{
|
|
||||||
instance.OnImmunityEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast immunity end event
|
|
||||||
OnImmunityEnded?.Invoke();
|
OnImmunityEnded?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Override this method to implement specific collision response behavior
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obstacle">The obstacle that was collided with</param>
|
|
||||||
protected abstract void HandleCollisionResponse(Collider2D obstacle);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when damage immunity starts (called on all instances)
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnImmunityStart()
|
|
||||||
{
|
|
||||||
Debug.Log($"[{GetType().Name}] Damage immunity started for {damageImmunityDuration} seconds");
|
|
||||||
|
|
||||||
// Block input if specified
|
|
||||||
if (blockInputDuringImmunity)
|
|
||||||
{
|
|
||||||
BlockPlayerInput();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when damage immunity ends (called on all instances)
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnImmunityEnd()
|
|
||||||
{
|
|
||||||
Debug.Log($"[{GetType().Name}] Damage immunity ended");
|
|
||||||
|
|
||||||
// Restore input if it was blocked
|
|
||||||
if (wasInputBlocked)
|
|
||||||
{
|
|
||||||
RestorePlayerInput();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Restores player input after immunity
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void RestorePlayerInput()
|
|
||||||
{
|
|
||||||
if (playerController != null && wasInputBlocked)
|
|
||||||
{
|
|
||||||
playerController.enabled = true;
|
|
||||||
wasInputBlocked = false;
|
|
||||||
|
|
||||||
// Update the controller's target position to current position to prevent snapping
|
|
||||||
UpdateControllerTarget();
|
|
||||||
|
|
||||||
Debug.Log($"[{GetType().Name}] Player input restored after immunity");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Blocks player input during immunity
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void BlockPlayerInput()
|
|
||||||
{
|
|
||||||
if (playerController != null && playerController.enabled)
|
|
||||||
{
|
|
||||||
playerController.enabled = false;
|
|
||||||
wasInputBlocked = true;
|
|
||||||
Debug.Log($"[{GetType().Name}] Player input blocked during immunity");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the PlayerController's internal target to match current position
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void UpdateControllerTarget()
|
|
||||||
{
|
|
||||||
if (playerController != null && playerCharacter != null)
|
|
||||||
{
|
|
||||||
// Use reflection to update the private _targetFingerX field
|
|
||||||
var targetField = typeof(PlayerController)
|
|
||||||
.GetField("_targetFingerX", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
||||||
|
|
||||||
if (targetField != null)
|
|
||||||
{
|
|
||||||
targetField.SetValue(playerController, playerCharacter.transform.position.x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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,5 +1,6 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -9,286 +10,159 @@ 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
|
private bool _bumpInputBlocked; // Tracks bump-specific input blocking
|
||||||
|
|
||||||
protected override void HandleCollisionResponse(Collider2D obstacle)
|
protected override void HandleCollisionResponse(Collider2D obstacle)
|
||||||
{
|
{
|
||||||
switch (bumpMode)
|
// Use bump mode from developer settings
|
||||||
|
switch (_devSettings.BumpMode)
|
||||||
{
|
{
|
||||||
case BumpMode.Impulse:
|
case 0: // Impulse mode
|
||||||
StartImpulseBump();
|
StartImpulseBump();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BumpMode.SmoothToCenter:
|
case 1: // SmoothToCenter mode
|
||||||
StartSmoothMoveToCenter();
|
StartSmoothMoveToCenter();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Log($"[TileBumpCollision] Collision handled with {bumpMode} mode");
|
Debug.Log($"[TileBumpCollision] Collision handled with {(_devSettings.BumpMode == 0 ? "Impulse" : "SmoothToCenter")} mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts an impulse bump toward the center with force-based distance
|
/// Applies an impulse force toward center
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void StartImpulseBump()
|
private void StartImpulseBump()
|
||||||
{
|
{
|
||||||
if (playerCharacter == null) return;
|
if (_isBumping || playerCharacter == null)
|
||||||
|
return;
|
||||||
float currentX = playerCharacter.transform.position.x;
|
|
||||||
|
|
||||||
// Calculate bump distance based on force and current position
|
|
||||||
float directionToCenter = currentX > 0 ? -1f : 1f; // Direction toward center
|
|
||||||
|
|
||||||
// Calculate target position - closer to center based on bump force
|
|
||||||
float bumpDistance = bumpForce * 0.2f; // Scale factor for distance
|
|
||||||
float targetX = currentX + (directionToCenter * bumpDistance);
|
|
||||||
|
|
||||||
// Clamp to center if we would overshoot
|
|
||||||
if ((currentX > 0 && targetX < 0) || (currentX < 0 && targetX > 0))
|
|
||||||
{
|
|
||||||
targetX = 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float bumpDuration = 0.5f; // Fixed duration for impulse
|
|
||||||
|
|
||||||
StartBump(currentX, targetX, bumpDuration);
|
|
||||||
|
|
||||||
Debug.Log($"[TileBumpCollision] Starting impulse bump from X={currentX} to X={targetX} (force={bumpForce})");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts smooth movement to the center
|
|
||||||
/// </summary>
|
|
||||||
private void StartSmoothMoveToCenter()
|
|
||||||
{
|
|
||||||
if (playerCharacter == null) return;
|
|
||||||
|
|
||||||
float currentX = playerCharacter.transform.position.x;
|
|
||||||
float distanceToCenter = Mathf.Abs(currentX);
|
|
||||||
|
|
||||||
float targetX = 0f; // Always move to center
|
|
||||||
float bumpDuration = distanceToCenter / smoothMoveSpeed; // Duration based on distance and speed
|
|
||||||
|
|
||||||
StartBump(currentX, targetX, bumpDuration);
|
|
||||||
|
|
||||||
Debug.Log($"[TileBumpCollision] Starting smooth move to center from X={currentX} (speed={smoothMoveSpeed}, duration={bumpDuration:F2}s)");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Common bump initialization using coroutines
|
|
||||||
/// </summary>
|
|
||||||
private void StartBump(float startX, float targetX, float duration)
|
|
||||||
{
|
|
||||||
// Stop any existing bump
|
|
||||||
if (_bumpCoroutine != null)
|
|
||||||
{
|
|
||||||
StopCoroutine(_bumpCoroutine);
|
|
||||||
_bumpCoroutine = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isBumping = true;
|
_isBumping = true;
|
||||||
|
|
||||||
// Block player input if enabled (use bump-specific blocking)
|
// Block input during bump if configured
|
||||||
if (blockInputDuringBump && playerController != null && playerController.enabled)
|
if (_gameSettings.EndlessDescenderBlockInputDuringBump && playerController != null)
|
||||||
{
|
{
|
||||||
playerController.enabled = false;
|
|
||||||
_bumpInputBlocked = true;
|
_bumpInputBlocked = true;
|
||||||
Debug.Log("[TileBumpCollision] Player input blocked during bump");
|
// TODO: Implement input blocking
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start bump coroutine
|
// Calculate direction to center (X = 0)
|
||||||
_bumpCoroutine = StartCoroutine(BumpCoroutine(startX, targetX, duration));
|
float directionToCenter = Mathf.Sign(-playerCharacter.transform.position.x);
|
||||||
|
|
||||||
|
// Start impulse bump coroutine
|
||||||
|
if (_bumpCoroutine != null)
|
||||||
|
StopCoroutine(_bumpCoroutine);
|
||||||
|
|
||||||
|
_bumpCoroutine = StartCoroutine(ImpulseBumpCoroutine(directionToCenter));
|
||||||
|
|
||||||
|
Debug.Log($"[TileBumpCollision] Started impulse bump with force {_gameSettings.EndlessDescenderBumpForce} in direction {directionToCenter}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Coroutine that handles the bump movement over time
|
/// Smoothly moves the player to center
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IEnumerator BumpCoroutine(float startX, float targetX, float duration)
|
private void StartSmoothMoveToCenter()
|
||||||
|
{
|
||||||
|
if (_isBumping || playerCharacter == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isBumping = true;
|
||||||
|
|
||||||
|
// Block input during bump if configured
|
||||||
|
if (_gameSettings.EndlessDescenderBlockInputDuringBump && playerController != null)
|
||||||
|
{
|
||||||
|
_bumpInputBlocked = true;
|
||||||
|
// TODO: Implement input blocking
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start smooth move coroutine
|
||||||
|
if (_bumpCoroutine != null)
|
||||||
|
StopCoroutine(_bumpCoroutine);
|
||||||
|
|
||||||
|
_bumpCoroutine = StartCoroutine(SmoothMoveToCenterCoroutine());
|
||||||
|
|
||||||
|
Debug.Log($"[TileBumpCollision] Started smooth move to center with speed {_gameSettings.EndlessDescenderSmoothMoveSpeed}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Coroutine to handle impulse bump movement
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerator ImpulseBumpCoroutine(float direction)
|
||||||
{
|
{
|
||||||
float elapsedTime = 0f;
|
float elapsedTime = 0f;
|
||||||
|
float bumpDuration = 0.5f; // Fixed duration for impulse
|
||||||
|
Vector3 startPosition = playerCharacter.transform.position;
|
||||||
|
|
||||||
while (elapsedTime < duration)
|
while (elapsedTime < bumpDuration)
|
||||||
{
|
{
|
||||||
elapsedTime += Time.deltaTime;
|
elapsedTime += Time.deltaTime;
|
||||||
|
|
||||||
// Calculate progress and apply curve
|
// Use evaluation time from curve for non-linear movement
|
||||||
float progress = elapsedTime / duration;
|
float t = elapsedTime / bumpDuration;
|
||||||
float curveValue = bumpCurve.Evaluate(progress);
|
float curveValue = _devSettings.BumpCurve.Evaluate(t);
|
||||||
|
|
||||||
// Interpolate position
|
// Calculate movement distance based on force and curve
|
||||||
float currentX = Mathf.Lerp(startX, targetX, curveValue);
|
float distance = _gameSettings.EndlessDescenderBumpForce * curveValue * Time.deltaTime;
|
||||||
|
|
||||||
// Apply the position to the player character
|
// Move the player toward center
|
||||||
if (playerCharacter != null)
|
Vector3 newPosition = playerCharacter.transform.position;
|
||||||
{
|
newPosition.x += direction * distance;
|
||||||
Vector3 currentPos = playerCharacter.transform.position;
|
|
||||||
playerCharacter.transform.position = new Vector3(currentX, currentPos.y, currentPos.z);
|
// Clamp to valid range
|
||||||
}
|
newPosition.x = Mathf.Clamp(newPosition.x, _gameSettings.EndlessDescenderClampXMin, _gameSettings.EndlessDescenderClampXMax);
|
||||||
|
|
||||||
|
// Apply the position
|
||||||
|
playerCharacter.transform.position = newPosition;
|
||||||
|
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we end exactly at target
|
// Finish bump
|
||||||
if (playerCharacter != null)
|
|
||||||
{
|
|
||||||
Vector3 currentPos = playerCharacter.transform.position;
|
|
||||||
playerCharacter.transform.position = new Vector3(targetX, currentPos.y, currentPos.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bump finished
|
|
||||||
_isBumping = false;
|
_isBumping = false;
|
||||||
|
_bumpInputBlocked = false;
|
||||||
_bumpCoroutine = null;
|
_bumpCoroutine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Coroutine to handle smooth movement to center
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerator SmoothMoveToCenterCoroutine()
|
||||||
|
{
|
||||||
|
Vector3 startPosition = playerCharacter.transform.position;
|
||||||
|
Vector3 targetPosition = new Vector3(0f, startPosition.y, startPosition.z);
|
||||||
|
|
||||||
if (_bumpInputBlocked)
|
// Calculate distance to center and expected duration
|
||||||
|
float distanceToCenter = Mathf.Abs(startPosition.x);
|
||||||
|
float expectedDuration = distanceToCenter / _gameSettings.EndlessDescenderSmoothMoveSpeed;
|
||||||
|
float elapsedTime = 0f;
|
||||||
|
|
||||||
|
// Move until we reach the center
|
||||||
|
while (elapsedTime < expectedDuration)
|
||||||
{
|
{
|
||||||
RestoreBumpInput();
|
elapsedTime += Time.deltaTime;
|
||||||
|
|
||||||
|
// Calculate progress based on time and curve
|
||||||
|
float t = elapsedTime / expectedDuration;
|
||||||
|
float curveValue = _devSettings.BumpCurve.Evaluate(t);
|
||||||
|
|
||||||
|
// Calculate interpolated position
|
||||||
|
Vector3 newPosition = Vector3.Lerp(startPosition, targetPosition, curveValue);
|
||||||
|
|
||||||
|
// Apply the position
|
||||||
|
playerCharacter.transform.position = newPosition;
|
||||||
|
|
||||||
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Log("[TileBumpCollision] Bump movement completed");
|
// Ensure we end at exactly the center
|
||||||
}
|
playerCharacter.transform.position = targetPosition;
|
||||||
|
|
||||||
/// <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
|
// Finish bump
|
||||||
if (_isBumping && _bumpCoroutine != null)
|
_isBumping = false;
|
||||||
{
|
_bumpInputBlocked = false;
|
||||||
StopCoroutine(_bumpCoroutine);
|
_bumpCoroutine = null;
|
||||||
_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()
|
||||||
{
|
{
|
||||||
|
// Get developer settings
|
||||||
|
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||||
|
if (_devSettings == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[WobbleBehavior] Failed to load developer settings!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize
|
||||||
lastPosition = transform.position;
|
lastPosition = transform.position;
|
||||||
smoothedVelocity = 0f;
|
basePosition = transform.position;
|
||||||
smoothedAngle = 0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
// Calculate movement speed (exclude vertical wobble from velocity calculation)
|
if (_devSettings == null) return;
|
||||||
Vector3 horizontalPosition = transform.position;
|
|
||||||
horizontalPosition.y = 0f; // Ignore Y for velocity if only horizontal movement matters
|
// Calculate velocity based on position change
|
||||||
Vector3 horizontalLastPosition = lastPosition;
|
Vector3 positionDelta = transform.position - lastPosition;
|
||||||
horizontalLastPosition.y = 0f;
|
velocity = positionDelta.x / Time.deltaTime;
|
||||||
velocity = (horizontalPosition - horizontalLastPosition).magnitude / Time.deltaTime;
|
|
||||||
lastPosition = transform.position;
|
lastPosition = transform.position;
|
||||||
|
basePosition = transform.position;
|
||||||
|
|
||||||
// Smooth velocity to prevent jitter
|
// Smooth velocity changes
|
||||||
smoothedVelocity = Mathf.Lerp(smoothedVelocity, velocity, velocitySmoothing * Time.deltaTime);
|
smoothedVelocity = Mathf.Lerp(smoothedVelocity, velocity, Time.deltaTime * _devSettings.PlayerVelocitySmoothing);
|
||||||
|
|
||||||
// Wobble amplitude scales with smoothed speed, but always has a base value
|
// Calculate wobble rotation based on velocity and time
|
||||||
float amplitude = baseWobbleAmplitude + smoothedVelocity * speedToAmplitude;
|
wobbleTime += Time.deltaTime * _devSettings.PlayerWobbleFrequency;
|
||||||
amplitude = Mathf.Min(amplitude, maxRotationLimit); // Prevent amplitude from exceeding limit
|
float rawWobble = Mathf.Sin(wobbleTime);
|
||||||
|
|
||||||
// Oscillate around horizontal (0 degrees)
|
// Calculate wobble amplitude based on velocity
|
||||||
wobbleTime += Time.deltaTime * wobbleFrequency;
|
float velocityFactor = Mathf.Abs(smoothedVelocity) * _devSettings.PlayerSpeedToAmplitude;
|
||||||
float targetAngle = Mathf.Sin(wobbleTime) * amplitude;
|
float wobbleAmplitude = _devSettings.PlayerBaseWobbleAmplitude + velocityFactor;
|
||||||
targetAngle = Mathf.Clamp(targetAngle, -maxRotationLimit, maxRotationLimit);
|
|
||||||
|
// Clamp to maximum rotation limit
|
||||||
|
wobbleAmplitude = Mathf.Min(wobbleAmplitude, _devSettings.PlayerMaxRotationLimit);
|
||||||
|
|
||||||
|
// Calculate target angle
|
||||||
|
float targetAngle = rawWobble * wobbleAmplitude;
|
||||||
|
|
||||||
|
// Smooth angle changes
|
||||||
|
smoothedAngle = Mathf.Lerp(smoothedAngle, targetAngle, Time.deltaTime * _devSettings.PlayerRotationSmoothing);
|
||||||
|
|
||||||
|
// Apply rotation
|
||||||
|
transform.rotation = Quaternion.Euler(0f, 0f, smoothedAngle);
|
||||||
|
|
||||||
// Smooth the rotation angle
|
// Calculate vertical bobbing
|
||||||
smoothedAngle = Mathf.Lerp(smoothedAngle, targetAngle, rotationSmoothing * Time.deltaTime);
|
float time = Time.time * _devSettings.PlayerVerticalFrequency;
|
||||||
|
verticalOffset = Mathf.Sin(time) * _devSettings.PlayerVerticalAmplitude;
|
||||||
// Apply rotation (Z axis for 2D)
|
|
||||||
transform.localRotation = Quaternion.Euler(0f, 0f, smoothedAngle);
|
|
||||||
|
|
||||||
// Calculate vertical up/down movement (wave riding) only once
|
|
||||||
verticalOffset = Mathf.Sin(wobbleTime * verticalFrequency) * verticalAmplitude;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +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("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;
|
||||||
@@ -29,8 +24,9 @@ namespace Minigames.DivingForPictures
|
|||||||
[FormerlySerializedAs("OnTileDestroyed")]
|
[FormerlySerializedAs("OnTileDestroyed")]
|
||||||
public UnityEvent<GameObject> onTileDestroyed;
|
public UnityEvent<GameObject> onTileDestroyed;
|
||||||
|
|
||||||
// Settings reference
|
// Settings references
|
||||||
private IDivingMinigameSettings _settings;
|
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>();
|
||||||
@@ -67,11 +63,18 @@ namespace Minigames.DivingForPictures
|
|||||||
|
|
||||||
// Get settings from GameManager
|
// Get settings from GameManager
|
||||||
_settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
|
_settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
|
||||||
|
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||||
|
|
||||||
if (_settings == null)
|
if (_settings == null)
|
||||||
{
|
{
|
||||||
Debug.LogError("[TrenchTileSpawner] Failed to load diving minigame settings!");
|
Debug.LogError("[TrenchTileSpawner] Failed to load diving minigame settings!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_devSettings == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[TrenchTileSpawner] Failed to load diving developer settings!");
|
||||||
|
}
|
||||||
|
|
||||||
_baseMoveSpeed = _settings?.EndlessDescenderMoveSpeed ?? 3f; // Store the original base speed
|
_baseMoveSpeed = _settings?.EndlessDescenderMoveSpeed ?? 3f; // Store the original base speed
|
||||||
|
|
||||||
// Calculate tile heights for each prefab
|
// Calculate tile heights for each prefab
|
||||||
@@ -80,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();
|
||||||
}
|
}
|
||||||
@@ -196,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);
|
||||||
@@ -374,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);
|
||||||
@@ -505,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)
|
||||||
@@ -537,7 +540,7 @@ namespace Minigames.DivingForPictures
|
|||||||
{
|
{
|
||||||
int prefabCount = tilePrefabs.Count;
|
int prefabCount = tilePrefabs.Count;
|
||||||
List<float> weights = new List<float>(prefabCount);
|
List<float> weights = new List<float>(prefabCount);
|
||||||
|
|
||||||
for (int i = 0; i < prefabCount; i++)
|
for (int i = 0; i < prefabCount; i++)
|
||||||
{
|
{
|
||||||
int lastUsed = _tileLastUsed.TryGetValue(i, out var value) ? value : -prefabCount;
|
int lastUsed = _tileLastUsed.TryGetValue(i, out var value) ? value : -prefabCount;
|
||||||
@@ -545,13 +548,13 @@ namespace Minigames.DivingForPictures
|
|||||||
float weight = Mathf.Clamp(age, 1, prefabCount * 2); // More unused = higher weight
|
float weight = Mathf.Clamp(age, 1, prefabCount * 2); // More unused = higher weight
|
||||||
weights.Add(weight);
|
weights.Add(weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
float totalWeight = 0f;
|
float totalWeight = 0f;
|
||||||
foreach (var weight in weights)
|
foreach (var weight in weights)
|
||||||
{
|
{
|
||||||
totalWeight += weight;
|
totalWeight += weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
float randomValue = Random.value * totalWeight;
|
float randomValue = Random.value * totalWeight;
|
||||||
for (int i = 0; i < prefabCount; i++)
|
for (int i = 0; i < prefabCount; i++)
|
||||||
{
|
{
|
||||||
@@ -561,10 +564,10 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
randomValue -= weights[i];
|
randomValue -= weights[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Random.Range(0, prefabCount); // fallback
|
return Random.Range(0, prefabCount); // fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the height of a tile based on its prefab or renderer bounds
|
/// Gets the height of a tile based on its prefab or renderer bounds
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -592,18 +595,18 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not found, calculate it from the renderer
|
// If not found, calculate it from the renderer
|
||||||
Renderer renderer = tile.GetComponentInChildren<Renderer>();
|
Renderer renderer = tile.GetComponentInChildren<Renderer>();
|
||||||
if (renderer != null)
|
if (renderer != null)
|
||||||
{
|
{
|
||||||
return renderer.bounds.size.y;
|
return renderer.bounds.size.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback
|
// Fallback
|
||||||
return DefaultTileHeight;
|
return DefaultTileHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the index of the prefab that was used to create this tile
|
/// Gets the index of the prefab that was used to create this tile
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -619,7 +622,7 @@ namespace Minigames.DivingForPictures
|
|||||||
for (int i = 0; i < tilePrefabs.Count; i++)
|
for (int i = 0; i < tilePrefabs.Count; i++)
|
||||||
{
|
{
|
||||||
if (tilePrefabs[i] == null) continue;
|
if (tilePrefabs[i] == null) continue;
|
||||||
|
|
||||||
if (tile.name.StartsWith(tilePrefabs[i].name))
|
if (tile.name.StartsWith(tilePrefabs[i].name))
|
||||||
{
|
{
|
||||||
return i;
|
return i;
|
||||||
|
|||||||
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:
|
||||||
78
Assets/Settings/Developer/DivingDeveloperSettings.asset
Normal file
78
Assets/Settings/Developer/DivingDeveloperSettings.asset
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
%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: 9
|
||||||
|
obstacleTileLayerMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
obstacleUseObjectPooling: 1
|
||||||
|
obstacleMaxPerPrefabPoolSize: 3
|
||||||
|
obstacleTotalMaxPoolSize: 15
|
||||||
|
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.5
|
||||||
|
playerVelocitySmoothing: 10
|
||||||
|
playerRotationSmoothing: 10
|
||||||
|
playerObstacleLayerMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
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:
|
||||||
@@ -21,7 +21,7 @@ TagManager:
|
|||||||
- Interactable
|
- Interactable
|
||||||
- QuarryObstacle
|
- QuarryObstacle
|
||||||
- QuarryMonster
|
- QuarryMonster
|
||||||
-
|
- QuarryTrenchTile
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
|
|||||||
Reference in New Issue
Block a user