bubble_spawner.dart 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import 'dart:math';
  2. import 'package:flame/components.dart';
  3. import 'bubble.dart';
  4. class BubbleSpawner extends Component with HasGameReference {
  5. static const double baseSpawnInterval = 1.5; // base seconds
  6. static const double spawnVariation = 1.0; // +/- variation in seconds
  7. static const int maxBubbles = 8;
  8. double _timeSinceLastSpawn = 0;
  9. double _nextSpawnTime = 0;
  10. final List<Bubble> _activeBubbles = [];
  11. final Random _random = Random();
  12. Function(Bubble, bool)? onBubblePopped;
  13. bool isActive = true;
  14. BubbleSpawner({this.onBubblePopped}) {
  15. _calculateNextSpawnTime();
  16. }
  17. @override
  18. void update(double dt) {
  19. super.update(dt);
  20. if (!isActive) return;
  21. _timeSinceLastSpawn += dt;
  22. // Spawn new bubble if conditions are met
  23. if (_timeSinceLastSpawn >= _nextSpawnTime && _activeBubbles.length < maxBubbles) {
  24. _spawnBubble();
  25. _timeSinceLastSpawn = 0;
  26. _calculateNextSpawnTime();
  27. }
  28. // Clean up popped bubbles
  29. _activeBubbles.removeWhere((bubble) => !bubble.isMounted);
  30. }
  31. void _spawnBubble() {
  32. if (game.size.x == 0 || game.size.y == 0) return;
  33. final spawnPosition = _getValidSpawnPosition();
  34. if (spawnPosition == null) return;
  35. final bubble = Bubble(
  36. position: spawnPosition,
  37. onPop: _onBubblePopped,
  38. );
  39. _activeBubbles.add(bubble);
  40. game.add(bubble);
  41. }
  42. Vector2? _getValidSpawnPosition() {
  43. // Calculate safe spawn area (avoiding edges and UI elements)
  44. // Increased margin to account for larger bubble sprites
  45. const margin = 120.0;
  46. final minX = margin;
  47. final maxX = game.size.x - margin;
  48. final minY = margin + 100; // Extra margin for score display
  49. final maxY = game.size.y - margin;
  50. if (maxX <= minX || maxY <= minY) return null;
  51. return Vector2(
  52. minX + _random.nextDouble() * (maxX - minX),
  53. minY + _random.nextDouble() * (maxY - minY),
  54. );
  55. }
  56. void _onBubblePopped(Bubble bubble, bool userTriggered) {
  57. _activeBubbles.remove(bubble);
  58. onBubblePopped?.call(bubble, userTriggered);
  59. }
  60. void spawnBubbleAt(Vector2 position) {
  61. // Ensure the position is within valid bounds
  62. final validPosition = _clampPositionToBounds(position);
  63. final bubble = Bubble(
  64. position: validPosition,
  65. onPop: _onBubblePopped,
  66. );
  67. _activeBubbles.add(bubble);
  68. game.add(bubble);
  69. }
  70. Vector2 _clampPositionToBounds(Vector2 position) {
  71. const margin = 120.0;
  72. final minX = margin;
  73. final maxX = game.size.x - margin;
  74. final minY = margin + 100;
  75. final maxY = game.size.y - margin;
  76. if (maxX <= minX || maxY <= minY) return position;
  77. return Vector2(
  78. position.x.clamp(minX, maxX),
  79. position.y.clamp(minY, maxY),
  80. );
  81. }
  82. void clearAllBubbles() {
  83. for (final bubble in _activeBubbles) {
  84. if (bubble.isMounted) {
  85. bubble.removeFromParent();
  86. }
  87. }
  88. _activeBubbles.clear();
  89. }
  90. void setActive(bool active) {
  91. isActive = active;
  92. }
  93. void _calculateNextSpawnTime() {
  94. // Random spawn time between baseSpawnInterval - spawnVariation and baseSpawnInterval + spawnVariation
  95. _nextSpawnTime = baseSpawnInterval + (_random.nextDouble() - 0.5) * 2 * spawnVariation;
  96. // Ensure minimum spawn time of 0.5 seconds
  97. _nextSpawnTime = _nextSpawnTime.clamp(0.5, double.infinity);
  98. }
  99. int get activeBubbleCount => _activeBubbles.length;
  100. /// Get list of active bubbles for performance optimization
  101. List<Bubble> getActiveBubbles() => List.unmodifiable(_activeBubbles);
  102. }