一个Flutter入门游戏,包含移动(iOS和Android)游戏的所有高级功能,包括以下功能:
- 声音
- 音乐
- 主菜单画面
- settings
- 广告(AdMob)
- 应用内购买
- 游戏服务(Game Center 和 Google Play Games Services)
- 崩溃报告(Firebase Crashlytics)
入门
游戏开箱即用,无需配置。它包含主菜单、路由器、设置画面和音频等功能。在构建新游戏时,这很可能是您首先需要的一切。
当您准备好启用更高级的集成(如广告和应用内支付)时,请阅读下面的“集成”部分。
开发
在调试模式下运行应用
flutter run
这假定您已连接Android模拟器、iOS模拟器或物理设备。
通常,将您的游戏开发为桌面应用程序会很方便。例如,您可以运行 flutter run -d macOS,然后在 Mac 上的桌面窗口中获得相同的 UI。这样,您就不需要使用模拟器/仿真器或连接移动设备了。此模板通过禁用 AdMob 等桌面集成来支持桌面开发。
代码组织
代码以一种松散且浅层的功能优先的方式组织。因此,在lib/src中,您会找到ads、audio或main_menu等目录。没什么花哨的,但可用。
lib
├── src
│ ├── ads
│ ├── app_lifecycle
│ ├── audio
│ ├── crashlytics
│ ├── game_internals
│ ├── games_services
│ ├── in_app_purchase
│ ├── level_selection
│ ├── main_menu
│ ├── play_session
│ ├── player_progress
│ ├── settings
│ ├── style
│ └── win_game
├── ...
└── main.dart
状态管理方法故意设计得非常底层。这样,您就可以轻松地采用此项目并继续开发,而无需学习新的范例,也无需记住运行 flutter pub run build_runner watch。当然,我们鼓励您使用您偏好的任何范例、辅助包或代码生成方案。
为生产版本构建
构建iOS应用(完成后打开Xcode)
flutter build ipa && open build/ios/archive/Runner.xcarchive
构建Android应用(完成后打开包含bundle的文件夹)
flutter build appbundle && open build/app/outputs/bundle/release
虽然此模板适用于手机游戏,但您也可以将其发布到 Web。这对于 Web 演示或快速试玩可能很有用。以下命令需要安装 peanut。
flutter pub global run peanut \
--web-renderer canvaskit \
--extra-args "--base-href=/name_of_your_github_repo/" \
&& git push origin --set-upstream gh-pages
上述命令的最后一行会自动将您新构建的Web游戏推送到GitHub pages,前提是您已进行相应的设置。
集成
更高级的集成默认是禁用的。例如,成就一开始并未启用,因为您,开发者,需要自己设置它们(成就必须先存在于 App Store Connect 和 Google Play Console 中才能在代码中使用)。
本节包括有关如何启用任何给定集成的说明。
一些通用说明
- 在开始任何更深入的集成之前,请更改您游戏的软件包名称。StackOverflow 有说明,而(pub.dev 上的)
rename工具可以自动化此过程。 - 以下指南都假定您已在Google Play Console和Apple的App Store Connect中注册了您的游戏。
广告
广告使用官方google_mobile_ads包实现,默认禁用。
// TODO: When ready, uncomment the following lines to enable integrations.
AdsController? adsController;
// if (!kIsWeb && (Platform.isIOS || Platform.isAndroid)) {
// /// Prepare the google_mobile_ads plugin so that the first ad loads
// /// faster. This can be done later or with a delay if startup
// /// experience suffers.
// adsController = AdsController(MobileAds.instance);
// adsController.initialize();
// }
lib/main.dart中的AdsController代码默认情况下为null,因此模板会优雅地回退到不显示桌面广告。
您可以在lib/src/ads/中找到与广告相关的代码。
在游戏中启用广告
-
前往AdMob并设置一个帐户。这可能需要相当长的时间,因为您需要提供银行信息、签署合同等。
-
在AdMob中为Android和iOS分别创建两个“应用”。
-
获取Android应用和iOS应用的AdMob“应用ID”。您可以在“应用设置”部分找到它们。它们看起来像
ca-app-pub-1234567890123456~1234567890(注意两个数字之间的波浪号)。 -
打开
android/app/src/main/AndroidManifest.xml,找到名为com.google.android.gms.ads.APPLICATION_ID的<meta-data>条目,然后用您在上一阶段获得的 Android AdMob 应用的 *App ID* 更新该值。<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-1234567890123456~1234567890"/>
-
打开
ios/Runner/Info.plist,找到名为GADApplicationIdentifier的条目,并将值更新为iOS AdMob应用的“应用ID”。<key>GADApplicationIdentifier</key> <string>ca-app-pub-1234567890123456~0987654321</string>
-
回到 AdMob,为每个 AdMob 应用创建一个 *广告单元*。这会要求广告单元的格式(横幅、插页式、激励式)。该模板设置为横幅广告单元,因此如果您想避免更改
lib/src/ads中的代码,请选择该选项。 -
获取 Android 应用和 iOS 应用的 *广告单元 ID*。您可以在 *广告单元* 部分找到它们。它们的格式类似于
ca-app-pub-1234567890123456/1234567890(是的,格式与 *App ID* 非常相似;注意两个数字之间的斜杠)。 -
打开
lib/src/ads/ads_controller.dart并更新其中“广告单元”ID的值。final adUnitId = defaultTargetPlatform == TargetPlatform.android ? 'ca-app-pub-1234567890123456/1234567890' : 'ca-app-pub-1234567890123456/0987654321';
-
取消注释
lib/main.dart中与广告相关的代码,并添加以下两个导入:import 'dart:io'; import 'package:google_mobile_ads/google_mobile_ads.dart';
-
在AdMob的“设置”→“测试设备”部分注册您的测试设备。
游戏模板定义了一个示例 AdMob *应用 ID* 和两个示例 _广告单元 ID_。这些允许您在不获取 AdMob 实际 ID 的情况下测试代码,但此“功能”的文档非常少,仅用于 Hello World 项目。这些示例 ID 在已发布的.*游戏*中**不会**生效。
如果您在任何时候感到迷茫,可以在Google AdMob的文档网站上找到完整的AdMob for Flutter教程。
如果您想实现更多的AdMob格式(例如插页式广告),一个好的起点是package:google_mobile_ads中的示例。
音频
音频默认启用,已准备就绪。您可以根据自己的喜好修改lib/src/audio/中的代码。
您可以在 assets/music 中找到一些音乐曲目 — 这些音乐曲目采用知识共享署名(CC-BY)许可,并已获得许可包含在此仓库中。如果您决定将这些曲目保留在您的游戏中,请不要忘记对音乐家 Mr Smith 表示感谢。
该存储库还在assets/sfx中包含了一些音效样本。这些是公共领域(CC0)的,您几乎肯定想替换它们,因为它们只是开发人员用嘴发出的傻傻的声音的录音。
Crashlytics
Crashlytics集成默认禁用。但即使您不启用它,也可能会在lib/src/crashlytics中找到有用的代码。它收集所有日志消息和错误,以便您至少可以将它们打印到控制台。
启用后,此集成功能会强大得多。
- 您应用的任何崩溃都会发送到Firebase Crashlytics控制台。
- 在您的代码中任何地方抛出的任何未捕获异常都会被捕获并发送到Firebase Crashlytics控制台。
- 这些报告中的每一项都包含以下信息:
- 错误消息
- 堆栈跟踪
- 设备型号、方向、可用RAM、可用磁盘空间
- 操作系统版本
- 应用版本
- 此外,在您的应用(以及您使用的包)中生成的任何日志消息都会在内存中记录,并与报告一起发送。这意味着您可以了解崩溃或异常发生之前发生了什么。
- 此外,任何带有
Level.severe或更高严重级的日志消息也会发送到Crashlytics。 - 您可以在
lib/src/crashlytics中自定义这些行为。
要启用Firebase Crashlytics,请执行以下操作:
- 在 console.firebase.google.com 中创建一个新项目。给 Firebase 项目起任何您喜欢的名字;只需**记住名字**即可。如果您不想使用分析功能,则无需在项目中启用分析。
- 在您的机器上安装
firebase-tools。 - 在您的机器上安装
flutterfireCLI。 - 在此项目根目录(包含
pubspec.yaml的目录)下,运行以下命令:flutterfire configure- 此命令会询问您之前创建的Firebase项目的名称以及您支持的目标平台列表。截至2022年4月,Crashlytics仅完全支持
android和ios。 - 该命令会使用正确的代码重写
lib/firebase_options.dart。
- 此命令会询问您之前创建的Firebase项目的名称以及您支持的目标平台列表。截至2022年4月,Crashlytics仅完全支持
- 转到
lib/main.dart并取消注释与Crashlytics相关的行。
现在您应该可以在 console.firebase.google.com 中看到崩溃、错误和严重日志消息了。要进行测试,请在项目中添加一个按钮,并在玩家按下该按钮时抛出您喜欢的任何异常。
TextButton(
onPressed: () => throw StateError('whoa!'),
child: Text('Test Crashlytics'),
)
游戏服务(Game Center 和 Play Games Services)
游戏服务(如成就和排行榜)由games_services包实现,默认禁用。
要启用游戏服务,请首先在iOS上设置Game Center,在Android上设置Google Play Games Services。
在iOS上启用Game Center(GameKit)
- 在Xcode中打开您的Flutter项目(
open ios/Runner.xcodeproj)。 - 选择根Runner项目,然后转到Signing & Capabilities选项卡。
- 点击
+按钮添加Game Center作为一项功能。您现在可以关闭Xcode。 - 在 App Store Connect 中找到您的应用,并在 *功能* 部分设置 *Game Center*。例如,您可能需要设置一个排行榜和几个成就。请记下您创建的排行榜和成就的 ID。
在Android上启用Play Games Services
-
转到Google Play Console中的您的应用。
-
从导航菜单中选择Play Games Services → Setup and management → Configuration,然后按照说明进行操作。
- 这需要大量的时间和耐心。除其他事项外,您还需要在 Google Cloud Console 中设置 OAuth 同意屏幕。如果您在任何时候感到迷茫,请查阅官方的 Play Games Services 指南。
-
完成后,您可以开始在Play Games Services → Setup and management中添加排行榜和成就。创建与您在iOS端相同的成就和排行榜。记下ID。
-
转到 *Play Games Services* → *设置和管理* → *发布*,然后点击*‘发布’*。不用担心,这实际上并不会发布您的游戏。它只会发布成就和排行榜。一旦排行榜(例如)以这种方式发布,就无法取消发布。
-
转到Play Games Services → Setup and management → Configuration → Credentials。找到一个名为‘Get resources’的按钮。您将获得一个包含Play Games Services ID的XML文件。
<?xml version="1.0" encoding="utf-8"?> <!--Google Play game services IDs. Save this file as res/values/games-ids.xml in your project.--> <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>
-
用您在上一步中获得的XML替换
android/app/src/main/res/values/games-ids.xml文件。
现在您已经设置了Game Center和Play Games Services,并且您的成就和排行榜ID也已准备就绪,终于可以开始Dart开发了。
-
打开
lib/src/games_services/games_services.dart并在showLeaderboard()函数中编辑排行榜ID。// TODO: When ready, change both these leaderboard IDs. iOSLeaderboardID: "some_id_from_app_store", androidLeaderboardID: "sOmE_iD_fRoM_gPlAy",
-
同一文件中的
awardAchievement()函数将ID作为参数。因此,您可以从游戏中的任何位置调用它,如下所示:final gamesServicesController = context.read<GamesServicesController?>(); await gamesServicesController?.awardAchievement( iOS: 'an_achievement_id', android: 'aNaChIeVeMenTiDfRoMgPlAy', );
您可能想将成就ID附加到关卡、敌人、地点、物品等。例如,该模板在
lib/src/level_selection/levels.dart中定义了关卡,如下所示:GameLevel( number: 1, difficulty: 5, achievementIdIOS: 'first_win', achievementIdAndroid: 'sOmEtHinG', ),
这样,在玩家到达一个关卡后,我们会检查该关卡是否具有非空成就ID,如果是,则使用这些ID调用
awardAchievement()。 -
取消注释
lib/main.dart中与游戏服务相关的代码。// TODO: When ready, uncomment the following lines. GamesServicesController? gamesServicesController; // if (!kIsWeb && (Platform.isIOS || Platform.isAndroid)) { // gamesServicesController = GamesServicesController() // // Attempt to log the player in. // ..initialize(); // }
如果您在任何时候感到迷茫,有一个由 package:games_services 的作者撰写的 操作指南。指南中的一些说明和截图略有[过时](例如,在文章发布后,*iTunes Connect* 已重命名为 *App Store Connect*),但它仍然是一个很好的资源。
应用内购买
应用内购买使用官方in_app_purchase包实现。集成默认禁用。
在Android上启用应用内购买
- 将游戏上传到Google Play Console的Closed Testing(封闭测试)轨道。
- 由于游戏已经依赖于
package:in_app_purchase,因此它会向Play Store表明自己是具有应用内购买的项目。 - 发布到封闭测试会触发审核流程,这是应用内购买正常工作的前提。审核流程可能需要几天时间,在此期间您无法在Android方面继续进行。
- 由于游戏已经依赖于
- 在Play Console → Monetize → In-app products中添加一个应用内产品。想一个产品ID(例如,
ad_removal)。 - 在Play Console中,激活该应用内产品。
在iOS上启用应用内购买
- 确保您已在App Store Connect中签署了Paid Apps Agreement。
- 在App Store Connect中,转到Features → In-App Purchases,然后通过点击
+按钮添加一个新的应用内购买。使用您在Android端使用的相同产品ID。 - 按照说明获取应用内购买的批准。
现在一切都已准备好在Dart代码中启用集成。
-
打开
lib/src/in_app_purchase/ad_removal.dart并将productId更改为您在Play Console和App Store Connect中输入的产品ID。/// The representation of this product on the stores. static const productId = 'remove_ads';
- 如果您的应用内购买不是广告移除,则创建一个类似于模板中
AdRemovalPurchase的类。 - 如果您创建了多个应用内购买,则需要修改
lib/src/in_app_purchase/in_app_purchase.dart中的代码。默认情况下,该模板仅支持一次应用内购买。
- 如果您的应用内购买不是广告移除,则创建一个类似于模板中
-
取消注释
lib/main.dart中与应用内购买相关的代码。// TODO: When ready, uncomment the following lines. InAppPurchaseController? inAppPurchaseController; // if (!kIsWeb && (Platform.isIOS || Platform.isAndroid)) { // inAppPurchaseController = InAppPurchaseController(InAppPurchase.instance) // // Subscribing to [InAppPurchase.instance.purchaseStream] as soon // // as possible in order not to miss any updates. // ..subscribe(); // // Ask the store what the player has bought already. // inAppPurchaseController.restorePurchases(); // }
如果您在任何时候感到迷茫,可以查看官方的为您的Flutter应用添加应用内购买 codelab。
设置
设置页面默认启用,并且可以从主菜单和游戏会话屏幕中的“齿轮”按钮访问。
设置使用package:shared_preferences保存到本地存储。要更改保存哪些首选项以及如何保存,请编辑lib/src/settings/persistence中的文件。
abstract class SettingsPersistence {
Future<bool> getMusicOn();
Future<bool> getMuted({required bool defaultValue});
Future<String> getPlayerName();
Future<bool> getSoundsOn();
Future<void> saveMusicOn(bool value);
Future<void> saveMuted(bool value);
Future<void> savePlayerName(String value);
Future<void> saveSoundsOn(bool value);
}
Icon
要更新启动图标,首先更改assets/icon-adaptive-foreground.png和assets/icon.png文件。然后,运行以下命令:
flutter pub run flutter_launcher_icons:main
您可以在pubspec.yaml的flutter_icons:部分配置图标的外观。