初探 Flutter 跨端遊戲開發

本文作者爲奇舞團大前端 CodeFarmer

背景

筆者在公司前前後後做了有小一年 Flutter 開發,從入門到後面業務方變動,到暫時放棄 Flutter。對於 Flutter 爭議不提,我們得承認 Flutter 是一款很優秀的跨端解決方案,到前段時間的 Flutter3.0 的提出,3.0 對遊戲做了很友好的支持,筆者又重新開始以遊戲爲切入點 去上手 Flutter。所以我們探索一下 Flutter3.0 對於遊戲的支持力度,是否可以低成本寫出一個自己的小遊戲呢?

Why?爲什麼要做 Flutter 遊戲開發?

Flutter3.0 環境準備

  1. 下載以下安裝包以獲取 Flutter SDK 的最新穩定版本:

xDHh7X

  1. 解壓 SDK

     cd ~/development
     unzip ~/Downloads/flutter_macos_arm64_3.0.1-stable.zip
  2. 添加環境變量:(關於 Mac 環境變量 不累述: 參考)

    export PATH="$PATH:`pwd`/flutter/bin"
    open ~/.bash_profile
    source  ~/.bash_profile
      - 查看Flutter環境完整性: flutter doctor
      flutter doctor

環境常見問題

  1. 問題 1:CocoaPods 環境依賴安裝 cocoapods

    sudo gem install cocoapods
    Error: To set up CocoaPods for ARM macOS, run:
      arch -x86_64 sudo gem install ffi
    arch -x86_64 sudo gem install ffi、
    Building native extensions. This could take a while...
    Successfully installed ffi-1.15.5
    Parsing documentation for ffi-1.15.5
    Done installing documentation for ffi after 3 seconds
    1 gem installed
  2. 問題 2:Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

    flutter doctor --android-licenses

完整環境如下:非必須,缺失部分環境不影響開發

    [] Flutter (Channel stable, 3.0.1, on macOS 12.4 21F79 darwin-arm, locale
        zh-Hans-CN)
    [] Android toolchain - develop for Android devices (Android SDK version
        32.1.0-rc1)
    [] Xcode - develop for iOS and macOS (Xcode 13.4)
    [] Chrome - develop for the web
    [] Android Studio (version 2021.2)
    [] IntelliJ IDEA Ultimate Edition (version 2020.3.2)
    [] VS Code (version 1.67.2)
    [] Connected device (4 available)
    [] HTTP Host Availability

    • No issues found!

第一個遊戲模板

這個 Flutter 示例遊戲 repo 預先集成了應用內購買、移動廣告 SDK 和許多其他成功遊戲的模塊;

    cd flutterdemo 
    git clone https://github.com/flutter/samples.git

Flutter 中的入門遊戲,具有移動端(iOS 和 Android)遊戲的所有到發佈基本集成,包括以下功能:

lib
├── src
│   ├── ads//廣告
│   ├── app_lifecycle//生命週期
│   ├── audio//音頻
│   ├── crashlytics//崩潰日誌
│   ├── game_internals//
│   ├── games_services//遊戲服務
│   ├── in_app_purchase//應用內購買
│   ├── level_selection//等級
│   ├── main_menu//menu
│   ├── play_session//
│   ├── player_progress//用戶進度
│   ├── settings//設置
│   ├── style
│   └── win_game//勝利
├── ...
└── main.dart
cd samples/game_template/
flutter run
Multiple devices found:
[1]: ANA AN00 (NAB5T20525007949)
[2]: iPhone 12 (159CF48A-D131-4187-9E51-391759D8ADC8)
[3]: macOS (macos)
[4]: Chrome (chrome)
Warning: CocoaPods not installed. Skipping pod install.
  CocoaPods is used to retrieve the iOS and macOS platform side's plugin code
  that responds to your plugin usage on the Dart side.
  Without CocoaPods, plugins will not work on iOS or macOS.
  For more info, see https://flutter.dev/platform-plugins
To install see
https://guides.cocoapods.org/using/getting-started.html#installation for
instructions.

啓動工程

flutter clean
flutter pub get
flutter run

通過以上模板,我們發現關鍵引入信息如下

  games_services: ^2.0.7  # 成就和排行榜
  google_mobile_ads: ^1.1.0  # 廣告
  in_app_purchase: ^3.0.1  # 應用內購買

廣告 id 切換:ios/Runner/Info.plist  android/app/src/main/AndroidManifest.xml

<key>GADApplicationIdentifier</key>
<string>ca-app-pub-1234567890123456~0987654321</string>

<meta-data
   android:
   android:value="ca-app-pub-1234567890123456~1234567890"/>

games_services

登錄

讓用戶登錄遊戲中心 (iOS) 或 Google play 遊戲服務 (Android)。在進行任何操作(例如發送分數或解鎖成就)之前,應該先登錄。

 GamesServices.signIn();

判斷登錄

檢查當前用戶是否登錄遊戲服務(ios 遊戲中心或者 Google play 遊戲服務)

GamesServices.isSignedIn;

登出

讓用戶退出 ios 遊戲中心 / Goole Play 服務。調用後,將無法再對該用戶的帳戶進行任何操作。

 GamesServices.signOut();

顯示成就界面

GamesServices.showAchievements();

顯示排行榜 - 入參需要 ios_leaderboard_id 和 android_leaderboard_id

 GamesServices.showLeaderboards(iOSLeaderboardID: 'ios_leaderboard_id', androidLeaderboardID: 'android_leaderboard_id');

提交分數

提交分數到排行榜

/**
入參需要android_leaderboard_id和ios_leaderboard_id
*/
GamesServices.submitScore(score: Score(androidLeaderboardID: 'android_leaderboard_id',
                                       iOSLeaderboardID: 'ios_leaderboard_id',
                                       value: 5));

解鎖成就

/**
android_id
ios_id
percentComplete` 成就的完成百分比,這個參數在iOS的情況下是可選的
`steps` Android 的成就步驟
*/
GamesServices.unlock(achievement: Achievement(androidID: 'android_id',
                                              iOSID: 'ios_id',
                                              percentComplete: 100,
                                              steps: 2));

增加步驟 (Android Only)

增加安卓成就的步驟

final result = await GamesServices.increment(achievement: Achievement(androidID: 'android_id', steps: 50));
print(result);

顯示接入點 (iOS Only)

GamesServices.showAccessPoint(AccessPointLocation.topLeading);

隱藏接入點 (iOS Only)

GamesServices.hideAccessPoint();

獲取 Player id

final playerID = GamesServices.getPlayerID();

獲取 Player name

final playerName = GamesServices.getPlayerName();

小結

以上介紹了 Flutter3.0 和 3.0 對遊戲友好的支持,可以方便的打通移動端,方便的接入廣告等服務,可以讓開發者更專注遊戲本身開發,而非 廣告、音頻控制、用戶排名,應用支付等,下面我們介紹一下 Flutter 遊戲的核心,常用的遊戲引擎和使用。

遊戲引擎

Flame engine:https://github.com/flame-engine/flame/blob/main/i18n/README-ZH.md

Flame 引擎的目的是爲使用 Flutter 開發的遊戲會遇到的常見問題提供一套完整的解決方案,Flame 利用了 Flutter 的強大功能,並提供了一種輕量級的方法來爲所有平臺開發 2-D 遊戲。

目前 Flame 提供了以下功能:

除了以上的功能以外,你可以使用一些橋接 Flame 的 package 來增強引擎本身的功能。 通過這些橋接 package,你可以訪問 Flame 的組件、幫助程序, 或是與其他 package 進行綁定,從而達到平滑集成的效果。 目前我們有以下的橋接 package(Flame 引擎是模塊化的,允許用戶選擇他們想要使用的 API):

2D 遊戲小例子

  1. dependencies:
      flutter:
        sdk: flutter
      flame: 1.1.1
  2. runApp(const App());-> GameWidget(game)& 一個自定義pad 佈局
    class Joypad extends StatefulWidget {
      //自定義pad 略 可以參考:https://pub.dev/packages/control_pad
    }

  1. class Player extends SpriteComponent with HasGameRef{
     @override
      Future<void> onLoad() async {
        super.onLoad();
        // TODO 1
        sprite = await gameRef.loadSprite('player/player.png');
        position = gameRef.size / 2;
      }
    }

    Plyaer:

  2. class MyGame extends FlameGame {
      final Player _player = Player();
        
      @override
      Future<void> onLoad() async {
        add(_player);
        // empty
      }
    }
  3. //Joypad(onDirectionChanged: onJoypadDirectionChanged) 方向控制pad
    void onJoypadDirectionChanged(Direction direction) {
        // TODO 2
        game.onJoypadDirectionChanged(direction);
      }
  4. game->player-> 更新方向

  5. //Player update 更新頻率爲 16毫秒左右
    @override
      void update(double delta) {
        super.update(delta);
        //移動小孩
        movePlayer(delta);
        print('update 更新時間---》${(DateTime.now().microsecondsSinceEpoch - _timeNow)}');
        _timeNow = DateTime.now().microsecondsSinceEpoch;
      }

60hz=16 毫秒刷新

  1. 關於移動速度和方向
void movePlayer(double delta) {
    // TODO
    switch (direction) {
      case Direction.up:
        moveUp(delta);
        break;
      case Direction.down:
        moveDown(delta);
        break;
      case Direction.left:
        moveLeft(delta);
        break;
      case Direction.right:
        moveRight(delta);
        break;
      case Direction.none:
        break;
    }
  }
  final double _playerSpeed = 300.0;
 void moveUp(double delta) {
    position.add(Vector2(0, -(delta * _playerSpeed)));
  }

  void moveDown(double delta) {
    position.add(Vector2(0, delta * _playerSpeed));
  }

  void moveRight(double delta) {
    position.add(Vector2(delta * _playerSpeed, 0));
  }

  void moveLeft(double delta) {
    position.add(Vector2(-delta * _playerSpeed, 0));
  }

如果遊戲視圖的直徑爲 2500×2500 像素,則您的玩家從座標 x:1250, y:1250 的中間開始。 調用 moveDown 會爲玩家的 Y 位置增加大約 300 像素,用戶在向下方向握住手柄時,會導致精靈向下移動遊戲視口。

  1. 地圖

    //地圖也是精靈,所以加載方式跟精靈一樣
    class MyMap extends SpriteComponent with HasGameRef {
      @override
      Future<void>? onLoad() async {
        sprite = await gameRef.loadSprite('player/rayworld_background.png');
        size = sprite!.originalSize;
        return super.onLoad();
      }
    }
    //地圖加載
    class MyGame extends FlameGame {
      final Player _player = Player();
      final MyMap _map = MyMap();
      @override
      Future<void> onLoad() async {
         await add(_map);//添加地圖
         add(_player);
      }
    }
  2. 添加會動的精靈 player

    player_spritesheet

    Player extends SpriteAnimationComponent with HasGameRef{
      @override
      Future<void> onLoad() async {
        super.onLoad();
         _loadAnimations().then((_) ={animation = _standingAnimation});
      }
     Future<void> _loadAnimations() async {
        final spriteSheet = SpriteSheet(
          image: await gameRef.images.load('player/player_spritesheet.png'),
          srcSize: Vector2(29.0, 32.0),//1個精靈的像素大小
        );
        _runDownAnimation =
            spriteSheet.createAnimation(row: 0, stepTime: _animationSpeed, to: 4);
        
        _runLeftAnimation =
            spriteSheet.createAnimation(row: 1, stepTime: _animationSpeed, to: 4);
        
        _runUpAnimation =
            spriteSheet.createAnimation(row: 2, stepTime: _animationSpeed, to: 4);
        
        _runRightAnimation =
            spriteSheet.createAnimation(row: 3, stepTime: _animationSpeed, to: 4);
        
        _standingAnimation =
            spriteSheet.createAnimation(row: 0, stepTime: _animationSpeed, to: 1);
      }
      
      void movePlayer(double delta) {
        
        switch (direction) {
          case Direction.up:
            //動畫方向切換
             animation = _runUpAnimation;
            moveUp(delta);
            break;
          case Direction.down:
            animation = _runDownAnimation;
            moveDown(delta);
            break;
          case Direction.left:
             animation = _runLeftAnimation;
            moveLeft(delta);
            break;
          case Direction.right:
             animation = _runRightAnimation;
            moveRight(delta);
            break;
          case Direction.none:
            break;
        }
      }
    }

    至此我們的利用 Flame 做的一個遊戲入門就結束了

    當然遊戲開發很複雜,想象力最重要!

Bonfire

Bonfire 引擎:(RPG 類) 可以創造 Flutter.2D 遊戲的引擎,基於 Flame

引用 & 資源

FlutterGame:https://flutter.dev/games games-toolkit

文檔:https://docs.flutter.dev/resources/games-toolkit 

遊戲資源 1:https://itch.io/game-assets 

遊戲資源 2:https://itch.io/ Flutter 

遊戲地圖:https://pub.dev/packages/level_map)

關於奇舞團

奇舞團是 360 集團最大的大前端團隊,代表集團參與 W3C 和 ECMA 會員(TC39)工作。奇舞團非常重視人才培養,有工程師、講師、翻譯官、業務接口人、團隊 Leader 等多種發展方向供員工選擇,並輔以提供相應的技術力、專業力、通用力、領導力等培訓課程。奇舞團以開放和求賢的心態歡迎各種優秀人才關注和加入奇舞團。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ww5E6zAWbEKquI__zg002w