瀏覽代碼

feat: implement Google Play Games Services integration

- Add games_services dependency for Google Play Games support
- Create GooglePlayGamesManager for handling authentication and services
- Implement achievement tracking for bubble popping milestones
- Add leaderboards for high scores, zen mode duration, and bubble counts
- Integrate Google Play Games tracking into ZenTapGame
- Add GooglePlayGamesWidget for sign-in/out and accessing features
- Update settings screen to include Google Play Games section
- Configure Android app for Google Play Games Services
- Add comprehensive documentation in GOOGLE_PLAY_GAMES.md

Features:
🏆 7 achievements: First Bubble, 100/1000/5000 Bubbles, Zen Master, Speed Demon, Perfect Session
📊 4 leaderboards: High Score, Zen Mode Duration, Total Bubbles, Longest Session
🎮 Automatic tracking and submission during gameplay
⚙️ Easy sign-in/out through settings interface

Note: Requires Google Play Console configuration with actual App ID
Fszontagh 9 月之前
父節點
當前提交
8624a986eb

+ 178 - 0
GOOGLE_PLAY_GAMES.md

@@ -0,0 +1,178 @@
+# Google Play Games Integration for ZenTap
+
+This document outlines the Google Play Games Services integration added to the ZenTap Flutter Flame game.
+
+## Features Implemented
+
+### 🏆 Achievements
+- **First Bubble**: Pop your first bubble
+- **100 Bubbles**: Pop 100 bubbles in total
+- **1000 Bubbles**: Pop 1,000 bubbles in total
+- **5000 Bubbles**: Pop 5,000 bubbles in total
+- **Zen Master**: Play in Zen mode for 10 minutes
+- **Speed Demon**: Reach 1,000 points in under 2 minutes
+- **Perfect Session**: Complete a session without missing any bubbles (minimum 50 bubbles)
+
+### 📊 Leaderboards
+- **High Score**: Highest score in regular mode
+- **Zen Mode Duration**: Longest time spent in Zen mode
+- **Total Bubbles Popped**: Lifetime bubble count
+- **Longest Session**: Longest single game session
+
+### 🎮 Game Integration
+- Automatic tracking of bubble pops, session time, and scores
+- Real-time achievement checking
+- Automatic submission of scores to leaderboards
+- Session statistics tracking (perfect sessions, streaks, etc.)
+
+## Setup Required
+
+### 1. Google Play Console Configuration
+
+1. **Create Google Play Games Project**:
+   - Go to [Google Play Console](https://play.google.com/console)
+   - Create a new game or link to existing app
+   - Note down the App ID
+
+2. **Configure Achievements**:
+   ```
+   Achievement ID: achievement_first_bubble
+   Name: First Bubble
+   Description: Pop your first bubble
+   Points: 5
+   Type: Standard
+   
+   Achievement ID: achievement_100_bubbles
+   Name: Bubble Rookie
+   Description: Pop 100 bubbles
+   Points: 10
+   Type: Standard
+   
+   Achievement ID: achievement_1000_bubbles
+   Name: Bubble Master
+   Description: Pop 1,000 bubbles
+   Points: 25
+   Type: Standard
+   
+   Achievement ID: achievement_5000_bubbles
+   Name: Bubble Legend
+   Description: Pop 5,000 bubbles
+   Points: 50
+   Type: Standard
+   
+   Achievement ID: achievement_zen_master
+   Name: Zen Master
+   Description: Meditate for 10 minutes in Zen mode
+   Points: 30
+   Type: Standard
+   
+   Achievement ID: achievement_speed_demon
+   Name: Speed Demon
+   Description: Score 1,000 points in under 2 minutes
+   Points: 25
+   Type: Standard
+   
+   Achievement ID: achievement_perfect_session
+   Name: Perfect Session
+   Description: Complete a session without missing any bubbles
+   Points: 20
+   Type: Standard
+   ```
+
+3. **Configure Leaderboards**:
+   ```
+   Leaderboard ID: leaderboard_high_score
+   Name: High Score
+   Order: Larger is better
+   Score Format: Numeric
+   
+   Leaderboard ID: leaderboard_zen_mode
+   Name: Zen Mode Duration
+   Order: Larger is better
+   Score Format: Time (seconds)
+   
+   Leaderboard ID: leaderboard_total_bubbles
+   Name: Total Bubbles Popped
+   Order: Larger is better
+   Score Format: Numeric
+   
+   Leaderboard ID: leaderboard_longest_session
+   Name: Longest Session
+   Order: Larger is better
+   Score Format: Time (seconds)
+   ```
+
+### 2. Android App Configuration
+
+1. **Update App ID**:
+   - Replace `YOUR_GOOGLE_PLAY_GAMES_APP_ID` in `android/app/src/main/res/values/strings.xml` with your actual App ID from Google Play Console
+
+2. **App Signing**:
+   - Ensure your app is signed with the same certificate used in Google Play Console
+   - Upload your SHA-1 fingerprint to Google Play Console
+
+### 3. Testing
+
+1. **Internal Testing**:
+   - Add test accounts in Google Play Console
+   - Use internal testing track to test Google Play Games integration
+
+2. **Debug Mode**:
+   - Use debug keystore for development testing
+   - Add debug SHA-1 to Google Play Console for testing
+
+## Usage
+
+### For Players
+1. **Sign In**: Go to Settings and tap "Sign In" under Google Play Games
+2. **View Achievements**: Tap "Achievements" to see progress
+3. **View Leaderboards**: Tap "Leaderboards" to compete with others
+4. **Automatic Tracking**: All game progress is automatically tracked when signed in
+
+### For Developers
+- All Google Play Games functionality is handled automatically
+- Statistics are tracked during gameplay
+- Results are submitted when game sessions end
+- UI provides sign-in/sign-out and access to achievements/leaderboards
+
+## File Structure
+
+```
+lib/
+├── utils/
+│   └── google_play_games_manager.dart    # Main Google Play Games service
+├── ui/
+│   └── google_play_games_widget.dart     # UI component for settings
+└── game/
+    └── zentap_game.dart                  # Game integration (updated)
+
+android/
+├── app/
+│   ├── build.gradle.kts                  # Added Google Play Services dependencies
+│   └── src/main/
+│       ├── AndroidManifest.xml          # Added Google Play Games permissions
+│       └── res/values/
+│           └── strings.xml               # App ID configuration
+```
+
+## Dependencies Added
+- `games_services: ^4.0.0` - Flutter plugin for Google Play Games Services
+
+## Notes
+- Google Play Games Services is only available on Android
+- iOS Game Center integration would require additional setup
+- All achievement and leaderboard IDs must match exactly between code and Google Play Console
+- Test thoroughly before production release
+
+## Troubleshooting
+
+### Common Issues
+1. **App ID Mismatch**: Ensure the App ID in strings.xml matches Google Play Console
+2. **SHA-1 Fingerprint**: Make sure the app's SHA-1 is added to Google Play Console
+3. **Achievement IDs**: Verify all achievement IDs match between code and console
+4. **Permissions**: Ensure INTERNET and ACCESS_NETWORK_STATE permissions are added
+
+### Debug Tips
+- Check Android logs for Google Play Games errors
+- Use Google Play Console's testing tools
+- Verify app is properly signed for production

+ 6 - 0
android/app/build.gradle.kts

@@ -42,3 +42,9 @@ android {
 flutter {
     source = "../.."
 }
+
+dependencies {
+    // Google Play Services for Games
+    implementation("com.google.android.gms:play-services-games-v2:17.0.0")
+    implementation("com.google.android.gms:play-services-auth:20.7.0")
+}

+ 13 - 0
android/app/src/main/AndroidManifest.xml

@@ -1,8 +1,21 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Permissions for Google Play Games Services -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    
     <application
         android:label="zentap"
         android:name="${applicationName}"
         android:icon="@mipmap/ic_launcher">
+        
+        <!-- Google Play Games Services Configuration -->
+        <meta-data
+            android:name="com.google.android.gms.games.APP_ID"
+            android:value="@string/app_id" />
+        <meta-data
+            android:name="com.google.android.gms.version"
+            android:value="@integer/google_play_services_version" />
+        
         <activity
             android:name="com.fsociety.zentap.zentap.MainActivity"
             android:exported="true"

+ 28 - 0
android/app/src/main/res/values/strings.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Google Play Games Services App ID -->
+    <!-- This should be replaced with your actual app ID from Google Play Console -->
+    <!-- For now, using a placeholder that needs to be configured -->
+    <string name="app_id">YOUR_GOOGLE_PLAY_GAMES_APP_ID</string>
+    
+    <!-- Application name -->
+    <string name="app_name">ZenTap</string>
+    
+    <!-- Achievement descriptions for Google Play Console reference -->
+    <!-- These are just for reference - actual configuration happens in Google Play Console -->
+    
+    <!-- Leaderboard names for Google Play Console reference -->
+    <!-- High Score Leaderboard -->
+    <!-- Zen Mode Duration Leaderboard -->
+    <!-- Total Bubbles Popped Leaderboard -->
+    <!-- Longest Session Leaderboard -->
+    
+    <!-- Achievement names for Google Play Console reference -->
+    <!-- First Bubble: Pop your first bubble -->
+    <!-- Bubble Popper: Pop 100 bubbles -->
+    <!-- Bubble Master: Pop 1000 bubbles -->
+    <!-- Bubble Legend: Pop 5000 bubbles -->
+    <!-- Zen Master: Spend 10 minutes in zen mode -->
+    <!-- Speed Demon: Reach 1000 points in under 2 minutes -->
+    <!-- Perfect Session: Complete a perfect session -->
+</resources>

+ 60 - 0
lib/game/zentap_game.dart

@@ -6,6 +6,7 @@ import 'dart:math';
 import '../utils/colors.dart';
 import '../utils/score_manager.dart';
 import '../utils/tilt_detector.dart';
+import '../utils/google_play_games_manager.dart';
 import 'components/bubble.dart';
 import 'components/bubble_spawner.dart';
 import 'audio/audio_manager.dart';
@@ -24,6 +25,12 @@ class ZenTapGame extends FlameGame with HasCollisionDetection {
   final AudioManager audioManager = AudioManager();
   final TiltDetector _tiltDetector = TiltDetector();
   final TiltDetector tiltDetector = TiltDetector();
+  final GooglePlayGamesManager _gamesManager = GooglePlayGamesManager.instance;
+  
+  // Google Play Games tracking
+  int _totalBubblesPopped = 0;
+  double _sessionStartTime = 0.0;
+  bool _perfectSession = true; // No missed bubbles
   
   // Performance optimization: cache last displayed second to avoid unnecessary text updates
   int _lastDisplayedSecond = -1;
@@ -35,6 +42,12 @@ class ZenTapGame extends FlameGame with HasCollisionDetection {
   Future<void> onLoad() async {
     await super.onLoad();
     
+    // Initialize Google Play Games
+    await _gamesManager.initialize();
+    
+    // Track session start time
+    _sessionStartTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
+    
     // Initialize score manager and audio
     await ScoreManager.initialize();
     await audioManager.initialize();
@@ -196,12 +209,33 @@ class ZenTapGame extends FlameGame with HasCollisionDetection {
     // Play pop sound and haptic feedback
     audioManager.playBubblePop();
     
+    // Track Google Play Games statistics
+    if (userTriggered) {
+      _totalBubblesPopped++;
+      
+      // Check for perfect session achievement after significant bubbles
+      if (_totalBubblesPopped >= 50 && _perfectSession) {
+        _gamesManager.unlockAchievement(
+          GooglePlayGamesManager.achievementPerfectSession
+        );
+      }
+    } else {
+      // User missed a bubble (it timed out)
+      _perfectSession = false;
+    }
+    
     if (!isZenMode && userTriggered) {
       // Only increment score for user-triggered pops in regular mode
       score += 10; // 10 points per bubble
       ScoreManager.addScore(10);
       _updateScore();
+      
+      // Check for speed achievements
+      _gamesManager.checkScoreAchievements(score, gameTime);
     }
+    
+    // Check bubble-based achievements
+    _gamesManager.checkBubbleAchievements(_totalBubblesPopped);
   }
 
   void _updateScore() {
@@ -313,6 +347,29 @@ class ZenTapGame extends FlameGame with HasCollisionDetection {
     audioManager.resumeBackgroundMusic();
     _initializeTiltDetection();
   }
+  
+  /// End the game session and submit results to Google Play Games
+  Future<void> endGameSession() async {
+    gameActive = false;
+    
+    final currentTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
+    final sessionLength = currentTime - _sessionStartTime;
+    
+    // Check for zen mode achievements
+    if (isZenMode) {
+      await _gamesManager.checkZenModeAchievements(sessionLength);
+    }
+    
+    // Submit final results to Google Play Games
+    await _gamesManager.submitGameResults(
+      finalScore: score,
+      isZenMode: isZenMode,
+      totalBubblesPopped: _totalBubblesPopped,
+      sessionLength: sessionLength,
+    );
+    
+    // Score is already tracked by ScoreManager.addScore() calls during gameplay
+  }
 
   void handleShake() {
     if (!gameActive) return;
@@ -347,6 +404,9 @@ class ZenTapGame extends FlameGame with HasCollisionDetection {
 
   @override
   void onRemove() {
+    // End the game session and submit results
+    endGameSession();
+    
     audioManager.stopBackgroundMusic();
     _tiltDetector.stopListening();
     super.onRemove();

+ 5 - 0
lib/main.dart

@@ -4,11 +4,16 @@ import 'ui/main_menu.dart';
 import 'utils/colors.dart';
 import 'utils/settings_manager.dart';
 import 'utils/score_manager.dart';
+import 'utils/google_play_games_manager.dart';
 
 void main() async {
   WidgetsFlutterBinding.ensureInitialized();
   await SettingsManager.init();
   await ScoreManager.initialize();
+  
+  // Initialize Google Play Games (silent init, don't require sign-in)
+  await GooglePlayGamesManager.instance.initialize();
+  
   runApp(const ZenTapApp());
 }
 

+ 201 - 0
lib/ui/google_play_games_widget.dart

@@ -0,0 +1,201 @@
+import 'package:flutter/material.dart';
+import '../utils/google_play_games_manager.dart';
+import '../utils/colors.dart';
+
+class GooglePlayGamesWidget extends StatefulWidget {
+  const GooglePlayGamesWidget({super.key});
+
+  @override
+  State<GooglePlayGamesWidget> createState() => _GooglePlayGamesWidgetState();
+}
+
+class _GooglePlayGamesWidgetState extends State<GooglePlayGamesWidget> {
+  final GooglePlayGamesManager _gamesManager = GooglePlayGamesManager.instance;
+  bool _isInitializing = false;
+
+  @override
+  Widget build(BuildContext context) {
+    return Card(
+      margin: const EdgeInsets.all(16),
+      child: Padding(
+        padding: const EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          mainAxisSize: MainAxisSize.min,
+          children: [
+            Row(
+              children: [
+                Icon(
+                  Icons.videogame_asset,
+                  color: _gamesManager.isSignedIn 
+                      ? ZenColors.defaultLink 
+                      : ZenColors.secondaryText,
+                ),
+                const SizedBox(width: 8),
+                Text(
+                  'Google Play Games',
+                  style: TextStyle(
+                    fontSize: 18,
+                    fontWeight: FontWeight.bold,
+                    color: _gamesManager.isSignedIn 
+                        ? ZenColors.primaryText 
+                        : ZenColors.secondaryText,
+                  ),
+                ),
+              ],
+            ),
+            const SizedBox(height: 12),
+            if (_gamesManager.isSignedIn) ...[
+              Text(
+                'Signed in as: ${_gamesManager.playerName ?? 'Player'}',
+                style: const TextStyle(
+                  color: ZenColors.primaryText,
+                  fontSize: 14,
+                ),
+              ),
+              const SizedBox(height: 12),
+              Row(
+                children: [
+                  Expanded(
+                    child: ElevatedButton.icon(
+                      onPressed: _showLeaderboards,
+                      icon: const Icon(Icons.leaderboard),
+                      label: const Text('Leaderboards'),
+                      style: ElevatedButton.styleFrom(
+                        backgroundColor: ZenColors.buttonBackground,
+                        foregroundColor: ZenColors.buttonText,
+                      ),
+                    ),
+                  ),
+                  const SizedBox(width: 8),
+                  Expanded(
+                    child: ElevatedButton.icon(
+                      onPressed: _showAchievements,
+                      icon: const Icon(Icons.emoji_events),
+                      label: const Text('Achievements'),
+                      style: ElevatedButton.styleFrom(
+                        backgroundColor: ZenColors.buttonBackground,
+                        foregroundColor: ZenColors.buttonText,
+                      ),
+                    ),
+                  ),
+                ],
+              ),
+              const SizedBox(height: 8),
+              TextButton(
+                onPressed: _signOut,
+                child: const Text(
+                  'Sign Out',
+                  style: TextStyle(color: ZenColors.secondaryText),
+                ),
+              ),
+            ] else ...[
+              Text(
+                'Sign in to save your achievements and compete on leaderboards!',
+                style: const TextStyle(
+                  color: ZenColors.secondaryText,
+                  fontSize: 14,
+                ),
+              ),
+              const SizedBox(height: 12),
+              SizedBox(
+                width: double.infinity,
+                child: ElevatedButton.icon(
+                  onPressed: _isInitializing ? null : _signIn,
+                  icon: _isInitializing 
+                      ? const SizedBox(
+                          width: 16,
+                          height: 16,
+                          child: CircularProgressIndicator(strokeWidth: 2),
+                        )
+                      : const Icon(Icons.login),
+                  label: Text(_isInitializing ? 'Signing In...' : 'Sign In'),
+                  style: ElevatedButton.styleFrom(
+                    backgroundColor: ZenColors.buttonBackground,
+                    foregroundColor: ZenColors.buttonText,
+                  ),
+                ),
+              ),
+            ],
+          ],
+        ),
+      ),
+    );
+  }
+
+  Future<void> _signIn() async {
+    setState(() {
+      _isInitializing = true;
+    });
+
+    try {
+      final success = await _gamesManager.signIn();
+      if (success) {
+        ScaffoldMessenger.of(context).showSnackBar(
+          const SnackBar(
+            content: Text('Successfully signed in to Google Play Games!'),
+            backgroundColor: ZenColors.defaultLink,
+          ),
+        );
+      } else {
+        ScaffoldMessenger.of(context).showSnackBar(
+          const SnackBar(
+            content: Text('Failed to sign in to Google Play Games'),
+            backgroundColor: Colors.red,
+          ),
+        );
+      }
+    } catch (e) {
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(
+          content: Text('Error signing in: $e'),
+          backgroundColor: Colors.red,
+        ),
+      );
+    } finally {
+      if (mounted) {
+        setState(() {
+          _isInitializing = false;
+        });
+      }
+    }
+  }
+
+  Future<void> _signOut() async {
+    await _gamesManager.signOut();
+    setState(() {});
+    
+    ScaffoldMessenger.of(context).showSnackBar(
+      const SnackBar(
+        content: Text('Signed out from Google Play Games'),
+        backgroundColor: ZenColors.secondaryText,
+      ),
+    );
+  }
+
+  Future<void> _showLeaderboards() async {
+    try {
+      await _gamesManager.showLeaderboards();
+    } catch (e) {
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(
+          content: Text('Error showing leaderboards: $e'),
+          backgroundColor: Colors.red,
+        ),
+      );
+    }
+  }
+
+  Future<void> _showAchievements() async {
+    try {
+      await _gamesManager.showAchievements();
+    } catch (e) {
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(
+          content: Text('Error showing achievements: $e'),
+          backgroundColor: Colors.red,
+        ),
+      );
+    }
+  }
+}

+ 9 - 2
lib/ui/settings_screen.dart

@@ -3,6 +3,7 @@ import '../utils/colors.dart';
 import '../utils/haptic_utils.dart';
 import '../utils/settings_manager.dart';
 import 'components/animated_background.dart';
+import 'google_play_games_widget.dart';
 
 class SettingsScreen extends StatefulWidget {
   const SettingsScreen({super.key});
@@ -73,7 +74,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
               
               // Settings List
               Expanded(
-                child: Padding(
+                child: SingleChildScrollView(
                   padding: const EdgeInsets.symmetric(horizontal: 20.0),
                   child: Column(
                     children: [
@@ -127,7 +128,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
                         onTap: _showTutorial,
                       ),
                       
-                      const Spacer(),
+                      const SizedBox(height: 30),
+                      
+                      // Google Play Games Section
+                      _buildSectionHeader('Google Play Games'),
+                      const GooglePlayGamesWidget(),
+                      
+                      const SizedBox(height: 30),
                       
                       // About Section
                       Padding(

+ 290 - 0
lib/utils/google_play_games_manager.dart

@@ -0,0 +1,290 @@
+import 'package:games_services/games_services.dart';
+import 'dart:async';
+import 'dart:io';
+
+/// Manages Google Play Games Services integration
+class GooglePlayGamesManager {
+  static GooglePlayGamesManager? _instance;
+  static GooglePlayGamesManager get instance {
+    _instance ??= GooglePlayGamesManager._internal();
+    return _instance!;
+  }
+  
+  GooglePlayGamesManager._internal();
+  
+  bool _isInitialized = false;
+  bool _isSignedIn = false;
+  String? _playerId;
+  String? _playerName;
+  
+  // Achievement IDs - These need to be configured in Google Play Console
+  static const String achievementFirstBubble = 'achievement_first_bubble';
+  static const String achievement100Bubbles = 'achievement_100_bubbles';
+  static const String achievement1000Bubbles = 'achievement_1000_bubbles';
+  static const String achievement5000Bubbles = 'achievement_5000_bubbles';
+  static const String achievementZenMaster = 'achievement_zen_master';
+  static const String achievementSpeedDemon = 'achievement_speed_demon';
+  static const String achievementPerfectSession = 'achievement_perfect_session';
+  
+  // Leaderboard IDs - These need to be configured in Google Play Console
+  static const String leaderboardHighScore = 'leaderboard_high_score';
+  static const String leaderboardZenMode = 'leaderboard_zen_mode';
+  static const String leaderboardTotalBubbles = 'leaderboard_total_bubbles';
+  static const String leaderboardLongestSession = 'leaderboard_longest_session';
+  
+  /// Initialize Google Play Games Services
+  Future<bool> initialize() async {
+    if (_isInitialized) return true;
+    
+    try {
+      // Only available on Android
+      if (!Platform.isAndroid) {
+        print('Google Play Games Services only available on Android');
+        return false;
+      }
+      
+      _isInitialized = true;
+      return true;
+    } catch (e) {
+      print('Error initializing Google Play Games: $e');
+      return false;
+    }
+  }
+  
+  /// Sign in to Google Play Games
+  Future<bool> signIn() async {
+    if (!_isInitialized) {
+      await initialize();
+    }
+    
+    try {
+      final result = await GamesServices.signIn();
+      _isSignedIn = result != null && result.isNotEmpty;
+      
+      if (_isSignedIn) {
+        _playerId = result;
+        print('Successfully signed in to Google Play Games: $_playerId');
+        
+        // Get player name
+        try {
+          final playerName = await GamesServices.getPlayerName();
+          _playerName = playerName;
+          print('Player name: $_playerName');
+        } catch (e) {
+          print('Could not get player name: $e');
+        }
+      }
+      
+      return _isSignedIn;
+    } catch (e) {
+      print('Error signing in to Google Play Games: $e');
+      _isSignedIn = false;
+      return false;
+    }
+  }
+  
+  /// Sign out from Google Play Games
+  Future<void> signOut() async {
+    try {
+      // Note: games_services package doesn't have a signOut method
+      // We'll just reset our local state
+      _isSignedIn = false;
+      _playerId = null;
+      _playerName = null;
+      print('Successfully signed out from Google Play Games');
+    } catch (e) {
+      print('Error signing out from Google Play Games: $e');
+    }
+  }
+  
+  /// Check if signed in
+  bool get isSignedIn => _isSignedIn;
+  
+  /// Get player ID
+  String? get playerId => _playerId;
+  
+  /// Get player name
+  String? get playerName => _playerName;
+  
+  /// Submit score to leaderboard
+  Future<bool> submitScore(String leaderboardId, int score) async {
+    if (!_isSignedIn) {
+      print('Not signed in to Google Play Games');
+      return false;
+    }
+    
+    try {
+      await GamesServices.submitScore(
+        score: Score(
+          androidLeaderboardID: leaderboardId,
+          value: score,
+        ),
+      );
+      print('Score $score submitted to leaderboard $leaderboardId');
+      return true;
+    } catch (e) {
+      print('Error submitting score: $e');
+      return false;
+    }
+  }
+  
+  /// Show leaderboards
+  Future<void> showLeaderboards() async {
+    if (!_isSignedIn) {
+      print('Not signed in to Google Play Games');
+      return;
+    }
+    
+    try {
+      await GamesServices.showLeaderboards();
+    } catch (e) {
+      print('Error showing leaderboards: $e');
+    }
+  }
+  
+  /// Show specific leaderboard
+  Future<void> showLeaderboard(String leaderboardId) async {
+    if (!_isSignedIn) {
+      print('Not signed in to Google Play Games');
+      return;
+    }
+    
+    try {
+      await GamesServices.showLeaderboards(
+        androidLeaderboardID: leaderboardId,
+      );
+    } catch (e) {
+      print('Error showing leaderboard: $e');
+    }
+  }
+  
+  /// Unlock achievement
+  Future<bool> unlockAchievement(String achievementId) async {
+    if (!_isSignedIn) {
+      print('Not signed in to Google Play Games');
+      return false;
+    }
+    
+    try {
+      await GamesServices.unlock(
+        achievement: Achievement(
+          androidID: achievementId,
+        ),
+      );
+      print('Achievement $achievementId unlocked');
+      return true;
+    } catch (e) {
+      print('Error unlocking achievement: $e');
+      return false;
+    }
+  }
+  
+  /// Increment achievement (for incremental achievements)
+  Future<bool> incrementAchievement(String achievementId, int steps) async {
+    if (!_isSignedIn) {
+      print('Not signed in to Google Play Games');
+      return false;
+    }
+    
+    try {
+      await GamesServices.increment(
+        achievement: Achievement(
+          androidID: achievementId,
+          steps: steps,
+        ),
+      );
+      print('Achievement $achievementId incremented by $steps steps');
+      return true;
+    } catch (e) {
+      print('Error incrementing achievement: $e');
+      return false;
+    }
+  }
+  
+  /// Show achievements
+  Future<void> showAchievements() async {
+    if (!_isSignedIn) {
+      print('Not signed in to Google Play Games');
+      return;
+    }
+    
+    try {
+      await GamesServices.showAchievements();
+    } catch (e) {
+      print('Error showing achievements: $e');
+    }
+  }
+  
+  /// Check achievement-related milestones for bubble popping
+  Future<void> checkBubbleAchievements(int totalBubbles) async {
+    if (!_isSignedIn) return;
+    
+    // First bubble achievement
+    if (totalBubbles >= 1) {
+      await unlockAchievement(achievementFirstBubble);
+    }
+    
+    // 100 bubbles achievement
+    if (totalBubbles >= 100) {
+      await unlockAchievement(achievement100Bubbles);
+    }
+    
+    // 1000 bubbles achievement
+    if (totalBubbles >= 1000) {
+      await unlockAchievement(achievement1000Bubbles);
+    }
+    
+    // 5000 bubbles achievement
+    if (totalBubbles >= 5000) {
+      await unlockAchievement(achievement5000Bubbles);
+    }
+  }
+  
+  /// Check zen mode achievements
+  Future<void> checkZenModeAchievements(double sessionTime) async {
+    if (!_isSignedIn) return;
+    
+    // Zen master achievement (10 minutes in zen mode)
+    if (sessionTime >= 600) {
+      await unlockAchievement(achievementZenMaster);
+    }
+  }
+  
+  /// Check score-based achievements
+  Future<void> checkScoreAchievements(int score, double timeToReachScore) async {
+    if (!_isSignedIn) return;
+    
+    // Speed demon achievement (reach 1000 points in under 2 minutes)
+    if (score >= 1000 && timeToReachScore <= 120) {
+      await unlockAchievement(achievementSpeedDemon);
+    }
+  }
+  
+  /// Submit various scores to leaderboards
+  Future<void> submitGameResults({
+    required int finalScore,
+    required bool isZenMode,
+    required int totalBubblesPopped,
+    required double sessionLength,
+  }) async {
+    if (!_isSignedIn) return;
+    
+    // Submit to appropriate leaderboards
+    if (isZenMode) {
+      await submitScore(leaderboardZenMode, sessionLength.round());
+    } else {
+      await submitScore(leaderboardHighScore, finalScore);
+    }
+    
+    await submitScore(leaderboardTotalBubbles, totalBubblesPopped);
+    await submitScore(leaderboardLongestSession, sessionLength.round());
+    
+    // Check achievements
+    await checkBubbleAchievements(totalBubblesPopped);
+    if (isZenMode) {
+      await checkZenModeAchievements(sessionLength);
+    } else {
+      await checkScoreAchievements(finalScore, sessionLength);
+    }
+  }
+}

+ 2 - 0
macos/Flutter/GeneratedPluginRegistrant.swift

@@ -7,12 +7,14 @@ import Foundation
 
 import audioplayers_darwin
 import device_info_plus
+import games_services
 import path_provider_foundation
 import shared_preferences_foundation
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
   AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
   DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
+  SwiftGamesServicesPlugin.register(with: registry.registrar(forPlugin: "SwiftGamesServicesPlugin"))
   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
   SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
 }

+ 16 - 0
pubspec.lock

@@ -232,6 +232,22 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  games_services:
+    dependency: "direct main"
+    description:
+      name: games_services
+      sha256: e968ca9dbca98a4fb0360b93dd00f072b6f018353e45dcb0049659b556c4a87d
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.1.1"
+  games_services_platform_interface:
+    dependency: transitive
+    description:
+      name: games_services_platform_interface
+      sha256: b4e2fd1f26c4e260a76756c8f36d1fef99dd7a3e102b8591a8abfa2a925e974e
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.1.1"
   http:
     dependency: transitive
     description:

+ 3 - 0
pubspec.yaml

@@ -58,6 +58,9 @@ dependencies:
   
   # Charts for statistics
   fl_chart: ^1.0.0
+  
+  # Google Play Games Services
+  games_services: ^4.0.0
 
 dev_dependencies:
   flutter_test: