【Flutter&Flame 游戏 – 肆】精灵图片加载方式

【Flutter&Flame 游戏 – 肆】精灵图片加载方式持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 5 天,点击查看活动详情 前言 这是一套 张风捷特烈 出品的 Flutter&Flame 系列教程,发布于掘金社区。如果你在其他

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 5 天,点击查看活动详情


前言

这是一套 张风捷特烈 出品的 Flutter&Flame 系列教程,发布于掘金社区。如果你在其他平台看到本文,可以根据对于链接移步到掘金中查看。因为文章可能会更新、修正,一切以掘金文章版本为准。本系列文章一览:

第一季完结,谢谢支持 ~


1. 什么是精灵图

我们前面用的角色动画帧有九张,就表示需要加载九次图片资源。对于动画帧来说,每帧的尺寸一般都是一样的,可以将它们拼接在一张图片中,如下图所示:图片取自于 【pinball】开源项目。

【Flutter&Flame 游戏 - 肆】精灵图片加载方式

这在前端开发中比较常见,因为每个小图片都需要发一次请求,将小图片拼在一起,可以减少请求的次数。在游戏开发者也是如此,将小图片拼合在一起可以有效减少加载的次数。


2. 如何从精灵图中获取图片

Flame 中通过 SpriteSheet 类对精灵图进行处理,如下通过 fromColumnsAndRows 构造可以指定行列。这也就说明该类只能加载的图片要求:精灵图中的单体必须尺寸一致。

---->[源码: SpriteSheet]----
SpriteSheet.fromColumnsAndRows({
  required this.image,
  required int columns,
  required int rows,
}) : srcSize = Vector2(
        image.width / columns,
        image.height / rows,
      );

然后通过 getSpriteById 传入索引来获取第几个图片对应的 Sprite 对象。另外还提供了 getSprite 方法,通过指定行列获取图片对应的 Sprite 对象。注意,索引和行列都是从 0 开始数的。

---->[源码: SpriteSheet]----
Sprite getSprite(int row, int column) {
  return getSpriteById(row * columns + column);
}

Sprite getSpriteById(int spriteId) {
  return _spriteCache[spriteId] ??= _computeSprite(spriteId);
}

3. 使用测试案例

如下案例中,加载第一帧作为另一个角色 Monster ,且该角色会随机出现在屏幕的最右侧。代码见 【04/01】

【Flutter&Flame 游戏 - 肆】精灵图片加载方式


Monster 继承自 SpriteComponent ,在构造方法里传入 精灵尺寸位置 信息,方便初始化。

class Monster extends SpriteComponent {
  Monster({
    required Sprite sprite,
    required Vector2 size,
    required Vector2 position,
  }) : super(
          sprite: sprite,
          size: size,
          position: position,
          anchor: Anchor.center,
        );
  
  @override
  Future<void> onLoad() async {
    add(RectangleHitbox()..debugMode = true);
  }
}

下面是 TolyGame 中的逻辑处理,在 onLoad 方法中加载精灵图,并取第一帧作为 Monster 的显示图片。另外随机出现在屏幕的最右侧,言外之意是横坐标固定,纵坐标随机,代码处理如下:

---->[04/01/TolyGame]----
final Random _random = Random();

@override
Future<void> onLoad() async {
  player = Adventurer();
  add(player);
  // 加载 SpriteSheet 
  const String src = 'adventurer/animatronic.png';
  await images.load(src);
  var image = images.fromCache(src);
  SpriteSheet sheet = SpriteSheet.fromColumnsAndRows(image: image, columns: 13, rows: 6);
  Sprite sprite = sheet.getSpriteById(0);
  // 初始化 Monster 
  Vector2 monsterSize = Vector2(64,64);
  final double pY = _random.nextDouble()*size.y;
  final double pX = size.x - monsterSize.x/2; // tag1
  monster = Monster(sprite: sprite,size: monsterSize,position: Vector2(pX, pY));
  add(monster);
}

*注*tag1 处减去 monsterSize.x/2,是因为 Monster 的锚点在中心,不减的话就会如下图所示。也就是说 Componentposition 指的是锚点处的坐标,想让Monster 的右侧显示出来,向左偏移一半宽度即可。

【Flutter&Flame 游戏 - 肆】精灵图片加载方式


4. 精灵图动画的加载

第一篇 我们就介绍过使用 SpriteAnimationComponent 构件显示多帧动画,其实本质上就是多个 Sprite 对象,循环切换而已。前面知道如何通过 SpriteSheet 获取对应索引的 Sprite ,那接下来的事情就好办了。这里完成如下图所示的效果:代码见 【04/02】

【Flutter&Flame 游戏 - 肆】精灵图片加载方式


实现,将 Monster 改为继承自 SpriteAnimationComponent ,支持帧动画。

class Monster extends SpriteAnimationComponent {
  Monster({
    required SpriteAnimation animation,
    required Vector2 size,
    required Vector2 position,
  }) : super(
          animation: animation,
          size: size,
          position: position,
          anchor: Anchor.center,
        );

  @override
  Future<void> onLoad() async {
    add(RectangleHitbox()..debugMode = true);
  }
}

如下遍历 frameCount 此,获取 Sprite 列表,来生成 SpriteAnimation 即可,如下所示:

---->[04/02/TolyGame$onLoad]----
const String src = 'adventurer/animatronic.png';
await images.load(src);
var image = images.fromCache(src);
SpriteSheet sheet = SpriteSheet.fromColumnsAndRows(
  image: image,
  columns: 13,
  rows: 6,
);

int frameCount = sheet.rows * sheet.columns;
List<Sprite> sprites = List.generate(frameCount, sheet.getSpriteById);
SpriteAnimation animation = SpriteAnimation.spriteList(
  sprites,
  stepTime: 1 / 24,
  loop: true,
);

对于这种一张图记录的全是一个动画的精灵图,也可以不使用 SpriteSheet 。通过 fromFrameData 构造可以更简单直接地创建动画精灵对象,也能完成同样的效果。也就是写法上简洁一点而已,本质上没有什么区别。

【Flutter&Flame 游戏 - 肆】精灵图片加载方式

const int rowCount = 6;
const int columnCount = 13;
final Vector2 textureSize = Vector2(
  image.width / columnCount,
  image.height / rowCount,
);

SpriteAnimation animation = SpriteAnimation.fromFrameData(
  image,
  SpriteAnimationData.sequenced(
    amount: rowCount * columnCount,
    amountPerRow: columnCount,
    stepTime: 1 / 24,
    textureSize: textureSize,
  ),
);

5. 分包管理 – 简单拓展 SpriteSheet

通过 SpriteSheet 可以更灵活地操作需要哪些帧,比如像这种多个角色出现在一张精灵图里,SpriteAnimation.fromFrameData 就没法用了。

【Flutter&Flame 游戏 - 肆】精灵图片加载方式


SpriteSheet 可以通过行列来获取指定的图片,比如下面红框所示的是 第四行,第五列图片,由于索引从 0 计数,也就是用 (3,4) 表示。

【Flutter&Flame 游戏 - 肆】精灵图片加载方式

SpriteSheet 中的方法非常少,并没有获取索引区间段 Sprite 列表的方法,像这种图要自己来数,就比较麻烦。可以写个 extension 来拓展一下,可能一般人顺手就在 lib 中创建的文件夹开写了。看 flutter 官方的 pinball 项目中,会对模块进行分包,而不是所有代码都塞在一块。

image-20220528103427445.png

这里的 extension 和项目本身关系不大,是对 flame 的拓展,相对独立。以后可能还会写其他的拓展方法以便使用,这里也在项目中创建一个 packages 来进行分包管理。这样的另一个好处是:我可以将 flame_ext 分享到 pub 中,让所有人都可以使用。


下面说下创建包的方式,在 New Flutter ProjrctProjrct type 选择 Package 即可,如下把包创建在项目根目录的 packages 下:

【Flutter&Flame 游戏 - 肆】精灵图片加载方式


然后在 pubspec.yaml 中通过 path 来引入本地库:

dependencies:
  #...
  flame_ext:
    path: packages/flame_ext

当前的包结构如下,在 flame_ext.dart 中导出 sprite_sheet_ext.dart ,这样引入 flame_ext.dart 即可使用 sprite_sheet_ext 中定义的拓展方法。

【Flutter&Flame 游戏 - 肆】精灵图片加载方式

下面是 sprite_sheet_ext 中的处理逻辑,拓展一个 getRowSprites 的方法,返回 Sprite 列表。该方法的作用是:取第几行,从第几个开始的多少个 Sprite 形成列表。

---->[sprite_sheet_ext.dart]----
extension SpriteSheetExt on SpriteSheet {
  /// 获取指定索引区间的 [Sprite] 列表
  /// [row] : 第几行
  /// [start] : 第几个开始
  /// [count] : 有几个
  List<Sprite> getRowSprites({
    required int row,
    int start = 0,
    required count,
  }) {
    return List.generate(count, (i) => getSprite(row, start + i)).toList();
  }
}


比如下面指定是第三行,从 0 开始取五帧,语义上就非常明确,而不需要每次使用都计算一下:

sheet.getRowSprites(row: 3,count: 5)

【Flutter&Flame 游戏 - 肆】精灵图片加载方式


如下是通过这种方法显示的效果,代码见: 【04/03】

【Flutter&Flame 游戏 - 肆】精灵图片加载方式

import 'package:flame_ext/flame_ext.dart';

List<Sprite> sprites = sheet.getRowSprites(row: 3,count: 5);
SpriteAnimation animation = SpriteAnimation.spriteList(
  sprites,
  stepTime: 1 / 10,
  loop: true,
);

再比如,第一行,从第六个开始,取 4 个,是石像怪的序列帧:

List<Sprite> sprites = sheet.getRowSprites(row: 0,start: 5,count: 4);

【Flutter&Flame 游戏 - 肆】精灵图片加载方式


到这里,对精灵图的使用就介绍完毕了,大家可以结合上一章的内容,思考一下,如何让 Monster 主动向左运动。如下代码实现在 【04/04】 ,那本文就到这里,明天见 ~

【Flutter&Flame 游戏 - 肆】精灵图片加载方式


\

今天的文章【Flutter&Flame 游戏 – 肆】精灵图片加载方式分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/20505.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注