本站公告

晚上好😎🫰

今天是2025年1月4日星期六

Santos
Creator @999-Blog.pro
https://github.com/SantosXXXE

Skip to content

如何用flutter框架与Flame引擎打造全平台游戏

更新: 2024/11/22 字数: 0 字 时长: 0 分钟

前言

INFO

最近在写题的时候遇到一个棘手的问题,如何用dart语言快速开发一个2d平面游戏,查询许多资料后才发现官方有这样的一个游戏引擎来协助开发,本文是对这个Flame游戏引擎的简单介绍,包括安装方式,文件结构和游戏循环,最后给出了平台的部署方式

关于Flame

Flame是一个模块化的Flutter游戏引擎,为游戏提供了一套完整的解决方案。它使用了Flutter提供的强大基础设施,但简化了构建项目所需的代码。

它为您提供了一个简单而有效的游戏循环实现,以及游戏中可能需要的必要功能。例如:输入、图像、精灵图、精灵表、动画、碰撞检测和一个组件系统,我们称之为Flame Component System(简称FCS)。

我们还提供了扩展Flame功能的独立包:

flame_audio:使用AudioPlayers软件包提供音频功能

flame_forge2d:使用我们自己的Box2D端口Forge2D提供物理功能

flame_tiled:整合了tiled包

您可以选择任何您需要的部分,因为它们都是独立的和模块化的。

安装方式

Flame是基于flutter的一个插件,所以我们只需要在pub.dev获取最新版本并添加到依赖 通过在pubspec.yaml中放入以下内容,将pub包作为您的依赖项:

yml
dependencies:
  flame: 1.2.1

文件结构

Flame 为您的项目提供了一个建议的结构,其中包括标准 Flutterassets 目录以及两个子目录:audioimages

如果使用以下示例代码:

dart
void main() {
  FlameAudio.play('explosion.mp3');

  Flame.images.load('player.png');
  Flame.images.load('enemy.png');
}

Flame 期望在其中找到文件的文件结构是:

.
└── assets
    ├── audio
    │   └── explosion.mp3
    └── images
        ├── enemy.png
        └── player.png

或者,您可以将audio文件夹拆分为两个子文件夹,一个music用于sfx

不要忘记将这些文件添加到您的pubspec.yaml文件中:

yml
flutter:
  assets:
    - assets/audio/explosion.mp3
    - assets/images/player.png
    - assets/images/enemy.png

如果您想改变这个结构,可以使用prefix参数创建您自己的AssetsCacheImagesCacheAudioCacheSoundPool实例,而不是使用由Flame提供的全局实例

游戏循环

FlameGame

FlameGame是Flame中最基本和最常用的Game类。

FlameGame是一个基于Game类实现的组件。基本上它有一个组件列表,并且通过更新和渲染调用所有已经被添加到游戏的组件。

这个组件系统被我们称之为 Flame Component System,简称FCS。

每次游戏需要调整大小时,例如当方向改变,FlameGame会调用所有组件调整大小的方法,并且会将这些信息传递给摄像头和视口。

FlameGame.camera控制空间坐标中的哪个点应该在屏幕的左上角(它的默认值是[0,0],就像一个普通的Canvas)。

下面是一个 FlameGame 实现的例子:

dart
class MyCrate extends SpriteComponent {
  // 创建一个渲染尺寸为16*16的crate.png精灵图组件
  MyCrate() : super(size: Vector2.all(16), anchor: Anchor.center);

  @override
  Future<void> onLoad() async {
    sprite = await Sprite.load('crate.png');
  }

  @override
  void onGameResize(Vector2 gameSize) {
    super.onGameResize(gameSize);
    // 我们不需要在构造函数中设置位置,我们可以直接在这里设置
    // 因为它将在第一次呈现之前被调用一次
    position = gameSize / 2;
  }
}

class MyGame extends FlameGame {
  @override
  Future<void> onLoad() async {
    await add(MyCrate());
  }
}

main() {
  final myGame = MyGame();
  runApp(
    GameWidget(
      game: myGame,
    ),
  );
}

注意:如果您在构建方法中实例化您的游戏,那么每次重新构建Flutter树时,您的游戏都会重新构建,这通常比您想要的更加频繁。为避免这种情况,您可以先创建一个游戏实例,然后在您的组件结构中引用它,就像在上面的示例中所做的那样。

要从FlameGame的列表中删除组件,可以使用removeremoveAll方法。如果您只想删除一个组件,可以使用第一种方法,如果您想删除一个组件列表,可以使用第二种方法。

任何调用了remove()方法的组件也将被删除,您只需执行yourComponent.remove();

生命周期

图片 当一个游戏第一次被添加到Flutter组件树时,以下生命周期方法将依次被调用:onGameResizeonLoadonMount。在此之后,它继续每次来回调用updaterender,直到组件从树中删除。一旦GameWidget从树中移除,onRemove就会被调用,就像从组件树中移除一个普通组件一样

调试模式

Flame的FlameGame类提供了一个名为debugMode的变量,默认为false。当它被设置为true时来启用游戏组件的调试功能。请注意,此变量的值在添加到游戏中时会传递给其组件,所以如果您在运行时更改debugMode,默认情况下不会影响已添加的组件

更改背景颜色

要想改变您的FlameGame的背景颜色,您必须重写backgroundColor()

在下面的示例中,背景颜色设置为完全透明,因此您可以看到GameWidget背后的小部件。默认为不透明黑色

dart
class MyGame extends FlameGame {
  @override
  Color backgroundColor() => const Color(0x00000000);
}

请注意,在游戏运行时,背景颜色无法动态变化。但是如果您想动态地改变它,您可以只画一个背景来覆盖整个画布

SingleGameInstance mixin

如果您正在制作一个单机游戏应用程序,可以将可选的SingleGameInstance通过mixin应用到您的游戏中。这是一种构建游戏时的常见场景:一个全屏的GameWidget托管了一个单一的Game实例

添加这个mixin可以在某些场景中提供性能优势。特别是,即使父组件尚未挂载,组件的onLoad方法也保证在将其添加到父组件时启动。因此, await 时在parent.add(component)上,始终保证组件加载完成

dart
class MyGame extends FlameGame with SingleGameInstance {
  // ...
}

低级游戏API

图片Game minix是一个低级API,可以在当您想要实现如何构建游戏引擎的功能时使用。比如,Game不实现任何updaterender的功能。

Loadable minxi有onLoadonMountonRemove几个生命周期方法。当游戏被加载 + 安装或删除时,这些方法会从 GameWidget (或者其他父类)中调用。只有在第一次将类添加到父类时才调用 onLoad,但是onMount(在onLoad之后被调用)会在每次被添加到一个新的父类时被调用。当类从父类中移除时,将调用onRemove

注意:Gamemixin允许更自由的实现任何事物,但如果您使用它,您也会错过它的所有内置功能。

下面是一个Game实现的例子:

dart
class MyGameSubClass with Game {
  @override
  void render(Canvas canvas) {
    // ...
  }

  @override
  void update(double dt) {
    // ...
  }
}

main() {
  final myGame = MyGameSubClass();
  runApp(
    GameWidget(
      game: myGame,
    )
  );
}

Game Loop 游戏循环

GameLoop是对游戏循环概念的一个简单抽象。基本上,大多数游戏都建立在两种方法之上:

渲染方法采用画布来绘制游戏的当前状态 更新方法接收自上次更新以来的增量时间(以秒为单位),并允许您移动到下一个状态 Flame所有的Game实现都使用GameLoop

游戏执行暂停/恢复

Flame的Game可以通过两种方式暂停和恢复:

使用pauseEngineresumeEngine 方法 通过更改paused属性 当一个Flame Game暂停时,GameLoop实际上也会被暂停,这意味着在游戏恢复之前不会有任何更新或者新的渲染

Flutter小部件和Game实例

由于Flame游戏可以被包装在一个小部件里,所以它可以很轻易的和其他Flutter小部件一起使用。但是,Widgets Overlay API可以让事情变得跟简单。

Game.overlays允许在游戏实例顶部显示任何Flutter小部件,这使得创建诸如暂停菜单或物品栏之类的东西非常容易。

这个管理是通过game.overlays.addgame.overlays.remove方法来完成的,它们分别通过识别覆盖的String参数来标记显示或隐藏、覆盖。

dart
// 内部游戏方法:
final pauseOverlayIdentifier = 'PauseMenu';

overlays.add(pauseOverlayIdentifier); // 标记“PauseMenu”被渲染
overlays.remove(pauseOverlayIdentifier); // 标记“PauseMenu”不渲染
dart
// 在小部件声明
final game = MyGame();

Widget build(BuildContext context) {
  return GameWidget(
    game: game,
    overlayBuilderMap: {
      'PauseMenu': (BuildContext context, MyGame game) {
        return Text('A pause menu');
      },
    },
  );
}

覆盖的渲染顺序取决于OverlayBuilderMap中键的顺序决定

支持的平台

因为Flame是基于Flutter的,所以Flame支持的平台取决于Flutter支持的平台。

目前,Flame同时支持移动端和Web端

Flutter通道

Flame保持它在Flutter稳定通道的支持。开发、测试和主通道应该可以工作,但我们不支持。这意味着发生在稳定通道之外的问题不是优先考虑的问题

Flame网页端

要在 web 上使用 Flame,您需要确保您的游戏使用 CanvasKit/Skia 渲染器。 这将提高它在 Web 上的性能,因为它是使用 canvas 元素而不是单独的 DOM 元素。

要使用skia运行您的游戏,用下面的命令:

$ flutter run -d chrome --web-renderer canvaskit

要构建用于生产的游戏,使用 skia,请使用以下命令:

$ flutter build web --release --web-renderer canvaskit

部署您的游戏到Github Pages

一个简单的在线部署游戏的方法是使用 GitHub Pages。这是 GitHub 的一个很酷的功能,通过它您可以很容易地从您的仓库中托管 web 内容。在这里,我们将讲解使用 GitHub 页面托管游戏的最简单方法。首先,让我们在部署文件的位置创建一个分支:

sh
git checkout -b gh-pages

这个分支可以从 main 或任何其他地方创建,这没有太大关系。您推送那个分支后,返回您的主分支。

现在您应该将 flutter-gh-pages 动作添加到存储库中,您可以通过在文件夹 .github/workflows 下创建文件 gh-pages.yaml 来做到这一点。

yml
name: Gh-Pages

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - uses: subosito/flutter-action@v1
      - uses: bluefireteam/flutter-gh-pages@v7
        with:
          baseHref: /NAME_OF_YOUR_REPOSITORY/
          webRenderer: canvaskit

请务必将 NAME_OF_YOUR_REPOSITORY 更改为您的 GitHub 存储库的名称。

现在,每当您将某些内容推送到主分支时,该操作都会运行并更新您部署的游戏。

游戏应该在这样的 URL 上可用:https://YOUR_GITHUB_USERNAME.github.io/YOUR_REPO_NAME/

Web支持

在网络上使用 Flame 时,某些方法可能不起作用。 例如 Flame.device.setOrientationFlame.device.fullScreen在 web 上不起作用,它们可以被调用,但不会发生任何事情。

另一个例子:使用flame_audio包预缓存音频也不起作用,因为Audioplayers在Web端不支持。 这可以通过使用 http 包并请求获取音频文件来解决,这将使浏览器缓存文件产生与移动设备相同的效果。

如果您想在Web平台创建 ui.Image 的实例,您可以使用我们的 Flame.images.decodeImageFromPixels 方法。 这包装了 ui 库中的 decodeImageFromPixels,支持 Web 平台。 如果 runAsWeb 参数设置为 true(默认设置为 kIsWeb),它将使用内部图像方法解码图像。 当 runAsWebfalse 时,它将使用 decodeImageFromPixels,目前 Web端不支持。

如果你发现这篇指南有用,或者有改进建议,请随时联系我们或参与讨论。🎉 🎉 🎉