在零基础:100个小案例玩转Python游戏开发!第三节:对对碰游戏(上)中,我们学会了游戏引擎的基本原理,并介绍了游戏界面和游戏场景的编辑,还学会了如何通过点击事件进行场景切换,下面我们来进行游戏战斗场景的制作。
本节内容需要先更新 PyMe至 1.5.4.8+ 版本
下载地址 : https://pyme.lanzoum.com/ivKAe3cps8he
在这个场景中,我们设置了两个层(GameLayer),背景(BG)层放置了一个棋盘背景图片,格子(Tile)层创建了一个二维网格,并让它左上角与棋盘内部的左上角对齐,有了这些格子,我们可以将图标填充在格子里,这样就可以方便的对齐和放置图标啦。
场景事件
选中图层Tile,然后在右上角面板中选择“事件响应”,这时会切换到图层的事件编辑列表,我们在左边可以看到以下事件:
Load:当前层加载完成
Update:每一帧更新时
Render:每一帧渲染时
Destroy:当前层被销毁时
PickDown:鼠标点击时
PickUp:鼠标抬起始
PickTile:鼠标点击到格子上时
...其它事件
这些事件和之前我们做界面控件事件的用法类似,只需要双击右边“对应函数”栏位就可以进入到代码编辑器,在这里我们双击Load事件右边的对应函数栏位。
进入到代码编辑器中,会进入当前场景类的实现代码,光标停在函数on_Tile_Load,代表Tile层加载完成后调用的函数。
在PyMe中的游戏,主要依靠GameFun.py来提供所有的游戏引擎支撑,它的使用和开发软件时的Fun函数库类似,只不过GameFun是用于游戏的。我们可以花点时间看一下GameFun.py文件,找到GameScene和GameLayer类,分别了解它们有什么函数功能,这将有助于我们更加理解下面的工作。
比如,我们在这里需要获取到当前Tile层的格子信息,并将图片资源设置到格子里。
def on_Tile_Load(self): tileImages = ["bag","barrel","beans","bottle","eggplant","fire","flower","glove","grass","hat","house","kettle","milk","paling","peach","pot","shoe","shovel","straw","waterpipe"] tileResIndexDict = {} tileImageCount = len(tileImages) for i in range(tileImageCount): tileResIndexDict[i] = self.AddLayerResNode(tileImages[i]+".png") #先把要放置的图片的索引加入到一个列表中,每个图片索引必须为2的倍数 tileImageIndexList = [] TileLayer = self.GetLayer("Tile") TiledMap = TileLayer.GetGameNode(0) #因为图片索引必须为2的倍数,这里取一半行数,每一格子存两个相同的图片索引 HalfRows = TiledMap.Tile_Row // 2 for TileY in range(0,HalfRows): for TileX in range(0,TiledMap.Tile_Col): randNumber = GameFun.RandNumber(0,tileImageCount-1) tileImageIndexList.append(randNumber) tileImageIndexList.append(randNumber) #把有图片都放置完毕后,写个算法把tileImageIndexList打乱 tileImageIndexList = GameFun.Shuffle(tileImageIndexList) TileLayer = self.GetLayer("Tile") TiledMap = TileLayer.GetGameNode(0) for TileY in range(0,TiledMap.Tile_Row): for TileX in range(0,TiledMap.Tile_Col): TileIndex = TileY * TiledMap.Tile_Col + TileX ResIndex = tileImageIndexList[TileIndex] TiledMap.SetTileObject(TileX,TileY,tileResIndexDict[ResIndex])在这段代码中,我们有一些重要的函数需要记一下:
AddLayerResNode:为场景加入资源并返回资源ID
GetLayer:取得当前场景中对应的GameLayer
GetGameNode:取得GameLayer中对应的GameNode,这里因为Tile层会创建一个TiledMap作为第一个GameNode,所以参数填0.
SetTileObject :为TiledMap的指定单元格设置对应的图片资源ID。
这就是运行的效果:
格子点击事件
没错,下面我们选中PickTile事件并双击“对应函数”栏位,将会进入到当前Tile层的格子被点选的事件函数:
具体代码如下:
def on_Tile_PickTile(self,tx,ty): TileLayer = self.GetLayer("Tile") TiledMap = TileLayer.GetGameNode(0) resID = TiledMap.GetTileObject(tx,ty) userData = TiledMap.GetUserData("last_tile") if userData: last_tx,last_ty = userData last_resID = TiledMap.GetTileObject(last_tx,last_ty) if last_resID == resID: TiledMap.DelTileObject(last_tx,last_ty) TiledMap.DelTileObject(tx,ty) TiledMap.SetUserData("last_tile",None) #如果格子为空 if TiledMap.IsEmpty() == True: UILayer = self.GetLayer("UI") UIInstance = UILayer.GetGameNode(0) Image_1 = UIInstance.GetChildByName("Image_1") Image_1.SetVisible(True) else: TiledMap.SetUserData("last_tile",[tx,ty]) else: TiledMap.SetUserData("last_tile",[tx,ty])在这段代码中,我们也有一些重要的函数需要记一下:
GetUserData:取得属于GameNode的用户变量
SetUserData:为GameNode设置用户自定义变量
GetTileObject:取得TiledMap上的对应格子的元素资源ID
DelTileObject :为TiledMap的指定单元格删除图片资源ID
IsEmpty:判断TiledMap的格子是否都为空
果然,再次运行时,用鼠标点击两个相同的图标,就会清掉哦~
胜利与失败
当游戏开始后,如果我们能在指定时间内将图标都消除掉,就弹出胜利界面,如果没有在指定时间内将图标都消除掉,就弹出失败界面。我们可以双击进入Resources\UI文件夹,然后在文件资源栏中用鼠标右键点击空白处,通过弹出菜单创建界面BattleUI。
在BattleUI中,我们首先选中Form_1,设置显示样式为“空心”,这样可以不显示Form_1,然后从左边控件工具条拖动创建一个Image对象,并设置图片背景为胜利图片win.png,放置在中间,然后把“Hello,World"放置在顶部居中,修改文字内容为:“时间:100秒”,此时我们可以看到,Label文字显示为乱码,这是因为pygame的默认系统字体不支持中文,我们需要使用ttf字体来显示中文。
在字体属性栏中双击,然后在弹出字体编辑框中点击“导入TTF文件”,找到一个可用的ttf字体加入。
点击“打开”按钮,就可以在文字编辑框的“字体名称”下拉列表框中找到对应的TTF字体名称,选择它后点击“确定”按钮,这时后当前Label的字体就能够正常显示中文啦!
搞定界面后,返回游戏场景,创建一个UI层,然后在右下角导入界面BattleUI。
选中UI层,再选中BattleUI后在场景中点击一下,就可以创建出界面,并在下方的位置栏设置对齐0,0位置。
我们默认情况是不应该显示胜利图片的,所以回到BattleUI中将Image的显示属性设置为“隐藏”保存,并将倒计时文字改为黑色,使用平坦样式边框,设置透明度128,再次回到Battle场景,这时就只显示倒计时啦~
那么,下面我们要做的,就是为当前的UI层,在右上角双击Load和Update事件,分别在Load和Update中添加函数:
在这段代码中,重要的函数如下:
def on_UI_Load(self): import time starttime = time.time() self.SetUserData("second",100) self.SetUserData("time",starttime) def on_UI_Update(self): import time currtime = time.time() starttime = self.GetUserData("time") second = self.GetUserData("second") offsettime = currtime - starttime if offsettime > 1 and second > 0: UILayer = self.GetLayer("UI") UIInstance = UILayer.GetGameNode(0) #获取标题文字 Label_2 = UIInstance.GetChildByName("Label_2") second = second - 1 Label_2.SetText(str("时间: %d" %second)) if second <= 0 : #设置中间的图片为失败 Image_1 = UIInstance.GetChildByName("Image_1") Image_1.SetImage("Fail.png") Image_1.SetVisible(True) else: self.SetUserData("time",currtime) self.SetUserData("second",second)GetChildByName:通过名称取得子节点
现在再次运行,已经可以看到倒计时的文字显示...呃,我有点后悔,开始时没有好好设计一下游戏的场景....
玩了一把,我得承认不是太容易。。。。
打包到手机上
在电脑上玩还是不如在手机上方便,所以我们可以选择打包为APK,在右上角先选一下Android模式,然后点击“发布”,这时提示说需要使用英文路径。。。。
我们先关闭项目返回项目管理界面,然后在“打开项目”页里右键单击,选择菜单项“工程另存为”,将其另存为“PMDDP”,这样就可以直接生成一个新的英文名项目了。
双击进入”PMDDP“项目,然后再次重新选择Android模式,并点击”发布“按钮,就可以打包成功了!
打包完成后,在当前工程上会生成PMDDP.apk。
我们将手机与电脑相连,并将APK安装到手机上。
现在看一下最终效果吧!
为什么PyMe是游戏开发入门神器?
经过这个项目的实践,相信您对于PyMe开发游戏的流程已经有所了解了,那我们来总结一下使用PyMe进行游戏开发的突出优点:
引擎支持
不用从零来写引擎渲染,直接基于工作流进行游戏开发。
工具强大
提供丰富的可视化编辑工具,使游戏开发工具流更加趋于商业游戏开发理念。
门槛较低
理解了基础的原理和事件函数编写方法,就可以上手开发,让游戏开发的门槛大大降底。
写在最后:编程也可以这么有趣
游戏开发最大的魅力在于:它让编程变成了创造世界的过程。每一行代码都是为这个世界添加新的规则,每一个函数都是赋予角色新的能力。
PyMe就像是我们手中的魔法棒,把抽象的代码变成了具象的体验。它降低了技术门槛,让我们这些初学者也能享受到创造的快乐。
所以,不要停下脚步!用PyMe继续创造更多有趣的游戏吧!无论是送给朋友当礼物,还是只是让自己开心,这都是编程带给我们的独特乐趣。