import 'dart:math'; import 'package:flame/components.dart'; import 'bubble.dart'; class BubbleSpawner extends Component with HasGameReference { static const double baseSpawnInterval = 1.5; // base seconds static const double spawnVariation = 1.0; // +/- variation in seconds static const int maxBubbles = 8; double _timeSinceLastSpawn = 0; double _nextSpawnTime = 0; final List _activeBubbles = []; final Random _random = Random(); Function(Bubble, bool)? onBubblePopped; bool isActive = true; BubbleSpawner({this.onBubblePopped}) { _calculateNextSpawnTime(); } double _lastCleanupTime = 0; static const double cleanupInterval = 0.5; // Clean up bubbles every 0.5 seconds instead of every frame @override void update(double dt) { super.update(dt); if (!isActive) return; _timeSinceLastSpawn += dt; _lastCleanupTime += dt; // Spawn new bubble if conditions are met if (_timeSinceLastSpawn >= _nextSpawnTime && _activeBubbles.length < maxBubbles) { _spawnBubble(); _timeSinceLastSpawn = 0; _calculateNextSpawnTime(); } // Clean up popped bubbles less frequently to improve performance if (_lastCleanupTime >= cleanupInterval) { _activeBubbles.removeWhere((bubble) => !bubble.isMounted); _lastCleanupTime = 0; } } void _spawnBubble() { if (game.size.x == 0 || game.size.y == 0) return; final spawnPosition = _getValidSpawnPosition(); if (spawnPosition == null) return; final bubble = Bubble( position: spawnPosition, onPop: _onBubblePopped, ); _activeBubbles.add(bubble); game.add(bubble); } Vector2? _getValidSpawnPosition() { // Calculate safe spawn area (avoiding edges and UI elements) // Increased margin to account for larger bubble sprites const margin = 120.0; final minX = margin; final maxX = game.size.x - margin; final minY = margin + 100; // Extra margin for score display final maxY = game.size.y - margin; if (maxX <= minX || maxY <= minY) return null; return Vector2( minX + _random.nextDouble() * (maxX - minX), minY + _random.nextDouble() * (maxY - minY), ); } void _onBubblePopped(Bubble bubble, bool userTriggered) { _activeBubbles.remove(bubble); onBubblePopped?.call(bubble, userTriggered); } void spawnBubbleAt(Vector2 position) { // Ensure the position is within valid bounds final validPosition = _clampPositionToBounds(position); final bubble = Bubble( position: validPosition, onPop: _onBubblePopped, ); _activeBubbles.add(bubble); game.add(bubble); } Vector2 _clampPositionToBounds(Vector2 position) { const margin = 120.0; final minX = margin; final maxX = game.size.x - margin; final minY = margin + 100; final maxY = game.size.y - margin; if (maxX <= minX || maxY <= minY) return position; return Vector2( position.x.clamp(minX, maxX), position.y.clamp(minY, maxY), ); } void clearAllBubbles() { for (final bubble in _activeBubbles) { if (bubble.isMounted) { bubble.removeFromParent(); } } _activeBubbles.clear(); } void setActive(bool active) { isActive = active; } void _calculateNextSpawnTime() { // Random spawn time between baseSpawnInterval - spawnVariation and baseSpawnInterval + spawnVariation _nextSpawnTime = baseSpawnInterval + (_random.nextDouble() - 0.5) * 2 * spawnVariation; // Ensure minimum spawn time of 0.5 seconds _nextSpawnTime = _nextSpawnTime.clamp(0.5, double.infinity); } int get activeBubbleCount => _activeBubbles.length; /// Get list of active bubbles for performance optimization List getActiveBubbles() => List.unmodifiable(_activeBubbles); }