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
index 069ad5a0..11a58e49 100644
--- a/Assets/Scripts/Minigames/DivingForPictures/FloatingObstacle.cs
+++ b/Assets/Scripts/Minigames/DivingForPictures/FloatingObstacle.cs
@@ -1,4 +1,5 @@
using UnityEngine;
+using System.Collections;
using Pooling;
namespace Minigames.DivingForPictures
@@ -7,6 +8,7 @@ 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
{
@@ -42,6 +44,8 @@ namespace Minigames.DivingForPictures
private Collider2D _collider;
private Camera _mainCamera;
private float _screenTop;
+ private Coroutine _movementCoroutine;
+ private Coroutine _offScreenCheckCoroutine;
private void Awake()
{
@@ -60,22 +64,77 @@ namespace Minigames.DivingForPictures
_mainCamera = Camera.main;
}
- private void Update()
+ private void OnEnable()
{
- if (enableMovement)
- {
- HandleMovement();
- }
-
- CheckIfOffScreen();
+ StartObstacleBehavior();
+ }
+
+ private void OnDisable()
+ {
+ StopObstacleBehavior();
}
///
- /// Moves the obstacle upward based on its speed
+ /// Starts the obstacle behavior coroutines
///
- private void HandleMovement()
+ private void StartObstacleBehavior()
{
- transform.position += Vector3.up * (moveSpeed * Time.deltaTime);
+ 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);
+ }
}
///
@@ -177,5 +236,22 @@ namespace Minigames.DivingForPictures
{
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/ObstacleSpawner.cs b/Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs
index d5d68581..0c9031d1 100644
--- a/Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs
+++ b/Assets/Scripts/Minigames/DivingForPictures/ObstacleSpawner.cs
@@ -32,7 +32,7 @@ namespace Minigames.DivingForPictures
[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;
@@ -71,6 +71,7 @@ namespace Minigames.DivingForPictures
private ObstaclePool _obstaclePool;
private Camera _mainCamera;
private float _screenBottom;
+ private float _spawnRangeX;
private Coroutine _spawnCoroutine;
private readonly List _activeObstacles = new List();
@@ -174,7 +175,7 @@ namespace Minigames.DivingForPictures
}
///
- /// Calculate screen bounds in world space
+ /// Calculate screen bounds in world space dynamically
///
private void CalculateScreenBounds()
{
@@ -188,8 +189,19 @@ namespace Minigames.DivingForPictures
}
}
+ // 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}");
}
///
@@ -273,9 +285,11 @@ namespace Minigames.DivingForPictures
///
private Vector3 GetRandomSpawnPosition()
{
- float randomX = Random.Range(-spawnRangeX, spawnRangeX);
- float spawnY = _screenBottom - spawnDistanceBelowScreen;
-
+ // 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);
}
@@ -416,6 +430,14 @@ namespace Minigames.DivingForPictures
maxMoveSpeed = max;
}
+ ///
+ /// Public method to recalculate screen bounds (useful if camera changes)
+ ///
+ public void RecalculateScreenBounds()
+ {
+ CalculateScreenBounds();
+ }
+
///
/// Gets the count of currently active obstacles
///
@@ -424,15 +446,19 @@ namespace Minigames.DivingForPictures
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
- // Draw spawn area
- Gizmos.color = Color.yellow;
- Vector3 center = new Vector3(0f, _screenBottom - spawnDistanceBelowScreen, 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);
+ // 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
}