Ver código fonte

prerformance opt.

Fszontagh 9 meses atrás
pai
commit
70f407a81c

+ 119 - 0
PERFORMANCE_OPTIMIZATIONS.md

@@ -0,0 +1,119 @@
+# ZenTap Performance Optimizations
+
+## Problem Identified
+The application was experiencing frame drops with the error "Skipped 31 frames! The application may be doing too much work on its main thread."
+
+## Root Causes and Solutions
+
+### 1. Excessive Particle Effects on Bubble Pop
+**Problem**: Each bubble pop created 8 particles with complex animations (scale, move, fade, gravity) lasting 500ms.
+
+**Solution**: 
+- Reduced particles from 8 to 4
+- Simplified effects by combining movement and gravity into single effect
+- Reduced animation duration from 500ms to 300ms
+- Removed scale effects to reduce computational load
+- **File**: [`lib/game/components/bubble.dart`](lib/game/components/bubble.dart:324)
+
+### 2. High-Frequency Tilt Detection Updates
+**Problem**: Accelerometer data was being processed at 10Hz (every 100ms) and immediately applied to all active bubbles.
+
+**Solutions**:
+- Reduced accelerometer sampling rate from 10Hz to 5Hz (200ms intervals)
+- Added throttling to tilt force application (max 10 updates per second)
+- Implemented batched processing of bubble tilt forces to prevent frame blocking
+- Added async processing with `Future.delayed(Duration.zero)` between batches
+- **Files**: 
+  - [`lib/utils/tilt_detector.dart`](lib/utils/tilt_detector.dart:33)
+  - [`lib/game/zentap_game.dart`](lib/game/zentap_game.dart:102)
+
+### 3. Blocking Audio Operations
+**Problem**: Audio playback was synchronous and could block the main thread.
+
+**Solution**:
+- Made audio playback completely asynchronous
+- Split audio operations into separate async methods
+- Removed `await` calls from main playback method to prevent blocking
+- **File**: [`lib/game/audio/audio_manager.dart`](lib/game/audio/audio_manager.dart:95)
+
+### 4. Frequent Bubble Cleanup Operations
+**Problem**: Bubble cleanup was running every frame in the update loop.
+
+**Solution**:
+- Changed cleanup frequency from every frame to every 0.5 seconds
+- Added cleanup timer to track cleanup intervals
+- **File**: [`lib/game/components/bubble_spawner.dart`](lib/game/components/bubble_spawner.dart:23)
+
+## Performance Improvements Summary
+
+| Optimization | Before | After | Impact |
+|-------------|--------|-------|---------|
+| Particle Count | 8 per pop | 4 per pop | 50% reduction |
+| Particle Duration | 500ms | 300ms | 40% faster cleanup |
+| Tilt Sampling Rate | 10Hz | 5Hz | 50% fewer sensor events |
+| Tilt Force Application | Immediate | Throttled to 10Hz + batched | Prevents frame blocking |
+| Audio Operations | Synchronous | Asynchronous | No main thread blocking |
+| Bubble Cleanup | Every frame (~60Hz) | Every 0.5s (2Hz) | 97% reduction in cleanup frequency |
+
+## Expected Results
+
+These optimizations should significantly reduce main thread blocking and eliminate the frame skipping issues. The game should now maintain a smooth 60 FPS even with multiple bubbles and simultaneous interactions.
+
+## Technical Details
+
+### Async Processing Pattern
+```dart
+// Before: Blocking
+await heavyOperation();
+
+// After: Non-blocking
+_heavyOperationAsync();
+
+void _heavyOperationAsync() async {
+  // Heavy work here
+}
+```
+
+### Batched Processing Pattern
+```dart
+// Process items in small batches with yielding
+const batchSize = 3;
+for (int i = 0; i < items.length; i += batchSize) {
+  final batch = items.sublist(i, endIndex);
+  // Process batch
+  if (endIndex < items.length) {
+    await Future.delayed(Duration.zero); // Yield control
+  }
+}
+```
+
+### Throttling Pattern
+```dart
+double _lastUpdateTime = 0.0;
+static const double updateInterval = 0.1;
+
+void onEvent() {
+  final currentTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
+  if (currentTime - _lastUpdateTime >= updateInterval) {
+    _lastUpdateTime = currentTime;
+    // Process event
+  }
+}
+```
+
+## Flutter Flame Integration
+
+The implementation successfully uses Flutter Flame game engine with:
+- **Flame Components**: Custom bubble and spawner components
+- **Flame Effects**: Optimized scale, opacity, and movement effects
+- **Flame Audio**: Audio pools for efficient sound playback
+- **Flame Collision Detection**: Circle hitboxes for bubble interactions
+- **Async Support**: Proper async/await patterns that don't block the main thread
+
+## Monitoring
+
+To monitor performance improvements:
+1. Check Flutter Inspector for frame rendering times
+2. Monitor console for frame skip warnings
+3. Use performance overlay: `flutter run --profile`
+4. Watch CPU usage during bubble interactions and tilt movements

+ 31 - 23
lib/game/audio/audio_manager.dart

@@ -92,9 +92,19 @@ class AudioManager {
     }
   }
 
-  Future<void> playBubblePop() async {
+  void playBubblePop() {
     if (!_soundEnabled) return;
     
+    // Make audio playback async to avoid blocking main thread
+    _playBubblePopAsync();
+    
+    // Handle haptic feedback asynchronously
+    if (_hapticEnabled && _isVibrationSupported) {
+      _playHapticFeedbackAsync();
+    }
+  }
+  
+  void _playBubblePopAsync() async {
     if (_isAudioSupported) {
       // Use Flame Audio for bubble pop
       final sound = _random.nextBool()
@@ -114,15 +124,11 @@ class AudioManager {
       }
     } else {
       // Fallback to system sound
-      await _playSystemSound();
-    }
-    
-    if (_hapticEnabled && _isVibrationSupported) {
-      await _playHapticFeedback();
+      _playSystemSoundAsync();
     }
   }
-
-  Future<void> _playSystemSound() async {
+  
+  void _playSystemSoundAsync() async {
     try {
       // Vary the system sound for some variety
       final soundType = _random.nextInt(3);
@@ -140,6 +146,23 @@ class AudioManager {
       print('Failed to play system sound: $e');
     }
   }
+  
+  void _playHapticFeedbackAsync() async {
+    if (!_isVibrationSupported) {
+      // Silently skip haptic feedback on unsupported platforms
+      return;
+    }
+    
+    try {
+      // Use Flutter's built-in HapticFeedback instead of vibration plugin
+      await HapticFeedback.lightImpact();
+    } catch (e) {
+      print('Failed to play haptic feedback: $e');
+      // Disable vibration support if it fails
+      _isVibrationSupported = false;
+    }
+  }
+
 
   Future<void> playBackgroundMusic() async {
     if (!_musicEnabled || !_isAudioSupported) {
@@ -194,21 +217,6 @@ class AudioManager {
     }
   }
   
-  Future<void> _playHapticFeedback() async {
-    if (!_isVibrationSupported) {
-      // Silently skip haptic feedback on unsupported platforms
-      return;
-    }
-    
-    try {
-      // Use Flutter's built-in HapticFeedback instead of vibration plugin
-      await HapticFeedback.lightImpact();
-    } catch (e) {
-      print('Failed to play haptic feedback: $e');
-      // Disable vibration support if it fails
-      _isVibrationSupported = false;
-    }
-  }
 
   void setSoundEnabled(bool enabled) {
     _soundEnabled = enabled;

+ 17 - 34
lib/game/components/bubble.dart

@@ -323,25 +323,24 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
 
   void _createExcitingPopParticles() {
     final random = Random();
-    const particleCount = 8; // Reduced from 16 for better performance
+    const particleCount = 4; // Further reduced for better performance
     
+    // Use object pooling approach - create fewer, simpler particles
     for (int i = 0; i < particleCount; i++) {
       final angle = (i / particleCount) * 2 * pi;
       
-      // Varied particle speeds for more dynamic effect
-      final baseSpeed = 60 + random.nextDouble() * 40; // 60-100 speed
+      // Simplified particle creation
+      final baseSpeed = 50 + random.nextDouble() * 30; // 50-80 speed
       final particleVelocity = Vector2(
         cos(angle) * baseSpeed,
         sin(angle) * baseSpeed,
       );
       
-      // Different particle sizes
-      final particleSize = 2 + random.nextDouble() * 5; // 2-7 radius
+      // Fixed particle size for performance
+      const particleSize = 3.0;
       
-      // Create particle with random color variation
-      final particleColor = bubbleColor.withValues(
-        alpha: 0.7 + random.nextDouble() * 0.3, // 70-100% alpha
-      );
+      // Simplified particle color
+      final particleColor = bubbleColor.withValues(alpha: 0.8);
       
       final particle = CircleComponent(
         radius: particleSize,
@@ -353,40 +352,24 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
       
       parent?.add(particle);
       
-      // Add movement effect with gravity
-      particle.add(
-        MoveEffect.by(
-          particleVelocity,
-          EffectController(duration: 0.5, curve: Curves.easeOut), // Reduced from 0.8
-        ),
-      );
-      
-      // Add simplified gravity effect
-      final gravityEffect = MoveEffect.by(
-        Vector2(0, 60), // Reduced gravity for shorter duration
-        EffectController(duration: 0.5, curve: Curves.easeIn), // Reduced from 0.8
+      // Combine effects for better performance
+      final combinedEffect = MoveEffect.by(
+        particleVelocity + Vector2(0, 40), // Combined movement + gravity
+        EffectController(duration: 0.3, curve: Curves.easeOut), // Faster animation
       );
       
-      particle.add(gravityEffect);
-      
-      // Add scale effect (shrink over time)
-      particle.add(
-        ScaleEffect.to(
-          Vector2.all(0.1),
-          EffectController(duration: 0.5, curve: Curves.easeIn), // Reduced from 0.8
-        ),
-      );
+      particle.add(combinedEffect);
       
-      // Add fade effect
+      // Single fade effect instead of multiple effects
       particle.add(
         OpacityEffect.to(
           0.0,
-          EffectController(duration: 0.5, curve: Curves.easeIn), // Reduced from 0.8
+          EffectController(duration: 0.3, curve: Curves.easeIn),
         ),
       );
       
-      // Remove particle after animation (reduced cleanup time)
-      Future.delayed(const Duration(milliseconds: 500), () {
+      // Faster cleanup
+      Future.delayed(const Duration(milliseconds: 300), () {
         if (particle.isMounted) {
           particle.removeFromParent();
         }

+ 9 - 2
lib/game/components/bubble_spawner.dart

@@ -19,6 +19,9 @@ class BubbleSpawner extends Component with HasGameReference {
     _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);
@@ -26,6 +29,7 @@ class BubbleSpawner extends Component with HasGameReference {
     if (!isActive) return;
     
     _timeSinceLastSpawn += dt;
+    _lastCleanupTime += dt;
     
     // Spawn new bubble if conditions are met
     if (_timeSinceLastSpawn >= _nextSpawnTime && _activeBubbles.length < maxBubbles) {
@@ -34,8 +38,11 @@ class BubbleSpawner extends Component with HasGameReference {
       _calculateNextSpawnTime();
     }
     
-    // Clean up popped bubbles
-    _activeBubbles.removeWhere((bubble) => !bubble.isMounted);
+    // Clean up popped bubbles less frequently to improve performance
+    if (_lastCleanupTime >= cleanupInterval) {
+      _activeBubbles.removeWhere((bubble) => !bubble.isMounted);
+      _lastCleanupTime = 0;
+    }
   }
 
   void _spawnBubble() {

+ 26 - 4
lib/game/zentap_game.dart

@@ -91,20 +91,42 @@ class ZenTapGame extends FlameGame with HasCollisionDetection {
     _initializeTiltDetection();
   }
   
+  double _lastTiltUpdateTime = 0.0;
+  static const double tiltUpdateInterval = 0.1; // Update tilt effects max 10 times per second
+  
   void _initializeTiltDetection() {
     _tiltDetector.startListening(
       onTiltChanged: (tiltAngle) {
-        _applyTiltToAllBubbles(_tiltDetector.normalizedTilt);
+        // Throttle tilt updates to improve performance
+        final currentTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
+        if (currentTime - _lastTiltUpdateTime >= tiltUpdateInterval) {
+          _lastTiltUpdateTime = currentTime;
+          _applyTiltToAllBubbles(_tiltDetector.normalizedTilt);
+        }
       },
     );
   }
   
-  void _applyTiltToAllBubbles(double tiltStrength) {
+  void _applyTiltToAllBubbles(double tiltStrength) async {
     if (bubbleSpawner == null) return;
     
+    // Apply tilt forces asynchronously to avoid blocking main thread
     final activeBubbles = bubbleSpawner!.getActiveBubbles();
-    for (final bubble in activeBubbles) {
-      bubble.applyTiltForce(tiltStrength);
+    
+    // Process bubbles in smaller batches to prevent frame drops
+    const batchSize = 3;
+    for (int i = 0; i < activeBubbles.length; i += batchSize) {
+      final endIndex = (i + batchSize).clamp(0, activeBubbles.length);
+      final batch = activeBubbles.sublist(i, endIndex);
+      
+      for (final bubble in batch) {
+        bubble.applyTiltForce(tiltStrength);
+      }
+      
+      // Yield control back to the framework between batches
+      if (endIndex < activeBubbles.length) {
+        await Future.delayed(Duration.zero);
+      }
     }
   }
 

+ 1 - 1
lib/utils/tilt_detector.dart

@@ -30,7 +30,7 @@ class TiltDetector {
     _isListening = true;
     
     _accelerometerSubscription = accelerometerEventStream(
-      samplingPeriod: const Duration(milliseconds: 100), // 10 Hz sampling rate
+      samplingPeriod: const Duration(milliseconds: 200), // 5 Hz sampling rate for better performance
     ).listen(_onAccelerometerEvent);
   }