前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Flutter&Flame 游戏 - 壹】开启新世界的大门

【Flutter&Flame 游戏 - 壹】开启新世界的大门

作者头像
张风捷特烈
发布2022-09-20 10:17:30
8830
发布2022-09-20 10:17:30
举报

一、 新的可能性

Google I/O 2022 对于 Flutter 而言,将 休闲游戏 带入了大众的视野。让 Flutter 除了应用开发之外,有了新的可能性。其中作为游戏开发引擎的 Flame ,也为更多人所知晓。说实话,之前我并不怎么看得上 Flame ,无论是官网还是文档,内容都很少,感觉非常小众。我期待着官方可以出一个游戏引擎,但现在看来,官方也倾向于使用 Flame 引擎来开发休闲的 2D 游戏,那我无需再等,开摆 。


1. 弹球开源弹球游戏 pinball

I/O 2022 中, 官方用 Flutter 写的弹球小游戏,确实让我眼前一亮,开源地址在:【pinball】。这说明基本的碰撞、音乐、动画没有什么问题,用来做休闲小游戏是足够的,我也就没什么好担心的了。

所以,接下来将开启一个系列,研究 Flutter&Flame 的游戏 2D 休闲游戏开发。另外,为了录屏、截图方便,这里主要在 macOS 平台上运行,实现桌面版的 Flutter 游戏。


2. 本文目标

本文作为 Flame 最简使用,相当于一个 Hello World 级别的案例。

  • 项目搭建与资源配置
  • 播放背景音乐
  • 显示如下的人物动作

二、项目搭建
1. 依赖与资源配置

首先在 pubspec.yaml 中引入 flameflame_audio 包。

代码语言:javascript
复制
---->[]----
dependencies:
  #...
  flame: ^1.1.1
  flame_audio: ^1.0.2

然后在根目录下创建 assets 目录,其中图片放在 images 文件夹下,音乐放在 audio 下,并在 pubspec.yaml 中配置对应的文件夹。这里的背景音乐,取自【pinball】 中,图片资源在网上找的。


2. 最简代码

这里先实现一下静态图片的展示 + 背景音乐播放:代码 【tag1-1】

目前 lib 代码结构如下:

代码语言:javascript
复制
├── lib
│   ├── component.dart
│   └── main.dart

main.dart 里,runApp 方法传入 GameWidget 组件,其中 game 入参对象是自定义的 TolyGame 。继承自 FlameGame ,并重写 onLoad 方法,添加一个自定义的 HeroComponent 。另外通过 FlameAudio.play 方法播放音乐。

代码语言:javascript
复制
---->[main.dart]----
main() {
  runApp(GameWidget(game: TolyGame()));
  FlameAudio.play('background.mp3');
}
​
​
class TolyGame extends FlameGame {
  @override
  Future<void> onLoad() async {
    await add(HeroComponent());
  }
}

component.dart 中,HeroComponent 继承自 SpriteComponent 。在 super 构造中指定 sizeanchor 参数。覆写 onLoad 方法,为 sprite 成员实例化。在 onGameResize 中将位置居中,这样运行项目,就可以得到如下的效果:

代码语言:javascript
复制
---->[component.dart]----
class HeroComponent extends SpriteComponent {
​
  HeroComponent() : super(size: Vector2(50,37), anchor: Anchor.center);
​
  @override
  Future<void> onLoad() async {
    sprite = await Sprite.load('adventurer/adventurer-bow-00.png');
  }
​
  @override
  void onGameResize(Vector2 gameSize) {
    super.onGameResize(gameSize);
    position = gameSize / 2;
  }
}

3. 初步分析

在自定义的 HeroComponent 中,我们操作了两个没有声明的对象,这说明肯定是在父类中声明过了。其中sprite 成员定义在 SpriteComponent 中,position 成员定义在 PositionComponent 中。

代码语言:javascript
复制
---->[源码-sprite_component.dart]----
class SpriteComponent extends PositionComponent with HasPaint {
  Sprite? sprite;
  
---->[源码-position_component.dart]----
@override
set position(Vector2 position) => transform.position = position;

简单瞄一下源码可以看到继承关系,所以在子类中可直接使用这两个属性。positionVector2 对象,可以确定位置,spriteSprite 对象,可以确定资源。

代码语言:javascript
复制
PositionComponent
    |--- SpriteComponent
        |--- HeroComponent (自定义)

从这里可以简单感知到,在 FlameComponent 是一个比较重要的概念,它可以决定显示。为了避免和 Flutter 中的 Widget 组件 语义冲突, 这里称 Component构件


三、多图人物的帧动画

上面简单地实现了展示一张图片,下面来看一下多帧的图片如何显示:代码 【tag1-2】


1. 代码实现

之所以看到射手在动,是因为在不断播放,如下文件夹是不同帧对应的图片,adventurer-bow9 帧。


实现起来也非常简单,单图有 SpriteComponent 构件,多图也有对应的 SpriteAnimationComponent 构件。该类中内置声明了SpriteAnimation 类型的 animation 对象,所以在 onLoad 中初始化即可。其中 stepTime 用于控制运动的速度,这里 0.15 比较正常,数值越小,运动越快。

代码语言:javascript
复制
class HeroComponent extends SpriteAnimationComponent {
  HeroComponent() : super(size: Vector2(50,37), anchor: Anchor.center);
  
  @override
  Future<void> onLoad() async {
    List<Sprite> sprites = [];
    for(int i=0;i<=8;i++){
      sprites.add(await Sprite.load('adventurer/adventurer-bow-0$i.png'));
    }
    animation = SpriteAnimation.spriteList(sprites, stepTime: 0.15);
  }
  
  @override
  void onGameResize(Vector2 gameSize) {
    super.onGameResize(gameSize);
    position = gameSize / 2;
  }
}

2. 本文小结

通过这个小案例,我们见到了几个类,这里来梳理一下。其中 GameWidget 是继承自 GameWidget 的组件,构造时必须传入 Game 类型的 game 入参。

代码语言:javascript
复制
class GameWidget<T extends Game> extends StatefulWidget {
    final T game;
  
    const GameWidget({
    Key? key,
    required this.game, 

另外, FlameGame 类继承自 Component 并且混入 Game ,这也是为什么 FlameGame 子类可以作为 game 参数的原因。

代码语言:javascript
复制
class FlameGame extends Component with Game {

最后,最重要的莫过于 Component 构件,可以看出我们当前的代码都是围绕 Component 一族展开的。我们自定义类中覆写的 onLoadonGameResize 方法,都是定义在 Component 中的。另外 add 方法,可以添加一个 Component 对象,这是很明显的组合设计模式。

代码语言:javascript
复制
---->[源码-component.dart]----
Future<void>? onLoad() => null;

@mustCallSuper
void onGameResize(Vector2 size) => handleResize(size);

 Future<void>? add(Component component) => component.addToParent(this);

在后面我们应该还会遇到功能各异的 Component ,或也可能自定义一个 Component 来实现某种特殊的功能。本文作为一个简单的引子,想介绍的就这么多,那就到这里,明天见 ~


\

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 新的可能性
  • 1. 弹球开源弹球游戏 pinball
  • 2. 本文目标
  • 二、项目搭建
    • 1. 依赖与资源配置
      • 2. 最简代码
        • 3. 初步分析
        • 三、多图人物的帧动画
          • 1. 代码实现
            • 2. 本文小结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档