跳到主內容

為您的移動遊戲新增成就和排行榜

如何使用 games_services 外掛為您的遊戲新增功能。

玩家玩遊戲有各種各樣的動機。總的來說,主要有四大動機:沉浸感、成就感、合作和競爭。無論您開發什麼樣的遊戲,總有一些玩家希望在其中獲得成就。這可以是贏得的獎盃或解鎖的秘密。有些玩家希望在其中競爭。這可以是重新整理高分或完成速通。這兩個想法分別對應成就排行榜的概念。

A simple graphic representing the four types of motivation explained above

App Store 和 Google Play 等生態系統為成就和排行榜提供了集中式服務。玩家可以在一個地方檢視他們所有遊戲的成就,而開發者無需為每個遊戲重新實現這些功能。

本指南演示瞭如何使用 games_services 軟體包為您的手機遊戲新增成就和排行榜功能。

1. 啟用平臺服務

#

要啟用遊戲服務,請在 iOS 上設定 Game Center,在 Android 上設定 Google Play 遊戲服務

iOS

#

在 iOS 上啟用 Game Center (GameKit)

  1. 在 Xcode 中開啟您的 Flutter 專案。開啟 ios/Runner.xcworkspace

  2. 選擇根目錄下的 Runner 專案。

  3. 轉到 Signing & Capabilities(簽名與功能)選項卡。

  4. 點選 + 按鈕以新增 Game Center 作為功能。

  5. 關閉 Xcode。

  6. 如果尚未註冊,請在 App Store Connect 中註冊您的遊戲,並從 My App 部分點選 + 圖示。

    Screenshot of the + button in App Store Connect

  7. 仍在 App Store Connect 中,尋找 Game Center 部分。在撰寫本文時,您可以在 Services 中找到它。在 Game Center 頁面上,您可能需要根據遊戲設定排行榜和多個成就。記下您建立的排行榜和成就的 ID。

Android

#

在 Android 上啟用 Play 遊戲服務

  1. 如果尚未註冊,請前往 Google Play 管理中心並在那裡註冊您的遊戲。

    Screenshot of the 'Create app' button in Google Play Console

  2. 仍在 Google Play 管理中心中,從導航選單中選擇 Play 遊戲服務設定與管理配置,然後按照說明操作。

    • 這需要花費大量的時間和耐心。除其他事項外,您還需要在 Google Cloud 控制檯中設定 OAuth 同意螢幕。如果在此過程中感到迷茫,請查閱官方的 Play 遊戲服務指南

      Screenshot showing the Games Services section in Google Play Console

  3. 完成後,您可以開始在 Play 遊戲服務設定與管理 中新增排行榜和成就。建立與 iOS 端完全相同的設定。記下 ID。

  4. 前往 Play 遊戲服務 → 設定與管理 → 釋出

  5. 點選 釋出。別擔心,這實際上並不會釋出您的遊戲。它只會釋出成就和排行榜。例如,一旦以這種方式釋出了排行榜,就無法撤銷釋出。

  6. 前往 Play 遊戲服務 → 設定與管理 → 配置 → 憑據

  7. 找到 獲取資源 按鈕。它會返回一個包含 Play 遊戲服務 ID 的 XML 檔案。

    xml
    <!-- THIS IS JUST AN EXAMPLE -->
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <!--app_id-->
        <string name="app_id" translatable="false">424242424242</string>
        <!--package_name-->
        <string name="package_name" translatable="false">dev.flutter.tictactoe</string>
        <!--achievement First win-->
        <string name="achievement_first_win" translatable="false">sOmEiDsTrInG</string>
        <!--leaderboard Highest Score-->
        <string name="leaderboard_highest_score" translatable="false">sOmEiDsTrInG</string>
    </resources>
    
  8. android/app/src/main/res/values/games-ids.xml 處新增一個檔案,其中包含您在上一步中獲取的 XML。

2. 登入遊戲服務

#

現在您已經設定了 Game CenterPlay 遊戲服務,並準備好了成就和排行榜 ID,終於可以開始編寫 Dart 程式碼了。

  1. 新增對 games_services 軟體包的依賴。

    flutter pub add games_services
    
  2. 在進行任何其他操作之前,您必須讓玩家登入遊戲服務。

    dart
    try {
      await GamesServices.signIn();
    } on PlatformException catch (e) {
      // ... deal with failures ...
    }
    

登入是在後臺進行的。這需要幾秒鐘,所以不要在 runApp() 之前呼叫 signIn(),否則玩家每次啟動遊戲時都將被迫面對空白螢幕。

games_services API 的呼叫可能會由於多種原因而失敗。因此,每個呼叫都應像前面的示例一樣包裹在 try-catch 塊中。本指南的其餘部分為清晰起見省略了異常處理。

3. 解鎖成就

#
  1. 在 Google Play 管理中心和 App Store Connect 中註冊成就,並記下它們的 ID。現在,您可以從 Dart 程式碼中頒發這些成就中的任何一個

    dart
    await GamesServices.unlock(
      achievement: Achievement(
        androidID: 'your android id',
        iOSID: 'your ios id',
      ),
    );
    

    玩家在 Google Play 遊戲或 Apple Game Center 上的帳戶現在會列出該成就。

  2. 要從遊戲中顯示成就介面,請呼叫 games_services API

    dart
    await GamesServices.showAchievements();
    

    這會將平臺成就介面作為覆蓋層顯示在您的遊戲之上。

  3. 要在您自己的 UI 中顯示成就,請使用 GamesServices.loadAchievements()

4. 提交分數

#

當玩家完成一局遊戲時,您的遊戲可以將該遊戲會話的結果提交到一個或多個排行榜中。

例如,《超級馬里奧》這樣的平臺遊戲可以同時將最終得分和完成關卡所花費的時間提交到兩個不同的排行榜中。

  1. 在第一步中,您在 Google Play 管理中心和 App Store Connect 中註冊了一個排行榜,並記下了它的 ID。使用此 ID,您可以為玩家提交新分數

    dart
    await GamesServices.submitScore(
      score: Score(
        iOSLeaderboardID: 'some_id_from_app_store',
        androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
        value: 100,
      ),
    );
    

    您不需要檢查新分數是否是玩家的最高分。平臺遊戲服務會為您處理這些問題。

  2. 要將排行榜顯示為遊戲上方的覆蓋層,請執行以下呼叫

    dart
    await GamesServices.showLeaderboards(
      iOSLeaderboardID: 'some_id_from_app_store',
      androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
    );
    
  3. 如果您想在自己的 UI 中顯示排行榜分數,可以使用 GamesServices.loadLeaderboardScores() 獲取它們。

5. 後續步驟

#

games_services 外掛的功能遠不止於此。使用此外掛,您可以:

  • 獲取玩家的圖示、名稱或唯一 ID
  • 儲存和載入遊戲狀態
  • 退出遊戲服務

有些成就可以是增量式的。例如:“你已經收集了所有 10 個 McGuffin 碎片。”

每款遊戲對遊戲服務的需求各不相同。

首先,您可能需要建立此控制器,以便將所有成就和排行榜邏輯集中在一個地方

dart
import 'dart:async';

import 'package:games_services/games_services.dart';
import 'package:logging/logging.dart';

/// Allows awarding achievements and leaderboard scores,
/// and also showing the platforms' UI overlays for achievements
/// and leaderboards.
///
/// A facade of `package:games_services`.
class GamesServicesController {
  static final Logger _log = Logger('GamesServicesController');

  final Completer<bool> _signedInCompleter = Completer();

  Future<bool> get signedIn => _signedInCompleter.future;

  /// Unlocks an achievement on Game Center / Play Games.
  ///
  /// You must provide the achievement ids via the [iOS] and [android]
  /// parameters.
  ///
  /// Does nothing when the game isn't signed into the underlying
  /// games service.
  Future<void> awardAchievement({
    required String iOS,
    required String android,
  }) async {
    if (!await signedIn) {
      _log.warning('Trying to award achievement when not logged in.');
      return;
    }

    try {
      await GamesServices.unlock(
        achievement: Achievement(androidID: android, iOSID: iOS),
      );
    } catch (e) {
      _log.severe('Cannot award achievement: $e');
    }
  }

  /// Signs into the underlying games service.
  Future<void> initialize() async {
    try {
      await GamesServices.signIn();
      // The API is unclear so we're checking to be sure. The above call
      // returns a String, not a boolean, and there's no documentation
      // as to whether every non-error result means we're safely signed in.
      final signedIn = await GamesServices.isSignedIn;
      _signedInCompleter.complete(signedIn);
    } catch (e) {
      _log.severe('Cannot log into GamesServices: $e');
      _signedInCompleter.complete(false);
    }
  }

  /// Launches the platform's UI overlay with achievements.
  Future<void> showAchievements() async {
    if (!await signedIn) {
      _log.severe('Trying to show achievements when not logged in.');
      return;
    }

    try {
      await GamesServices.showAchievements();
    } catch (e) {
      _log.severe('Cannot show achievements: $e');
    }
  }

  /// Launches the platform's UI overlay with leaderboard(s).
  Future<void> showLeaderboard() async {
    if (!await signedIn) {
      _log.severe('Trying to show leaderboard when not logged in.');
      return;
    }

    try {
      await GamesServices.showLeaderboards(
        // TODO: When ready, change both these leaderboard IDs.
        iOSLeaderboardID: 'some_id_from_app_store',
        androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
      );
    } catch (e) {
      _log.severe('Cannot show leaderboard: $e');
    }
  }

  /// Submits [score] to the leaderboard.
  Future<void> submitLeaderboardScore(int score) async {
    if (!await signedIn) {
      _log.warning('Trying to submit leaderboard when not logged in.');
      return;
    }

    _log.info('Submitting $score to leaderboard.');

    try {
      await GamesServices.submitScore(
        score: Score(
          // TODO: When ready, change these leaderboard IDs.
          iOSLeaderboardID: 'some_id_from_app_store',
          androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
          value: score,
        ),
      );
    } catch (e) {
      _log.severe('Cannot submit leaderboard score: $e');
    }
  }
}

更多資訊

#

Flutter 休閒遊戲工具包 (Casual Games Toolkit) 包含以下模板:

  • basic:基礎入門遊戲
  • card:卡牌遊戲入門模板
  • endless runner:無盡奔跑遊戲入門模板(使用 Flame),玩家在其中不斷奔跑,避開陷阱並獲得獎勵