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_GUID: c62e6f02418e19949bca4cccdd5fa02e
|
||||
m_SerializeEntries:
|
||||
- m_GUID: 328ce914b893df646be3ad3c62755453
|
||||
m_Address: Settings/Developer/DivingDeveloperSettings
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels: []
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: 35bfcff00faa72c4eb272a9e8288f965
|
||||
m_Address: Settings/PlayerFollowerSettings
|
||||
m_ReadOnly: 0
|
||||
|
||||
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 UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using System;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
@@ -38,8 +40,8 @@ namespace AppleHills.Core.Settings
|
||||
// Dictionary to cache loaded settings
|
||||
private Dictionary<System.Type, BaseDeveloperSettings> _settingsCache = new Dictionary<System.Type, BaseDeveloperSettings>();
|
||||
|
||||
// Default developer settings stored in the Resources folder
|
||||
[SerializeField] private string _resourcesPath = "Settings/Developer";
|
||||
// Path prefix for addressable developer settings
|
||||
[SerializeField] private string _addressablePath = "Settings/Developer";
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -71,16 +73,35 @@ namespace AppleHills.Core.Settings
|
||||
return cachedSettings as T;
|
||||
}
|
||||
|
||||
// Load from Resources if not cached
|
||||
T settings = Resources.Load<T>($"{_resourcesPath}/{type.Name}");
|
||||
// Load from Addressables if not cached
|
||||
string key = $"{_addressablePath}/{type.Name}";
|
||||
|
||||
if (settings != null)
|
||||
try
|
||||
{
|
||||
_settingsCache[type] = settings;
|
||||
return settings;
|
||||
T settings = Addressables.LoadAssetAsync<T>(key).WaitForCompletion();
|
||||
|
||||
if (settings != null)
|
||||
{
|
||||
_settingsCache[type] = settings;
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Failed to load developer settings at '{key}': {e.Message}");
|
||||
}
|
||||
|
||||
Debug.LogWarning($"Developer settings of type {type.Name} not found at addressable path '{key}'");
|
||||
|
||||
// Fallback to Resources for backward compatibility
|
||||
T resourcesSettings = Resources.Load<T>($"{_addressablePath}/{type.Name}");
|
||||
if (resourcesSettings != null)
|
||||
{
|
||||
Debug.Log($"Found developer settings in Resources instead of Addressables at '{_addressablePath}/{type.Name}'");
|
||||
_settingsCache[type] = resourcesSettings;
|
||||
return resourcesSettings;
|
||||
}
|
||||
|
||||
Debug.LogWarning($"Developer settings of type {type.Name} not found in Resources/{_resourcesPath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,11 +54,9 @@ namespace AppleHills.Core.Settings
|
||||
|
||||
[Header("Obstacle System")]
|
||||
[Tooltip("Layer for obstacles to be placed on")]
|
||||
[Layer]
|
||||
[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")]
|
||||
[SerializeField] private bool obstacleUseObjectPooling = true;
|
||||
|
||||
@@ -68,6 +66,72 @@ namespace AppleHills.Core.Settings
|
||||
[Tooltip("Total maximum size of obstacle pool across all prefab types")]
|
||||
[SerializeField] private int obstacleTotalMaxPoolSize = 15;
|
||||
|
||||
[Header("Trench Tile System")]
|
||||
[Tooltip("Layer for trench tiles to be placed on")]
|
||||
[Layer]
|
||||
[SerializeField] private int trenchTileLayer = 13; // QuarryTrenchTile layer
|
||||
|
||||
[Tooltip("Whether to use object pooling for trench tiles")]
|
||||
[SerializeField] private bool trenchTileUseObjectPooling = true;
|
||||
|
||||
[Tooltip("Maximum objects per prefab type in trench tile pool")]
|
||||
[SerializeField] private int trenchTileMaxPerPrefabPoolSize = 2;
|
||||
|
||||
[Tooltip("Total maximum size of trench tile pool across all prefab types")]
|
||||
[SerializeField] private int trenchTileTotalMaxPoolSize = 10;
|
||||
|
||||
[Header("Player Blink Behavior")]
|
||||
[Tooltip("Color to blink to when taking damage (typically red for damage indication)")]
|
||||
[SerializeField] private Color playerBlinkDamageColor = Color.red;
|
||||
|
||||
[Tooltip("How fast to blink between normal and damage colors (seconds between color changes)")]
|
||||
[SerializeField] private float playerBlinkRate = 0.15f;
|
||||
|
||||
[Tooltip("Alpha value for the damage color (0 = transparent, 1 = opaque)")]
|
||||
[Range(0f, 1f)]
|
||||
[SerializeField] private float playerDamageColorAlpha = 0.7f;
|
||||
|
||||
[Header("Player Wobble Behavior")]
|
||||
[Tooltip("Frequency of wobble (higher = faster rocking)")]
|
||||
[SerializeField] private float playerWobbleFrequency = 1.5f;
|
||||
|
||||
[Tooltip("Base wobble amplitude in degrees from horizontal")]
|
||||
[SerializeField] private float playerBaseWobbleAmplitude = 8f;
|
||||
|
||||
[Tooltip("How much speed affects amplitude")]
|
||||
[SerializeField] private float playerSpeedToAmplitude = 2f;
|
||||
|
||||
[Tooltip("Maximum allowed rotation in degrees")]
|
||||
[SerializeField] private float playerMaxRotationLimit = 45f;
|
||||
|
||||
[Tooltip("Frequency of vertical bobbing")]
|
||||
[SerializeField] private float playerVerticalFrequency = 0.5f;
|
||||
|
||||
[Tooltip("How far the object moves up/down")]
|
||||
[SerializeField] private float playerVerticalAmplitude = 0.5f;
|
||||
|
||||
[Tooltip("How quickly velocity changes are smoothed")]
|
||||
[SerializeField] private float playerVelocitySmoothing = 10f;
|
||||
|
||||
[Tooltip("How quickly rotation is smoothed")]
|
||||
[SerializeField] private float playerRotationSmoothing = 10f;
|
||||
|
||||
[Header("Collision Settings")]
|
||||
[Tooltip("Layer mask for obstacle detection - configure which layers contain obstacles")]
|
||||
[LayerMask]
|
||||
[SerializeField] private LayerMask playerObstacleLayerMask = -1;
|
||||
|
||||
[Tooltip("Whether to block player input during damage immunity period")]
|
||||
[SerializeField] private bool blockInputDuringImmunity = true;
|
||||
|
||||
[Tooltip("Type of bump response: 0=Impulse, 1=SmoothToCenter")]
|
||||
[SerializeField] private 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
|
||||
public bool BubbleUseObjectPooling => bubbleUseObjectPooling;
|
||||
public int BubbleInitialPoolSize => bubbleInitialPoolSize;
|
||||
@@ -86,11 +150,37 @@ namespace AppleHills.Core.Settings
|
||||
|
||||
// Obstacle properties access
|
||||
public int ObstacleLayer => obstacleLayer;
|
||||
public LayerMask ObstacleTileLayerMask => obstacleTileLayerMask;
|
||||
public bool ObstacleUseObjectPooling => obstacleUseObjectPooling;
|
||||
public int ObstacleMaxPerPrefabPoolSize => obstacleMaxPerPrefabPoolSize;
|
||||
public int ObstacleTotalMaxPoolSize => obstacleTotalMaxPoolSize;
|
||||
|
||||
// Trench Tile System properties
|
||||
public int TrenchTileLayer => trenchTileLayer;
|
||||
public bool TrenchTileUseObjectPooling => trenchTileUseObjectPooling;
|
||||
public int TrenchTileMaxPerPrefabPoolSize => trenchTileMaxPerPrefabPoolSize;
|
||||
public int TrenchTileTotalMaxPoolSize => trenchTileTotalMaxPoolSize;
|
||||
|
||||
// Player Blink Behavior properties
|
||||
public Color PlayerBlinkDamageColor => playerBlinkDamageColor;
|
||||
public float PlayerBlinkRate => playerBlinkRate;
|
||||
public float PlayerDamageColorAlpha => playerDamageColorAlpha;
|
||||
|
||||
// Player Wobble Behavior properties
|
||||
public float PlayerWobbleFrequency => playerWobbleFrequency;
|
||||
public float PlayerBaseWobbleAmplitude => playerBaseWobbleAmplitude;
|
||||
public float PlayerSpeedToAmplitude => playerSpeedToAmplitude;
|
||||
public float PlayerMaxRotationLimit => playerMaxRotationLimit;
|
||||
public float PlayerVerticalFrequency => playerVerticalFrequency;
|
||||
public float PlayerVerticalAmplitude => playerVerticalAmplitude;
|
||||
public float PlayerVelocitySmoothing => playerVelocitySmoothing;
|
||||
public float PlayerRotationSmoothing => playerRotationSmoothing;
|
||||
|
||||
// Collision Settings properties
|
||||
public LayerMask PlayerObstacleLayerMask => playerObstacleLayerMask;
|
||||
public bool BlockInputDuringImmunity => blockInputDuringImmunity;
|
||||
public int BumpMode => bumpMode;
|
||||
public AnimationCurve BumpCurve => bumpCurve;
|
||||
|
||||
public override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
@@ -122,6 +212,26 @@ namespace AppleHills.Core.Settings
|
||||
// Validate obstacle settings
|
||||
obstacleMaxPerPrefabPoolSize = Mathf.Max(1, obstacleMaxPerPrefabPoolSize);
|
||||
obstacleTotalMaxPoolSize = Mathf.Max(obstacleMaxPerPrefabPoolSize, obstacleTotalMaxPoolSize);
|
||||
|
||||
// Validate Trench Tile settings
|
||||
trenchTileMaxPerPrefabPoolSize = Mathf.Max(1, trenchTileMaxPerPrefabPoolSize);
|
||||
trenchTileTotalMaxPoolSize = Mathf.Max(trenchTileMaxPerPrefabPoolSize, trenchTileTotalMaxPoolSize);
|
||||
|
||||
// Validate Player Blink settings
|
||||
playerBlinkRate = Mathf.Max(0.01f, playerBlinkRate);
|
||||
playerDamageColorAlpha = Mathf.Clamp01(playerDamageColorAlpha);
|
||||
|
||||
// Validate Player Wobble settings
|
||||
playerWobbleFrequency = Mathf.Max(0.01f, playerWobbleFrequency);
|
||||
playerBaseWobbleAmplitude = Mathf.Max(0f, playerBaseWobbleAmplitude);
|
||||
playerMaxRotationLimit = Mathf.Max(0f, playerMaxRotationLimit);
|
||||
playerVerticalFrequency = Mathf.Max(0.01f, playerVerticalFrequency);
|
||||
playerVerticalAmplitude = Mathf.Max(0f, playerVerticalAmplitude);
|
||||
playerVelocitySmoothing = Mathf.Max(0.1f, playerVelocitySmoothing);
|
||||
playerRotationSmoothing = Mathf.Max(0.1f, playerRotationSmoothing);
|
||||
|
||||
// Validate Collision settings
|
||||
bumpMode = Mathf.Clamp(bumpMode, 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,19 @@ namespace AppleHills.Core.Settings
|
||||
[Tooltip("Maximum movement speed for spawned obstacles")]
|
||||
[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
|
||||
public float EndlessDescenderLerpSpeed => endlessDescenderLerpSpeed;
|
||||
public float EndlessDescenderMaxOffset => endlessDescenderMaxOffset;
|
||||
@@ -149,6 +162,12 @@ namespace AppleHills.Core.Settings
|
||||
public float EndlessDescenderObstacleMinMoveSpeed => endlessDescenderObstacleMinMoveSpeed;
|
||||
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()
|
||||
{
|
||||
base.OnValidate();
|
||||
@@ -205,6 +224,11 @@ namespace AppleHills.Core.Settings
|
||||
endlessDescenderObstacleSpawnCollisionRadius = Mathf.Max(0.1f, endlessDescenderObstacleSpawnCollisionRadius);
|
||||
endlessDescenderObstacleMinMoveSpeed = Mathf.Max(0.1f, endlessDescenderObstacleMinMoveSpeed);
|
||||
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 EndlessDescenderObstacleMinMoveSpeed { 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>
|
||||
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)
|
||||
{
|
||||
// Mark the obstacle as having dealt damage to prevent multiple hits
|
||||
@@ -21,21 +39,20 @@ namespace Minigames.DivingForPictures
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to prevent input blocking during damage immunity
|
||||
/// Since obstacles pass through the player, we don't want to block input
|
||||
/// Handler for immunity started event - replaces OnImmunityStart method
|
||||
/// </summary>
|
||||
protected override void OnImmunityStart()
|
||||
private void HandleImmunityStarted()
|
||||
{
|
||||
Debug.Log($"[ObstacleCollision] Damage immunity started for {damageImmunityDuration} seconds");
|
||||
Debug.Log($"[ObstacleCollision] Damage immunity started for {_gameSettings.EndlessDescenderDamageImmunityDuration} seconds");
|
||||
|
||||
// Don't block input for obstacle damage - let player keep moving
|
||||
// The shared immunity system will handle the collision prevention
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to handle immunity end
|
||||
/// Handler for immunity ended event - replaces OnImmunityEnd method
|
||||
/// </summary>
|
||||
protected override void OnImmunityEnd()
|
||||
private void HandleImmunityEnded()
|
||||
{
|
||||
Debug.Log($"[ObstacleCollision] Damage immunity ended");
|
||||
// No special handling needed - shared immunity system handles collider re-enabling
|
||||
|
||||
@@ -298,8 +298,9 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
private bool IsValidSpawnPosition(Vector3 position)
|
||||
{
|
||||
// Use OverlapCircle to check for collisions with tiles
|
||||
Collider2D collision = Physics2D.OverlapCircle(position, _settings.EndlessDescenderObstacleSpawnCollisionRadius, _devSettings.ObstacleTileLayerMask);
|
||||
// Use OverlapCircle to check for collisions with tiles using just the layer
|
||||
// Convert the single layer to a layer mask inline (1 << layerNumber)
|
||||
Collider2D collision = Physics2D.OverlapCircle(position, _settings.EndlessDescenderObstacleSpawnCollisionRadius, 1 << _devSettings.TrenchTileLayer);
|
||||
return collision == null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace Minigames.DivingForPictures
|
||||
{
|
||||
@@ -9,28 +10,27 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
public class PlayerBlinkBehavior : MonoBehaviour
|
||||
{
|
||||
[Header("Blink Settings")]
|
||||
[Tooltip("Color to blink to when taking damage (typically red for damage indication)")]
|
||||
[SerializeField] private Color damageBlinkColor = Color.red;
|
||||
|
||||
[Tooltip("How fast to blink between normal and damage colors (seconds between color changes)")]
|
||||
[SerializeField] private float blinkRate = 0.15f;
|
||||
|
||||
[Tooltip("Alpha value for the damage color (0 = transparent, 1 = opaque)")]
|
||||
[Range(0f, 1f)]
|
||||
[SerializeField] private float damageColorAlpha = 0.7f;
|
||||
|
||||
[Header("References")]
|
||||
[Tooltip("The SpriteRenderer to apply blink effects to (auto-assigned if empty)")]
|
||||
[SerializeField] private SpriteRenderer targetSpriteRenderer;
|
||||
|
||||
// Developer settings reference
|
||||
private DivingDeveloperSettings _devSettings;
|
||||
|
||||
private bool _isBlinking;
|
||||
private bool _isShowingDamageColor;
|
||||
private Coroutine _blinkCoroutine;
|
||||
private Color _originalColor; // Missing field declaration
|
||||
private Color _originalColor;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Get developer settings
|
||||
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||
if (_devSettings == null)
|
||||
{
|
||||
Debug.LogError("[PlayerBlinkBehavior] Failed to load developer settings!");
|
||||
}
|
||||
|
||||
// Auto-assign sprite renderer if not set
|
||||
if (targetSpriteRenderer == null)
|
||||
{
|
||||
@@ -51,192 +51,101 @@ namespace Minigames.DivingForPictures
|
||||
return;
|
||||
}
|
||||
|
||||
// Store original color
|
||||
// Store the original color
|
||||
_originalColor = targetSpriteRenderer.color;
|
||||
|
||||
// Ensure damage color has the correct alpha
|
||||
damageBlinkColor.a = damageColorAlpha;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// Subscribe to immunity events (renamed from damage events)
|
||||
PlayerCollisionBehavior.OnImmunityStarted += StartBlinking;
|
||||
PlayerCollisionBehavior.OnImmunityEnded += StopBlinking;
|
||||
// Subscribe to damage events
|
||||
PlayerCollisionBehavior.OnDamageTaken += StartBlinkEffect;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// Unsubscribe from immunity events
|
||||
PlayerCollisionBehavior.OnImmunityStarted -= StartBlinking;
|
||||
PlayerCollisionBehavior.OnImmunityEnded -= StopBlinking;
|
||||
// Unsubscribe to prevent memory leaks
|
||||
PlayerCollisionBehavior.OnDamageTaken -= StartBlinkEffect;
|
||||
|
||||
// Stop any ongoing blink effect
|
||||
if (_blinkCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_blinkCoroutine);
|
||||
_blinkCoroutine = null;
|
||||
}
|
||||
|
||||
// Restore original color
|
||||
RestoreOriginalColor();
|
||||
// Restore original color if disabled during blinking
|
||||
if (targetSpriteRenderer != null)
|
||||
{
|
||||
targetSpriteRenderer.color = _originalColor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the red blinking effect when damage begins
|
||||
/// Start the blinking effect coroutine
|
||||
/// </summary>
|
||||
private void StartBlinking()
|
||||
private void StartBlinkEffect()
|
||||
{
|
||||
if (targetSpriteRenderer == null) return;
|
||||
if (targetSpriteRenderer == null || _devSettings == null) return;
|
||||
|
||||
Debug.Log("[PlayerBlinkBehavior] Starting damage blink effect");
|
||||
|
||||
// Stop any existing blink coroutine
|
||||
if (_blinkCoroutine != null)
|
||||
// If already blinking, stop the current coroutine
|
||||
if (_isBlinking && _blinkCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_blinkCoroutine);
|
||||
}
|
||||
|
||||
_isBlinking = true;
|
||||
_isShowingDamageColor = false;
|
||||
|
||||
// Start the blink coroutine
|
||||
// Start a new blink coroutine
|
||||
_blinkCoroutine = StartCoroutine(BlinkCoroutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the blinking effect when damage ends
|
||||
/// </summary>
|
||||
private void StopBlinking()
|
||||
{
|
||||
Debug.Log("[PlayerBlinkBehavior] Stopping damage blink effect");
|
||||
|
||||
_isBlinking = false;
|
||||
|
||||
// Stop the blink coroutine
|
||||
if (_blinkCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_blinkCoroutine);
|
||||
_blinkCoroutine = null;
|
||||
}
|
||||
|
||||
// Restore original color
|
||||
RestoreOriginalColor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that handles the blinking animation
|
||||
/// Coroutine that handles the blink effect timing
|
||||
/// </summary>
|
||||
private IEnumerator BlinkCoroutine()
|
||||
{
|
||||
while (_isBlinking && targetSpriteRenderer != null)
|
||||
{
|
||||
// Toggle between original and damage colors
|
||||
if (_isShowingDamageColor)
|
||||
{
|
||||
targetSpriteRenderer.color = _originalColor;
|
||||
_isShowingDamageColor = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetSpriteRenderer.color = damageBlinkColor;
|
||||
_isShowingDamageColor = true;
|
||||
}
|
||||
|
||||
// Wait for blink interval
|
||||
yield return new WaitForSeconds(blinkRate);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the sprite renderer to its original color
|
||||
/// </summary>
|
||||
private void RestoreOriginalColor()
|
||||
{
|
||||
if (targetSpriteRenderer != null)
|
||||
{
|
||||
targetSpriteRenderer.color = _originalColor;
|
||||
_isShowingDamageColor = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the original color reference (useful if sprite color changes during gameplay)
|
||||
/// </summary>
|
||||
public void UpdateOriginalColor()
|
||||
{
|
||||
if (targetSpriteRenderer != null && !_isBlinking)
|
||||
{
|
||||
_originalColor = targetSpriteRenderer.color;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to change blink color at runtime
|
||||
/// </summary>
|
||||
public void SetDamageBlinkColor(Color newColor)
|
||||
{
|
||||
damageBlinkColor = newColor;
|
||||
damageBlinkColor.a = damageColorAlpha;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to change blink rate at runtime
|
||||
/// </summary>
|
||||
public void SetBlinkRate(float rate)
|
||||
{
|
||||
blinkRate = rate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if currently blinking
|
||||
/// </summary>
|
||||
public bool IsBlinking => _isBlinking;
|
||||
|
||||
/// <summary>
|
||||
/// Manually trigger blink effect (useful for testing or other damage sources)
|
||||
/// </summary>
|
||||
public void TriggerBlink(float duration)
|
||||
{
|
||||
if (_blinkCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_blinkCoroutine);
|
||||
}
|
||||
|
||||
StartCoroutine(ManualBlinkCoroutine(duration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine for manually triggered blink effects
|
||||
/// </summary>
|
||||
private IEnumerator ManualBlinkCoroutine(float duration)
|
||||
{
|
||||
_isBlinking = true;
|
||||
_isShowingDamageColor = false;
|
||||
|
||||
float elapsed = 0f;
|
||||
// Create damage color with configured alpha
|
||||
Color damageColor = _devSettings.PlayerBlinkDamageColor;
|
||||
damageColor.a = _devSettings.PlayerDamageColorAlpha;
|
||||
|
||||
while (elapsed < duration && targetSpriteRenderer != null)
|
||||
// Wait for immunity to end
|
||||
PlayerCollisionBehavior.OnImmunityEnded += StopBlinking;
|
||||
|
||||
// Blink while immunity is active
|
||||
while (_isBlinking)
|
||||
{
|
||||
// Toggle between original and damage colors
|
||||
// Toggle between original and damage color
|
||||
if (_isShowingDamageColor)
|
||||
{
|
||||
targetSpriteRenderer.color = _originalColor;
|
||||
_isShowingDamageColor = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetSpriteRenderer.color = damageBlinkColor;
|
||||
_isShowingDamageColor = true;
|
||||
targetSpriteRenderer.color = damageColor;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(blinkRate);
|
||||
elapsed += blinkRate;
|
||||
_isShowingDamageColor = !_isShowingDamageColor;
|
||||
|
||||
// Wait for next blink
|
||||
yield return new WaitForSeconds(_devSettings.PlayerBlinkRate);
|
||||
}
|
||||
|
||||
// Ensure we end with original color
|
||||
RestoreOriginalColor();
|
||||
// Restore original color when done blinking
|
||||
targetSpriteRenderer.color = _originalColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when immunity ends to stop the blinking effect
|
||||
/// </summary>
|
||||
private void StopBlinking()
|
||||
{
|
||||
// Unsubscribe from the event to avoid memory leaks
|
||||
PlayerCollisionBehavior.OnImmunityEnded -= StopBlinking;
|
||||
|
||||
_isBlinking = false;
|
||||
|
||||
// No need to stop the coroutine, it will exit naturally
|
||||
// This avoids race conditions if immunity ends during a blink cycle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace Minigames.DivingForPictures
|
||||
{
|
||||
@@ -10,17 +11,6 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
public abstract class PlayerCollisionBehavior : MonoBehaviour
|
||||
{
|
||||
[Header("Collision Settings")]
|
||||
[Tooltip("Duration in seconds of damage immunity after being hit")]
|
||||
[SerializeField] protected float damageImmunityDuration = 1.0f;
|
||||
|
||||
[Tooltip("Layer mask for obstacle detection - configure which layers contain obstacles")]
|
||||
[SerializeField] protected LayerMask obstacleLayerMask = -1;
|
||||
|
||||
[Header("Input Blocking")]
|
||||
[Tooltip("Whether to block player input during damage immunity period")]
|
||||
[SerializeField] protected bool blockInputDuringImmunity;
|
||||
|
||||
[Header("References")]
|
||||
[Tooltip("The player character GameObject (auto-assigned if empty)")]
|
||||
[SerializeField] protected GameObject playerCharacter;
|
||||
@@ -28,6 +18,10 @@ namespace Minigames.DivingForPictures
|
||||
[Tooltip("Reference to the PlayerController component (auto-assigned if empty)")]
|
||||
[SerializeField] protected PlayerController playerController;
|
||||
|
||||
// Settings references
|
||||
protected IDivingMinigameSettings _gameSettings;
|
||||
protected DivingDeveloperSettings _devSettings;
|
||||
|
||||
// Static shared immunity state across all collision behaviors
|
||||
private static bool _isGloballyImmune;
|
||||
private static Coroutine _globalImmunityCoroutine;
|
||||
@@ -67,305 +61,152 @@ namespace Minigames.DivingForPictures
|
||||
OnDamageTaken?.Invoke();
|
||||
}
|
||||
|
||||
protected bool wasInputBlocked;
|
||||
|
||||
protected virtual void Awake()
|
||||
/// <summary>
|
||||
/// Called when the component is enabled
|
||||
/// </summary>
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
// Auto-assign if not set in inspector
|
||||
_allInstances.Add(this);
|
||||
|
||||
// Auto-assign references if needed
|
||||
if (playerCharacter == null)
|
||||
playerCharacter = gameObject;
|
||||
|
||||
if (playerController == null)
|
||||
playerController = GetComponent<PlayerController>();
|
||||
|
||||
// Set up shared collider reference (only once)
|
||||
// Initialize the shared player collider if not already set
|
||||
if (_sharedPlayerCollider == null)
|
||||
{
|
||||
_sharedPlayerCollider = GetComponent<Collider2D>();
|
||||
if (_sharedPlayerCollider == null)
|
||||
{
|
||||
_sharedPlayerCollider = GetComponentInChildren<Collider2D>();
|
||||
if (_sharedPlayerCollider != null)
|
||||
{
|
||||
Debug.Log($"[PlayerCollisionBehavior] Found collider on child object: {_sharedPlayerCollider.gameObject.name}");
|
||||
}
|
||||
}
|
||||
|
||||
if (_sharedPlayerCollider == null)
|
||||
{
|
||||
Debug.LogError($"[PlayerCollisionBehavior] No Collider2D found on this GameObject or its children!");
|
||||
Debug.LogError("[PlayerCollisionBehavior] No Collider2D found on this GameObject!");
|
||||
}
|
||||
}
|
||||
|
||||
// Set up coroutine runner (use first instance)
|
||||
// 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!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
_coroutineRunner = this;
|
||||
}
|
||||
|
||||
// Register this instance
|
||||
_allInstances.Add(this);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Unregister this instance
|
||||
_allInstances.Remove(this);
|
||||
|
||||
// Clean up static references if this was the coroutine runner
|
||||
if (_coroutineRunner == this)
|
||||
// Block input if configured
|
||||
if (_devSettings.BlockInputDuringImmunity && playerController != null)
|
||||
{
|
||||
if (_globalImmunityCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_globalImmunityCoroutine);
|
||||
_globalImmunityCoroutine = null;
|
||||
}
|
||||
_coroutineRunner = null;
|
||||
|
||||
// Find a new coroutine runner if there are other instances
|
||||
foreach (var instance in _allInstances)
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
_coroutineRunner = instance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
// Notify player controller to block input
|
||||
}
|
||||
|
||||
// Stop any existing immunity coroutine
|
||||
// Trigger event for all listeners
|
||||
OnImmunityStarted?.Invoke();
|
||||
|
||||
// Stop existing coroutine if one is running
|
||||
if (_globalImmunityCoroutine != null && _coroutineRunner != null)
|
||||
{
|
||||
_coroutineRunner.StopCoroutine(_globalImmunityCoroutine);
|
||||
}
|
||||
|
||||
// Start new immunity coroutine
|
||||
if (_coroutineRunner != null)
|
||||
{
|
||||
_globalImmunityCoroutine = _coroutineRunner.StartCoroutine(ImmunityCoroutine());
|
||||
}
|
||||
|
||||
// Notify all instances about immunity start
|
||||
foreach (var instance in _allInstances)
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
instance.OnImmunityStart();
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast immunity start event
|
||||
OnImmunityStarted?.Invoke();
|
||||
// Start immunity timer coroutine on this instance
|
||||
_globalImmunityCoroutine = StartCoroutine(ImmunityTimerCoroutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that handles the immunity timer
|
||||
/// Coroutine to handle the immunity duration timer
|
||||
/// </summary>
|
||||
private IEnumerator ImmunityCoroutine()
|
||||
private IEnumerator ImmunityTimerCoroutine()
|
||||
{
|
||||
Debug.Log($"[PlayerCollisionBehavior] Starting immunity coroutine for {damageImmunityDuration} seconds");
|
||||
// Wait for the immunity duration
|
||||
yield return new WaitForSeconds(_gameSettings.EndlessDescenderDamageImmunityDuration);
|
||||
|
||||
yield return new WaitForSeconds(damageImmunityDuration);
|
||||
|
||||
Debug.Log($"[PlayerCollisionBehavior] Immunity period ended");
|
||||
|
||||
// End immunity
|
||||
// Reset immunity state
|
||||
_isGloballyImmune = false;
|
||||
_globalImmunityCoroutine = null;
|
||||
|
||||
// Re-enable the shared collider
|
||||
if (_sharedPlayerCollider != null)
|
||||
{
|
||||
_sharedPlayerCollider.enabled = true;
|
||||
}
|
||||
|
||||
// Notify all instances about immunity end
|
||||
foreach (var instance in _allInstances)
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
instance.OnImmunityEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast immunity end event
|
||||
// Trigger event for all listeners
|
||||
OnImmunityEnded?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to implement specific collision response behavior
|
||||
/// </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 System.Collections;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace Minigames.DivingForPictures
|
||||
{
|
||||
@@ -9,286 +10,159 @@ namespace Minigames.DivingForPictures
|
||||
/// </summary>
|
||||
public class TileBumpCollision : PlayerCollisionBehavior
|
||||
{
|
||||
[Header("Bump Settings")]
|
||||
[Tooltip("Type of bump response: Impulse pushes with force, SmoothToCenter moves directly to center")]
|
||||
[SerializeField] private BumpMode bumpMode = BumpMode.Impulse;
|
||||
|
||||
[Tooltip("Force strength for impulse bumps - higher values push further toward center")]
|
||||
[SerializeField] private float bumpForce = 5.0f;
|
||||
|
||||
[Tooltip("Speed for smooth movement to center (units per second)")]
|
||||
[SerializeField] private float smoothMoveSpeed = 8.0f;
|
||||
|
||||
[Tooltip("Animation curve controlling bump movement over time")]
|
||||
[SerializeField] private AnimationCurve bumpCurve = new AnimationCurve(new Keyframe(0f, 0f, 0f, 2f), new Keyframe(1f, 1f, 0f, 0f));
|
||||
|
||||
[Tooltip("Whether to block player input during bump movement")]
|
||||
[SerializeField] private bool blockInputDuringBump = true;
|
||||
|
||||
public enum BumpMode
|
||||
{
|
||||
Impulse, // Force-based push toward center (distance depends on force)
|
||||
SmoothToCenter // Smooth movement to center with configurable speed
|
||||
}
|
||||
|
||||
private bool _isBumping;
|
||||
private Coroutine _bumpCoroutine;
|
||||
private bool _bumpInputBlocked; // Tracks bump-specific input blocking
|
||||
|
||||
protected override void HandleCollisionResponse(Collider2D obstacle)
|
||||
{
|
||||
switch (bumpMode)
|
||||
// Use bump mode from developer settings
|
||||
switch (_devSettings.BumpMode)
|
||||
{
|
||||
case BumpMode.Impulse:
|
||||
case 0: // Impulse mode
|
||||
StartImpulseBump();
|
||||
break;
|
||||
|
||||
case BumpMode.SmoothToCenter:
|
||||
case 1: // SmoothToCenter mode
|
||||
StartSmoothMoveToCenter();
|
||||
break;
|
||||
}
|
||||
|
||||
Debug.Log($"[TileBumpCollision] Collision handled with {bumpMode} mode");
|
||||
Debug.Log($"[TileBumpCollision] Collision handled with {(_devSettings.BumpMode == 0 ? "Impulse" : "SmoothToCenter")} mode");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts an impulse bump toward the center with force-based distance
|
||||
/// Applies an impulse force toward center
|
||||
/// </summary>
|
||||
private void StartImpulseBump()
|
||||
{
|
||||
if (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;
|
||||
}
|
||||
if (_isBumping || playerCharacter == null)
|
||||
return;
|
||||
|
||||
_isBumping = true;
|
||||
|
||||
// Block player input if enabled (use bump-specific blocking)
|
||||
if (blockInputDuringBump && playerController != null && playerController.enabled)
|
||||
// Block input during bump if configured
|
||||
if (_gameSettings.EndlessDescenderBlockInputDuringBump && playerController != null)
|
||||
{
|
||||
playerController.enabled = false;
|
||||
_bumpInputBlocked = true;
|
||||
Debug.Log("[TileBumpCollision] Player input blocked during bump");
|
||||
// TODO: Implement input blocking
|
||||
}
|
||||
|
||||
// Start bump coroutine
|
||||
_bumpCoroutine = StartCoroutine(BumpCoroutine(startX, targetX, duration));
|
||||
// Calculate direction to center (X = 0)
|
||||
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>
|
||||
/// Coroutine that handles the bump movement over time
|
||||
/// Smoothly moves the player to center
|
||||
/// </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 bumpDuration = 0.5f; // Fixed duration for impulse
|
||||
Vector3 startPosition = playerCharacter.transform.position;
|
||||
|
||||
while (elapsedTime < duration)
|
||||
while (elapsedTime < bumpDuration)
|
||||
{
|
||||
elapsedTime += Time.deltaTime;
|
||||
|
||||
// Calculate progress and apply curve
|
||||
float progress = elapsedTime / duration;
|
||||
float curveValue = bumpCurve.Evaluate(progress);
|
||||
// Use evaluation time from curve for non-linear movement
|
||||
float t = elapsedTime / bumpDuration;
|
||||
float curveValue = _devSettings.BumpCurve.Evaluate(t);
|
||||
|
||||
// Interpolate position
|
||||
float currentX = Mathf.Lerp(startX, targetX, curveValue);
|
||||
// Calculate movement distance based on force and curve
|
||||
float distance = _gameSettings.EndlessDescenderBumpForce * curveValue * Time.deltaTime;
|
||||
|
||||
// Apply the position to the player character
|
||||
if (playerCharacter != null)
|
||||
{
|
||||
Vector3 currentPos = playerCharacter.transform.position;
|
||||
playerCharacter.transform.position = new Vector3(currentX, currentPos.y, currentPos.z);
|
||||
}
|
||||
// Move the player toward center
|
||||
Vector3 newPosition = playerCharacter.transform.position;
|
||||
newPosition.x += direction * distance;
|
||||
|
||||
// Clamp to valid range
|
||||
newPosition.x = Mathf.Clamp(newPosition.x, _gameSettings.EndlessDescenderClampXMin, _gameSettings.EndlessDescenderClampXMax);
|
||||
|
||||
// Apply the position
|
||||
playerCharacter.transform.position = newPosition;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Ensure we end exactly at target
|
||||
if (playerCharacter != null)
|
||||
{
|
||||
Vector3 currentPos = playerCharacter.transform.position;
|
||||
playerCharacter.transform.position = new Vector3(targetX, currentPos.y, currentPos.z);
|
||||
}
|
||||
|
||||
// Bump finished
|
||||
// Finish bump
|
||||
_isBumping = false;
|
||||
_bumpInputBlocked = false;
|
||||
_bumpCoroutine = null;
|
||||
}
|
||||
|
||||
if (_bumpInputBlocked)
|
||||
/// <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);
|
||||
|
||||
// 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
|
||||
if (_isBumping && _bumpCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_bumpCoroutine);
|
||||
_bumpCoroutine = null;
|
||||
_isBumping = false;
|
||||
|
||||
if (_bumpInputBlocked)
|
||||
{
|
||||
RestoreBumpInput();
|
||||
}
|
||||
|
||||
Debug.Log("[TileBumpCollision] Bump interrupted by immunity end");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when component is destroyed - cleanup coroutines
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_bumpCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_bumpCoroutine);
|
||||
_bumpCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to change bump mode at runtime
|
||||
/// </summary>
|
||||
public void SetBumpMode(BumpMode mode)
|
||||
{
|
||||
bumpMode = mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to change bump force at runtime
|
||||
/// </summary>
|
||||
public void SetBumpForce(float force)
|
||||
{
|
||||
bumpForce = force;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to change smooth move speed at runtime
|
||||
/// </summary>
|
||||
public void SetSmoothMoveSpeed(float speed)
|
||||
{
|
||||
smoothMoveSpeed = speed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if player is currently being bumped
|
||||
/// </summary>
|
||||
public bool IsBumping => _isBumping;
|
||||
|
||||
/// <summary>
|
||||
/// Check if input is currently blocked by bump
|
||||
/// </summary>
|
||||
public bool IsBumpInputBlocked => _bumpInputBlocked;
|
||||
|
||||
/// <summary>
|
||||
/// Public method to manually stop bump movement
|
||||
/// </summary>
|
||||
public void StopBump()
|
||||
{
|
||||
if (_isBumping && _bumpCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_bumpCoroutine);
|
||||
_bumpCoroutine = null;
|
||||
_isBumping = false;
|
||||
|
||||
if (_bumpInputBlocked)
|
||||
{
|
||||
RestoreBumpInput();
|
||||
}
|
||||
|
||||
Debug.Log("[TileBumpCollision] Bump manually stopped");
|
||||
}
|
||||
// Finish bump
|
||||
_isBumping = false;
|
||||
_bumpInputBlocked = false;
|
||||
_bumpCoroutine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,13 @@
|
||||
using UnityEngine;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a wobble (rocking and vertical movement) effect to the object, based on speed and time.
|
||||
/// </summary>
|
||||
public class WobbleBehavior : MonoBehaviour
|
||||
{
|
||||
[Header("Wobble Settings")]
|
||||
public float wobbleFrequency = 1.5f;
|
||||
/// <summary>
|
||||
/// Max degrees from horizontal.
|
||||
/// </summary>
|
||||
public float baseWobbleAmplitude = 8f;
|
||||
/// <summary>
|
||||
/// How much speed affects amplitude.
|
||||
/// </summary>
|
||||
public float speedToAmplitude = 2f;
|
||||
/// <summary>
|
||||
/// Maximum allowed rotation in degrees.
|
||||
/// </summary>
|
||||
public float maxRotationLimit = 45f;
|
||||
|
||||
[Header("Vertical Movement Settings")]
|
||||
public float verticalFrequency = 0.5f;
|
||||
/// <summary>
|
||||
/// How far the object moves up/down.
|
||||
/// </summary>
|
||||
public float verticalAmplitude = 0.5f;
|
||||
|
||||
[Header("Smoothing Settings")]
|
||||
public float velocitySmoothing = 10f;
|
||||
/// <summary>
|
||||
/// How quickly rotation is smoothed.
|
||||
/// </summary>
|
||||
public float rotationSmoothing = 10f;
|
||||
// Developer settings reference
|
||||
private DivingDeveloperSettings _devSettings;
|
||||
|
||||
private Vector3 lastPosition;
|
||||
private float wobbleTime;
|
||||
@@ -46,47 +21,61 @@ public class WobbleBehavior : MonoBehaviour
|
||||
/// The current velocity of the object (horizontal only).
|
||||
/// </summary>
|
||||
public float Velocity => velocity;
|
||||
|
||||
/// <summary>
|
||||
/// The current vertical offset due to wobble.
|
||||
/// </summary>
|
||||
public float VerticalOffset => verticalOffset;
|
||||
|
||||
void Start()
|
||||
private void Awake()
|
||||
{
|
||||
// Get developer settings
|
||||
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||
if (_devSettings == null)
|
||||
{
|
||||
Debug.LogError("[WobbleBehavior] Failed to load developer settings!");
|
||||
}
|
||||
|
||||
// Initialize
|
||||
lastPosition = transform.position;
|
||||
smoothedVelocity = 0f;
|
||||
smoothedAngle = 0f;
|
||||
basePosition = transform.position;
|
||||
}
|
||||
|
||||
void Update()
|
||||
private void Update()
|
||||
{
|
||||
// Calculate movement speed (exclude vertical wobble from velocity calculation)
|
||||
Vector3 horizontalPosition = transform.position;
|
||||
horizontalPosition.y = 0f; // Ignore Y for velocity if only horizontal movement matters
|
||||
Vector3 horizontalLastPosition = lastPosition;
|
||||
horizontalLastPosition.y = 0f;
|
||||
velocity = (horizontalPosition - horizontalLastPosition).magnitude / Time.deltaTime;
|
||||
if (_devSettings == null) return;
|
||||
|
||||
// Calculate velocity based on position change
|
||||
Vector3 positionDelta = transform.position - lastPosition;
|
||||
velocity = positionDelta.x / Time.deltaTime;
|
||||
lastPosition = transform.position;
|
||||
basePosition = transform.position;
|
||||
|
||||
// Smooth velocity to prevent jitter
|
||||
smoothedVelocity = Mathf.Lerp(smoothedVelocity, velocity, velocitySmoothing * Time.deltaTime);
|
||||
// Smooth velocity changes
|
||||
smoothedVelocity = Mathf.Lerp(smoothedVelocity, velocity, Time.deltaTime * _devSettings.PlayerVelocitySmoothing);
|
||||
|
||||
// Wobble amplitude scales with smoothed speed, but always has a base value
|
||||
float amplitude = baseWobbleAmplitude + smoothedVelocity * speedToAmplitude;
|
||||
amplitude = Mathf.Min(amplitude, maxRotationLimit); // Prevent amplitude from exceeding limit
|
||||
// Calculate wobble rotation based on velocity and time
|
||||
wobbleTime += Time.deltaTime * _devSettings.PlayerWobbleFrequency;
|
||||
float rawWobble = Mathf.Sin(wobbleTime);
|
||||
|
||||
// Oscillate around horizontal (0 degrees)
|
||||
wobbleTime += Time.deltaTime * wobbleFrequency;
|
||||
float targetAngle = Mathf.Sin(wobbleTime) * amplitude;
|
||||
targetAngle = Mathf.Clamp(targetAngle, -maxRotationLimit, maxRotationLimit);
|
||||
// Calculate wobble amplitude based on velocity
|
||||
float velocityFactor = Mathf.Abs(smoothedVelocity) * _devSettings.PlayerSpeedToAmplitude;
|
||||
float wobbleAmplitude = _devSettings.PlayerBaseWobbleAmplitude + velocityFactor;
|
||||
|
||||
// Smooth the rotation angle
|
||||
smoothedAngle = Mathf.Lerp(smoothedAngle, targetAngle, rotationSmoothing * Time.deltaTime);
|
||||
// Clamp to maximum rotation limit
|
||||
wobbleAmplitude = Mathf.Min(wobbleAmplitude, _devSettings.PlayerMaxRotationLimit);
|
||||
|
||||
// Apply rotation (Z axis for 2D)
|
||||
transform.localRotation = Quaternion.Euler(0f, 0f, smoothedAngle);
|
||||
// Calculate target angle
|
||||
float targetAngle = rawWobble * wobbleAmplitude;
|
||||
|
||||
// Calculate vertical up/down movement (wave riding) only once
|
||||
verticalOffset = Mathf.Sin(wobbleTime * verticalFrequency) * verticalAmplitude;
|
||||
// Smooth angle changes
|
||||
smoothedAngle = Mathf.Lerp(smoothedAngle, targetAngle, Time.deltaTime * _devSettings.PlayerRotationSmoothing);
|
||||
|
||||
// Apply rotation
|
||||
transform.rotation = Quaternion.Euler(0f, 0f, smoothedAngle);
|
||||
|
||||
// Calculate vertical bobbing
|
||||
float time = Time.time * _devSettings.PlayerVerticalFrequency;
|
||||
verticalOffset = Mathf.Sin(time) * _devSettings.PlayerVerticalAmplitude;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,6 @@ namespace Minigames.DivingForPictures
|
||||
[Tooltip("List of possible trench tile prefabs.")]
|
||||
[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")]
|
||||
[FormerlySerializedAs("OnTileSpawned")]
|
||||
public UnityEvent<GameObject> onTileSpawned;
|
||||
@@ -29,8 +24,9 @@ namespace Minigames.DivingForPictures
|
||||
[FormerlySerializedAs("OnTileDestroyed")]
|
||||
public UnityEvent<GameObject> onTileDestroyed;
|
||||
|
||||
// Settings reference
|
||||
// Settings references
|
||||
private IDivingMinigameSettings _settings;
|
||||
private DivingDeveloperSettings _devSettings;
|
||||
|
||||
// Private fields
|
||||
private readonly Dictionary<GameObject, float> _tileHeights = new Dictionary<GameObject, float>();
|
||||
@@ -67,11 +63,18 @@ namespace Minigames.DivingForPictures
|
||||
|
||||
// Get settings from GameManager
|
||||
_settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
|
||||
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
||||
|
||||
if (_settings == null)
|
||||
{
|
||||
Debug.LogError("[TrenchTileSpawner] Failed to load diving minigame settings!");
|
||||
}
|
||||
|
||||
if (_devSettings == null)
|
||||
{
|
||||
Debug.LogError("[TrenchTileSpawner] Failed to load diving developer settings!");
|
||||
}
|
||||
|
||||
_baseMoveSpeed = _settings?.EndlessDescenderMoveSpeed ?? 3f; // Store the original base speed
|
||||
|
||||
// Calculate tile heights for each prefab
|
||||
@@ -80,7 +83,7 @@ namespace Minigames.DivingForPictures
|
||||
// Ensure all prefabs have Tile components
|
||||
ValidateTilePrefabs();
|
||||
|
||||
if (useObjectPooling)
|
||||
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling)
|
||||
{
|
||||
InitializeObjectPool();
|
||||
}
|
||||
@@ -196,9 +199,9 @@ namespace Minigames.DivingForPictures
|
||||
poolGO.transform.SetParent(transform);
|
||||
_tilePool = poolGO.AddComponent<TrenchTilePool>();
|
||||
|
||||
// Set up the pool configuration
|
||||
_tilePool.maxPerPrefabPoolSize = maxPerPrefabPoolSize;
|
||||
_tilePool.totalMaxPoolSize = totalMaxPoolSize;
|
||||
// Set up the pool configuration using developer settings
|
||||
_tilePool.maxPerPrefabPoolSize = _devSettings.TrenchTileMaxPerPrefabPoolSize;
|
||||
_tilePool.totalMaxPoolSize = _devSettings.TrenchTileTotalMaxPoolSize;
|
||||
|
||||
// Convert the GameObject list to a Tile list
|
||||
List<Tile> prefabTiles = new List<Tile>(tilePrefabs.Count);
|
||||
@@ -374,7 +377,7 @@ namespace Minigames.DivingForPictures
|
||||
_activeTiles.RemoveAt(0);
|
||||
onTileDestroyed?.Invoke(topTile);
|
||||
|
||||
if (useObjectPooling && _tilePool != null)
|
||||
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _tilePool != null)
|
||||
{
|
||||
// Find the prefab index for this tile
|
||||
int prefabIndex = GetPrefabIndex(topTile);
|
||||
@@ -505,7 +508,7 @@ namespace Minigames.DivingForPictures
|
||||
|
||||
GameObject tile;
|
||||
|
||||
if (useObjectPooling && _tilePool != null)
|
||||
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _tilePool != null)
|
||||
{
|
||||
tile = _tilePool.GetTile(prefabIndex);
|
||||
if (tile == null)
|
||||
|
||||
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
|
||||
- QuarryObstacle
|
||||
- QuarryMonster
|
||||
-
|
||||
- QuarryTrenchTile
|
||||
-
|
||||
-
|
||||
-
|
||||
|
||||
Reference in New Issue
Block a user