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

玩家玩游戏的动机各不相同。总的来说,有四种主要的动机:沉浸、成就、合作和竞争。无论您构建的是什么游戏,有些玩家都希望在游戏中取得成就。这可能是赢得的奖杯或解锁的秘密。有些玩家希望在游戏中竞争。这可能是获得高分或完成速通。这两个想法对应于成就排行榜的概念。

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 Games Services

  1. 如果您还没有,请访问 Google Play Console 并注册您的游戏。

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

  2. 仍在 Google Play Console 中,从导航菜单中选择 Play Games ServicesSetup and managementConfiguration,并按照其说明进行操作。

    • 这需要大量时间和耐心。除其他事项外,您需要在 Google Cloud Console 中设置 OAuth 同意屏幕。如果您在任何时候感到迷茫,请参考官方的 Play Games Services 指南

      Screenshot showing the Games Services section in Google Play Console

  3. 完成后,您可以在 Play Games ServicesSetup and management 中开始添加排行榜和成就。创建与 iOS 端相同的设置。记下 ID。

  4. 转到 Play Games Services → Setup and management → Publishing

  5. 点击 Publish。不用担心,这不会真正发布您的游戏。它只发布成就和排行榜。例如,一旦排行榜以这种方式发布,就不能取消发布。

  6. 转到 Play Games Services → Setup and management → Configuration → Credentials

  7. 找到 Get resources 按钮。它将返回包含 Play Games Services 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 Games Services,并且已经准备好了成就和排行榜 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 的 API 调用可能会由于多种原因而失败。因此,每个调用都应该像前面的示例一样包装在 try-catch 块中。为了清晰起见,本食谱的其余部分省略了异常处理。

3. 解锁成就

#
  1. 在 Google Play Console 和 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. 要从您的游戏中显示成就 UI,请调用 `games_services` API

    dart
    await GamesServices.showAchievements();

    这将显示平台成就 UI 作为您游戏的叠加层。

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

4. 提交分数

#

当玩家完成游戏时,您的游戏可以将该游戏会话的结果提交到一个或多个排行榜。

例如,像超级马里奥这样的平台游戏可以将最终得分和完成关卡所花费的时间提交到两个独立的排行榜。

  1. 在第一步中,您在 Google Play Console 和 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 个麦格芬碎片。”

每个游戏对游戏服务的需要都不同。

首先,你可能需要创建这个控制器,以便将所有成就和排行榜逻辑集中在一个地方

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 休闲游戏工具包包含以下模板

  • basic: 基本入门游戏
  • card: 入门纸牌游戏
  • endless runner: 入门游戏(使用 Flame),玩家可以无限奔跑,躲避陷阱并获得奖励