Flutter中文网

Flutter中文网

Flaming Stacks:在Flame中使用堆叠精灵stacked sprites

21
2024-07-16

使用基于Flutter的Flame游戏引擎构建游戏或对其进行研究的人已经知道,就像Flutter一样,Flame仅限于2D。因此,虽然不支持真正的3D,但并非全无希望,你仍然可以通过一种称为“精灵堆叠”(Sprite Stacking)的技术为你的游戏获得一种类似3D的,“2.5D”体验。对于那些刚接触 Flame 游戏开发的人,你们可以通过前面的基础知识来快速入门。

Sprite Stacking是什么

那么,“Sprite Stacking”是什么呢?这是一种利用特性来产生伪3D效果的技术,即如果你将一个对象绘制为一组堆叠在一起的2D层或“切片”,并给它们一个单一像素的垂直偏移,你将得到一个类似“3D”的对象的视觉效果,这就是所谓的精灵堆叠(sprite stacking)。

下面的动画演示了如何通过一系列二维“切片”将一个简单的树形对象制作成三维效果,这应该有助于更清楚地阐释这个概念:

通过二维切片把树制作成三维效果

在Flame中使用 stacked sprites

现在我们已经了解了stacked sprites的基本工作原理,那么如何在我们的Flame游戏中使用它们呢?首先,我们需要一个stacked sprite asset!!为此,我前往了Itch.io网站,在那里我找到了Edu提供的汽车精灵包。虽然该包是免费的,但请考虑向作者捐款,以感谢他们慷慨地提供这些酷炫的精灵!

现在我们有了一些可用的资产,让我们来看看其中一张汽车图片(放大了很多):

汽车精灵包

看这张图片(在游戏中称为“精灵图”),我们可以看到它被分成了9个独立的“单元格”或子图像,每个单元格构成了汽车的垂直“切片”。有了这个资产,我们需要做的是,按照我之前描述的方法,读取图像的每个部分,然后绘制每个切片,一个叠加在另一个上面,但在垂直方向上将每个切片向右偏移1像素。从头开始编写所有这些代码会非常复杂,但幸运的是,Flame提供了一个名为 SpriteBatch 的方便类,专门用于处理精灵图,因此所需的代码仅为:

Future<void> getBatch(String name) async {
	late SpriteBatch _batch;
	final sheetImg = await game.images.load('$name.png');
   _batch = await SpriteBatch.load('$name.png');

   const double spriteWidth = 16; //hardcode width of sprite sheet cells
   const double spriteHeight = 16; //hardcode height of sprite sheet cells
   final sliceCount = sheetImg.width ~/ spriteWidth;

   for (int i = 0; i < sliceCount; i++) {
      _batch.add(
        source: Rect.fromLTWH(spriteWidth * i, 0, spriteWidth, spriteHeight),
        offset: Vector2(0, -i.toDouble()),
        anchor: Vector2.all(spriteWidth / 2),
        rotation: 0,
      );
   }
}

我们可以看到,除了用于加载图像并设置图像切片大小(以像素为单位)的前几行代码外,主要操作发生在 for 循环中,我们逐个将切片添加到 SpriteBatch 中,对每个切片应用1像素的偏移量 y ,并将 anchor 设置为每个切片的中间位置。它还允许我们指定 rotation 属性,稍后我们会用到它。

如果我们现在将上述代码封装在 onLoad() 方法中,作为 PositionComponent 类的一个成员方法,并为其设置位置、缩放和平台背景颜色,那么代码就会变成如下所示的样子:

class FlamingSprites extends FlameGame with HasDraggableComponents {
  late final StackedSpriteComponent _carOne;

  @override
  Color backgroundColor() => Colors.blueGrey;

  @override
  FutureOr<void> onLoad() async {
    await super.onLoad();
    _carOne = StackedPreview(this, "BlueCar", false)..scale = Vector2.all(9);
    _carOne.position = Vector2.all(300);

    add(_carOne);
  }
...

当我们运行游戏时,应该得到类似这样的东西:

游戏示例界面

如果我们现在将旋转值(以弧度为单位)从0更改为一个具体的值,情况会如何呢?

 rotation: _stackAngle * radians2Degrees,

我们在游戏的update方法中随着时间的推移进行更新:

 @override
  void update(double dt) {
    super.update(dt);
    angle += 0.0005; //spin car around slowly
  }

能看到:

游戏示例界面-旋转

在实际游戏中,我们可能希望根据游戏玩法来控制堆叠精灵的朝向。要实现这一点,只需在更新角度时不再根据时间进行更新,而是通过计算来实现,例如从用户在屏幕上触摸或鼠标点击的相对角度。

制作堆叠的精灵模型

虽然这涵盖了我们在Flame游戏中使用堆叠精灵的编码方面,但我们到目前为止只使用了预先制作的精灵,但如果你想制作自己的精灵呢?虽然你可以尝试使用像Piskel* 这样的精灵编辑器手动制作精灵表,但更简单的方法是使用优秀的开源工具Goxel ,它可以在Linux、Android、Windows、MacOS、iOS以及基于Web的版本上使用!🎉🎉 Goxel可以让您以更简单的方式设计3D模型,然后快速将它们导出为所需的精灵表PNG文件。

制作堆叠的精灵模型

注意:如果您使用的是高分辨率显示器或笔记本电脑屏幕(如我在Ubuntu 22.04上使用),您可能希望使用dev build of Goxel,该版本已修复了UI缩放问题。在将来发布的0.11.1版本中,Goxel将包含此修复程序。

我还发现了另一个不错的免费但非开源的网格编辑器——MagicaVoxel。有关如何使用它的详细说明可以在本文中找到,并且也可以通过Wine在Linux上运行。

如果你选择使用Magica Voxel制作自己的精灵图,你很快就会发现,当它将精灵图导出为PNG格式的精灵图时,它是以垂直而不是水平的切片数组形式进行的,并且将最下面的切片放置在图表的底部。当然,这与我之前展示的代码不兼容,但修改代码以适应这种格式的精灵图并不困难,所需的代码如下:

final sliceCount = sheetImg.height ~/ spriteHeight;

    for (int i = 0; i < sliceCount; i++) {
      _batch.add(
        source: Rect.fromLTWH(0, spriteHeight * (sliceCount - i), spriteWidth, spriteHeight),
        offset: Vector2(0, -i.toDouble()),
        anchor: Vector2.all(spriteHeight / 2),
        rotation: _stackAngle * radians2Degrees,
      );
    }

使用 $first_name 变量。

如果你想看这篇文章所涉及代码的完整示例(更完善的版本),可以在Github repo中找到。

学习更多

要了解使用精灵堆叠的基本原理,我可以推荐@Noon这段视频。他用绘图软件绘制了一棵基本的树来阐释精灵堆叠的概念,就像我上面动画示意图中展示。

如果你想更深入地了解叠加精灵的工作原理以及如何使用它们,我发现@HeartBeast在YouTube上发布的使用GameMaker Studio 2工具进行叠加精灵的视频非常有帮助。

同样,如果您想更详细地了解如何使用堆叠精灵技术的高级技巧,请参阅本文

更多

虽然我们已经研究了如何使用单个堆叠精灵的方法,但这种技术通常用于游戏世界的所有或几乎所有视觉元素。如果我们希望确保Z轴排序处理正确,那么我之前在Flame中使用的方法将无法适用于多个堆叠精灵。幸运的是,Wolfenrain已经为我们完成了这项艰苦的工作,他的酷炫的Sashimi包可以处理整个游戏的精灵堆叠,我们将在探索Flame精灵的旅程中探讨如何使用它。

下次见,尽情享受堆叠精灵的乐趣吧!

  • 1