博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于Flutter Canvas的飞机大战(二)
阅读量:7174 次
发布时间:2019-06-29

本文共 5093 字,大约阅读时间需要 16 分钟。

回顾

昨天下午笔者已经完成了背景动画的循环播放. 晚上笔者就开发中发现的问题在stackoverflow上进行提问. 问题大概内容:

如何在Canvas中, 将一个较小的图片, 拉伸平铺

这个问题, 收到了二个有效的回答

  • Canvas.drawImageRect()
  • paintImage()

进过笔者测试

二者视觉效果相似, 可是 paintImage 的性能问题, 严重消耗了GPU资源. 查看了paintImage的源码, 发现这个函数实现的方式也是调用了 drawImageRect, 这个问题.有兴趣的同学可以深入了解一下. 共同探讨一下, 也行对于Flutter性能优化有很大的帮助.

void paintImage(  ...  if (centerSlice == null) {    for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))      canvas.drawImageRect(image, sourceRect, tileRect, paint);  } else {    for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))      canvas.drawImageNine(image, centerSlice, tileRect, paint);  }  if (needSave)    canvas.restore();}复制代码

开始

本篇我们的主要任务是, 在画板上增加我们控制的飞机, 可以操作飞机移动.

绘制飞机

考虑到我们未来要绘制玩家的战机. 还要绘制敌机. 我们先抽象出一个 Plan 的类, 方便以后我们的开发.我们在 src 下, 新建一个叫 plan.dart的文件. 定义他的方法.

abstract class Plan {  void init() {}  void moveTo(double x, double y) {}  void destroy() {}  void paint(Canvas canvas, Size size) async {}}复制代码

接下来我们就可以定义的的 MainHero我们的主角了. 我们的src下新建一个 hero.dart, 引用并继承 Plan, 并实现在上边定义的方法. 关于基本方法与属性如下:

enum PlanStatus {stay, move, die}class MainHero extends Plan {  // 飞机的中心坐标x  double x = 100.0;  // 飞机的中心坐标y  double y = 100.0;  // 战机宽度  double width = 132.0;  // 战机高度  double height = 160.0;  ui.Image image;  @override  void init() async {    // TODO: implement init    image = await Utils.getImage('assets/images/hero.png');  }  @override  void moveTo(double x, double y) {    // TODO: implement moveTo  }  @override  void destroy() {    // TODO: implement destroy    super.destroy();  }    @override  void paint(Canvas canvas, Size size) {    Rect paintArea = Offset(100, 100) & Size(width, height);    Rect planArea = Offset(0, 0) & Size(image.width, image.height)    canvas.save();    // 将画布向左上方偏移, 把绘图点, 迁移到飞机正中心    canvas.translate( -width / 2, -height / 2);    canvas.drawImageRect(image, planArea, paintArea, new Paint());    frameIndex++;    canvas.restore();  }}复制代码

在本次我们的绘图接口用的是 drawImageRect, 使用方法, 我们在游戏的 Enter入口文件中, 新建一个主角的实例, 完成初始化, 与绘图的逻辑, 具体细节与背景图类似, 我们就不细说了.

废话不多说, 直接上效果图

飞机的动效

在我们玩过的飞机类游戏里边. 我们控制的飞机通常都会有一个动态效果, 这个动态的效果会增强玩家的视觉体验, 笔者从网上找到了一份游戏飞机的动效如下:

这个飞机动效是一个
gif 类型的文件循环播放, 给人以动态的感觉. 我查阅了
flutter 貌似没有直接绘制
gif的接口. 所以我们只能用绘制静态图的方式去想办法让
飞机动起来, 做过h5的同学可能比较了解, 在早期html界面中的动画是由多帧拼接成一个胶片, 循环播放, 造成一种视觉停留的动画效果. 这里我们依然采用这种方式去实现本次的动态效果. 我们通过ps, 把每一帧拼接做成一个有2帧的132*80长帧图;

接下来, 我们就要盘这张图,对我们的 MainHero进行改造, 把他动态显示在我们的屏幕上. 我们给它增加二个属性和一个方法, 每一次屏幕刷新, 我们都把 frameIndex 进行加1的操作, 当达到最后一帧, 将 frameIndex重置为0, 这样我们的飞机就可以动起来了

// 总帧数int frameNumber = 2;// 当前帧数int frameIndex = 0;// 动态获取飞机的长帧图的绘制区域Rect getPlanAreaSize(int _frameIndex) {double perFrameWidth = image.width / frameNumber;double offsetX = perFrameWidth * _frameIndex;double offsetY = 0;if (offsetX >= image.width) {  frameIndex = 0;  return this.getPlanAreaSize(0);}return Offset(offsetX, offsetY) & Size(66.0, 80.0);}复制代码

效果图如下:

飞机的控制

关于控制飞机飞行的思路是, 我们通过监听屏幕, 手指的运动, 动态的更新飞机绘制 (x,y) 的坐标点, 从而达到我们想要的效果.

Flutter的文档中, 我们找到了 GestureDetector 接口, 在 Enter 入口中 我们用GestureDetector控件包围住我们的CustomPaint画板 控件。我们接下来的工作就是,使用 GestureDetector 控件来捕获用户的拖动事件。并更新我们 MainHero 的坐标点.

实现方式如下:

Widget build(BuildContext context) build () {    ...    return GestureDetector(      child: CustomPaint(          painter: MainPainter(background: background, hero: hero)      ),      onPanStart: (DragDownDetails) {        hero.moveTo(DragDownDetails.globalPosition.dx, DragDownDetails.globalPosition.dy);      },      onPanUpdate: (DragDownDetails) {             hero.moveTo(DragDownDetails.globalPosition.dx, DragDownDetails.globalPosition.dy);      }    )}复制代码

接下来我们来改造我们的 MainHero 类, 完善他的 moveTo 方法. 在游戏过程中, 我们手指拖动, 飞机不可能以闪现的方式进行闪动, 它需要一点点移动到我们的想要的位置. 我们在 MainHero中定义几个属性与方法

// 飞行目标点坐标double _x;double _y;double speed = 20;// 动态计算新的坐标点void calculatePosition() {}复制代码

我们在这里用一张图, 去展示新旧坐标点之前的关系:

通过以上这张图, 我们要以明白在飞机在x与y轴上, 速度的矢量关系与运算方法, 我们完善我们的
calculatePosition

void moveTo(double x, double y) {    // TODO: implement moveTo    this._x = x;    this._y = y; }void calculatePosition() {    Point  p1 = Point(x, y);    Point  p2 = Point(_x, _y);    double distance = p1.distanceTo(p2);    double flyRadian = acos(((y - _y) / distance).abs());    // 判断位移方向    if (_x < x) {      x -= speed * sin(flyRadian);    } else {      x += speed * sin(flyRadian);    }    if (_y < y) {      y -= speed * cos(flyRadian);    } else {      y += speed * cos(flyRadian);    }  }复制代码

通过以上改造, 我们进行测试发现, 在运动到终点时,飞机会在终点发生抖动, 排查问题发现, 是我们的calculatePosition方法, 在计算x值的时候, 会在最后一次计算中, 产生一个 |x - _x| > 0的结果, 所以飞机会在坐标点来回的跳动. 为了避免这种情况, 我们再次改造 calculatePosition 方法

我们为
MainHero 增加一个飞机的飞行状态, 当飞机与目标点及其接近时, 直接手动覆盖(x, y), 并将飞机的状态设为 stay.

// stay 无人控制, 自由飞行// move 有人控制, 飞行运动状态// die  死了enum PlanStatus {stay, move, die}void calculatePosition() {    ...    // 避免抖动, 做一个判断. 距离    if (distance < 10) {      x = _x;      y = _y;      status = PlanStatus.stay;      return null;    }}// 同时为了更好的优化我们的Pain方法函数, 我们为其增加一个逻辑的判断void paint(Canvas canvas, Size size) {    ...    if (status == PlanStatus.move) {      calculatePosition();    }}复制代码

通过以上改造, 我们看一下最终的效果.

总结

第二部份, 大工告成, 内容可能会有错别字, 请大家指出, 我将进行改正, 剩下的逻辑. 我会一点点补上, 如果觉得本篇内容对您有帮助, 期待您的赞~

转载地址:http://zrbzm.baihongyu.com/

你可能感兴趣的文章
安卓 使用Gradle生成正式签名apk文件
查看>>
@Html.Raw()
查看>>
ES6 Proxy
查看>>
图的基本算法(BFS和DFS)
查看>>
Linux时区详解
查看>>
61.node.js开发错误——Error: Connection strategy not found
查看>>
算法逆向第一篇——简单算法逆向
查看>>
机房收费系统数据库概念结构设计
查看>>
NanoJIT
查看>>
一个最简单GAL游戏资源文件黑盒分析(二)
查看>>
SQL Server 2005允许远程连接的配置说明
查看>>
HQL 语句
查看>>
一起谈.NET技术,Silverlight中本地化的实现
查看>>
PC上的手机模拟器大全(安卓/苹果/黑莓/塞班/微软)
查看>>
ToolTip
查看>>
27 款漂亮的网站导航的设计
查看>>
索引的一些总结
查看>>
js 正则替换换行符
查看>>
制单表查询all终于搞定了辅助核算显示
查看>>
Winforms SkinFramework
查看>>