Flutter游戏:垃圾里会生蚊子
加载游戏资源
在开始下面的内容之前,最好的话是先把《开始用Flutter做游戏吧》过一遍,然后再完成《Flutter游戏:万有引力定律》里的游戏,因为下面的内容是在该游戏的基础上开发的。
首先下载这个游戏要用到的游戏资源文件,然后在项目目录下建立assets/images
目录,在该目录下再分别建立bg
和flies
目录,用于存放背景图片和组件图片。
资源文件就位后,在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
列表作为参数传递给images
的loadAll
方法,该方法用于预加载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
,然后添加一个名为background
的Backyard
类型的新实例变量。
然后在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游戏:垃圾里会生蚊子相关推荐
- Flutter游戏:蚊子飞来飞去
本文紧接上文<Flutter游戏:垃圾里会生蚊子>中完成的代码内容,建议先完成前面的代码呦. 更多蚊子种类 现在我们可以为蚊子添加更多种类,即为Fly类添加更多子类,这一步应该很快就可以完 ...
- Flutter游戏:简单规则与结束页
本篇文章的内容需要在完成以下内容代码的基础上进行哦! <开始用Flutter做游戏吧> <Flutter游戏:万有引力定律> <Flutter游戏:垃圾里会生蚊子> ...
- Flutter游戏:启动时的欢迎页
本篇文章的内容需要在完成以下内容代码的基础上进行哦! <开始用Flutter做游戏吧> <Flutter游戏:万有引力定律> <Flutter游戏:垃圾里会生蚊子> ...
- 202313读书笔记|《山居七年》——我只想在广袤璀璨的星河里享受生的鲜活,独自飞,游走
202313读书笔记|<山居七年>--我只想在广袤璀璨的星河里享受生的鲜活,独自飞,游走 <山居七年> 作者张二冬,选择隐士山居是一种很自由随性的生活态度,我觉得这不是普通人可 ...
- 3D游戏世界里的万向节锁
我们通常认为当前世界是一个三维的世界,用坐标系来表示就是XYZ,在3D游戏世界里也是用这样的三维坐标系来表示的. X代表水平坐标,Y代表垂直坐标,Z代表的是由屏幕往里的一个深度坐标,也就是垂直于屏幕的 ...
- Flutter 游戏教程之使用 Flutter 和 Flame 重现著名的 T-Rex 游戏
Flutter 游戏教程之使用 Flutter 和 Flame 重现著名的 T-Rex 游戏 这个想法 所以我把我的东西放在一起,开始想知道如何用 Flutter 制作游戏,最重要的是要做什么.由于我 ...
- Flutter游戏制作flame官方范例(一)打蚊子
1.准备工作:vsCode https://code.visualstudio.com && install the Flutter and Dart plugins for V ...
- Google 要在游戏世界里训练 AI 了!
现在,Google将通过强化学习和虚拟世界来"发展"更智能的算法了! 智能设计与进化不仅仅是人们在世界观上的一个分歧.这也是人工智能社区的一个分歧.直到几年前,AI的主要用途还只是 ...
- 计算机里没有四款小游戏,电脑里一些隐藏在程序里有趣的小游戏
如果电脑不能上网 我们能干点什么 请在一个新打开的word文档里面输入: =rand(200,99) 然后回车 > 看看会发生 什么? 据说,连比尔盖茨都不知道为什么会这样: > > ...
最新文章
- 腾讯云 短信服务 【学习记录 】
- python解压文件_使用Python实现文件压缩和解压
- SAP发布S4/HANA 意义超过R3
- 二叉树的相关性质及其前中后层序遍历实现
- JZOJ__Day 1:【普及模拟】PLES
- 供来宾访问计算机打开安全吗,计算机安全设置操作手册(22页)-原创力文档
- foursquare nyc数据集_炫酷的python地理数据可视化
- Tomcat启动项目没问题,网页一片空白
- 关于Golang的4个小秘密
- 关于ssm框架的整理(三) 2021-05-11
- BI的需求调研的方法分类
- ajax在php中使用方法,在项目中如何使用ajax请求
- 计算机第二显示器黑屏的处理,电脑显示器黑屏的维修处理办法
- Webpack使用教程五(Babel)
- SQL数据表中按中文数字一二三四五排序
- 2017 idea 代码字体加粗
- 初级加密技术之块设备加密
- 在线工具:将图片透明化
- matlab sisotool工具DEMO
- 我的团队人物介绍卡片布局