跳到主内容

为您的移动游戏添加成就和排行榜

如何使用 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),玩家在其中不断奔跑,避开陷阱并获得奖励