From 46755fecb34c38f145bb23deefdb0a06d1a9ecf3 Mon Sep 17 00:00:00 2001 From: tschesky Date: Sun, 21 Sep 2025 07:32:56 +0000 Subject: [PATCH] Populate minigame with obstacles and monster spawns (#5) - Simulated "fake" physics and collisions - Object pooling for tiles, obstacles and monster spawns - Base monster scoring with proximity triggers and depth multiplier Co-authored-by: AlexanderT Co-authored-by: Michal Pikulski Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/5 --- .gitignore | 2 + .../Utilities/SpriteColliderGenerator.cs | 92 ++++ .../DivingForPictures/FloatingObstacle.prefab | 154 ++++++ .../FloatingObstacle.prefab.meta | 7 + .../DivingForPictures/QuarryMonster.prefab | 2 +- .../Minigames/DivingForPictures/Tile1.prefab | 14 +- .../DivingForPictures/Tile1_flipped.prefab | 124 ++++- .../Minigames/DivingForPictures/Tile2.prefab | 131 ++++- .../DivingForPictures/Tile2_flipped.prefab | 131 ++++- .../Minigames/DivingForPictures/Tile3.prefab | 136 ++++- .../DivingForPictures/Tile3_flipped.prefab | 136 ++++- .../Scenes/MiniGames/DivingForPictures.unity | 254 ++++++++- .../Minigames/DivingForPictures/Bubble.cs | 139 ++++- .../DivingForPictures/FloatingObstacle.cs | 283 ++++++++++ .../FloatingObstacle.cs.meta | 3 + .../DivingForPictures/ObstacleCollision.cs | 44 ++ .../ObstacleCollision.cs.meta | 3 + .../DivingForPictures/ObstaclePool.cs | 54 ++ .../DivingForPictures/ObstaclePool.cs.meta | 3 + .../DivingForPictures/ObstacleSpawner.cs | 486 ++++++++++++++++++ .../DivingForPictures/ObstacleSpawner.cs.meta | 3 + .../DivingForPictures/PlayerBlinkBehavior.cs | 242 +++++++++ .../PlayerBlinkBehavior.cs.meta | 3 + .../PlayerCollisionBehavior.cs | 371 +++++++++++++ .../PlayerCollisionBehavior.cs.meta | 3 + .../DivingForPictures/PlayerController.cs | 8 +- .../DivingForPictures/TileBumpCollision.cs | 294 +++++++++++ .../TileBumpCollision.cs.meta | 3 + Assets/Scripts/Pooling/MultiPrefabPool.cs | 120 ++++- ProjectSettings/TagManager.asset | 4 +- 30 files changed, 3181 insertions(+), 68 deletions(-) create mode 100644 Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab create mode 100644 Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab.meta create mode 100644 Assets/Scripts/Minigames/DivingForPictures/FloatingObstacle.cs create mode 100644 Assets/Scripts/Minigames/DivingForPictures/FloatingObstacle.cs.meta create mode 100644 Assets/Scripts/Minigames/DivingForPictures/ObstacleCollision.cs create mode 100644 Assets/Scripts/Minigames/DivingForPictures/ObstacleCollision.cs.meta create mode 100644 Assets/Scripts/Minigames/DivingForPictures/ObstaclePool.cs create mode 100644 Assets/Scripts/Minigames/DivingForPictures/ObstaclePool.cs.meta create mode 100644 Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs create mode 100644 Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs.meta create mode 100644 Assets/Scripts/Minigames/DivingForPictures/PlayerBlinkBehavior.cs create mode 100644 Assets/Scripts/Minigames/DivingForPictures/PlayerBlinkBehavior.cs.meta create mode 100644 Assets/Scripts/Minigames/DivingForPictures/PlayerCollisionBehavior.cs create mode 100644 Assets/Scripts/Minigames/DivingForPictures/PlayerCollisionBehavior.cs.meta create mode 100644 Assets/Scripts/Minigames/DivingForPictures/TileBumpCollision.cs create mode 100644 Assets/Scripts/Minigames/DivingForPictures/TileBumpCollision.cs.meta diff --git a/.gitignore b/.gitignore index 6ad71155..6229a730 100644 --- a/.gitignore +++ b/.gitignore @@ -99,6 +99,8 @@ InitTestScene*.unity* # Auto-generated scenes by play mode tests /[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity* +.idea/.idea.AppleHillsProduction/.idea/indexLayout.xml .vscode/extensions.json .vscode/launch.json .vscode/settings.json +.idea/.idea.AppleHillsProduction/.idea/indexLayout.xml diff --git a/Assets/Editor/Utilities/SpriteColliderGenerator.cs b/Assets/Editor/Utilities/SpriteColliderGenerator.cs index 1d483390..02371e3c 100644 --- a/Assets/Editor/Utilities/SpriteColliderGenerator.cs +++ b/Assets/Editor/Utilities/SpriteColliderGenerator.cs @@ -43,6 +43,9 @@ namespace Editor.Utilities [Tooltip("Color used for previewing colliders in the scene view")] private Color previewColor = new Color(0.2f, 1f, 0.3f, 0.5f); + [Tooltip("Layer to assign to GameObjects when colliders are generated")] + private int targetLayer = 0; + private List previewMeshes = new List(); [MenuItem("Tools/Sprite Collider Generator")] @@ -51,8 +54,29 @@ namespace Editor.Utilities GetWindow("Sprite Collider Generator"); } + private void OnEnable() + { + // Subscribe to scene change events to clear invalid object references + UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += OnSceneOpened; + UnityEditor.SceneManagement.EditorSceneManager.sceneClosed += OnSceneClosed; + + // Also subscribe to playmode changes as they can invalidate references + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + + // Subscribe to prefab stage changes (Unity 2018.3+) + UnityEditor.SceneManagement.PrefabStage.prefabStageOpened += OnPrefabStageOpened; + UnityEditor.SceneManagement.PrefabStage.prefabStageClosing += OnPrefabStageClosing; + } + private void OnDisable() { + // Unsubscribe from events + UnityEditor.SceneManagement.EditorSceneManager.sceneOpened -= OnSceneOpened; + UnityEditor.SceneManagement.EditorSceneManager.sceneClosed -= OnSceneClosed; + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; + UnityEditor.SceneManagement.PrefabStage.prefabStageOpened -= OnPrefabStageOpened; + UnityEditor.SceneManagement.PrefabStage.prefabStageClosing -= OnPrefabStageClosing; + // Clean up any preview meshes when window is closed foreach (var mesh in previewMeshes) { @@ -63,7 +87,53 @@ namespace Editor.Utilities } previewMeshes.Clear(); } + + private void OnSceneOpened(UnityEngine.SceneManagement.Scene scene, UnityEditor.SceneManagement.OpenSceneMode mode) + { + // Clear selected objects when a scene is opened + ClearInvalidReferences(); + } + + private void OnSceneClosed(UnityEngine.SceneManagement.Scene scene) + { + // Clear selected objects when a scene is closed + ClearInvalidReferences(); + } + + private void OnPlayModeStateChanged(PlayModeStateChange state) + { + // Clear references when entering/exiting play mode as they become invalid + if (state == PlayModeStateChange.ExitingEditMode || state == PlayModeStateChange.ExitingPlayMode) + { + ClearInvalidReferences(); + } + } + + private void OnPrefabStageOpened(UnityEditor.SceneManagement.PrefabStage stage) + { + // Clear selected objects when entering a prefab stage + ClearInvalidReferences(); + } + private void OnPrefabStageClosing(UnityEditor.SceneManagement.PrefabStage stage) + { + // Clear selected objects when exiting a prefab stage + ClearInvalidReferences(); + } + + /// + /// Clears invalid GameObject references from the selected objects list + /// + private void ClearInvalidReferences() + { + if (selectedObjects.Count > 0) + { + selectedObjects.Clear(); + ClearPreviews(); // Also clear any preview meshes + Repaint(); // Refresh the window UI + } + } + private void OnGUI() { EditorGUILayout.BeginVertical(); @@ -146,6 +216,12 @@ namespace Editor.Utilities new GUIContent("Generate Trigger Colliders", "When enabled, creates trigger colliders instead of solid colliders."), generateTriggerColliders); + // Layer selection + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(new GUIContent("Target Layer:", "Layer to assign to GameObjects when colliders are generated. Leave as 'Nothing' to keep current layer."), GUILayout.Width(180)); + targetLayer = EditorGUILayout.LayerField(targetLayer); + EditorGUILayout.EndHorizontal(); + // Offset option offsetFromCenter = EditorGUILayout.Toggle( new GUIContent("Offset From Center", "When enabled, allows scaling the collider outward or inward from the sprite center."), @@ -445,8 +521,24 @@ namespace Editor.Utilities polygonCollider.SetPath(i, paths[i]); } + // Set the layer on the GameObject if a specific layer is selected + SetTargetLayer(targetObject); + return true; } + + /// + /// Sets the target layer on the GameObject if a layer is selected + /// + /// The GameObject to set the layer on + private void SetTargetLayer(GameObject targetObject) + { + if (targetLayer != 0) + { + Undo.RecordObject(targetObject, "Set GameObject Layer"); + targetObject.layer = targetLayer; + } + } private List GetSpritePaths(Sprite sprite, float tolerance) { diff --git a/Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab b/Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab new file mode 100644 index 00000000..fb73fd83 --- /dev/null +++ b/Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab @@ -0,0 +1,154 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &4743746373562280435 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7889589254526244618} + - component: {fileID: 2565520060777160406} + - component: {fileID: 586678365535466516} + - component: {fileID: 3635110434097976059} + m_Layer: 11 + m_Name: FloatingObstacle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7889589254526244618 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4743746373562280435} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.62358, y: 4.23222, z: 0} + m_LocalScale: {x: 5, y: 5, z: 5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &2565520060777160406 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4743746373562280435} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 32718083aef44be2a4318681fcdf5b2e, type: 3} + m_Name: + m_EditorClassIdentifier: + prefabIndex: 0 + damage: 1 + moveSpeed: 2 + enableMovement: 1 + spawner: {fileID: 0} +--- !u!212 &586678365535466516 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4743746373562280435} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Sprite: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0} + m_Color: {r: 0.9150943, g: 0, b: 0, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 0.16, y: 0.16} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!61 &3635110434097976059 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4743746373562280435} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0.049999997, y: 0.049999997, z: 0.049999997, w: 0.049999997} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 0.16, y: 0.16} + newSize: {x: 0.16, y: 0.16} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Size: {x: 0.16, y: 0.16} + m_EdgeRadius: 0 diff --git a/Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab.meta b/Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab.meta new file mode 100644 index 00000000..7e6b992d --- /dev/null +++ b/Assets/Prefabs/Minigames/DivingForPictures/FloatingObstacle.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 315a624eb99600444a51bb1d37c51742 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster.prefab b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster.prefab index 4203173d..4245050c 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster.prefab @@ -12,7 +12,7 @@ GameObject: - component: {fileID: 8447572436637192077} - component: {fileID: 4998672042618199381} - component: {fileID: 3714732064953161914} - m_Layer: 0 + m_Layer: 12 m_Name: QuarryMonster m_TagString: Untagged m_Icon: {fileID: 0} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile1.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile1.prefab index 2557bd2d..58964572 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile1.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile1.prefab @@ -10,8 +10,8 @@ GameObject: m_Component: - component: {fileID: 7111145574660306503} - component: {fileID: 3889795708575321074} - - component: {fileID: 7249681423942450184} - m_Layer: 0 + - component: {fileID: 8688779957837852254} + m_Layer: 6 m_Name: Left_Tile2_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -88,7 +88,7 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 ---- !u!60 &7249681423942450184 +--- !u!60 &8688779957837852254 PolygonCollider2D: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -155,8 +155,8 @@ GameObject: m_Component: - component: {fileID: 1003080013996268193} - component: {fileID: 4856205316150460481} - - component: {fileID: 2843103852598642252} - m_Layer: 0 + - component: {fileID: 8615330168962481848} + m_Layer: 6 m_Name: Right_Tile1_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -233,7 +233,7 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 ---- !u!60 &2843103852598642252 +--- !u!60 &8615330168962481848 PolygonCollider2D: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -282,8 +282,6 @@ PolygonCollider2D: - - {x: -0.48499998, y: 2.5} - {x: -0.49499997, y: 2.19} - {x: 0.035, y: 1.66} - - {x: 0.035, y: 1.42} - - {x: -0.24499999, y: 1.15} - {x: -0.285, y: 0.90999997} - {x: -0.13499999, y: 0.45999998} - {x: -1.115, y: -0.03} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile1_flipped.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile1_flipped.prefab index fc6ab7f1..adb3cac4 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile1_flipped.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile1_flipped.prefab @@ -10,7 +10,8 @@ GameObject: m_Component: - component: {fileID: 7111145574660306503} - component: {fileID: 3889795708575321074} - m_Layer: 0 + - component: {fileID: 8698932925238153222} + m_Layer: 6 m_Name: Left_Tile2_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -87,6 +88,63 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!60 &8698932925238153222 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 864595161669782950} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 2.37, y: 5} + newSize: {x: 2.37, y: 5} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: 0.575, y: -1.5} + - {x: 0.185, y: -0.63} + - {x: 1.145, y: 0.85999995} + - {x: 1.185, y: 1.41} + - {x: 0.835, y: 1.8499999} + - {x: 0.635, y: 2.5} + - {x: -1.185, y: 2.5} + - {x: -1.185, y: -2.5} + - {x: 0.635, y: -2.5} + - {x: 0.625, y: -1.64} + m_UseDelaunayMesh: 0 --- !u!1 &2171518497100337372 GameObject: m_ObjectHideFlags: 0 @@ -97,7 +155,8 @@ GameObject: m_Component: - component: {fileID: 1003080013996268193} - component: {fileID: 4856205316150460481} - m_Layer: 0 + - component: {fileID: 1368284191430742655} + m_Layer: 6 m_Name: Right_Tile1_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -174,6 +233,67 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!60 &1368284191430742655 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2171518497100337372} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 2.65, y: 5} + newSize: {x: 2.65, y: 5} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: -0.48499998, y: 2.5} + - {x: -0.49499997, y: 2.19} + - {x: 0.035, y: 1.66} + - {x: -0.285, y: 0.90999997} + - {x: -0.13499999, y: 0.45999998} + - {x: -1.115, y: -0.03} + - {x: -1.3249999, y: -0.35} + - {x: -1.2049999, y: -0.84999996} + - {x: -0.36499998, y: -1.0699999} + - {x: -0.585, y: -1.27} + - {x: -0.625, y: -1.65} + - {x: -0.48499998, y: -2.5} + - {x: 1.3249999, y: -2.5} + - {x: 1.3249999, y: 2.5} + m_UseDelaunayMesh: 0 --- !u!1 &2956826569642009690 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile2.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile2.prefab index bc9918dc..467e61f5 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile2.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile2.prefab @@ -10,7 +10,8 @@ GameObject: m_Component: - component: {fileID: 7111145574660306503} - component: {fileID: 3889795708575321074} - m_Layer: 0 + - component: {fileID: 6919687589473331973} + m_Layer: 6 m_Name: Left_Tile2_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -87,6 +88,72 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!60 &6919687589473331973 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 864595161669782950} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 2.65, y: 5} + newSize: {x: 2.37, y: 5} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: 0.635, y: -1.78} + - {x: 0.585, y: -1.27} + - {x: 0.36499998, y: -1.14} + - {x: 0.415, y: -1.05} + - {x: 1.2149999, y: -0.85999995} + - {x: 1.3249999, y: -0.7} + - {x: 1.3249999, y: -0.35} + - {x: 1.145, y: -0.049999997} + - {x: 0.13499999, y: 0.45999998} + - {x: 0.275, y: 0.87} + - {x: 0.265, y: 1.0699999} + - {x: -0.044999998, y: 1.41} + - {x: -0.035, y: 1.66} + - {x: 0.48499998, y: 2.2} + - {x: 0.49499997, y: 2.48} + - {x: -1.3249999, y: 2.5} + - {x: -1.3249999, y: -2.5} + - {x: 0.505, y: -2.5} + - {x: 0.555, y: -2.12} + m_UseDelaunayMesh: 0 --- !u!1 &2171518497100337372 GameObject: m_ObjectHideFlags: 0 @@ -97,7 +164,8 @@ GameObject: m_Component: - component: {fileID: 1003080013996268193} - component: {fileID: 4856205316150460481} - m_Layer: 0 + - component: {fileID: 1911291775322313535} + m_Layer: 6 m_Name: Right_Tile1_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -174,6 +242,65 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!60 &1911291775322313535 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2171518497100337372} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 2.37, y: 5} + newSize: {x: 2.65, y: 5} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: -0.635, y: 2.5} + - {x: -0.835, y: 1.8499999} + - {x: -1.185, y: 1.42} + - {x: -1.185, y: 1.06} + - {x: -1.055, y: 0.69} + - {x: -0.185, y: -0.65999997} + - {x: -0.175, y: -0.78999996} + - {x: -0.585, y: -1.49} + - {x: -0.675, y: -2.08} + - {x: -0.635, y: -2.5} + - {x: 1.185, y: -2.5} + - {x: 1.185, y: 2.5} + m_UseDelaunayMesh: 0 --- !u!1 &2956826569642009690 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile2_flipped.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile2_flipped.prefab index 08587f8a..d6cb1781 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile2_flipped.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile2_flipped.prefab @@ -10,7 +10,8 @@ GameObject: m_Component: - component: {fileID: 7111145574660306503} - component: {fileID: 3889795708575321074} - m_Layer: 0 + - component: {fileID: 3320970211105392876} + m_Layer: 6 m_Name: Left_Tile2_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -87,6 +88,72 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!60 &3320970211105392876 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 864595161669782950} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 2.65, y: 5} + newSize: {x: 2.37, y: 5} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: 0.635, y: -1.78} + - {x: 0.585, y: -1.27} + - {x: 0.36499998, y: -1.14} + - {x: 0.415, y: -1.05} + - {x: 1.2149999, y: -0.85999995} + - {x: 1.3249999, y: -0.7} + - {x: 1.3249999, y: -0.35} + - {x: 1.145, y: -0.049999997} + - {x: 0.13499999, y: 0.45999998} + - {x: 0.275, y: 0.87} + - {x: 0.265, y: 1.0699999} + - {x: -0.044999998, y: 1.41} + - {x: -0.035, y: 1.66} + - {x: 0.48499998, y: 2.2} + - {x: 0.49499997, y: 2.48} + - {x: -1.3249999, y: 2.5} + - {x: -1.3249999, y: -2.5} + - {x: 0.505, y: -2.5} + - {x: 0.555, y: -2.12} + m_UseDelaunayMesh: 0 --- !u!1 &2171518497100337372 GameObject: m_ObjectHideFlags: 0 @@ -97,7 +164,8 @@ GameObject: m_Component: - component: {fileID: 1003080013996268193} - component: {fileID: 4856205316150460481} - m_Layer: 0 + - component: {fileID: 3931598674086383704} + m_Layer: 6 m_Name: Right_Tile1_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -174,6 +242,65 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!60 &3931598674086383704 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2171518497100337372} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 2.37, y: 5} + newSize: {x: 2.65, y: 5} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: -0.635, y: 2.5} + - {x: -0.835, y: 1.8499999} + - {x: -1.185, y: 1.42} + - {x: -1.185, y: 1.06} + - {x: -1.055, y: 0.69} + - {x: -0.185, y: -0.65999997} + - {x: -0.175, y: -0.78999996} + - {x: -0.585, y: -1.49} + - {x: -0.675, y: -2.08} + - {x: -0.635, y: -2.5} + - {x: 1.185, y: -2.5} + - {x: 1.185, y: 2.5} + m_UseDelaunayMesh: 0 --- !u!1 &2956826569642009690 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile3.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile3.prefab index 42766dba..2c8a383c 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile3.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile3.prefab @@ -10,7 +10,8 @@ GameObject: m_Component: - component: {fileID: 7111145574660306503} - component: {fileID: 3889795708575321074} - m_Layer: 0 + - component: {fileID: 1709932086434338450} + m_Layer: 6 m_Name: Left_Tile2_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -87,6 +88,72 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!60 &1709932086434338450 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 864595161669782950} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 2.65, y: 5} + newSize: {x: 2.37, y: 5} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: 0.635, y: -1.78} + - {x: 0.585, y: -1.27} + - {x: 0.36499998, y: -1.14} + - {x: 0.415, y: -1.05} + - {x: 1.2149999, y: -0.85999995} + - {x: 1.3249999, y: -0.7} + - {x: 1.3249999, y: -0.35} + - {x: 1.145, y: -0.049999997} + - {x: 0.13499999, y: 0.45999998} + - {x: 0.275, y: 0.87} + - {x: 0.265, y: 1.0699999} + - {x: -0.044999998, y: 1.41} + - {x: -0.035, y: 1.66} + - {x: 0.48499998, y: 2.2} + - {x: 0.49499997, y: 2.48} + - {x: -1.3249999, y: 2.5} + - {x: -1.3249999, y: -2.5} + - {x: 0.505, y: -2.5} + - {x: 0.555, y: -2.12} + m_UseDelaunayMesh: 0 --- !u!1 &2171518497100337372 GameObject: m_ObjectHideFlags: 0 @@ -97,7 +164,8 @@ GameObject: m_Component: - component: {fileID: 1003080013996268193} - component: {fileID: 4856205316150460481} - m_Layer: 0 + - component: {fileID: 9036823987933958098} + m_Layer: 6 m_Name: Right_Tile1_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -174,6 +242,70 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!60 &9036823987933958098 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2171518497100337372} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 2.65, y: 5} + newSize: {x: 2.65, y: 5} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: -0.48499998, y: 2.5} + - {x: -0.49499997, y: 2.19} + - {x: 0.035, y: 1.66} + - {x: 0.035, y: 1.42} + - {x: -0.24499999, y: 1.15} + - {x: -0.285, y: 0.90999997} + - {x: -0.13499999, y: 0.45999998} + - {x: -1.115, y: -0.03} + - {x: -1.3249999, y: -0.35} + - {x: -1.3249999, y: -0.71} + - {x: -1.2049999, y: -0.84999996} + - {x: -0.36499998, y: -1.0699999} + - {x: -0.585, y: -1.27} + - {x: -0.625, y: -1.65} + - {x: -0.48499998, y: -2.5} + - {x: 1.3249999, y: -2.5} + - {x: 1.3249999, y: 2.5} + m_UseDelaunayMesh: 0 --- !u!1 &2956826569642009690 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile3_flipped.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile3_flipped.prefab index 35f0cb88..60f44546 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile3_flipped.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile3_flipped.prefab @@ -10,7 +10,8 @@ GameObject: m_Component: - component: {fileID: 7111145574660306503} - component: {fileID: 3889795708575321074} - m_Layer: 0 + - component: {fileID: 6967660003360049346} + m_Layer: 6 m_Name: Left_Tile2_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -87,6 +88,72 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!60 &6967660003360049346 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 864595161669782950} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 2.65, y: 5} + newSize: {x: 2.37, y: 5} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: 0.635, y: -1.78} + - {x: 0.585, y: -1.27} + - {x: 0.36499998, y: -1.14} + - {x: 0.415, y: -1.05} + - {x: 1.2149999, y: -0.85999995} + - {x: 1.3249999, y: -0.7} + - {x: 1.3249999, y: -0.35} + - {x: 1.145, y: -0.049999997} + - {x: 0.13499999, y: 0.45999998} + - {x: 0.275, y: 0.87} + - {x: 0.265, y: 1.0699999} + - {x: -0.044999998, y: 1.41} + - {x: -0.035, y: 1.66} + - {x: 0.48499998, y: 2.2} + - {x: 0.49499997, y: 2.48} + - {x: -1.3249999, y: 2.5} + - {x: -1.3249999, y: -2.5} + - {x: 0.505, y: -2.5} + - {x: 0.555, y: -2.12} + m_UseDelaunayMesh: 0 --- !u!1 &2171518497100337372 GameObject: m_ObjectHideFlags: 0 @@ -97,7 +164,8 @@ GameObject: m_Component: - component: {fileID: 1003080013996268193} - component: {fileID: 4856205316150460481} - m_Layer: 0 + - component: {fileID: 5503039050943823342} + m_Layer: 6 m_Name: Right_Tile1_0 m_TagString: Untagged m_Icon: {fileID: 0} @@ -174,6 +242,70 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!60 &5503039050943823342 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2171518497100337372} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 2.65, y: 5} + newSize: {x: 2.65, y: 5} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: -0.48499998, y: 2.5} + - {x: -0.49499997, y: 2.19} + - {x: 0.035, y: 1.66} + - {x: 0.035, y: 1.42} + - {x: -0.24499999, y: 1.15} + - {x: -0.285, y: 0.90999997} + - {x: -0.13499999, y: 0.45999998} + - {x: -1.115, y: -0.03} + - {x: -1.3249999, y: -0.35} + - {x: -1.3249999, y: -0.71} + - {x: -1.2049999, y: -0.84999996} + - {x: -0.36499998, y: -1.0699999} + - {x: -0.585, y: -1.27} + - {x: -0.625, y: -1.65} + - {x: -0.48499998, y: -2.5} + - {x: 1.3249999, y: -2.5} + - {x: 1.3249999, y: 2.5} + m_UseDelaunayMesh: 0 --- !u!1 &2956826569642009690 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scenes/MiniGames/DivingForPictures.unity b/Assets/Scenes/MiniGames/DivingForPictures.unity index 60604ec8..36a7fbf4 100644 --- a/Assets/Scenes/MiniGames/DivingForPictures.unity +++ b/Assets/Scenes/MiniGames/DivingForPictures.unity @@ -492,6 +492,73 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &323864663 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 323864665} + - component: {fileID: 323864664} + m_Layer: 0 + m_Name: ObstacleSpawner + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &323864664 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 323864663} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 49ec62157fd945fab730193e9ea0bff7, type: 3} + m_Name: + m_EditorClassIdentifier: + obstaclePrefabs: + - {fileID: 4743746373562280435, guid: 315a624eb99600444a51bb1d37c51742, type: 3} + spawnInterval: 3 + spawnIntervalVariation: 1 + maxSpawnAttempts: 10 + spawnCollisionRadius: 1 + spawnDistanceBelowScreen: 2 + spawnRangeX: 8 + minMoveSpeed: 1 + maxMoveSpeed: 4 + useObjectPooling: 1 + maxPerPrefabPoolSize: 15 + totalMaxPoolSize: 30 + tileLayerMask: + serializedVersion: 2 + m_Bits: 576 + obstacleLayer: 11 + onObstacleSpawned: + m_PersistentCalls: + m_Calls: [] + onObstacleDestroyed: + m_PersistentCalls: + m_Calls: [] +--- !u!4 &323864665 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 323864663} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.66745, y: 0.68592, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &424805724 GameObject: m_ObjectHideFlags: 0 @@ -531,8 +598,8 @@ MonoBehaviour: probabilityIncreaseRate: 0.01 guaranteedSpawnTime: 10 spawnCooldown: 5 - basePoints: 100 - depthMultiplier: 10 + basePoints: 10 + depthMultiplier: 2 --- !u!4 &424805726 Transform: m_ObjectHideFlags: 0 @@ -646,9 +713,14 @@ GameObject: - component: {fileID: 747976397} - component: {fileID: 747976398} - component: {fileID: 747976399} + - component: {fileID: 747976400} + - component: {fileID: 747976401} + - component: {fileID: 747976402} + - component: {fileID: 747976403} + - component: {fileID: 747976404} m_Layer: 0 m_Name: BottleMarine - m_TagString: Untagged + m_TagString: Player m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 @@ -701,6 +773,181 @@ MonoBehaviour: verticalAmplitude: 0.2 velocitySmoothing: 10 rotationSmoothing: 10 +--- !u!114 &747976400 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 747976396} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8222f0e3aeeb4fc4975aaead6cf7afbe, type: 3} + m_Name: + m_EditorClassIdentifier: + damageImmunityDuration: 1 + obstacleLayerMask: + serializedVersion: 2 + m_Bits: 64 + blockInputDuringImmunity: 0 + playerCharacter: {fileID: 747976396} + playerController: {fileID: 747976398} + bumpMode: 0 + bumpForce: 5 + smoothMoveSpeed: 8 + 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 + blockInputDuringBump: 1 +--- !u!114 &747976401 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 747976396} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c9c18dbd013d42ae8c221e6205e4d49c, type: 3} + m_Name: + m_EditorClassIdentifier: + damageImmunityDuration: 1 + obstacleLayerMask: + serializedVersion: 2 + m_Bits: 2048 + blockInputDuringImmunity: 0 + playerCharacter: {fileID: 747976396} + playerController: {fileID: 747976398} +--- !u!60 &747976402 +PolygonCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 747976396} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 1 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0, y: 0} + oldSize: {x: 0, y: 0} + newSize: {x: 0, y: 0} + adaptiveTilingThreshold: 0 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Points: + m_Paths: + - - {x: -0.7990411, y: 2.6669068} + - {x: -0.9294033, y: 2.502293} + - {x: -1.005722, y: 2.1038642} + - {x: -0.9038467, y: 1.652195} + - {x: -0.7700032, y: 1.5415951} + - {x: 0.28712562, y: 1.5507066} + - {x: 0.48471105, y: 1.6954134} + - {x: 0.5636723, y: 1.8504347} + - {x: 0.63941646, y: 1.8455681} + - {x: 0.69998366, y: 1.761524} + - {x: 0.95461226, y: 1.7980554} + - {x: 1.0003967, y: 2.1063333} + - {x: 0.94308287, y: 2.433347} + - {x: 0.8147154, y: 2.464337} + - {x: 0.6922814, y: 2.465095} + - {x: 0.6549096, y: 2.3498883} + - {x: 0.5677627, y: 2.3522122} + - {x: 0.38462818, y: 2.6102822} + - {x: 0.20396256, y: 2.6596293} + m_UseDelaunayMesh: 0 +--- !u!50 &747976403 +Rigidbody2D: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 747976396} + m_BodyType: 1 + m_Simulated: 1 + m_UseFullKinematicContacts: 0 + m_UseAutoMass: 0 + m_Mass: 1 + m_LinearDamping: 0 + m_AngularDamping: 0.05 + m_GravityScale: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_Interpolate: 0 + m_SleepingMode: 1 + m_CollisionDetection: 0 + m_Constraints: 0 +--- !u!114 &747976404 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 747976396} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d8ea29cc80524de8affe17b930cd75c1, type: 3} + m_Name: + m_EditorClassIdentifier: + damageBlinkColor: {r: 1, g: 0, b: 0, a: 1} + blinkRate: 0.15 + damageColorAlpha: 0.7 + targetSpriteRenderer: {fileID: 730962734} --- !u!1 &824396214 GameObject: m_ObjectHideFlags: 0 @@ -1847,3 +2094,4 @@ SceneRoots: - {fileID: 424805726} - {fileID: 116234201} - {fileID: 824396217} + - {fileID: 323864665} diff --git a/Assets/Scripts/Minigames/DivingForPictures/Bubble.cs b/Assets/Scripts/Minigames/DivingForPictures/Bubble.cs index 76a1c2fc..56969ee1 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Bubble.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Bubble.cs @@ -1,30 +1,38 @@ using UnityEngine; +using System.Collections; using Pooling; namespace Minigames.DivingForPictures { /// /// Represents a single bubble, handling its movement, wobble effect, scaling, and sprite assignment. + /// Uses coroutines for better performance instead of Update() calls. /// public class Bubble : MonoBehaviour, IPoolableWithReference { public float speed = 1f; public float wobbleSpeed = 1f; private SpriteRenderer spriteRenderer; - private SpriteRenderer bubbleSpriteRenderer; // Renamed from bottleSpriteRenderer + private SpriteRenderer bubbleSpriteRenderer; private float timeOffset; private float minScale = 0.2f; private float maxScale = 1.2f; - private float baseScale = 1f; // Added to store the initial scale + private float baseScale = 1f; - private Camera mainCamera; // Cache camera reference - private BubblePool parentPool; // Reference to the pool this bubble came from + private Camera mainCamera; + private BubblePool parentPool; + + // Coroutine references + private Coroutine _movementCoroutine; + private Coroutine _wobbleCoroutine; + private Coroutine _offScreenCheckCoroutine; void Awake() { // Cache references and randomize time offset for wobble spriteRenderer = GetComponent(); timeOffset = Random.value * 100f; + // Find the child named "BubbleSprite" and get its SpriteRenderer Transform bubbleSpriteTransform = transform.Find("BubbleSprite"); if (bubbleSpriteTransform != null) @@ -36,20 +44,101 @@ namespace Minigames.DivingForPictures mainCamera = Camera.main; } - void Update() + private void OnEnable() { - // Move bubble upward - transform.position += Vector3.up * (speed * Time.deltaTime); - - // Wobble effect (smooth oscillation between min and max scale) - float t = (Mathf.Sin((Time.time + timeOffset) * wobbleSpeed) + 1f) * 0.5f; // t in [0,1] - float wobbleFactor = Mathf.Lerp(minScale, maxScale, t); - transform.localScale = Vector3.one * (baseScale * wobbleFactor); - - // Destroy when off screen - using cached camera reference - if (mainCamera != null && transform.position.y > mainCamera.orthographicSize + 2f) + StartBubbleBehavior(); + } + + private void OnDisable() + { + StopBubbleBehavior(); + } + + /// + /// Starts all bubble behavior coroutines + /// + private void StartBubbleBehavior() + { + _movementCoroutine = StartCoroutine(MovementCoroutine()); + _wobbleCoroutine = StartCoroutine(WobbleCoroutine()); + _offScreenCheckCoroutine = StartCoroutine(OffScreenCheckCoroutine()); + } + + /// + /// Stops all bubble behavior coroutines + /// + private void StopBubbleBehavior() + { + if (_movementCoroutine != null) { - OnBubbleDestroy(); + StopCoroutine(_movementCoroutine); + _movementCoroutine = null; + } + + if (_wobbleCoroutine != null) + { + StopCoroutine(_wobbleCoroutine); + _wobbleCoroutine = null; + } + + if (_offScreenCheckCoroutine != null) + { + StopCoroutine(_offScreenCheckCoroutine); + _offScreenCheckCoroutine = null; + } + } + + /// + /// Coroutine that handles bubble upward movement + /// + private IEnumerator MovementCoroutine() + { + while (enabled && gameObject.activeInHierarchy) + { + // Move bubble upward + transform.position += Vector3.up * (speed * Time.deltaTime); + + // Wait for next frame + yield return null; + } + } + + /// + /// Coroutine that handles the wobble scaling effect + /// + private IEnumerator WobbleCoroutine() + { + while (enabled && gameObject.activeInHierarchy) + { + // Wobble effect (smooth oscillation between min and max scale) + float t = (Mathf.Sin((Time.time + timeOffset) * wobbleSpeed) + 1f) * 0.5f; // t in [0,1] + float wobbleFactor = Mathf.Lerp(minScale, maxScale, t); + transform.localScale = Vector3.one * (baseScale * wobbleFactor); + + // Wait for next frame + yield return null; + } + } + + /// + /// Coroutine that checks if bubble has moved off-screen + /// Runs at a lower frequency for better performance + /// + private IEnumerator OffScreenCheckCoroutine() + { + const float checkInterval = 0.1f; // Check every 100ms instead of every frame + + while (enabled && gameObject.activeInHierarchy) + { + // Check if bubble is off screen + if (mainCamera != null && transform.position.y > mainCamera.orthographicSize + 2f) + { + OnBubbleDestroy(); + yield break; // Exit coroutine since bubble is being destroyed + } + + // Wait for the check interval + yield return new WaitForSeconds(checkInterval); } } @@ -143,6 +232,24 @@ namespace Minigames.DivingForPictures minScale = min; maxScale = max; } + + /// + /// Sets the movement speed at runtime + /// + /// New movement speed + public void SetSpeed(float newSpeed) + { + speed = newSpeed; + } + + /// + /// Sets the wobble speed at runtime + /// + /// New wobble speed + public void SetWobbleSpeed(float newWobbleSpeed) + { + wobbleSpeed = newWobbleSpeed; + } /// /// Resets the bubble state for reuse from object pool diff --git a/Assets/Scripts/Minigames/DivingForPictures/FloatingObstacle.cs b/Assets/Scripts/Minigames/DivingForPictures/FloatingObstacle.cs new file mode 100644 index 00000000..52338634 --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/FloatingObstacle.cs @@ -0,0 +1,283 @@ +using UnityEngine; +using System.Collections; +using Pooling; + +namespace Minigames.DivingForPictures +{ + /// + /// Complete floating obstacle component that handles movement and pooling. + /// Obstacles move upward toward the surface. Collision detection is handled by the player. + /// Once an obstacle hits the player, its collider is disabled to prevent further collisions. + /// Uses coroutines for better performance instead of Update() calls. + /// + public class FloatingObstacle : MonoBehaviour, IPoolable + { + [Header("Obstacle Properties")] + [Tooltip("Index of the prefab this obstacle was created from")] + [SerializeField] private int prefabIndex; + + [Tooltip("Movement speed of this obstacle")] + [SerializeField] private float moveSpeed = 2f; + + [Header("Movement")] + [Tooltip("Whether this obstacle moves (can be disabled for static obstacles)")] + [SerializeField] private bool enableMovement = true; + + [Header("References")] + [Tooltip("Reference to the spawner that created this obstacle")] + [SerializeField] private ObstacleSpawner spawner; + + // Public properties + public int PrefabIndex + { + get => prefabIndex; + set => prefabIndex = value; + } + + public float MoveSpeed + { + get => moveSpeed; + set => moveSpeed = value; + } + + // Private fields + private Collider2D _collider; + private Camera _mainCamera; + private float _screenTop; + private Coroutine _movementCoroutine; + private Coroutine _offScreenCheckCoroutine; + + private void Awake() + { + _collider = GetComponent(); + + if (_collider == null) + { + _collider = GetComponentInChildren(); + } + + if (_collider == null) + { + Debug.LogError($"[FloatingObstacle] No Collider2D found on {gameObject.name}!"); + } + + _mainCamera = Camera.main; + } + + private void OnEnable() + { + StartObstacleBehavior(); + } + + private void OnDisable() + { + StopObstacleBehavior(); + } + + /// + /// Starts the obstacle behavior coroutines + /// + private void StartObstacleBehavior() + { + if (enableMovement) + { + _movementCoroutine = StartCoroutine(MovementCoroutine()); + } + + _offScreenCheckCoroutine = StartCoroutine(OffScreenCheckCoroutine()); + } + + /// + /// Stops all obstacle behavior coroutines + /// + private void StopObstacleBehavior() + { + if (_movementCoroutine != null) + { + StopCoroutine(_movementCoroutine); + _movementCoroutine = null; + } + + if (_offScreenCheckCoroutine != null) + { + StopCoroutine(_offScreenCheckCoroutine); + _offScreenCheckCoroutine = null; + } + } + + /// + /// Coroutine that handles obstacle movement + /// + private IEnumerator MovementCoroutine() + { + while (enabled && gameObject.activeInHierarchy) + { + // Move the obstacle upward + transform.position += Vector3.up * (moveSpeed * Time.deltaTime); + + // Wait for next frame + yield return null; + } + } + + /// + /// Coroutine that checks if obstacle has moved off-screen + /// Runs at a lower frequency than movement for better performance + /// + private IEnumerator OffScreenCheckCoroutine() + { + const float checkInterval = 0.2f; // Check every 200ms instead of every frame + + while (enabled && gameObject.activeInHierarchy) + { + CheckIfOffScreen(); + + // Wait for the check interval + yield return new WaitForSeconds(checkInterval); + } + } + + /// + /// Disables the collider after hitting the player to prevent further collisions + /// This is more performant than tracking hit state + /// + public void MarkDamageDealt() + { + if (_collider != null && _collider.enabled) + { + _collider.enabled = false; + Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} hit player - collider disabled"); + } + } + + /// + /// Checks if the obstacle has moved off-screen and should be despawned + /// + private void CheckIfOffScreen() + { + if (_mainCamera == null) + { + _mainCamera = Camera.main; + if (_mainCamera == null) return; + } + + // Always recalculate screen bounds to ensure accuracy + Vector3 topWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 1f, _mainCamera.transform.position.z)); + _screenTop = topWorldPoint.y; + + // Check if obstacle is significantly above screen top (obstacles move upward) + // Use a larger buffer to ensure obstacles are truly off-screen before returning to pool + if (transform.position.y > _screenTop + 5f) + { + Debug.Log($"[FloatingObstacle] {gameObject.name} off-screen at Y:{transform.position.y:F2}, screen top:{_screenTop:F2}"); + ReturnToPool(); + } + } + + /// + /// Returns this obstacle to the spawner's pool + /// + private void ReturnToPool() + { + // CRITICAL: Stop all behavior first to prevent race conditions + // This ensures no more off-screen checks or movement happen during pool return + StopObstacleBehavior(); + + if (spawner != null) + { + spawner.ReturnObstacleToPool(gameObject, prefabIndex); + } + else + { + // Try to find the spawner instead of destroying the object + ObstacleSpawner foundSpawner = FindFirstObjectByType(); + if (foundSpawner != null) + { + Debug.LogWarning($"[FloatingObstacle] Obstacle {gameObject.name} lost spawner reference, found replacement spawner"); + spawner = foundSpawner; + spawner.ReturnObstacleToPool(gameObject, prefabIndex); + } + else + { + // No spawner found - just deactivate the object instead of destroying it + Debug.LogWarning($"[FloatingObstacle] No spawner found for {gameObject.name}, deactivating safely"); + gameObject.SetActive(false); + + // Move to a safe location to avoid interference + transform.position = new Vector3(1000f, 1000f, 0f); + } + } + } + + /// + /// Sets the spawner reference for this obstacle + /// + /// The spawner that created this obstacle + public void SetSpawner(ObstacleSpawner obstacleSpawner) + { + spawner = obstacleSpawner; + } + + /// + /// Called when the obstacle is retrieved from the pool + /// + public void OnSpawn() + { + // Reset all state first + _screenTop = 0f; // Reset cached screen bounds + _mainCamera = Camera.main; // Refresh camera reference + + // Re-enable the collider for reuse + if (_collider != null) + { + _collider.enabled = true; + } + + Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} spawned from pool"); + + // Note: Don't start coroutines here - OnEnable() will handle that when SetActive(true) is called + } + + /// + /// Called when the obstacle is returned to the pool + /// + public void OnDespawn() + { + // Stop all coroutines before returning to pool + StopObstacleBehavior(); + + // Re-enable collider for next use (in case it was disabled) + if (_collider != null) + { + _collider.enabled = true; + } + + Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} despawned to pool"); + } + + /// + /// Public method to manually trigger return to pool (for external systems) + /// + public void ForceReturnToPool() + { + ReturnToPool(); + } + + /// + /// Public method to enable/disable movement at runtime + /// + public void SetMovementEnabled(bool enabled) + { + if (enableMovement == enabled) return; + + enableMovement = enabled; + + // Restart coroutines to apply movement change + if (gameObject.activeInHierarchy) + { + StopObstacleBehavior(); + StartObstacleBehavior(); + } + } + } +} diff --git a/Assets/Scripts/Minigames/DivingForPictures/FloatingObstacle.cs.meta b/Assets/Scripts/Minigames/DivingForPictures/FloatingObstacle.cs.meta new file mode 100644 index 00000000..89513a32 --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/FloatingObstacle.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 32718083aef44be2a4318681fcdf5b2e +timeCreated: 1758117709 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/ObstacleCollision.cs b/Assets/Scripts/Minigames/DivingForPictures/ObstacleCollision.cs new file mode 100644 index 00000000..30eaf7ee --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/ObstacleCollision.cs @@ -0,0 +1,44 @@ +using UnityEngine; + +namespace Minigames.DivingForPictures +{ + /// + /// Collision behavior that handles damage from mobile obstacles. + /// Uses trigger-based collision detection with shared immunity state. + /// + public class ObstacleCollision : PlayerCollisionBehavior + { + protected override void HandleCollisionResponse(Collider2D obstacle) + { + // Mark the obstacle as having dealt damage to prevent multiple hits + FloatingObstacle obstacleComponent = obstacle.GetComponent(); + if (obstacleComponent != null) + { + obstacleComponent.MarkDamageDealt(); + } + + Debug.Log($"[ObstacleCollision] Player hit by obstacle {obstacle.gameObject.name}"); + } + + /// + /// Override to prevent input blocking during damage immunity + /// Since obstacles pass through the player, we don't want to block input + /// + protected override void OnImmunityStart() + { + Debug.Log($"[ObstacleCollision] Damage immunity started for {damageImmunityDuration} seconds"); + + // Don't block input for obstacle damage - let player keep moving + // The shared immunity system will handle the collision prevention + } + + /// + /// Override to handle immunity end + /// + protected override void OnImmunityEnd() + { + Debug.Log($"[ObstacleCollision] Damage immunity ended"); + // No special handling needed - shared immunity system handles collider re-enabling + } + } +} diff --git a/Assets/Scripts/Minigames/DivingForPictures/ObstacleCollision.cs.meta b/Assets/Scripts/Minigames/DivingForPictures/ObstacleCollision.cs.meta new file mode 100644 index 00000000..e8d0c5be --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/ObstacleCollision.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c9c18dbd013d42ae8c221e6205e4d49c +timeCreated: 1758116850 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/ObstaclePool.cs b/Assets/Scripts/Minigames/DivingForPictures/ObstaclePool.cs new file mode 100644 index 00000000..d11a77fa --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/ObstaclePool.cs @@ -0,0 +1,54 @@ +using UnityEngine; +using Pooling; + +namespace Minigames.DivingForPictures +{ + /// + /// Manages a pool of floating obstacle objects to reduce garbage collection overhead. + /// Optimized for handling a large number of different obstacle prefab types. + /// + public class ObstaclePool : MultiPrefabPool + { + /// + /// Returns an obstacle to the pool + /// + /// The obstacle to return to the pool + /// The index of the prefab this obstacle was created from + public void ReturnObstacle(GameObject obstacle, int prefabIndex) + { + if (obstacle != null) + { + FloatingObstacle obstacleComponent = obstacle.GetComponent(); + if (obstacleComponent != null) + { + Debug.Log($"[ObstaclePool] Returning obstacle {obstacle.name} to pool"); + Return(obstacleComponent, prefabIndex); + } + else + { + Debug.LogWarning($"Attempted to return a GameObject without a FloatingObstacle component: {obstacle.name}"); + Destroy(obstacle); + } + } + } + + /// + /// Gets an obstacle from the pool, or creates a new one if the pool is empty + /// + /// An obstacle instance ready to use + public GameObject GetObstacle(int prefabIndex) + { + Debug.Log($"[ObstaclePool] GetObstacle called for prefab index {prefabIndex}"); + FloatingObstacle obstacleComponent = Get(prefabIndex); + + if (obstacleComponent == null) + { + Debug.LogError($"[ObstaclePool] Get() returned null for prefab index {prefabIndex}"); + return null; + } + + Debug.Log($"[ObstaclePool] Get() returned obstacle {obstacleComponent.name}, active state: {obstacleComponent.gameObject.activeInHierarchy}"); + return obstacleComponent.gameObject; + } + } +} diff --git a/Assets/Scripts/Minigames/DivingForPictures/ObstaclePool.cs.meta b/Assets/Scripts/Minigames/DivingForPictures/ObstaclePool.cs.meta new file mode 100644 index 00000000..230d34ab --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/ObstaclePool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a53ba79246a94dc4a71d2fb0d7214cfb +timeCreated: 1758116804 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs b/Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs new file mode 100644 index 00000000..67f0cffd --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs @@ -0,0 +1,486 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Events; +using Pooling; + +namespace Minigames.DivingForPictures +{ + /// + /// Spawns and manages mobile obstacles for the diving minigame. + /// Uses object pooling and validates spawn positions to avoid colliding with tiles. + /// + public class ObstacleSpawner : MonoBehaviour + { + [Header("Obstacle Prefabs")] + [Tooltip("List of possible obstacle prefabs to spawn")] + [SerializeField] private List obstaclePrefabs; + + [Header("Spawn Settings")] + [Tooltip("Time interval between spawn attempts (in seconds)")] + [SerializeField] private float spawnInterval = 2f; + + [Tooltip("Random variation in spawn timing (+/- seconds)")] + [SerializeField] private float spawnIntervalVariation = 0.5f; + + [Tooltip("Maximum number of spawn position attempts before skipping")] + [SerializeField] private int maxSpawnAttempts = 10; + + [Tooltip("Radius around spawn point to check for tile collisions")] + [SerializeField] private float spawnCollisionRadius = 1f; + + [Header("Spawn Position")] + [Tooltip("How far below screen to spawn obstacles")] + [SerializeField] private float spawnDistanceBelowScreen = 2f; + + [Tooltip("Horizontal spawn range (distance from center)")] + [SerializeField] private float spawnRangeX = 8f; + + [Header("Obstacle Properties Randomization")] + [Tooltip("Minimum movement speed for spawned obstacles")] + [SerializeField] private float minMoveSpeed = 1f; + + [Tooltip("Maximum movement speed for spawned obstacles")] + [SerializeField] private float maxMoveSpeed = 4f; + + [Header("Object Pooling")] + [Tooltip("Whether to use object pooling for obstacles")] + [SerializeField] private bool useObjectPooling = true; + + [Tooltip("Maximum objects per prefab type in pool")] + [SerializeField] private int maxPerPrefabPoolSize = 3; + + [Tooltip("Total maximum pool size across all prefab types")] + [SerializeField] private int totalMaxPoolSize = 15; + + [Header("Layer Settings")] + [Tooltip("Layer mask for tile collision detection during spawn position validation")] + [SerializeField] private LayerMask tileLayerMask = -1; // Let user configure which layers to avoid + + [Tooltip("Target layer for spawned obstacles - obstacles will be placed on this layer")] + [SerializeField] private int obstacleLayer = 11; // Default to layer 11, but configurable + + [Header("Events")] + [Tooltip("Called when an obstacle is spawned")] + public UnityEvent onObstacleSpawned; + + [Tooltip("Called when an obstacle is returned to pool")] + public UnityEvent onObstacleDestroyed; + + // Private fields + private ObstaclePool _obstaclePool; + private Camera _mainCamera; + private float _screenBottom; + private float _spawnRangeX; + private Coroutine _spawnCoroutine; + private readonly List _activeObstacles = new List(); + private int _obstacleCounter = 0; // Counter for unique obstacle naming + + private void Awake() + { + _mainCamera = Camera.main; + + // Validate obstacle prefabs + ValidateObstaclePrefabs(); + + if (useObjectPooling) + { + InitializeObjectPool(); + } + } + + private void Start() + { + CalculateScreenBounds(); + StartSpawning(); + } + + private void OnDestroy() + { + StopSpawning(); + } + + /// + /// Validates that all prefabs have required components + /// + private void ValidateObstaclePrefabs() + { + for (int i = 0; i < obstaclePrefabs.Count; i++) + { + if (obstaclePrefabs[i] == null) continue; + + // Check if the prefab has a FloatingObstacle component + if (obstaclePrefabs[i].GetComponent() == null) + { + Debug.LogWarning($"Obstacle prefab {obstaclePrefabs[i].name} does not have a FloatingObstacle component. Adding one automatically."); + obstaclePrefabs[i].AddComponent(); + } + + // Ensure the prefab is on the correct layer (using configurable obstacleLayer) + if (obstaclePrefabs[i].layer != obstacleLayer) + { + Debug.LogWarning($"Obstacle prefab {obstaclePrefabs[i].name} is not on the configured obstacle layer ({obstacleLayer}). Setting layer automatically."); + SetLayerRecursively(obstaclePrefabs[i], obstacleLayer); + } + } + } + + /// + /// Sets the layer of a GameObject and all its children + /// + private void SetLayerRecursively(GameObject obj, int layer) + { + obj.layer = layer; + foreach (Transform child in obj.transform) + { + SetLayerRecursively(child.gameObject, layer); + } + } + + /// + /// Initialize the object pool system + /// + private void InitializeObjectPool() + { + GameObject poolGO = new GameObject("ObstaclePool"); + poolGO.transform.SetParent(transform); + _obstaclePool = poolGO.AddComponent(); + + // Set up pool configuration + _obstaclePool.maxPerPrefabPoolSize = maxPerPrefabPoolSize; + _obstaclePool.totalMaxPoolSize = totalMaxPoolSize; + + // Convert GameObject list to FloatingObstacle list + List prefabObstacles = new List(obstaclePrefabs.Count); + foreach (var prefab in obstaclePrefabs) + { + if (prefab != null) + { + FloatingObstacle obstacleComponent = prefab.GetComponent(); + if (obstacleComponent != null) + { + prefabObstacles.Add(obstacleComponent); + } + else + { + Debug.LogError($"Obstacle prefab {prefab.name} is missing a FloatingObstacle component!"); + } + } + } + + // Initialize the pool + _obstaclePool.Initialize(prefabObstacles); + } + + /// + /// Calculate screen bounds in world space dynamically + /// + private void CalculateScreenBounds() + { + if (_mainCamera == null) + { + _mainCamera = Camera.main; + if (_mainCamera == null) + { + Debug.LogError("[ObstacleSpawner] No main camera found!"); + return; + } + } + + // Calculate screen bottom (Y spawn position will be 2 units below this) + Vector3 bottomWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 0f, _mainCamera.nearClipPlane)); + _screenBottom = bottomWorldPoint.y; + + // Calculate screen width in world units + Vector3 leftWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(0f, 0.5f, _mainCamera.nearClipPlane)); + Vector3 rightWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(1f, 0.5f, _mainCamera.nearClipPlane)); + float screenWidth = rightWorldPoint.x - leftWorldPoint.x; + + // Calculate spawn range based on 80% of screen width (40% on each side from center) + _spawnRangeX = (screenWidth * 0.8f) / 2f; + + Debug.Log($"[ObstacleSpawner] Screen calculated - Width: {screenWidth:F2}, Bottom: {_screenBottom:F2}, Spawn Range X: ±{_spawnRangeX:F2}"); + } + + /// + /// Starts the obstacle spawning coroutine + /// + public void StartSpawning() + { + if (_spawnCoroutine == null) + { + _spawnCoroutine = StartCoroutine(SpawnObstaclesCoroutine()); + Debug.Log("[ObstacleSpawner] Started spawning obstacles"); + } + } + + /// + /// Stops the obstacle spawning coroutine + /// + public void StopSpawning() + { + if (_spawnCoroutine != null) + { + StopCoroutine(_spawnCoroutine); + _spawnCoroutine = null; + Debug.Log("[ObstacleSpawner] Stopped spawning obstacles"); + } + } + + /// + /// Main spawning coroutine that runs continuously + /// + private IEnumerator SpawnObstaclesCoroutine() + { + while (true) + { + // Calculate next spawn time with variation + float nextSpawnTime = spawnInterval + Random.Range(-spawnIntervalVariation, spawnIntervalVariation); + nextSpawnTime = Mathf.Max(0.1f, nextSpawnTime); // Ensure minimum interval + + yield return new WaitForSeconds(nextSpawnTime); + + // Attempt to spawn an obstacle + TrySpawnObstacle(); + } + } + + /// + /// Attempts to spawn an obstacle at a valid position + /// + private void TrySpawnObstacle() + { + Debug.Log($"[ObstacleSpawner] TrySpawnObstacle called at {Time.time:F2}"); + + if (obstaclePrefabs == null || obstaclePrefabs.Count == 0) + { + Debug.LogWarning("[ObstacleSpawner] No obstacle prefabs available for spawning!"); + return; + } + + Vector3 spawnPosition; + bool foundValidPosition = false; + + // Try to find a valid spawn position + for (int attempts = 0; attempts < maxSpawnAttempts; attempts++) + { + spawnPosition = GetRandomSpawnPosition(); + + if (IsValidSpawnPosition(spawnPosition)) + { + Debug.Log($"[ObstacleSpawner] Found valid position at {spawnPosition} after {attempts + 1} attempts"); + SpawnObstacleAt(spawnPosition); + foundValidPosition = true; + break; + } + else + { + Debug.Log($"[ObstacleSpawner] Position {spawnPosition} invalid (attempt {attempts + 1}/{maxSpawnAttempts})"); + } + } + + if (!foundValidPosition) + { + Debug.LogWarning($"[ObstacleSpawner] SPAWN MISSED: Could not find valid spawn position after {maxSpawnAttempts} attempts at {Time.time:F2}"); + } + } + + /// + /// Gets a random spawn position below the screen + /// + private Vector3 GetRandomSpawnPosition() + { + // Use dynamically calculated spawn range (80% of screen width) + float randomX = Random.Range(-_spawnRangeX, _spawnRangeX); + // Spawn 2 units below screen bottom + float spawnY = _screenBottom - 2f; + + return new Vector3(randomX, spawnY, 0f); + } + + /// + /// Checks if a spawn position is valid (not colliding with tiles) + /// + private bool IsValidSpawnPosition(Vector3 position) + { + // Use OverlapCircle to check for collisions with tiles + Collider2D collision = Physics2D.OverlapCircle(position, spawnCollisionRadius, tileLayerMask); + return collision == null; + } + + /// + /// Spawns an obstacle at the specified position + /// + private void SpawnObstacleAt(Vector3 position) + { + Debug.Log($"[ObstacleSpawner] SpawnObstacleAt called for position {position}"); + + // Select random prefab + int prefabIndex = Random.Range(0, obstaclePrefabs.Count); + GameObject prefab = obstaclePrefabs[prefabIndex]; + + if (prefab == null) + { + Debug.LogError($"[ObstacleSpawner] SPAWN FAILED: Obstacle prefab at index {prefabIndex} is null!"); + return; + } + + GameObject obstacle; + + // Spawn using pool or instantiate directly + if (useObjectPooling && _obstaclePool != null) + { + Debug.Log($"[ObstacleSpawner] Requesting obstacle from pool (prefab index {prefabIndex})"); + obstacle = _obstaclePool.GetObstacle(prefabIndex); + if (obstacle == null) + { + Debug.LogError($"[ObstacleSpawner] SPAWN FAILED: Failed to get obstacle from pool for prefab index {prefabIndex}!"); + return; + } + + Debug.Log($"[ObstacleSpawner] Got obstacle {obstacle.name} from pool, active state: {obstacle.activeInHierarchy}"); + + // FORCE ACTIVATION - bypass pool issues + if (!obstacle.activeInHierarchy) + { + Debug.LogWarning($"[ObstacleSpawner] Pool returned inactive object {obstacle.name}, force activating!"); + obstacle.SetActive(true); + Debug.Log($"[ObstacleSpawner] After force activation, {obstacle.name} active state: {obstacle.activeInHierarchy}"); + } + + obstacle.transform.position = position; + obstacle.transform.rotation = prefab.transform.rotation; + obstacle.transform.SetParent(transform); + Debug.Log($"[ObstacleSpawner] After positioning, obstacle {obstacle.name} active state: {obstacle.activeInHierarchy}"); + } + else + { + Debug.Log($"[ObstacleSpawner] Instantiating new obstacle (pooling disabled)"); + obstacle = Instantiate(prefab, position, prefab.transform.rotation, transform); + } + + // Assign unique name with counter + _obstacleCounter++; + string oldName = obstacle.name; + obstacle.name = $"Obstacle{_obstacleCounter:D3}"; + Debug.Log($"[ObstacleSpawner] Renamed obstacle from '{oldName}' to '{obstacle.name}', active state: {obstacle.activeInHierarchy}"); + + // Configure the obstacle + ConfigureObstacle(obstacle, prefabIndex); + Debug.Log($"[ObstacleSpawner] After configuration, obstacle {obstacle.name} active state: {obstacle.activeInHierarchy}"); + + // Track active obstacles + _activeObstacles.Add(obstacle); + + // Invoke events + onObstacleSpawned?.Invoke(obstacle); + Debug.Log($"[ObstacleSpawner] After events, obstacle {obstacle.name} active state: {obstacle.activeInHierarchy}"); + + Debug.Log($"[ObstacleSpawner] Successfully spawned obstacle {obstacle.name} at {position}. Active count: {_activeObstacles.Count}, Final active state: {obstacle.activeInHierarchy}"); + } + + /// + /// Configures an obstacle with randomized properties + /// + private void ConfigureObstacle(GameObject obstacle, int prefabIndex) + { + FloatingObstacle obstacleComponent = obstacle.GetComponent(); + if (obstacleComponent != null) + { + // Set prefab index + obstacleComponent.PrefabIndex = prefabIndex; + + // Randomize properties + obstacleComponent.MoveSpeed = Random.Range(minMoveSpeed, maxMoveSpeed); + + // Set spawner reference (since FloatingObstacle has this built-in now) + obstacleComponent.SetSpawner(this); + } + } + + /// + /// Returns an obstacle to the pool (called by FloatingObstacle) + /// + public void ReturnObstacleToPool(GameObject obstacle, int prefabIndex) + { + if (obstacle == null) return; + + Debug.Log($"[ObstacleSpawner] ReturnObstacleToPool called for {obstacle.name}, active state: {obstacle.activeInHierarchy}"); + + // Remove from active list + _activeObstacles.Remove(obstacle); + + // Invoke events + onObstacleDestroyed?.Invoke(obstacle); + + // Return to pool or destroy + if (useObjectPooling && _obstaclePool != null) + { + Debug.Log($"[ObstacleSpawner] Returning {obstacle.name} to pool"); + _obstaclePool.ReturnObstacle(obstacle, prefabIndex); + } + else + { + Debug.Log($"[ObstacleSpawner] Destroying {obstacle.name} (pooling disabled)"); + Destroy(obstacle); + } + } + + /// + /// Public method to change spawn interval at runtime + /// + public void SetSpawnInterval(float interval) + { + spawnInterval = interval; + } + + /// + /// Public method to change spawn range at runtime + /// + public void SetSpawnRange(float range) + { + spawnRangeX = range; + } + + /// + /// Public method to set speed range at runtime + /// + public void SetSpeedRange(float min, float max) + { + minMoveSpeed = min; + maxMoveSpeed = max; + } + + /// + /// Public method to recalculate screen bounds (useful if camera changes) + /// + public void RecalculateScreenBounds() + { + CalculateScreenBounds(); + } + + /// + /// Gets the count of currently active obstacles + /// + public int ActiveObstacleCount => _activeObstacles.Count; + +#if UNITY_EDITOR + private void OnDrawGizmosSelected() + { + // Only draw if screen bounds have been calculated + if (_spawnRangeX > 0f) + { + // Draw spawn area using dynamic calculations + Gizmos.color = Color.yellow; + Vector3 center = new Vector3(0f, _screenBottom - 2f, 0f); + Vector3 size = new Vector3(_spawnRangeX * 2f, 1f, 1f); + Gizmos.DrawWireCube(center, size); + + // Draw collision radius at spawn point + Gizmos.color = Color.red; + Gizmos.DrawWireSphere(center, spawnCollisionRadius); + } + } +#endif + } +} diff --git a/Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs.meta b/Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs.meta new file mode 100644 index 00000000..e8591a97 --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 49ec62157fd945fab730193e9ea0bff7 +timeCreated: 1758116903 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/PlayerBlinkBehavior.cs b/Assets/Scripts/Minigames/DivingForPictures/PlayerBlinkBehavior.cs new file mode 100644 index 00000000..022a779b --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/PlayerBlinkBehavior.cs @@ -0,0 +1,242 @@ +using UnityEngine; +using System.Collections; + +namespace Minigames.DivingForPictures +{ + /// + /// Handles visual feedback for player damage by making the sprite renderer blink red. + /// Automatically subscribes to damage events from PlayerCollisionBehavior components. + /// + 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; + + private bool _isBlinking; + private bool _isShowingDamageColor; + private Coroutine _blinkCoroutine; + private Color _originalColor; // Missing field declaration + + private void Awake() + { + // Auto-assign sprite renderer if not set + if (targetSpriteRenderer == null) + { + targetSpriteRenderer = GetComponent(); + if (targetSpriteRenderer == null) + { + targetSpriteRenderer = GetComponentInChildren(); + if (targetSpriteRenderer != null) + { + Debug.Log($"[PlayerBlinkBehavior] Found SpriteRenderer on child object: {targetSpriteRenderer.gameObject.name}"); + } + } + } + + if (targetSpriteRenderer == null) + { + Debug.LogError("[PlayerBlinkBehavior] No SpriteRenderer found on this GameObject or its children!"); + return; + } + + // Store 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; + } + + private void OnDisable() + { + // Unsubscribe from immunity events + PlayerCollisionBehavior.OnImmunityStarted -= StartBlinking; + PlayerCollisionBehavior.OnImmunityEnded -= StopBlinking; + + // Stop any ongoing blink effect + if (_blinkCoroutine != null) + { + StopCoroutine(_blinkCoroutine); + _blinkCoroutine = null; + } + + // Restore original color + RestoreOriginalColor(); + } + + /// + /// Starts the red blinking effect when damage begins + /// + private void StartBlinking() + { + if (targetSpriteRenderer == null) return; + + Debug.Log("[PlayerBlinkBehavior] Starting damage blink effect"); + + // Stop any existing blink coroutine + if (_blinkCoroutine != null) + { + StopCoroutine(_blinkCoroutine); + } + + _isBlinking = true; + _isShowingDamageColor = false; + + // Start the blink coroutine + _blinkCoroutine = StartCoroutine(BlinkCoroutine()); + } + + /// + /// Stops the blinking effect when damage ends + /// + private void StopBlinking() + { + Debug.Log("[PlayerBlinkBehavior] Stopping damage blink effect"); + + _isBlinking = false; + + // Stop the blink coroutine + if (_blinkCoroutine != null) + { + StopCoroutine(_blinkCoroutine); + _blinkCoroutine = null; + } + + // Restore original color + RestoreOriginalColor(); + } + + /// + /// Coroutine that handles the blinking animation + /// + private IEnumerator BlinkCoroutine() + { + while (_isBlinking && targetSpriteRenderer != null) + { + // Toggle between original and damage colors + if (_isShowingDamageColor) + { + targetSpriteRenderer.color = _originalColor; + _isShowingDamageColor = false; + } + else + { + targetSpriteRenderer.color = damageBlinkColor; + _isShowingDamageColor = true; + } + + // Wait for blink interval + yield return new WaitForSeconds(blinkRate); + } + } + + /// + /// Restores the sprite renderer to its original color + /// + private void RestoreOriginalColor() + { + if (targetSpriteRenderer != null) + { + targetSpriteRenderer.color = _originalColor; + _isShowingDamageColor = false; + } + } + + /// + /// Updates the original color reference (useful if sprite color changes during gameplay) + /// + public void UpdateOriginalColor() + { + if (targetSpriteRenderer != null && !_isBlinking) + { + _originalColor = targetSpriteRenderer.color; + } + } + + /// + /// Public method to change blink color at runtime + /// + public void SetDamageBlinkColor(Color newColor) + { + damageBlinkColor = newColor; + damageBlinkColor.a = damageColorAlpha; + } + + /// + /// Public method to change blink rate at runtime + /// + public void SetBlinkRate(float rate) + { + blinkRate = rate; + } + + /// + /// Check if currently blinking + /// + public bool IsBlinking => _isBlinking; + + /// + /// Manually trigger blink effect (useful for testing or other damage sources) + /// + public void TriggerBlink(float duration) + { + if (_blinkCoroutine != null) + { + StopCoroutine(_blinkCoroutine); + } + + StartCoroutine(ManualBlinkCoroutine(duration)); + } + + /// + /// Coroutine for manually triggered blink effects + /// + private IEnumerator ManualBlinkCoroutine(float duration) + { + _isBlinking = true; + _isShowingDamageColor = false; + + float elapsed = 0f; + + while (elapsed < duration && targetSpriteRenderer != null) + { + // Toggle between original and damage colors + if (_isShowingDamageColor) + { + targetSpriteRenderer.color = _originalColor; + _isShowingDamageColor = false; + } + else + { + targetSpriteRenderer.color = damageBlinkColor; + _isShowingDamageColor = true; + } + + yield return new WaitForSeconds(blinkRate); + elapsed += blinkRate; + } + + // Ensure we end with original color + RestoreOriginalColor(); + _isBlinking = false; + } + } +} diff --git a/Assets/Scripts/Minigames/DivingForPictures/PlayerBlinkBehavior.cs.meta b/Assets/Scripts/Minigames/DivingForPictures/PlayerBlinkBehavior.cs.meta new file mode 100644 index 00000000..74837a08 --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/PlayerBlinkBehavior.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d8ea29cc80524de8affe17b930cd75c1 +timeCreated: 1758114229 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/PlayerCollisionBehavior.cs b/Assets/Scripts/Minigames/DivingForPictures/PlayerCollisionBehavior.cs new file mode 100644 index 00000000..d7c7a3be --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/PlayerCollisionBehavior.cs @@ -0,0 +1,371 @@ +using UnityEngine; +using System; +using System.Collections; + +namespace Minigames.DivingForPictures +{ + /// + /// Base class for handling player collisions with world obstacles. + /// Uses trigger-based collision detection with shared immunity state across all collision behaviors. + /// + 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; + + [Tooltip("Reference to the PlayerController component (auto-assigned if empty)")] + [SerializeField] protected PlayerController playerController; + + // Static shared immunity state across all collision behaviors + private static bool _isGloballyImmune; + private static Coroutine _globalImmunityCoroutine; + private static MonoBehaviour _coroutineRunner; + private static Collider2D _sharedPlayerCollider; + + // Events for immunity and damage state changes + public static event Action OnImmunityStarted; + public static event Action OnImmunityEnded; + public static event Action OnDamageTaken; + + // Instance tracking for shared state management + private static readonly System.Collections.Generic.HashSet _allInstances = + new System.Collections.Generic.HashSet(); + + /// + /// Public static method to trigger immunity start event from external classes + /// + public static void TriggerImmunityStarted() + { + OnImmunityStarted?.Invoke(); + } + + /// + /// Public static method to trigger immunity end event from external classes + /// + public static void TriggerImmunityEnded() + { + OnImmunityEnded?.Invoke(); + } + + /// + /// Public static method to trigger damage taken event from external classes + /// + public static void TriggerDamageTaken() + { + OnDamageTaken?.Invoke(); + } + + protected bool wasInputBlocked; + + protected virtual void Awake() + { + // Auto-assign if not set in inspector + if (playerCharacter == null) + playerCharacter = gameObject; + + if (playerController == null) + playerController = GetComponent(); + + // Set up shared collider reference (only once) + if (_sharedPlayerCollider == null) + { + _sharedPlayerCollider = GetComponent(); + if (_sharedPlayerCollider == null) + { + _sharedPlayerCollider = GetComponentInChildren(); + if (_sharedPlayerCollider != null) + { + Debug.Log($"[PlayerCollisionBehavior] Found collider on child object: {_sharedPlayerCollider.gameObject.name}"); + } + } + + if (_sharedPlayerCollider == null) + { + Debug.LogError($"[PlayerCollisionBehavior] No Collider2D found on this GameObject or its children!"); + } + } + + // Set up coroutine runner (use first instance) + 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) + { + if (_globalImmunityCoroutine != null) + { + StopCoroutine(_globalImmunityCoroutine); + _globalImmunityCoroutine = null; + } + _coroutineRunner = null; + + // Find a new coroutine runner if there are other instances + foreach (var instance in _allInstances) + { + if (instance != null) + { + _coroutineRunner = instance; + break; + } + } + } + } + + /// + /// Called when another collider enters this trigger collider + /// + /// The other collider that entered the trigger + private void OnTriggerEnter2D(Collider2D other) + { + Debug.Log($"[{GetType().Name}] OnTriggerEnter2D called with collider: {other.gameObject.name} on layer: {other.gameObject.layer}"); + + // Check if the other collider is on one of our obstacle layers and we're not immune + if (IsObstacleLayer(other.gameObject.layer) && !_isGloballyImmune) + { + OnCollisionDetected(other); + } + } + + /// + /// Called when a collision with an obstacle is detected + /// + /// The obstacle collider that was hit + protected virtual void OnCollisionDetected(Collider2D obstacle) + { + if (_isGloballyImmune) return; + + // Trigger damage taken event first + OnDamageTaken?.Invoke(); + + // Start shared immunity period + StartGlobalImmunity(); + + // Call the specific collision response + HandleCollisionResponse(obstacle); + } + + /// + /// Starts the shared immunity period across all collision behaviors + /// + private void StartGlobalImmunity() + { + if (_isGloballyImmune) return; // Already immune + + _isGloballyImmune = true; + + // Disable the shared collider to prevent further collisions + if (_sharedPlayerCollider != null) + { + _sharedPlayerCollider.enabled = false; + } + + // Stop any existing immunity coroutine + 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(); + } + + /// + /// Coroutine that handles the immunity timer + /// + private IEnumerator ImmunityCoroutine() + { + Debug.Log($"[PlayerCollisionBehavior] Starting immunity coroutine for {damageImmunityDuration} seconds"); + + yield return new WaitForSeconds(damageImmunityDuration); + + Debug.Log($"[PlayerCollisionBehavior] Immunity period ended"); + + // End immunity + _isGloballyImmune = false; + _globalImmunityCoroutine = null; + + // Re-enable the shared collider + if (_sharedPlayerCollider != null) + { + _sharedPlayerCollider.enabled = true; + } + + // Notify all instances about immunity end + foreach (var instance in _allInstances) + { + if (instance != null) + { + instance.OnImmunityEnd(); + } + } + + // Broadcast immunity end event + OnImmunityEnded?.Invoke(); + } + + /// + /// Override this method to implement specific collision response behavior + /// + /// The obstacle that was collided with + protected abstract void HandleCollisionResponse(Collider2D obstacle); + + /// + /// Called when damage immunity starts (called on all instances) + /// + protected virtual void OnImmunityStart() + { + Debug.Log($"[{GetType().Name}] Damage immunity started for {damageImmunityDuration} seconds"); + + // Block input if specified + if (blockInputDuringImmunity) + { + BlockPlayerInput(); + } + } + + /// + /// Called when damage immunity ends (called on all instances) + /// + protected virtual void OnImmunityEnd() + { + Debug.Log($"[{GetType().Name}] Damage immunity ended"); + + // Restore input if it was blocked + if (wasInputBlocked) + { + RestorePlayerInput(); + } + } + + /// + /// Restores player input after immunity + /// + 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"); + } + } + + /// + /// Blocks player input during immunity + /// + protected virtual void BlockPlayerInput() + { + if (playerController != null && playerController.enabled) + { + playerController.enabled = false; + wasInputBlocked = true; + Debug.Log($"[{GetType().Name}] Player input blocked during immunity"); + } + } + + /// + /// Updates the PlayerController's internal target to match current position + /// + 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); + } + } + } + + /// + /// Checks if the given layer is included in our obstacle layer mask + /// + /// The layer to check + /// True if the layer is included in the obstacle layer mask + private bool IsObstacleLayer(int layer) + { + return (obstacleLayerMask.value & (1 << layer)) != 0; + } + + /// + /// Public property to check if player is currently immune (shared across all instances) + /// + public static bool IsImmune => _isGloballyImmune; + + /// + /// Public method to manually end immunity (affects all collision behaviors) + /// + public static void EndImmunity() + { + if (_isGloballyImmune && _globalImmunityCoroutine != null && _coroutineRunner != null) + { + _coroutineRunner.StopCoroutine(_globalImmunityCoroutine); + _globalImmunityCoroutine = null; + _isGloballyImmune = false; + + // Re-enable the shared collider + if (_sharedPlayerCollider != null) + { + _sharedPlayerCollider.enabled = true; + } + + // Notify all instances + foreach (var instance in _allInstances) + { + if (instance != null) + { + instance.OnImmunityEnd(); + } + } + + OnImmunityEnded?.Invoke(); + } + } + } +} diff --git a/Assets/Scripts/Minigames/DivingForPictures/PlayerCollisionBehavior.cs.meta b/Assets/Scripts/Minigames/DivingForPictures/PlayerCollisionBehavior.cs.meta new file mode 100644 index 00000000..6aaae8b0 --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/PlayerCollisionBehavior.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e6c959bca2e24e72bf22e92439580d79 +timeCreated: 1758109598 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/PlayerController.cs b/Assets/Scripts/Minigames/DivingForPictures/PlayerController.cs index 899fea46..06f2392b 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/PlayerController.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/PlayerController.cs @@ -31,7 +31,7 @@ namespace Minigames.DivingForPictures /// public void OnTap(Vector2 worldPosition) { - Debug.Log($"[EndlessDescenderController] OnTap at {worldPosition}"); + // Debug.Log($"[EndlessDescenderController] OnTap at {worldPosition}"); _targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); _isTouchActive = true; } @@ -41,7 +41,7 @@ namespace Minigames.DivingForPictures /// public void OnHoldStart(Vector2 worldPosition) { - Debug.Log($"[EndlessDescenderController] OnHoldStart at {worldPosition}"); + // Debug.Log($"[EndlessDescenderController] OnHoldStart at {worldPosition}"); _targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); _isTouchActive = true; } @@ -51,7 +51,7 @@ namespace Minigames.DivingForPictures /// public void OnHoldMove(Vector2 worldPosition) { - Debug.Log($"[EndlessDescenderController] OnHoldMove at {worldPosition}"); + // Debug.Log($"[EndlessDescenderController] OnHoldMove at {worldPosition}"); _targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); } @@ -60,7 +60,7 @@ namespace Minigames.DivingForPictures /// public void OnHoldEnd(Vector2 worldPosition) { - Debug.Log($"[EndlessDescenderController] OnHoldEnd at {worldPosition}"); + // Debug.Log($"[EndlessDescenderController] OnHoldEnd at {worldPosition}"); _isTouchActive = false; } diff --git a/Assets/Scripts/Minigames/DivingForPictures/TileBumpCollision.cs b/Assets/Scripts/Minigames/DivingForPictures/TileBumpCollision.cs new file mode 100644 index 00000000..ab9efb77 --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/TileBumpCollision.cs @@ -0,0 +1,294 @@ +using UnityEngine; +using System.Collections; + +namespace Minigames.DivingForPictures +{ + /// + /// Collision behavior that bumps the player toward the center of the trench. + /// Uses trigger-based collision detection with coroutine-based bump timing. + /// + 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) + { + case BumpMode.Impulse: + StartImpulseBump(); + break; + + case BumpMode.SmoothToCenter: + StartSmoothMoveToCenter(); + break; + } + + Debug.Log($"[TileBumpCollision] Collision handled with {bumpMode} mode"); + } + + /// + /// Starts an impulse bump toward the center with force-based distance + /// + 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})"); + } + + /// + /// Starts smooth movement to the center + /// + 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)"); + } + + /// + /// Common bump initialization using coroutines + /// + private void StartBump(float startX, float targetX, float duration) + { + // Stop any existing bump + if (_bumpCoroutine != null) + { + StopCoroutine(_bumpCoroutine); + _bumpCoroutine = null; + } + + _isBumping = true; + + // Block player input if enabled (use bump-specific blocking) + if (blockInputDuringBump && playerController != null && playerController.enabled) + { + playerController.enabled = false; + _bumpInputBlocked = true; + Debug.Log("[TileBumpCollision] Player input blocked during bump"); + } + + // Start bump coroutine + _bumpCoroutine = StartCoroutine(BumpCoroutine(startX, targetX, duration)); + } + + /// + /// Coroutine that handles the bump movement over time + /// + private IEnumerator BumpCoroutine(float startX, float targetX, float duration) + { + float elapsedTime = 0f; + + while (elapsedTime < duration) + { + elapsedTime += Time.deltaTime; + + // Calculate progress and apply curve + float progress = elapsedTime / duration; + float curveValue = bumpCurve.Evaluate(progress); + + // Interpolate position + float currentX = Mathf.Lerp(startX, targetX, curveValue); + + // 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); + } + + 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 + _isBumping = false; + _bumpCoroutine = null; + + if (_bumpInputBlocked) + { + RestoreBumpInput(); + } + + Debug.Log("[TileBumpCollision] Bump movement completed"); + } + + /// + /// Restores player input after bump movement + /// + private void RestoreBumpInput() + { + if (_bumpInputBlocked && playerController != null) + { + playerController.enabled = true; + _bumpInputBlocked = false; + + // Update the controller's target position to current position to prevent snapping + UpdateControllerTarget(); + + Debug.Log("[TileBumpCollision] Player input restored after bump"); + } + } + + /// + /// Override to handle bump-specific input blocking during immunity + /// + protected override void OnImmunityStart() + { + Debug.Log($"[TileBumpCollision] Damage immunity started for {damageImmunityDuration} seconds"); + + // Block input if specified (in addition to any bump input blocking) + if (blockInputDuringImmunity && !_bumpInputBlocked) + { + BlockPlayerInput(); + } + } + + /// + /// Override to handle immunity end and bump cleanup + /// + protected override void OnImmunityEnd() + { + base.OnImmunityEnd(); + + // Stop any ongoing bump if immunity ends + if (_isBumping && _bumpCoroutine != null) + { + StopCoroutine(_bumpCoroutine); + _bumpCoroutine = null; + _isBumping = false; + + if (_bumpInputBlocked) + { + RestoreBumpInput(); + } + + Debug.Log("[TileBumpCollision] Bump interrupted by immunity end"); + } + } + + /// + /// Called when component is destroyed - cleanup coroutines + /// + private void OnDestroy() + { + if (_bumpCoroutine != null) + { + StopCoroutine(_bumpCoroutine); + _bumpCoroutine = null; + } + } + + /// + /// Public method to change bump mode at runtime + /// + public void SetBumpMode(BumpMode mode) + { + bumpMode = mode; + } + + /// + /// Public method to change bump force at runtime + /// + public void SetBumpForce(float force) + { + bumpForce = force; + } + + /// + /// Public method to change smooth move speed at runtime + /// + public void SetSmoothMoveSpeed(float speed) + { + smoothMoveSpeed = speed; + } + + /// + /// Check if player is currently being bumped + /// + public bool IsBumping => _isBumping; + + /// + /// Check if input is currently blocked by bump + /// + public bool IsBumpInputBlocked => _bumpInputBlocked; + + /// + /// Public method to manually stop bump movement + /// + public void StopBump() + { + if (_isBumping && _bumpCoroutine != null) + { + StopCoroutine(_bumpCoroutine); + _bumpCoroutine = null; + _isBumping = false; + + if (_bumpInputBlocked) + { + RestoreBumpInput(); + } + + Debug.Log("[TileBumpCollision] Bump manually stopped"); + } + } + } +} diff --git a/Assets/Scripts/Minigames/DivingForPictures/TileBumpCollision.cs.meta b/Assets/Scripts/Minigames/DivingForPictures/TileBumpCollision.cs.meta new file mode 100644 index 00000000..78fed5e3 --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/TileBumpCollision.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8222f0e3aeeb4fc4975aaead6cf7afbe +timeCreated: 1758109619 \ No newline at end of file diff --git a/Assets/Scripts/Pooling/MultiPrefabPool.cs b/Assets/Scripts/Pooling/MultiPrefabPool.cs index 978d1131..75e2d0da 100644 --- a/Assets/Scripts/Pooling/MultiPrefabPool.cs +++ b/Assets/Scripts/Pooling/MultiPrefabPool.cs @@ -100,7 +100,7 @@ namespace Pooling /// An object ready to use public virtual T Get(int prefabIndex) { - T obj; + T obj = null; // Track usage frequency if (prefabUsageCount.ContainsKey(prefabIndex)) @@ -112,27 +112,71 @@ namespace Pooling prefabUsageCount[prefabIndex] = 1; } + // Try to get a valid object from the pool, cleaning up any destroyed objects if (pooledObjects.ContainsKey(prefabIndex) && pooledObjects[prefabIndex].Count > 0) { - obj = pooledObjects[prefabIndex].Pop(); - totalPooledCount--; + Debug.Log($"[{GetType().Name}] Found {pooledObjects[prefabIndex].Count} objects in pool for prefab index {prefabIndex}"); + + // Keep trying until we find a valid object or the pool is empty + while (pooledObjects[prefabIndex].Count > 0) + { + obj = pooledObjects[prefabIndex].Pop(); + totalPooledCount--; + + // Check if the object is still valid (not destroyed) + if (obj != null && obj.gameObject != null) + { + Debug.Log($"[{GetType().Name}] Retrieved valid object {obj.name} from pool, current active state: {obj.gameObject.activeInHierarchy}"); + break; // Found a valid object + } + else + { + // Object was destroyed, continue looking + Debug.LogWarning($"[{GetType().Name}] Found destroyed object in pool, removing it"); + obj = null; + } + } } else { - // Create new object without adding to pool - T prefab = prefabs[prefabIndex]; - obj = Instantiate(prefab, transform); + Debug.Log($"[{GetType().Name}] No objects in pool for prefab index {prefabIndex}, creating new one"); } + // If we couldn't find a valid object in the pool, create a new one + if (obj == null) + { + T prefab = prefabs[prefabIndex]; + obj = Instantiate(prefab, transform); + Debug.Log($"[{GetType().Name}] Created new object {obj.name} from prefab, active state: {obj.gameObject.activeInHierarchy}"); + } + + // Ensure the object is valid before proceeding + if (obj == null || obj.gameObject == null) + { + Debug.LogError($"[{GetType().Name}] Failed to create valid object for prefab index {prefabIndex}"); + return null; + } + + // CRITICAL FIX: Reset position to safe location BEFORE activation + // This prevents off-screen checks from triggering during spawn process + Vector3 originalPosition = obj.transform.position; + obj.transform.position = new Vector3(0f, -1000f, 0f); + Debug.Log($"[{GetType().Name}] Moved object {obj.name} from {originalPosition} to safe position before activation"); + + Debug.Log($"[{GetType().Name}] About to activate object {obj.name}, current state: {obj.gameObject.activeInHierarchy}"); obj.gameObject.SetActive(true); + Debug.Log($"[{GetType().Name}] After SetActive(true), object {obj.name} state: {obj.gameObject.activeInHierarchy}"); // Call OnSpawn for IPoolable components IPoolable poolable = obj.GetComponent(); if (poolable != null) { + Debug.Log($"[{GetType().Name}] Calling OnSpawn for object {obj.name}"); poolable.OnSpawn(); + Debug.Log($"[{GetType().Name}] After OnSpawn, object {obj.name} state: {obj.gameObject.activeInHierarchy}"); } + Debug.Log($"[{GetType().Name}] Returning object {obj.name} with final state: {obj.gameObject.activeInHierarchy}"); return obj; } @@ -152,33 +196,61 @@ namespace Pooling poolable.OnDespawn(); } - // Check if we're under the maximum pool size for this prefab type - bool keepObject = totalPooledCount < totalMaxPoolSize; + // Always deactivate and parent the object + obj.gameObject.SetActive(false); + obj.transform.SetParent(transform); - // Additional constraint: don't keep too many of any single prefab type - if (pooledObjects.ContainsKey(prefabIndex) && - pooledObjects[prefabIndex].Count >= maxPerPrefabPoolSize) + // Initialize stack if it doesn't exist + if (!pooledObjects.ContainsKey(prefabIndex)) { - keepObject = false; + pooledObjects[prefabIndex] = new Stack(); } - if (keepObject) + // Check if we need to trim this specific prefab type's pool + if (pooledObjects[prefabIndex].Count >= maxPerPrefabPoolSize) { - obj.gameObject.SetActive(false); - obj.transform.SetParent(transform); - - if (!pooledObjects.ContainsKey(prefabIndex)) + // Remove the oldest object from this prefab's pool to make room + if (pooledObjects[prefabIndex].Count > 0) { - pooledObjects[prefabIndex] = new Stack(); + T oldestObj = pooledObjects[prefabIndex].Pop(); + if (oldestObj != null && oldestObj.gameObject != null) + { + Destroy(oldestObj.gameObject); + totalPooledCount--; + } + } + } + + // Check global pool limit + if (totalPooledCount >= totalMaxPoolSize) + { + // Find the prefab type with the most pooled objects and remove one + int maxCount = 0; + int prefabToTrim = -1; + + foreach (var kvp in pooledObjects) + { + if (kvp.Value.Count > maxCount) + { + maxCount = kvp.Value.Count; + prefabToTrim = kvp.Key; + } } - pooledObjects[prefabIndex].Push(obj); - totalPooledCount++; - } - else - { - Destroy(obj.gameObject); + if (prefabToTrim >= 0 && pooledObjects[prefabToTrim].Count > 0) + { + T oldestObj = pooledObjects[prefabToTrim].Pop(); + if (oldestObj != null && oldestObj.gameObject != null) + { + Destroy(oldestObj.gameObject); + totalPooledCount--; + } + } } + + // Now add the current object to the pool + pooledObjects[prefabIndex].Push(obj); + totalPooledCount++; } /// diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset index 88875ac6..c795aa89 100644 --- a/ProjectSettings/TagManager.asset +++ b/ProjectSettings/TagManager.asset @@ -18,8 +18,8 @@ TagManager: - Pulver - WorldBoundary - Interactable - - - - + - QuarryObstacle + - QuarryMonster - - -