玩家玩游戏有多种动机。概括来说,有四种主要动机:沉浸、成就、合作和竞争。无论您构建什么游戏,有些玩家都希望在其中获得成就。这可以是赢得奖杯或解锁秘密。有些玩家希望在其中进行竞争。这可以是获得高分或完成速通。这两个概念分别对应成就排行榜

A simple graphic representing the four types of motivation explained above

App Store 和 Google Play 等生态系统为成就和排行榜提供集中式服务。玩家可以在一个地方查看所有游戏的成就,开发人员无需为每个游戏重新实现这些功能。

本教程将演示如何使用 games_services package 为您的移动游戏添加成就和排行榜功能。

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 注册您的游戏,并从我的 App 部分按下 + 图标。

    Screenshot of the + button in App Store Connect

  7. 仍在 App Store Connect 中,查找游戏中心 (Game Center) 部分。截至本文撰写时,您可以在服务中找到它。在游戏中心 (Game Center) 页面上,您可能需要根据您的游戏设置排行榜和多个成就。记下您创建的排行榜和成就的 ID。

Android

#

在 Android 上启用 Play 游戏服务

  1. 如果您尚未注册,请前往 Google Play Console 并在此注册您的游戏。

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

  2. 仍在 Google Play Console 中,从导航菜单中选择 Play 游戏服务设置和管理配置,并按照其说明操作。

    • 这需要大量时间和耐心。其中包括,您需要在 Google Cloud Console 中设置 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 package 的依赖。

    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 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
  • 保存和加载游戏状态
  • 退出游戏服务

有些成就可以是递增的。例如:“您已收集到 McGuffin 的全部 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),玩家在其中无休止地奔跑,避开陷阱并获得奖励