一个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中,您会找到adsaudiomain_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 中)。

本节包括有关如何启用任何给定集成的说明。

一些通用说明

广告

广告使用官方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/中找到与广告相关的代码。

在游戏中启用广告

  1. 前往AdMob并设置一个帐户。这可能需要相当长的时间,因为您需要提供银行信息、签署合同等。

  2. AdMob中为Android和iOS分别创建两个“应用”。

  3. 获取Android应用和iOS应用的AdMob“应用ID”。您可以在“应用设置”部分找到它们。它们看起来像ca-app-pub-1234567890123456~1234567890(注意两个数字之间的波浪号)。

  4. 打开 android/app/src/main/AndroidManifest.xml,找到名为 com.google.android.gms.ads.APPLICATION_ID<meta-data> 条目,并使用您在上一步中获取的 Android AdMob 应用的应用 ID 更新该值。

    <meta-data
       android:name="com.google.android.gms.ads.APPLICATION_ID"
       android:value="ca-app-pub-1234567890123456~1234567890"/>
  5. 打开ios/Runner/Info.plist,找到名为GADApplicationIdentifier的条目,并将值更新为iOS AdMob应用的“应用ID”。

    <key>GADApplicationIdentifier</key>
    <string>ca-app-pub-1234567890123456~0987654321</string>
  6. 回到 AdMob,为每个 AdMob 应用创建广告单元。这会询问广告单元的格式(横幅、插页式、奖励式)。该模板已设置为横幅广告单元,因此如果您想避免更改 lib/src/ads 中的代码,请选择此项。

  7. 获取 Android 应用和 iOS 应用的广告单元 ID。您可以在广告单元部分找到它们。它们看起来类似于 ca-app-pub-1234567890123456/1234567890(是的,格式与应用 ID非常相似;请注意两个数字之间的斜杠)。

  8. 打开lib/src/ads/ads_controller.dart并更新其中“广告单元”ID的值。

    final adUnitId = defaultTargetPlatform == TargetPlatform.android
        ? 'ca-app-pub-1234567890123456/1234567890'
        : 'ca-app-pub-1234567890123456/0987654321';
  9. 取消注释lib/main.dart中与广告相关的代码,并添加以下两个导入:

    import 'dart:io';
    import 'package:google_mobile_ads/google_mobile_ads.dart';
  10. 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,请执行以下操作:

  1. console.firebase.google.com 中创建一个新项目。随意命名您的 Firebase 项目;只需记住名称。如果您不想使用分析,则无需在项目中启用分析。
  2. 在您的机器上安装firebase-tools
  3. 在您的机器上安装flutterfire CLI
  4. 在此项目根目录(包含pubspec.yaml的目录)下,运行以下命令:

    • flutterfire configure
      • 此命令会询问您之前创建的Firebase项目的名称以及您支持的目标平台列表。截至2022年4月,Crashlytics仅完全支持androidios
      • 该命令会使用正确的代码重写lib/firebase_options.dart
  5. 转到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)

  1. 在Xcode中打开您的Flutter项目(open ios/Runner.xcodeproj)。
  2. 选择根Runner项目,然后转到Signing & Capabilities选项卡。
  3. 点击+按钮添加Game Center作为一项功能。您现在可以关闭Xcode。
  4. 转到 App Store Connect 中的您的应用,并在功能部分设置Game Center。例如,您可能需要设置一个排行榜和几个成就。记下您创建的排行榜和成就的 ID。

在Android上启用Play Games Services

  1. 转到Google Play Console中的您的应用。

  2. 从导航菜单中选择Play Games ServicesSetup and managementConfiguration,然后按照说明进行操作。

    • 这需要大量的时间和耐心。其中,您需要在 Google Cloud Console 中设置 OAuth 同意屏幕。如果您在任何时候感到迷茫,请查阅官方 Play Games Services 指南
  3. 完成后,您可以开始在Play Games ServicesSetup and management中添加排行榜和成就。创建与您在iOS端相同的成就和排行榜。记下ID。

  4. 转到Play Games Services设置和管理发布,然后点击“发布”。不用担心,这并不会真正发布您的游戏。它只会发布成就和排行榜。例如,一旦排行榜以这种方式发布,就无法取消发布。

  5. 转到Play Games ServicesSetup and managementConfigurationCredentials。找到一个名为‘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>
  6. 用您在上一步中获得的XML替换android/app/src/main/res/values/games-ids.xml文件。

现在您已经设置了Game CenterPlay Games Services,并且您的成就和排行榜ID也已准备就绪,终于可以开始Dart开发了。

  1. 打开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",
  2. 同一文件中的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()

  3. 取消注释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上启用应用内购买

  1. 将游戏上传到Google Play Console的Closed Testing(封闭测试)轨道。

    • 由于游戏已经依赖于package:in_app_purchase,因此它会向Play Store表明自己是具有应用内购买的项目。
    • 发布到封闭测试会触发审核流程,这是应用内购买正常工作的前提。审核流程可能需要几天时间,在此期间您无法在Android方面继续进行。
  2. Play ConsoleMonetizeIn-app products中添加一个应用内产品。想一个产品ID(例如,ad_removal)。
  3. 在Play Console中,激活该应用内产品。

在iOS上启用应用内购买

  1. 确保您已在App Store Connect中签署了Paid Apps Agreement
  2. 在App Store Connect中,转到FeaturesIn-App Purchases,然后通过点击+按钮添加一个新的应用内购买。使用您在Android端使用的相同产品ID。
  3. 按照说明获取应用内购买的批准。

现在一切都已准备好在Dart代码中启用集成。

  1. 打开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中的代码。默认情况下,该模板仅支持一次应用内购买。
  2. 取消注释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.pngassets/icon.png文件。然后,运行以下命令:

flutter pub run flutter_launcher_icons:main

您可以在pubspec.yamlflutter_icons:部分配置图标的外观。

GitHub

查看 Github