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 }