前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Flutter&Flame 游戏 - 贰玖】pinball 源码分析 - 视口与相机

【Flutter&Flame 游戏 - 贰玖】pinball 源码分析 - 视口与相机

作者头像
张风捷特烈
发布2022-09-09 10:29:42
9710
发布2022-09-09 10:29:42
举报
文章被收录于专栏:Android知识点总结

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


前言

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


1. 认识视口与相机

相机是我们日常生活中非常常见的概念,在 Flame 中,相机的概念如何理解呢?现实生活中,当你使用相机拍出一张照片,其囊括的区域是有限的,这个区域也就是视口 Viewport。下面是 Flame 中对Camera 类的定义,其继承自 Projector ,且持有 Viewport 对象。


其中 Projector 是对投影的抽象,Flame 只是个二维的游戏引擎,所以投影的概念也很简单。就是对一个平面空间点位,进行操作,产出与之对应的点位而已。如下的 projectVector 方法的作用是:传入一个 Vector2 ,进行变换后,产出一个 Vector2

简单来说,相机的作用是:在视口内对原本空间坐标信息进行变换,完成对应的功能需求。注意,这里的 Camera 类和硬件设备的相机没有半毛钱关系。


2. 简单使用 Camera

FlameGame 中持有 CameraWrapper 对象,该对象内部持有 Camera 对象。如下箭头所示,FlameGame 中,可以通过 get camera 来访问相机。另外 FlameGame 的尺寸也是由相机决定的。


我们知道, 默认情况在 FlameGame 会填充整个窗口,而且背景是黑色的。


当窗口尺寸发生变化时,由于角色的坐标、尺寸等数据和逻辑像素是 1:1 的对应关系,也就是说坐标点没有进行过任何变换。所以角色的显示情况不会有任何变化:代码见 【29/01】


下面通过使用 FixedResolutionViewport 视口,实现固定视口尺寸的需求。此时游戏视口尺寸窗口尺寸 就不是一个概念了。无论应用窗口有多大,对游戏而言视口尺寸是恒定的。如下白色背景构件添加到游戏场景中,布满视口,视口会根据大小来适应窗口 ,不在视口区域内的部分会显示底色。【29/02】

比如上图中默认相机的视口尺寸是 900*600 ,并不是指白色区域的是 900*600 逻辑像素。另外,可以看到角色的尺寸没有改动,但在这个视口尺寸下,就会显得较小。为相机指定视口直接用 camera.viewport 指定即可。

代码语言:javascript
复制
---->[TolyGame]----
@override
Future<void> onLoad() async {
  final Vector2 fixSize = Vector2(900, 600,);
  camera.viewport = FixedResolutionViewport(fixSize);
  add(Background());
  add(HeroMan()..position=size/2);
}
复制代码

比如下面将视口指定为 90*60 ,相对而言角色的尺寸就会变大。所以,从这里可以体会一下相机视口对于坐标系变换的特性。

此时改变窗口尺寸,通过打印日志可以发现, FlameGame 中的尺寸始终保持不变。这就是 FixedResolutionViewport 的作用,它可以保证在任何窗口尺寸下,游戏视口尺寸的恒定。也就是说,让游戏的可见部分在所有设备上都是相同的。


2. 相机的变换操作

相机的变换是针对于整个视口进行的,也就是说,可视区域内的角色呈现都会受到相机变换的影响。比如在现实生活中,当你移动相机,或者拉进、远离相机和目标的位置,都会影响最终的成像情况。

通过如下案例来说明一下相机变换操作对显示的影响:小人在中间,背景中左右各有 18 个原点。可以注意到,当圆点在视口之外,是无法显示的。就像相机拍照时,只能显示出其成像的区域。代码详见 【29/03】


相机缩放是比较简单的,对 camera.zoom 值进行改变即可:

代码语言:javascript
复制
if (event.logicalKey == LogicalKeyboardKey.keyZ && isKeyDown) {
  camera.zoom += 0.1;
}

if (event.logicalKey == LogicalKeyboardKey.keyX && isKeyDown) {
  camera.zoom -= 0.1;
}
复制代码

如下,通过减小 zoom 值,可以达到缩小的目的;就相当于照相机远离目标,从而成像区域可以包含更多内容,但视口中的内容也会相对变小。


同理,增加 zoom 值,可以达到放大的目的;就相当于照相机靠近目标,从而成像区域包含内容减少,但视口中的内容也会相对变大。简单来说,就是近大远小。


我们也可以对相机进行移动,从而改变成像区域的内容。Camera 中提供了 moveTosnapTo 两个移动方法,分别表示动画移动到某点和立刻移动到某点。并且可以通过 camera.speed 设置移动的速度。

代码语言:javascript
复制
if (event.logicalKey == LogicalKeyboardKey.arrowUp && isKeyDown) {
  camera.moveTo(Vector2(0, size.y/2-37/2));
}
复制代码

3.相机的伴随移动

相机伴随角色移动很好理解,比如现实生活中拍电影,摄像机需要跟随演员同步运动,这样才能保证演员在移动时常驻在视图中。官方的案例对这个知识点的说明比较好,这里就对它介绍一下。

场景中主要有 3 种构件:主角背景场地岩石方块 。场地是圆形和正方向构成的,颜色随机,其中圆形是正方向的内接圆。岩石随机出现在场地中,主角是一个动画帧。


如下所示,在角色移动过程中,始终保持在中心位置,但感官上它确实在运动。通过相机和角色的伴随移动,就可以始终让角色成为焦点,角色在移动的过程中,视口内容因相机的移动而扩展,这是符合我们常识的。代码详见 【29/04】

代码实现起来非常简单,只要调用 camera.followComponent 方法,指定需要跟随的构件即可。这样当构件的位置发生改变,相机也会随之变化。

代码语言:javascript
复制
@override
Future<void> onLoad() async {
  final Vector2 fixSize = Vector2(500, 500,);
  camera.viewport = FixedResolutionViewport(fixSize);
  add(Ground());
  add(ember = MovableEmber());
  camera.speed = 1;
  camera.followComponent(ember, worldBounds: Ground.bounds);
  for (var i = 0; i < 30; i++) {
    add(Rock(Vector2(Ground.genCoord(), Ground.genCoord())));
  }
}
复制代码

该案例,当角色和岩石碰撞时,可以看出角色在视口区域的 中上方 ,而且会动画平滑过渡;离开岩石后,又会在视口中间。在 MovableEmber 中可以看到碰撞逻辑,执行的是相机的 setRelativeOffset 方法。可以看出,相机的使用还是比较简单的。

代码语言:javascript
复制
@override
void onCollision(Set<Vector2> points, PositionComponent other) {
  super.onCollision(points, other);
  if (other is Rock) {
    gameRef.camera.setRelativeOffset(Anchor.topCenter);
  }
}

@override
void onCollisionEnd(PositionComponent other) {
  super.onCollisionEnd(other);
  if (other is Rock) {
    gameRef.camera.setRelativeOffset(Anchor.center);
  }
}
复制代码

4. pinball 中相机的处理

pinball 中相机的行为被封装为 CharacterSelectionBehavior 构建,用于处理相机的行为。

如下所示,在点击 Play 时,场景会进行放大和移动。同样,游戏结束时也会有个类似的放大,移动到排行榜的位置。


对于不同的状态,操纵摄像机进行不同的处理,这里通过 _foci 映射来维护状态 GameStatus 和相机参数信息 _FocusData

代码语言:javascript
复制
final Map<GameStatus, _FocusData> _foci = {};
复制代码

CameraFocusingBehavior 监听着 GameState 的变化,所以可以在游戏状态变化时进行对应的处理。和新方法是 onNewState 回调中执行的 _zoomTo 方法:


这里 pinball 项目中封装了 CameraZoom 特效对动画缩放进行了封装,本质就是不断改变 zoom 值产生动画效果而已。其实 flame 本身应该提供对相机的动画缩放,已经动画结束的回调监听。


到这里,关于相机和视口就简单地介绍完毕。这个系列中,整个 Flame 的各个方面基本上都涵盖了,并且结合 Flutter 官方开源的 pinball 项目进行源码分析,或多或少对大家研究 Flutter 休闲游戏开发有所帮助。那第一季的 Flutter&Flame 游戏开发入门教程就到这里。

另外关于地图、flame_forge2d 等知识以后再说吧,是否开启第二季,会根据本系列的关注度、热度、或是 Flame 的发展综合考虑是否继续研究。目前看来,本系列的文章并没有太多人看,所以没有太大的动力去研究,我也不想投入太多的精力在游戏开发中。所以如果本系列对你有所帮助,还望多多点赞支持,后会有期 ~

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 1. 认识视口与相机
  • 2. 简单使用 Camera
  • 2. 相机的变换操作
  • 3.相机的伴随移动
  • 4. pinball 中相机的处理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档