using UnityEngine; using System.Collections; using Pooling; using AppleHills.Core.Interfaces; 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, IPausable { public float speed = 1f; public float wobbleSpeed = 1f; private SpriteRenderer spriteRenderer; private SpriteRenderer bubbleSpriteRenderer; private float timeOffset; private float minScale = 0.2f; private float maxScale = 1.2f; private float baseScale = 1f; private Camera mainCamera; private BubblePool parentPool; // Coroutine references private Coroutine _movementCoroutine; private Coroutine _wobbleCoroutine; private Coroutine _offScreenCheckCoroutine; // Pause state tracking private bool _isPaused = false; // IPausable implementation public bool IsPaused => _isPaused; 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) { bubbleSpriteRenderer = bubbleSpriteTransform.GetComponent(); } // Cache camera reference mainCamera = Camera.main; } private void OnEnable() { StartBubbleBehavior(); } private void OnDisable() { StopBubbleBehavior(); } /// /// Pauses all bubble behaviors /// public void Pause() { if (_isPaused) return; // Already paused _isPaused = true; StopBubbleBehavior(); // Debug log for troubleshooting Debug.Log($"[Bubble] Paused bubble: {name}"); } /// /// Resumes all bubble behaviors /// public void Resume() { if (!_isPaused) return; // Already running _isPaused = false; StartBubbleBehavior(); // Debug log for troubleshooting Debug.Log($"[Bubble] Resumed bubble: {name}"); } /// /// Starts all bubble behavior coroutines /// private void StartBubbleBehavior() { if (_isPaused) return; // Don't start if paused _movementCoroutine = StartCoroutine(MovementCoroutine()); _wobbleCoroutine = StartCoroutine(WobbleCoroutine()); _offScreenCheckCoroutine = StartCoroutine(OffScreenCheckCoroutine()); } /// /// Stops all bubble behavior coroutines /// private void StopBubbleBehavior() { if (_movementCoroutine != null) { 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); } } /// /// Called when bubble is about to be destroyed /// private void OnBubbleDestroy() { // Use the cached pool reference instead of finding it each time if (parentPool != null) { parentPool.ReturnBubble(this); } else { // Fallback to find the pool if the reference is somehow lost BubblePool pool = FindFirstObjectByType(); if (pool != null) { Debug.LogWarning("Bubble is missing its parent pool reference, finding pool as fallback"); pool.ReturnBubble(this); } else { Destroy(gameObject); } } } /// /// Sets the parent pool for this bubble /// /// The bubble pool that created this bubble public void SetPool(BubblePool pool) { parentPool = pool; } /// /// Called when the object is retrieved from the pool. /// public void OnSpawn() { ResetState(); } /// /// Called when the object is returned to the pool. /// public void OnDespawn() { // Nothing to do here for now, but we could clean up resources } /// /// Sets the main sprite for the bubble. /// /// Sprite to assign. public void SetSprite(Sprite sprite) { if (spriteRenderer != null) spriteRenderer.sprite = sprite; } /// /// Sets the sprite for the child "BubbleSprite" renderer. /// /// Sprite to assign. public void SetBubbleSprite(Sprite sprite) { if (bubbleSpriteRenderer != null) bubbleSpriteRenderer.sprite = sprite; } /// /// Sets the base scale for the bubble /// /// Base scale value public void SetBaseScale(float scale) { baseScale = scale; } /// /// Sets the minimum and maximum scale for the wobble effect. /// /// Minimum scale. /// Maximum scale. public void SetWobbleScaleLimits(float min, float max) { 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 /// public void ResetState() { timeOffset = Random.value * 100f; } } }