基于Flutter和Flame游戏开发引擎学习资料,Create a Mobile Game with Flutter and Flame – Beginner Tutorial,对于更加适用于复杂一点手机游戏的Flutter 开发的2D游戏引擎SpriteWidget也制作了boxGame游戏例子,希望通过起始能够对于SpriteWidget有比较好的了解和掌握,如果能够帮助到你,别忘了给我点赞哦!
如果你有什么问题,可以发邮件给我,或者在Github上留言,方便的话,我会尽力帮助你。
你在了解的过程,也可以参考下面的文章:
1. Create a Mobile Game with Flutter and Flame – Beginner Tutorial
2.飞行射击游戏spritewidget/spaceblast
3.Flutter高性能复杂游戏2D开发游戏引擎spritewidget
前提条件:
1. Android Studio - Flutter开发工具,当然你也可以使用其他的,但本例使用AS开发。
2.Flutter SDK/Framework - AS开发插件,你需要具备开发Flutter的AS开发环境,如果你还没有掌握Flutter的开发基础,请先尝试Flutter开发学习。
你可以在Github找到这个练习的完整代码。
开始撸代码吧:
Step 1: 创建一个Flutter Application(略,希望你是了解Flutter的)
Step 2: 添加spritewidget插件以及清理Application
打开./pubspec.yamland,增加下面的内容在thecupertino_icons行下面并且在derdependencies分类之下(注意缩进).
然后记得执行flutter packages get,或者点击AS界面上的Packages Get来添加插件。
下一步是清理代码,Flutter Project新创建的是一个example,打开./lib/main.dart,清空代码只保留void main() {},并且确保使用material library来运行runApp() .
然后删除./test目录,不然会出现错误,这里我们用不上test方法。
Step 3: 添加主程序
打开./lib/main.dart,添加一下代码,和Flutter创建一个新的界面主程序一样:
这样就创建了一个Flutter的主程序。
Step 4: 添加游戏根节点RootNode:
新建一个dart文件命名为box_game.dart,创建class继承NodeWithSize,import游戏包:
说明:创建一个BoxGame集成NodeWithSize作为RootNode,这样就可以实现spritewidget游戏引擎的游戏loop。一个基本的游戏loop在NodeWithSize以及继承的Node中可以通过update()和paint()方法来实现。
update()方法实现游戏node的移动或者更新(比如timer)。
paint()方法实现游戏node的绘制。
这里有一个非常重要的概念,spritewidget产生了自己的cooridinate system,使用Node自身坐标系进行对象位置的处理,因此:
就是给BoxGame这个Node初始化一个320*320尺寸的作为Node的自身坐标系,这样就可以按照这个坐标系进行新的子Node(child)的添加,每个Node,无论parent/child,都可以有自己自身的坐标系,以及起始位置相对于parent的坐标位置。
这个会在文后详细的把自己研究的坐标系相关信息进行描述。
Step 4: 添加RootNode到主程序
在main.dart中引入box_game.dart,然后在主程序app的state中初始化一个NodeWithSize,然后返回spritewidget():
这是main.dart就像下面的代码:
这时候你的app已经是个游戏app了。你可以运行一下看看。
Step 5: 绘制游戏主界面
添加游戏主节点
spritewidget添加对象非常简单,只需要在parentNode中addChild(childNode)就可以了,当然,需要你首先定义childNode。
我们在RootNode中添加一个游戏主界面_gameScreen,打开box_game.dart, 在BoxGame初始化时添加childNode。
在rootNode中添加一个_gameScreen,是为了更好的在各Node进行交互时能够更有层次,方便不同parentNode和childNode的不同更新和绘制。
添加虚拟游戏操纵杆
spritewidget有一个炫酷的Node称之为VirtualJoystick,可以通过这个组件来控制游戏对象的输入,比如个方向在按住屏幕是的移动。
打开box_game.dart,相同于添加_gameScreen的方法,但是把_gameScreen作为父节点,将VirtualJoystick添加到_gameScreen中,
这时候你可以运行程序看看,你会发现VirtualJoystick并不存在,怎么回事?
原来,我们定义的RootNode使用了一个size(320, 320)的区域作为自己的坐标系统,那么对于不同尺寸的手机屏幕,并不是320x320的,这样就会根据rootNode的定义区域将屏幕进行适配,这时候VirtualJoystick缺省会被添加到屏幕的最下方,因此,我们需要初始化320x320
上的_gameScreen,设置它为最下端的屏幕Node,需要重新设置_gameScreen.position,
打开box_game.dart,在class BoxGame 中override方法spriteBoxPerformedLayout(),
让_gameScreen.position设置为基于ParentNode的高度方向坐标为y设置为spriteBox.visibleArea.height, 也就是320,向上平移320的高度。spriteBox.visibleArea之的是屏幕可见部分在320x320父节点中显示的部分,具体值可以参考后续说明。
这时再运行程序,一个很不错的游戏操纵杆在界面上显示了。
绘制并添加一个Box
新建一个BoxNode类集成Node,然后在绘制一个正方形,打开box_gam.dart,在BoxGame类下面创建一个BoxNode类,也可以新生成一个dart文件来创建BoxNode,然后在box_gam.dart中引用。
和BoxGame一样,BoxNode除了继承Node类以外,同样可以初始化相对于parentNode的position = new Offset(0,0); 通过paint()和update()进行绘制和更新。
说明:NodeWithSize实际上继承Node,但是增加了size和pivot点,可以通过更好的尺寸和支点来对Node进行更新和绘制。适合作为ParentNode或者RootNode,各个游戏对象可以使用Node创建。
说明:基本上来讲,如果child的中点就是parent的size的中点减去偏移(前提是child的position初始化为new Offset(0, 0)。绘制对象使用canvas进行,对象和Paint()就可以实时绘制对象。
然后将BoxNode实例化,并添加到BoxGame的_gameScreen游戏主节点中,在BoxGame类的BoxGame() :super(new Size(320.0,320.0)){}初始化的_gameScreen下面添加,
OK,这时候运行程序,一个绿色的box以及一个虚拟游戏操纵杆就会出现在游戏主界面。
Step 6: 处理虚拟游戏操作
我们要通过虚拟游戏操纵杆来控制box的移动,需要在主界面update()中来通过VirtualJoystick来根据VirtualJoystick.value来改变box.position.
给BoxNode添加根据VirtualJoystick.value来更新自身position的方法,在class BoxNode中添加,
说明:可以看到首先获取BoxNode的当前position作为旧的oldPos, 然后根据VirtualJoystick.value计算VirtualJoystick滑动屏幕的移动亮并根据屏幕尺寸进行放大,这里使用x放大160,y放大220,基本上是根据测试结果,会让VirtualJoystick操作box时看着比较流畅。
GameMath.filter方法可以在oldPos和target之间按照一个0-1之间的filterFactor插入多个移动位置,而不是直接将Box对象从oldPos移动到target,这样在更新的时候就会产生Box连续移动的效果,移动效果会比较流畅。
然后在BoxGame中,override主游戏RootNode的update()方法,将box的position的改变进行更新,这样就可以是的Box在屏幕上根据VirtualJoystick的操作进行移动,
这样整个box_game.dart就像下面的代码,
好了,运行一下程序,你可以通过VirtualJoystick来控制Box的移动了,是不是很酷?
Step 7: 添加点击事件控制box变色展示win状态
我们希望游戏对象可以被点击操作,spritewidget使用handleEvent(SpriteBoxEvent event){}来处理输入交互,SpriteBoxEvent包含多种点击屏幕处理事件,我们这里需要使用PointerDownEvent事件,当Box被点中时进行变色。
如果需要实现点击事件,需要对于点击检测Node进行设置,允许点击检测对象可以进行交互,但是不允许多点碰触,我们希望通过对BoxGame作为RootNode进行对象检测,如果发现在RootNode中点击到的区域是box的区域,则表明box被点中了,然后处理box被点击方法,首先在BoxGame初始化的时候BoxGame() :super(new Size(320.0,320.0)){}设置BoxGame定义为,
然后在BoxGame类中添加override方法,
说明:当点击事件发生时,判断是否为屏幕被点中,这是由于点中的位置为屏幕缺省坐标点,如果需要和RootNode的childNode的范围进行比较,需要首先使用convertPointToNodeSpace()方法将缺省坐标点转换为RootNode坐标系统,然后再和box的范围进行比较,由于_gameScreen作为主Node,初始化为new Offset(0.0, spriteBox.visibleArea.height),所以在对点击的位置判断是,需要把_box的位置也添加相同的偏移进行对比。
添加点击位置判断是box范围时,处理box的方法,在BoxNode类中定义,
定义一个布尔参数来分辨点击每次的状态,然后在paint()函数中,更改
布尔参数判读来使用不同颜色画笔,
如果布尔参数为true,画笔为绿色,否则为白色,然后定义一个点击调用的方法,来根据点击改变布尔参数的值,
box被点中一次布尔参数为true,再点击变为false,这样不同的点击布尔参数就会有不同的状态,这样就会在paint()方法中使用不同颜色的画笔来绘制box。
然后在BoxGame的bool handleEvent(SpriteBoxEvent event) {} 方法中判断点中box时调用onTapDown()方法,替换
为
这样就完成了点击输入方式交互,整体box_game.dart的代码如下,
好了你可以体验一下这个游戏了。
关于Node的坐标系统和世界坐标系统的相关联,后期添加。
你可以在Github找到这个练习的完整代码。