bubble.dart 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import 'dart:math';
  2. import 'package:flame/components.dart';
  3. import 'package:flame/effects.dart';
  4. import 'package:flame/events.dart';
  5. import 'package:flutter/material.dart';
  6. import '../../utils/colors.dart';
  7. class Bubble extends CircleComponent with HasGameReference, TapCallbacks {
  8. static const double defaultRadius = 30.0;
  9. static const double maxRadius = 50.0;
  10. static const double minRadius = 20.0;
  11. late Color bubbleColor;
  12. bool isPopping = false;
  13. Function(Bubble)? onPop;
  14. Bubble({
  15. required Vector2 position,
  16. double? radius,
  17. this.onPop,
  18. }) : super(
  19. radius: radius ?? _randomRadius(),
  20. position: position,
  21. anchor: Anchor.center,
  22. );
  23. static double _randomRadius() {
  24. final random = Random();
  25. return minRadius + random.nextDouble() * (maxRadius - minRadius);
  26. }
  27. @override
  28. Future<void> onLoad() async {
  29. await super.onLoad();
  30. // Set random bubble color with transparency
  31. bubbleColor = _getRandomBubbleColor();
  32. paint = Paint()
  33. ..color = bubbleColor.withValues(alpha: 0.8)
  34. ..style = PaintingStyle.fill;
  35. // Add a subtle floating animation
  36. add(
  37. MoveEffect.by(
  38. Vector2(0, -10),
  39. EffectController(
  40. duration: 2.0,
  41. alternate: true,
  42. infinite: true,
  43. ),
  44. ),
  45. );
  46. // Add a gentle scaling animation
  47. add(
  48. ScaleEffect.by(
  49. Vector2.all(0.1),
  50. EffectController(
  51. duration: 1.5,
  52. alternate: true,
  53. infinite: true,
  54. ),
  55. ),
  56. );
  57. }
  58. Color _getRandomBubbleColor() {
  59. final random = Random();
  60. final colors = [
  61. ZenColors.bubbleDefault,
  62. ZenColors.defaultLink,
  63. ZenColors.hoverLink,
  64. ZenColors.lightModeHover,
  65. ZenColors.buttonBackground,
  66. ];
  67. return colors[random.nextInt(colors.length)];
  68. }
  69. @override
  70. bool onTapDown(TapDownEvent event) {
  71. if (!isPopping) {
  72. pop();
  73. }
  74. return true;
  75. }
  76. void pop() {
  77. if (isPopping) return;
  78. isPopping = true;
  79. // Create pop animation
  80. final popEffect = ScaleEffect.to(
  81. Vector2.all(1.5),
  82. EffectController(duration: 0.2),
  83. );
  84. final fadeEffect = OpacityEffect.to(
  85. 0.0,
  86. EffectController(duration: 0.2),
  87. );
  88. add(popEffect);
  89. add(fadeEffect);
  90. // Create particle effects
  91. _createPopParticles();
  92. // Notify parent and remove bubble
  93. onPop?.call(this);
  94. Future.delayed(const Duration(milliseconds: 200), () {
  95. if (isMounted) {
  96. removeFromParent();
  97. }
  98. });
  99. }
  100. void _createPopParticles() {
  101. final random = Random();
  102. const particleCount = 8;
  103. for (int i = 0; i < particleCount; i++) {
  104. final angle = (i / particleCount) * 2 * pi;
  105. final particleVelocity = Vector2(
  106. cos(angle) * (50 + random.nextDouble() * 30),
  107. sin(angle) * (50 + random.nextDouble() * 30),
  108. );
  109. final particle = CircleComponent(
  110. radius: 3 + random.nextDouble() * 3,
  111. position: position.clone(),
  112. paint: Paint()
  113. ..color = bubbleColor.withValues(alpha: 0.8)
  114. ..style = PaintingStyle.fill,
  115. );
  116. parent?.add(particle);
  117. // Add movement effect to particle
  118. particle.add(
  119. MoveEffect.by(
  120. particleVelocity,
  121. EffectController(duration: 0.5),
  122. ),
  123. );
  124. // Add fade effect to particle
  125. particle.add(
  126. OpacityEffect.to(
  127. 0.0,
  128. EffectController(duration: 0.5),
  129. ),
  130. );
  131. // Remove particle after animation
  132. Future.delayed(const Duration(milliseconds: 500), () {
  133. if (particle.isMounted) {
  134. particle.removeFromParent();
  135. }
  136. });
  137. }
  138. }
  139. }