加载游戏资源

在开始下面的内容之前,最好的话是先把《开始用Flutter做游戏吧》过一遍,然后再完成《Flutter游戏:万有引力定律》里的游戏,因为下面的内容是在该游戏的基础上开发的。

首先下载这个游戏要用到的游戏资源文件,然后在项目目录下建立assets/images目录,在该目录下再分别建立bgflies目录,用于存放背景图片和组件图片。

资源文件就位后,在pubspec.yaml文件里添加对这些资源文件的引用。

flutter:uses-material-design: trueassets:- assets/images/bg/backyard.png- assets/images/flies/agile-fly-1.png- assets/images/flies/agile-fly-2.png- assets/images/flies/agile-fly-dead.png- assets/images/flies/drooler-fly-1.png- assets/images/flies/drooler-fly-2.png- assets/images/flies/drooler-fly-dead.png- assets/images/flies/mosquito-fly-1.png- assets/images/flies/mosquito-fly-2.png- assets/images/flies/mosquito-fly-dead.png- assets/images/flies/hungry-fly-1.png- assets/images/flies/hungry-fly-2.png- assets/images/flies/hungry-fly-dead.png- assets/images/flies/macho-fly-1.png- assets/images/flies/macho-fly-2.png- assets/images/flies/macho-fly-dead.png

下面我们要在游戏开始时加载所有资源,这会花费几毫秒的时间,但是又有谁会注意到这几毫秒内的黑屏显示呢。打开main.dart文件,在顶部添加代码以导入flame/flame.dart包,然后就可以预加载游戏资源了。

...import 'package:flame/flame.dart';void main() async {Util flameUtil = Util();await flameUtil.fullScreen();await flameUtil.setOrientation(DeviceOrientation.portraitUp);Flame.images.loadAll(<String>['bg/backyard.png','flies/agile-fly-1.png','flies/agile-fly-2.png','flies/agile-fly-dead.png','flies/drooler-fly-1.png','flies/drooler-fly-2.png','flies/drooler-fly-dead.png','flies/mosquito-fly-1.png','flies/mosquito-fly-2.png','flies/mosquito-fly-dead.png','flies/hungry-fly-1.png','flies/hungry-fly-2.png','flies/hungry-fly-dead.png','flies/macho-fly-1.png','flies/macho-fly-2.png','flies/macho-fly-dead.png',]);...
}

上面的代码中,使用一个String列表作为参数传递给imagesloadAll方法,该方法用于预加载String列表指向的图像文件。这些图像将缓存在Flame的静态变量中,以便以后可以重复使用。

设置游戏背景

现在游戏的背景是一个灰蓝纯色背景,看起来还不错,但是我们接下来还是要改变它。我们预加载的资源中有一个bg/backyard.png,这是一个高度很高的垂直图片,因为我们的游戏目前只关心宽度,不管手机的纵横比如何,这张背景图都可以覆盖整个屏幕。

接下来,创建一个组件文件components/backyard.dart,将背景逻辑分开来,该文件声明了一个Backyard类,有一个构造函数和另外渲染(render)、更新(update)方法。

这个Backyard类还有一个最终(final)的HitGame实例变量,它将作为包含该组件的游戏实例及其属性的链接,可以参考components/fly.dart中的实现。另一个实例变量是一个名为bgSprite的精灵(Sprite),它会保存我们稍后将绘制在屏幕上的精灵(Sprite)数据。

import 'dart:ui';
import 'package:flame/sprite.dart';
import 'package:hello_flame/hit-game.dart';class Backyard {final HitGame game;Sprite bgSprite;Backyard(this.game) {bgSprite = Sprite('bg/backyard.png');}void render(Canvas c) {}void update(double t) {}
}

在构造函数中,通过创建一个新的精灵(Sprite)并传递要使用的资源文件名来初始化bgSprite变量。文件bg/backyard.png已经在main.dart中被预加载,因此无需任何加载时间即可使用。

文件顶部的import语句导入了3个内容,dart:ui允许我们访问画布(Canvas)类,flame/sprite.dart允许我们使用精灵(Sprite)类,hello_flame/hit-game.dart使我们可以访问HitGame类。

那么在添加背景图以后,我们怎么定位游戏组件的位置勒?如果打开bg/backyard.png文件,可以看到它的大小为1080 x 2760像素,我们不用关注它的物理像素或逻辑像素,我们只要关心我们的背景有9个图块的宽度就好了。

1080(像素) ÷ 9(图块) = 120(每个图块的像素)

2760(像素) ÷ 120(每个图块的像素) = 23(图块)

如同上面的计算结果所示,我们当前使用的背景图像宽为9个图块、高为23个图块。

绘制游戏背景

现在我们可以开始绘制游戏背景了,将背景图像底部锚定在屏幕的底部,为此需要定义一个包含背景尺寸的矩形(Rect),这里需要正确计算大小,以便在渲染过程中保留背景图像的宽高比。

components/backyard.dart文件中添加一个名为bgRect的矩形(Rect)实例变量,并在构造函数内部,初始化这个矩形(Rect)。

class Backyard {...Rect bgRect;Backyard(this.game) {bgSprite = Sprite('bg/backyard.png');bgRect = Rect.fromLTWH(0,game.screenSize.height - (game.tileSize * 23),game.tileSize * 9,game.tileSize * 23,);}...
}

上面代码中,矩形(Rect)构造函数fromLTWH的4个参数分别对应于x坐标、y坐标、宽度和高度的值。我们以最大宽度绘制背景,因此,它的宽度从x开始延伸到game.tileSize * 9为止,我们也可以在这里使用game.screenSize.width,因为game.tileSize是等于game.screenSize.width ÷ 9的。

在前面的计算中,我们已知背景图像为9 x 23的图块大小,因此要绘制整个背景图像的话,只需要设置game.tileSize * 23的高度即可。最后,y坐标是一个负数,对应于屏幕大小和背景图像的差异。

如果设备屏幕的宽高比为9:16,则屏幕的高度为16 * 图块大小,如果从中减去23 * 图块大小,我们就可以得到-7 * 图块大小的值,这意味着背景图片是使用屏幕顶部边缘上方的7个图块大小的地方开始绘制的。

通过上面的计算,背景图像将始终锚定在设备屏幕的底部,最后,我们在调用此组件的渲染(render)方法时就绘制背景图像。

  void render(Canvas c) {bgSprite.renderRect(c, bgRect);}

到这里为止,我们的components/backyard.dart里面应该有以下代码。

import 'dart:ui';
import 'package:flame/sprite.dart';
import 'package:hello_flame/hit-game.dart';class Backyard {final HitGame game;Sprite bgSprite;Rect bgRect;Backyard(this.game) {bgSprite = Sprite('bg/backyard.png');bgRect = Rect.fromLTWH(0,game.screenSize.height - (game.tileSize * 23),game.tileSize * 9,game.tileSize * 23,);}void render(Canvas c) {bgSprite.renderRect(c, bgRect);}void update(double t) {}
}

添加游戏背景

上面我们已经完成了背景组件,现在让我们把它添加到游戏逻辑中,打开hit-game.dart文件,导入hello_flame/components/backyard.dart,然后添加一个名为backgroundBackyard类型的新实例变量。

然后在initialize方法中,实例化一个新的Backyard对象,并将其分配给实例变量background,而且,必须在确定屏幕大小后再执行此操作,因为Backyard类的构造函数中使用到了屏幕大小和图块大小值。

还有就是,要像我们创建游戏组件Fly一样,使用关键字this传递当前的HitGame实例。

...
import 'package:hello_flame/components/backyard.dart';class HitGame extends Game {...Backyard background;...void initialize() async {enemy = List<Fly>();rnd = Random();resize(await Flame.util.initialDimensions());background = Backyard(this);produceFly();}...
}

然后在渲染(render)方法中,调用background的渲染(render)方法并将画布(Canvas)传递给它。同时,删除我们之前绘制的一个纯色矩形背景。

  void render(Canvas canvas) {// 删除以下内容// Rect bgRect = Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);// Paint bgPaint = Paint();// bgPaint.color = Color(0xff576574);// canvas.drawRect(bgRect, bgPaint);background.render(canvas);enemy.forEach((Fly fly) => fly.render(canvas));}

当我们现在运行游戏时,应该可以看到游戏背景在不同手机上会有不同的高度。

改变游戏组件

目前,文件的预加载资源中,有五种不同的蚊子素材,我们现在就重点看下它们的视觉差异。具体可以通过子类来实现,就是创建一个类作为子类扩展现有的父类。

我们的蚊子素材的大小相对于实例变量flyRect的矩形来说,会占用更大的图块大小,要解释清楚的话,可能要借助于下面的示例图。

在上面的示例图中,精灵(sprite)将被绘制在蓝色图块内,我们就称它为精灵(sprite)矩形。但是点击需要发生在红色图块内,我们就称它为命中矩形,而在代码中它被命名为flyRect

在开始创建第一个子类之前,我们要先准备好一个可以进行扩展的父类。打开components/fly.dart文件,Fly类将具有所有蚊子种类共享的常用方法和变量。首先,删除画矩形(drawRect)方法,因为我们不需要绘制矩形了,再清空渲染(render)方法。

然后,删除所有对flyPaint的引用,因为该对象仅用于绘制矩形,从实例变量、onTapDown处理方法和构造函数中都删除它。但是我们仍然会使用flyRect作为命中矩形,所以让它留在文件中。

class Fly {final HitGame game;Rect flyRect;// 删除内容// Paint flyPaint;bool isDead = false;bool isOffScreen = false;Fly(this.game, double x, double y) {flyRect = Rect.fromLTWH(x, y, game.tileSize, game.tileSize);// 删除内容// flyPaint = Paint();// flyPaint.color = Color(0xff6ab04c);}void render(Canvas c) {// 删除内容// c.drawRect(flyRect, flyPaint);}...void onTapDown() {isDead = true;// 删除内容// flyPaint.color = Color(0xffff4757);game.produceFly();}
}

对于Fly类或其子类的每个实例,都需要准备和存储2组精灵(sprite),1组将由2个精灵(sprite)组成,这些精灵将1个接1个地显示,以绘制出“飞行”的动画效果,我们需要一个List

另一组将只有1个精灵(sprite)将在蚊子死亡时显示,这里需要另一个实例变量来存储将为“飞行”动画显示的精灵(sprite)。

在文件顶部导入flame/sprite.dart,并在实例变量部分中添加下面代码。

...
import 'package:flame/sprite.dart';class Fly {...List<Sprite> flyingSprite;Sprite deadSprite;double flyingSpriteIndex = 0;

但是这些精灵(sprite)变量不会在Fly类中初始化,因为每个子类都会使用不同的精灵(sprite),在渲染(render)方法中,我们会根据实例的状态(死或生)来渲染精灵(sprite)。

  void render(Canvas c) {if (isDead) {deadSprite.renderRect(c, flyRect.inflate(2));} else {flyingSprite[flyingSpriteIndex.toInt()].renderRect(c, flyRect.inflate(2));}}

在上面的代码中,渲染(render)方法通过检查isDead变量来决定显示哪个精灵(sprite),如果当前实例已死,则渲染deadSprite,如果没有,则渲染flyingSprite列表中的第0个下标项。

对于flyingSpriteIndex.toInt()来说,List的精灵(sprite)项由整数索引访问,而flyingSpriteIndex是双精度(double)类型的,所以需要先转换为整型(int)。那么为啥它是双精度(double)类型的呢,因为我们将使用更新(update)方法中的时间增量(t)来递增它。

最后一部分的.inflate(2)只是创建了一个被调用的矩形的副本,但是从中心开始按乘数膨胀,这里我们把乘数设置为2,因为从蚊子素材的大小来看,精灵(sprite)矩形的大小约是命中矩形的2倍。

到这里为止,我们的fly.dart里面应该有以下代码。

import 'dart:ui';
import 'package:hello_flame/hit-game.dart';
import 'package:flame/sprite.dart';class Fly {final HitGame game;List<Sprite> flyingSprite;Sprite deadSprite;double flyingSpriteIndex = 0;Rect flyRect;bool isDead = false;bool isOffScreen = false;Fly(this.game, double x, double y) {flyRect = Rect.fromLTWH(x, y, game.tileSize, game.tileSize);}void render(Canvas c) {if (isDead) {deadSprite.renderRect(c, flyRect.inflate(2));} else {flyingSprite[flyingSpriteIndex.toInt()].renderRect(c, flyRect.inflate(2));}}void update(double t) {if (isDead) {flyRect = flyRect.translate(0, game.tileSize * 12 * t);if (flyRect.top > game.screenSize.height) {isOffScreen = true;}}}void onTapDown() {isDead = true;game.produceFly();}
}

创建组件子类

现在创建第一个蚊子种类,这是一个“正常”的种类,就将它命名为MosquitoFly,一只正常飞行的蚊子。在components文件夹下新建一个mosquito-fly.dart文件并打开它,创建基本的组件类,但这次我们扩展了Fly类。

import 'package:flame/sprite.dart';
import 'package:hello_flame/components/fly.dart';
import 'package:hello_flame/hit-game.dart';class MosquitoFly extends Fly {MosquitoFly(HitGame game, double x, double y) : super(game, x, y) {flyingSprite = List<Sprite>();flyingSprite.add(Sprite('flies/mosquito-fly-1.png'));flyingSprite.add(Sprite('flies/mosquito-fly-2.png'));deadSprite = Sprite('flies/mosquito-fly-dead.png');}
}

上面的代码中,先导入该类所依赖的包和类,然后声明一个名为MosquitoFly的类,并使其扩展Fly类,从而有效地创建一个Fly子类,其可以访问和覆盖Fly类的变量和方法。

构造函数中调用super,这样在构造函数执行代码当前类代码之前,就会先运行父类的构造函数。构造函数只映射父类构造函数所需的参数,并在调用super期间转发它们。

在构造函数中,通过创建一个精灵(Sprite)列表的新实例来初始化此子类从Fly类继承的flyingSprite变量,然后我们在这个列表中添加两个精灵(Sprite),它们对应于飞行动画的2个帧。

然后我们将“正常”蚊子的掉落图加载到精灵(Sprite)中并将其分配给deadSprite

我们现在不会覆盖更新(update)和渲染(render)方法,因为目前没有针对这类蚊子的特定内容,所有蚊子都相同。

生产正常蚊子

现在回到hit-game.dart文件中,编辑produceFly方法以一个MosquitoFly而不是父类Fly。在文件顶部导入刚刚创建的子类,然后替换之前生成Fly的代码。

...
import 'package:hello_flame/components/mosquito-fly.dart';class HitGame extends Game {...void produceFly() {double x = rnd.nextDouble() * (screenSize.width - tileSize);double y = rnd.nextDouble() * (screenSize.height - tileSize);// 删除内容// enemy.add(Fly(this, x, y));enemy.add(MosquitoFly(this, x, y));}

现在运行游戏,应该可以看到下面图片所示的效果。

Flutter游戏:垃圾里会生蚊子相关推荐

  1. Flutter游戏:蚊子飞来飞去

    本文紧接上文<Flutter游戏:垃圾里会生蚊子>中完成的代码内容,建议先完成前面的代码呦. 更多蚊子种类 现在我们可以为蚊子添加更多种类,即为Fly类添加更多子类,这一步应该很快就可以完 ...

  2. Flutter游戏:简单规则与结束页

    本篇文章的内容需要在完成以下内容代码的基础上进行哦! <开始用Flutter做游戏吧> <Flutter游戏:万有引力定律> <Flutter游戏:垃圾里会生蚊子> ...

  3. Flutter游戏:启动时的欢迎页

    本篇文章的内容需要在完成以下内容代码的基础上进行哦! <开始用Flutter做游戏吧> <Flutter游戏:万有引力定律> <Flutter游戏:垃圾里会生蚊子> ...

  4. 202313读书笔记|《山居七年》——我只想在广袤璀璨的星河里享受生的鲜活,独自飞,游走

    202313读书笔记|<山居七年>--我只想在广袤璀璨的星河里享受生的鲜活,独自飞,游走 <山居七年> 作者张二冬,选择隐士山居是一种很自由随性的生活态度,我觉得这不是普通人可 ...

  5. 3D游戏世界里的万向节锁

    我们通常认为当前世界是一个三维的世界,用坐标系来表示就是XYZ,在3D游戏世界里也是用这样的三维坐标系来表示的. X代表水平坐标,Y代表垂直坐标,Z代表的是由屏幕往里的一个深度坐标,也就是垂直于屏幕的 ...

  6. Flutter 游戏教程之使用 Flutter 和 Flame 重现著名的 T-Rex 游戏

    Flutter 游戏教程之使用 Flutter 和 Flame 重现著名的 T-Rex 游戏 这个想法 所以我把我的东西放在一起,开始想知道如何用 Flutter 制作游戏,最重要的是要做什么.由于我 ...

  7. Flutter游戏制作flame官方范例(一)打蚊子

    1.准备工作:vsCode https://code.visualstudio.com   &&  install the Flutter and Dart plugins for V ...

  8. Google 要在游戏世界里训练 AI 了!

    现在,Google将通过强化学习和虚拟世界来"发展"更智能的算法了! 智能设计与进化不仅仅是人们在世界观上的一个分歧.这也是人工智能社区的一个分歧.直到几年前,AI的主要用途还只是 ...

  9. 计算机里没有四款小游戏,电脑里一些隐藏在程序里有趣的小游戏

    如果电脑不能上网 我们能干点什么 请在一个新打开的word文档里面输入: =rand(200,99) 然后回车 > 看看会发生 什么? 据说,连比尔盖茨都不知道为什么会这样: > > ...

最新文章

  1. 腾讯云 短信服务 【学习记录 】
  2. python解压文件_使用Python实现文件压缩和解压
  3. SAP发布S4/HANA 意义超过R3
  4. 二叉树的相关性质及其前中后层序遍历实现
  5. JZOJ__Day 1:【普及模拟】PLES
  6. 供来宾访问计算机打开安全吗,计算机安全设置操作手册(22页)-原创力文档
  7. foursquare nyc数据集_炫酷的python地理数据可视化
  8. Tomcat启动项目没问题,网页一片空白
  9. 关于Golang的4个小秘密
  10. 关于ssm框架的整理(三) 2021-05-11
  11. BI的需求调研的方法分类
  12. ajax在php中使用方法,在项目中如何使用ajax请求
  13. 计算机第二显示器黑屏的处理,电脑显示器黑屏的维修处理办法
  14. Webpack使用教程五(Babel)
  15. SQL数据表中按中文数字一二三四五排序
  16. 2017 idea 代码字体加粗
  17. 初级加密技术之块设备加密
  18. 在线工具:将图片透明化
  19. matlab sisotool工具DEMO
  20. 我的团队人物介绍卡片布局

热门文章

  1. 蓝桥杯——输出米字形
  2. 目标检测——各个框架下Tensor和矩阵的维度次序
  3. CSS中的偏僻知识点
  4. Java安装配置环境变量及介绍数据类型
  5. JZOJ 1286. 太空电梯
  6. 01_python2.x和python3.x中range()的区别
  7. 深入出不来nodejs源码-内置模块引入初探
  8. 2017 Multi-University Training Contest - Team 1
  9. 学c语言做练习之文件
  10. php session实现原理