为您的移动游戏添加成就和排行榜
如何使用 `games_services` 插件向您的游戏添加功能。
玩家玩游戏有各种各样的动机。从广义上讲,有四种主要的动机:沉浸感、成就感、合作和竞争。无论您构建什么游戏,有些玩家都想在其中取得成就。这可能是赢得奖杯或解锁秘密。有些玩家想在其中竞争。这可能是获得高分或完成加速通关。这两个想法对应于成就和排行榜的概念。
App Store 和 Google Play 等生态系统为成就和排行榜提供集中的服务。玩家可以在一个地方查看所有游戏的成就,而开发者无需为每个游戏重新实现它们。
本教程演示了如何使用 games_services 包将成就和排行榜功能添加到您的手游中。
1. 启用平台服务
#要启用游戏服务,请在 iOS 上设置 Game Center,并在 Android 上设置 Google Play 游戏服务。
iOS
#要在 iOS 上启用 Game Center (GameKit)
-
在 Xcode 中打开您的 Flutter 项目。打开
ios/Runner.xcworkspace 选择根 Runner 项目。
转到 Signing & Capabilities 选项卡。
单击
+按钮将 Game Center 添加为功能。关闭 Xcode。
-
如果您尚未这样做,请在 App Store Connect 中注册您的游戏,并从 My App 部分按
+图标。
-
仍然在 App Store Connect 中,查找 Game Center 部分。撰写本文时,您可以在 Services 中找到它。在 Game Center 页面上,您可能需要根据您的游戏设置排行榜和几个成就。请记下您创建的排行榜和成就的 ID。
Android
#要启用 Android 上的 Play 游戏服务
-
如果您尚未这样做,请转到 Google Play Console 并在此处注册您的游戏。

-
仍然在 Google Play Console 中,从导航菜单中选择 Play 游戏服务 → 设置和管理 → 配置,并按照他们的说明操作。
这需要花费大量时间和耐心。在其他事项中,您需要在 Google Cloud Console 中设置 OAuth 同意屏幕。如果您在任何时候感到迷茫,请参阅官方 Play 游戏服务指南。

-
完成操作后,您可以开始在 Play 游戏服务 → 设置和管理 中添加排行榜和成就。创建与 iOS 端完全相同的一组。记下 ID。
转到 Play 游戏服务 → 设置和管理 → 发布。
-
单击 发布。不用担心,这实际上不会发布您的游戏。它只会发布成就和排行榜。例如,一旦排行榜以这种方式发布,就无法取消发布。
-
转到 Play 游戏服务 → 设置和管理 → 配置 → 凭据。
-
找到 获取资源 按钮。它返回一个包含 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> -
在
android/app/src/main/res/values/games-ids.xml中添加一个文件,其中包含您在上一步骤中收到的 XML。
2. 登录游戏服务
#现在您已经设置了 Game Center 和 Play 游戏服务,并且已经准备好了成就和排行榜 ID,终于可以开始使用 Dart 了。
-
添加对
games_services包的依赖项。flutter pub add games_services -
在您执行任何操作之前,您必须将玩家登录到游戏服务。
darttry { await GamesServices.signIn(); } on PlatformException catch (e) { // ... deal with failures ... }
登录发生在后台。这需要几秒钟,因此不要在 runApp() 之前调用 signIn(),否则玩家每次启动游戏时都会被迫盯着空白屏幕。
对 games_services API 的 API 调用可能由于多种原因而失败。因此,每个调用都应包装在 try-catch 块中,如前一个示例所示。为了清晰起见,本教程的其余部分省略了异常处理。
3. 解锁成就
#-
在 Google Play Console 和 App Store Connect 中注册成就,并记下它们的 ID。现在您可以从您的 Dart 代码中授予任何这些成就
dartawait GamesServices.unlock( achievement: Achievement( androidID: 'your android id', iOSID: 'your ios id', ), );玩家在 Google Play 游戏或 Apple Game Center 上的帐户现在列出了该成就。
-
要从您的游戏中显示成就 UI,请调用
games_servicesAPIdartawait GamesServices.showAchievements();这会将平台成就 UI 作为叠加层显示在您的游戏上。
-
要以您自己的 UI 显示成就,请使用
GamesServices.loadAchievements()。
4. 提交分数
#当玩家完成游戏时,您的游戏可以将该游戏会话的结果提交到一个或多个排行榜。
例如,像超级马里奥这样的平台游戏可以将最终得分和完成关卡所用的时间提交到两个单独的排行榜。
-
在第一步中,您在 Google Play Console 和 App Store Connect 中注册了一个排行榜,并记下了它的 ID。使用此 ID,您可以为玩家提交新的分数
dartawait GamesServices.submitScore( score: Score( iOSLeaderboardID: 'some_id_from_app_store', androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy', value: 100, ), );您无需检查新的分数是否是玩家的最高分。平台游戏服务会为您处理。
-
要将排行榜作为游戏的叠加层显示,请进行以下调用
dartawait GamesServices.showLeaderboards( iOSLeaderboardID: 'some_id_from_app_store', androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy', ); -
如果您想以您自己的 UI 显示排行榜分数,可以使用
GamesServices.loadLeaderboardScores()获取它们。
5. 后续步骤
#games_services 插件还有更多功能。使用此插件,您可以
- 获取玩家的图标、姓名或唯一 ID
- 保存和加载游戏状态
- 退出游戏服务
有些成就是增量的。例如:“您已收集了所有 10 个麦高芬碎片。”
每个游戏对游戏服务都有不同的需求。
首先,您可能想创建此控制器,以便将所有成就和排行榜逻辑放在一个地方
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),玩家无休止地奔跑,躲避陷阱并获得奖励