如何用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包作为您的依赖项:
dependencies:
flame: 1.2.1
文件结构
Flame 为您的项目提供了一个建议的结构,其中包括标准 Flutterassets 目录以及两个子目录:audio
和images
如果使用以下示例代码:
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
文件中:
flutter:
assets:
- assets/audio/explosion.mp3
- assets/images/player.png
- assets/images/enemy.png
如果您想改变这个结构,可以使用prefix参数创建您自己的AssetsCache
、 ImagesCache
、AudioCache
和SoundPool
实例,而不是使用由Flame提供的全局实例
游戏循环
FlameGame
FlameGame
是Flame中最基本和最常用的Game
类。
FlameGame
是一个基于Game
类实现的组件。基本上它有一个组件列表,并且通过更新和渲染调用所有已经被添加到游戏的组件。
这个组件系统被我们称之为 Flame Component System,简称FCS。
每次游戏需要调整大小时,例如当方向改变,FlameGame
会调用所有组件调整大小的方法,并且会将这些信息传递给摄像头和视口。
FlameGame.camera
控制空间坐标中的哪个点应该在屏幕的左上角(它的默认值是[0,0],就像一个普通的Canvas)。
下面是一个 FlameGame 实现的例子:
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
的列表中删除组件,可以使用remove
或removeAll
方法。如果您只想删除一个组件,可以使用第一种方法,如果您想删除一个组件列表,可以使用第二种方法。
任何调用了remove()
方法的组件也将被删除,您只需执行yourComponent.remove()
;
生命周期
当一个游戏第一次被添加到Flutter组件树时,以下生命周期方法将依次被调用:
onGameResize
,onLoad
和onMount
。在此之后,它继续每次来回调用update
和render
,直到组件从树中删除。一旦GameWidget
从树中移除,onRemove
就会被调用,就像从组件树中移除一个普通组件一样
调试模式
Flame的FlameGame
类提供了一个名为debugMode
的变量,默认为false
。当它被设置为true
时来启用游戏组件的调试功能。请注意,此变量的值在添加到游戏中时会传递给其组件,所以如果您在运行时更改debugMode
,默认情况下不会影响已添加的组件
更改背景颜色
要想改变您的FlameGame
的背景颜色,您必须重写backgroundColor()
在下面的示例中,背景颜色设置为完全透明,因此您可以看到GameWidget
背后的小部件。默认为不透明黑色
class MyGame extends FlameGame {
@override
Color backgroundColor() => const Color(0x00000000);
}
请注意,在游戏运行时,背景颜色无法动态变化。但是如果您想动态地改变它,您可以只画一个背景来覆盖整个画布
SingleGameInstance mixin
如果您正在制作一个单机游戏应用程序,可以将可选的SingleGameInstance
通过mixin应用到您的游戏中。这是一种构建游戏时的常见场景:一个全屏的GameWidget
托管了一个单一的Game
实例
添加这个mixin
可以在某些场景中提供性能优势。特别是,即使父组件尚未挂载,组件的onLoad
方法也保证在将其添加到父组件时启动。因此, await
时在parent.add(component)
上,始终保证组件加载完成
class MyGame extends FlameGame with SingleGameInstance {
// ...
}
低级游戏API
Game minix
是一个低级API,可以在当您想要实现如何构建游戏引擎的功能时使用。比如,Game
不实现任何update
或render
的功能。
Loadable
minxi有onLoad
、onMount
和onRemove
几个生命周期方法。当游戏被加载 + 安装或删除时,这些方法会从 GameWidget
(或者其他父类)中调用。只有在第一次将类添加到父类时才调用 onLoad
,但是onMount
(在onLoad
之后被调用)会在每次被添加到一个新的父类时被调用。当类从父类中移除时,将调用onRemove
。
注意:Game
mixin允许更自由的实现任何事物,但如果您使用它,您也会错过它的所有内置功能。
下面是一个Game
实现的例子:
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
可以通过两种方式暂停和恢复:
使用pauseEngine
和resumeEngine
方法 通过更改paused
属性 当一个Flame Game
暂停时,GameLoop
实际上也会被暂停,这意味着在游戏恢复之前不会有任何更新或者新的渲染
Flutter小部件和Game实例
由于Flame游戏可以被包装在一个小部件里,所以它可以很轻易的和其他Flutter小部件一起使用。但是,Widgets Overlay API可以让事情变得跟简单。
Game.overlays
允许在游戏实例顶部显示任何Flutter小部件,这使得创建诸如暂停菜单或物品栏之类的东西非常容易。
这个管理是通过game.overlays.add
和game.overlays.remove
方法来完成的,它们分别通过识别覆盖的String
参数来标记显示或隐藏、覆盖。
// 内部游戏方法:
final pauseOverlayIdentifier = 'PauseMenu';
overlays.add(pauseOverlayIdentifier); // 标记“PauseMenu”被渲染
overlays.remove(pauseOverlayIdentifier); // 标记“PauseMenu”不渲染
// 在小部件声明
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 页面托管游戏的最简单方法。首先,让我们在部署文件的位置创建一个分支:
git checkout -b gh-pages
这个分支可以从 main
或任何其他地方创建,这没有太大关系。您推送那个分支后,返回您的主分支。
现在您应该将 flutter-gh-pages 动作添加到存储库中,您可以通过在文件夹 .github/workflows
下创建文件 gh-pages.yaml
来做到这一点。
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.setOrientation
和 Flame.device.fullScreen
在 web 上不起作用,它们可以被调用,但不会发生任何事情。
另一个例子:使用flame_audio
包预缓存音频也不起作用,因为Audioplayers在Web端不支持。 这可以通过使用 http
包并请求获取音频文件来解决,这将使浏览器缓存文件产生与移动设备相同的效果。
如果您想在Web平台创建 ui.Image
的实例,您可以使用我们的 Flame.images.decodeImageFromPixels
方法。 这包装了 ui 库中的 decodeImageFromPixels
,支持 Web 平台。 如果 runAsWeb
参数设置为 true
(默认设置为 kIsWeb
),它将使用内部图像方法解码图像。 当 runAsWeb
为 false
时,它将使用 decodeImageFromPixels
,目前 Web端不支持。
如果你发现这篇指南有用,或者有改进建议,请随时联系我们或参与讨论。🎉 🎉 🎉