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;
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;
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();
}
///
/// 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)
{
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;
}
}
}