赞
踩
今天继续:用python自带的tkinter做游戏系列的第四弹,推箱子重制版 篇
之前的三篇博文介绍的分别是贪食蛇和俄罗斯方块,还有推箱子的简易版。
用python自带的tkinter做游戏(一)—— 贪吃蛇 篇
用python自带的tkinter做游戏(二)—— 俄罗斯方块 篇
用python自带的tkinter做游戏(三)—— 推箱子简易版 篇
上回发布的推箱子游戏中,我后来发现一个BUG,就是在大地图的关卡中(第六关),如果人物远离初始地后,按J键复位的话画面不会返回到人物所在的坐标处。今天要讲的重制版里已经修复好了。
先来谈谈重制版的思路吧。我最初的想法是做成像素版的,就是用更多的像素来构成游戏画面。打个比方,本来黄色单元格代表的人,等于只有一个像素来表达,如果把这个单元格再分成16x16,或者甚至更多,就可以展现出更精致的画面。像素升级法优点就是不需要用到图片,全部用代码就能搞定,缺点么也很明显,比较费力,等于是在画像素图。
不过像素图的问题也好解决,我可以用tkinter来做一个像素图编辑器,这个还是比较容易实现的,而且有了这个编辑器,制作关卡也会变的方便许多。
可惜,虽然后来我是完成了像素上的升级,但我发现即使是只有8x8的像素升级,游戏过程中还是能肉眼所见的卡顿。如果像素再高的话就成了幻灯片了。
当然我也想过用一些方案来优化,比如多线程,或者只刷新局部区域,效果都不是很明显,只能说自身水平有限吧,只好作罢。不过在这次的重制版中,我还是保留了像素模式,用的是8x8的像素图,各位可以感受一下~
# 人物在空地上的动画效果(用于像素动画模式) pe2 = [[ [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 1, 1, 2, 2, 1, 0, 0], [0, 0, 1, 2, 2, 1, 1, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0] ],[ [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 1, 5, 5, 1, 1, 0], [0, 1, 1, 5, 5, 1, 0, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0] ]]
这就是人物的像素图,在游戏画面中就如下图:
因为只有8x8的像素,所以也只能画画这种简单的画面。这两张反复切换所形成的动图就成了这个推箱小人。
既然像素模式行不通,那我只能用图片模式了,这也是游戏制作最常用的方法,直接用图片,简单明了。
也懒得网上去找图片,我就直接用做好的像素图截图下来当图片用。当然为了区分,箱子的颜色略有区别,还有就是人物造型也略有修改。
嗯,比之前的简易版来的确是有点游戏的样子了。按数字键0可以切换模式,原始的简易模式,图片模式和像素模式,三种模式逐一切换。
图片模式比较好理解,就根据原本单元格的值,插入相应的图片即可。
def create_pic_cells(self,a,b): """ 创建图片模式的单元格 """ global man_gif man_2 = ["2.png","22.png"] # 两张空地上人的图片 man_6 = ["6.png","66.png"] # 两张终点上人的图片 man_gif = man_gif + 1 if man_gif > 1: man_gif = 0 man_2[0] = man_2[man_gif] man_6[0] = man_6[man_gif] pic= ["0.png","1.png",man_2[0],"3.png","4.png","5.png",man_6[0]] bm = [] for i in range(len(pic)): bm.append(('bm' + str(i))) exec('bm'+str(i)+' = tk.PhotoImage(file='+str(pic)+'['+str(i)+'])',globals()) #bm = ['bm0','bm1','bm2','bm3','bm4','bm5','bm6'] for y in range(0,len(game_map[self.game_stage-1])-b): for x in range(0,len(game_map[self.game_stage-1][0])-a): n = game_map[self.game_stage-1][y+b][x+a] # 单元格的值 canvas.create_image(self.frame_x*2 + self.cell_size*x + x*self.cell_gap, self.frame_y*2 + self.cell_size*y + y*self.cell_gap, image = eval(bm[n])) canvas.place(x=0,y=0)
像素模式也是差不多的思路,就有一点不同,网格线的显示。像素模式下,网格线是每8个单元格出现一次,所以解决的方案就是看像素格的坐标值能否整除8,能整除的就加一条网格线,有余数的就略过。
好了,推箱子的重制版算是完成了,掌握了这些技巧后,就能制作出更多类似的游戏来,比如经典的魔塔。从制作的角度上来说,魔塔和推箱子是属于同一类的游戏,都是以单元格为单位移动的。本来是想做一个魔塔,图片资源都找好了,后来想想工程有点浩大,而且技术上也没什么大的难点,也就作罢了。有需要图片资源的朋友可以联系我(wx:znix1116),欢迎来交流。
至于音效,其实python自带的winsound就可以播放音乐,有兴趣的朋友可以自行试一试。
最后我来谈谈其它类型的游戏吧。其实大多数的游戏,动作类的也好,射击类的也好,就算是RPG类的,对象的移动基本上都是以像素为单位的。不像推箱子或是魔塔,是以单元格为单位的。有什么区别呢?就拿坦克大战来说吧,玩坦克大战的时候,玩家可以躲在砖墙后面,一点一点的移动出来。而推箱工只能是要么在墙后面,要么就整个人都出来,没法做到人一半在墙后,一半在外的情况。
那么tkinter能否做到以像素为单位来移动呢?答案当然是可以的,下一期我就会来谈谈如何实现以像素为单位的移动。
好了,今天就到此结束,最后附上推箱子重制版的完整版代码,别忘了还要下载9张图片,想要更完美的画面可自行替换,图片像素是48x48
66.png
22.png
6.png
3.png
5.png
4.png
1.png
0.png
2.png
图片一共有九张(第八张是个白底),名字别忘了修改一下,不然不认。
2022.06.08 更新:修复BUG
完整版代码:
# -*- coding: utf-8 -*- """ # -*- coding: utf-8 -*- """ Created on Tue Mar 16 13:31:29 2021 @author: Juni Zhu (wechat:znix1116) """ import tkinter as tk import copy from tkinter.messagebox import showinfo remake = 1 # 版本切换 0为原始模式,1为图片模式,2为像素动画模式(像素动画模式有点卡) try: # 同目录下搜寻m.txt地图扩充文件,若没有此文件就用内置地图 with open("m.txt", "r") as f: game_map = f.read() game_map = eval(game_map) except: game_map = [[ [0, 0, 1, 1, 1, 0, 0, 0], [0, 0, 1, 4, 1, 0, 0, 0], [0, 0, 1, 0, 1, 1, 1, 1], [1, 1, 1, 3, 0, 3, 4, 1], [1, 4, 0, 3, 2, 1, 1, 1], [1, 1, 1, 1, 3, 1, 0, 0], [0, 0, 0, 1, 4, 1, 0, 0], [0, 0, 0, 1, 1, 1, 0, 0] ],[ [0, 0, 0, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 0, 0, 0, 0, 1, 0], [1, 1, 4, 0, 3, 1, 1, 0, 1, 1], [1, 4, 4, 3, 0, 3, 0, 0, 2, 1], [1, 4, 4, 0, 3, 0, 3, 0, 1, 1], [1, 1, 1, 1, 1, 1, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1, 1, 1, 1, 0] ],[ [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 1, 4, 4, 1, 0, 0], [0, 1, 1, 0, 4, 1, 1, 0], [0, 1, 0, 0, 3, 4, 1, 0], [1, 1, 0, 3, 0, 0, 1, 1], [1, 0, 0, 1, 3, 3, 0, 1], [1, 0, 0, 2, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1] ],[ [1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 1, 0, 0, 0, 1], [1, 0, 3, 4, 4, 3, 0, 1], [1, 2, 3, 4, 5, 0, 1, 1], [1, 0, 3, 4, 4, 3, 0, 1], [1, 0, 0, 1, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1] ],[ [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1, 0, 0, 3, 3, 0, 1, 0], [1, 1, 1, 1, 1, 1, 0, 3, 1, 0, 0, 1, 0], [1, 4, 4, 4, 1, 1, 1, 0, 1, 0, 0, 1, 1], [1, 4, 0, 0, 1, 0, 0, 3, 0, 1, 0, 0, 1], [1, 4, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 1], [1, 4, 0, 0, 1, 0, 0, 3, 0, 1, 0, 0, 1], [1, 4, 4, 4, 1, 1, 1, 0, 1, 0, 0, 1, 1], [1, 1, 1, 1, 1, 1, 0, 3, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1, 0, 2, 1, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0], ],[ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1], [1, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1], [1, 0, 4, 4, 4, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 4, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 5, 0, 0, 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ]] # 8*8单元格的像素图(用于像素动画模式) cell = [[ [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0] ],[ [1, 1, 1, 1, 7, 1, 1, 1], [1, 1, 1, 1, 7, 1, 1, 1], [7, 7, 7, 7, 7, 7, 7, 7], [1, 1, 7, 1, 1, 1, 1, 1], [1, 1, 7, 1, 1, 1, 1, 1], [7, 7, 7, 7, 7, 7, 7, 7], [1, 1, 1, 1, 7, 1, 1, 1], [1, 1, 1, 1, 7, 1, 1, 1] ],[ [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 1, 1, 2, 2, 1, 0, 0], [0, 0, 1, 2, 2, 1, 1, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0] ],[ [0, 0, 0, 0, 0, 0, 0, 0], [0, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 0, 0, 3, 3, 0], [0, 3, 0, 3, 3, 0, 3, 0], [0, 3, 0, 3, 3, 0, 3, 0], [0, 3, 3, 0, 0, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [0, 0, 0, 0, 0, 0, 0, 0] ],[ [4, 4, 4, 4, 4, 4, 4, 4], [4, 4, 4, 4, 4, 4, 4, 4], [4, 4, 4, 4, 4, 4, 4, 4], [4, 4, 4, 4, 4, 4, 4, 4], [4, 4, 4, 4, 4, 4, 4, 4], [4, 4, 4, 4, 4, 4, 4, 4], [4, 4, 4, 4, 4, 4, 4, 4], [4, 4, 4, 4, 4, 4, 4, 4] ],[ [4, 4, 4, 4, 4, 4, 4, 4], [4, 5, 5, 5, 5, 5, 5, 4], [4, 5, 5, 4, 4, 5, 5, 4], [4, 5, 4, 5, 5, 4, 5, 4], [4, 5, 4, 5, 5, 4, 5, 4], [4, 5, 5, 4, 4, 5, 5, 4], [4, 5, 5, 5, 5, 5, 5, 4], [4, 4, 4, 4, 4, 4, 4, 4] ],[ [4, 4, 4, 4, 4, 4, 4, 4], [4, 4, 1, 1, 1, 1, 4, 4], [4, 1, 1, 2, 2, 1, 4, 4], [4, 4, 1, 2, 2, 1, 1, 4], [4, 4, 1, 1, 1, 1, 4, 4], [4, 4, 4, 1, 1, 4, 4, 4], [4, 1, 1, 4, 4, 4, 4, 4], [4, 4, 4, 4, 4, 4, 4, 4] ]] # 人物在空地上的动画效果(用于像素动画模式) pe2 = [[ [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 1, 1, 2, 2, 1, 0, 0], [0, 0, 1, 2, 2, 1, 1, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0] ],[ [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 1, 5, 5, 1, 1, 0], [0, 1, 1, 5, 5, 1, 0, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0] ]] # 人物在终点上的动画效果(用于像素动画模式) pe6 = [[ [4, 4, 4, 4, 4, 4, 4, 4], [4, 4, 1, 1, 1, 1, 4, 4], [4, 1, 1, 2, 2, 1, 4, 4], [4, 4, 1, 2, 2, 1, 1, 4], [4, 4, 1, 1, 1, 1, 4, 4], [4, 4, 4, 1, 1, 4, 4, 4], [4, 1, 1, 4, 4, 4, 4, 4], [4, 4, 4, 4, 4, 4, 4, 4] ],[ [4, 4, 4, 4, 4, 4, 4, 4], [4, 4, 1, 1, 1, 1, 4, 4], [4, 4, 1, 5, 5, 1, 1, 4], [4, 1, 1, 5, 5, 1, 4, 4], [4, 4, 1, 1, 1, 1, 4, 4], [4, 4, 4, 1, 1, 4, 4, 4], [4, 4, 4, 4, 4, 1, 1, 4], [4, 4, 4, 4, 4, 4, 4, 4] ]] class Boxman(): """ 推箱子游戏 """ def __init__(self, game_stage): """ 游戏参数设置 """ self.p = 8 # 小方格内一行像素的数量(用于像素动画模式) self.s = 6 # 单像素的宽度(用于像素动画模式) self.game_stage = game_stage # 游戏关卡 self.canvas_bg = '#d7d7d7' # 游戏背景色 self.cell_size = self.p * self.s # 方格单元格大小 self.cell_gap = 1 # 方格间距 self.frame_x = 25 # 左右边距 self.frame_y = 25 # 上下边距 self.max_cells = 10 # 游戏画面长宽最大单元格数 self.win_w_plus = 220 # 窗口右边额外多出的宽度 self.big_map = 0 # 判断当前地图是否是超出窗口大小。1为是,0为不是 # 根据地图自动调整窗口大小 self.canvas_w = len(game_map[self.game_stage-1][0]) * self.cell_size + self.frame_x*2 self.canvas_h = len(game_map[self.game_stage-1] ) * self.cell_size + self.frame_y*2 self.color_dict = {0: 'white', # 0表示空白 1:'#808080', # 1表示墙 2: 'yellow', # 2表示空地上的人 3: 'green', # 3表示空地上的箱子 4: 'pink', # 4表示终点 5: 'red', # 5表示终点上的的箱子 6:'#ffa579', # 6表示在终点上的人 7:'#d7d7d7' # 额外的颜色(用于像素动画模式) } # 若地图过大,窗口则根据max_cells值来设定大小 if len(game_map[self.game_stage-1][0]) > self.max_cells: self.big_map = 1 self.canvas_w = self.cell_size*self.max_cells + self.frame_x*2 if len(game_map[self.game_stage-1]) > self.max_cells: self.big_map = 1 self.canvas_h = self.cell_size*self.max_cells + self.frame_y*2 self.win_w_size = self.canvas_w + self.win_w_plus self.win_h_size = self.canvas_h def create_pixel_map(self): """ 创建像素版的地图 """ global pixel_map p = self.p # 小方格内一行像素的数量 gy = len(game_map[self.game_stage-1]) gx = len(game_map[self.game_stage-1][0]) pixel_map = [] for i in range(0,gy*p): pixel_map.append([]) for i in range(0,gy*p): for j in range(0,gx*p): pixel_map[i].append(j) pixel_map[i][j] = 0 # 生成一个全是0的空数列 def load_pixel_map(self): """ 加载像素版地图 """ global pixel_map p = self.p gy = len(game_map[self.game_stage-1]) gx = len(game_map[self.game_stage-1][0]) for a in range(0,gy): for b in range(0,gx): for y in range(0,p): for x in range(0,p): pixel_map[y + p*a][x + p*b] = cell[game_map[self.game_stage-1][a][b]][y][x] def create_canvas(self): """ 创建canvas """ global canvas canvas = tk.Canvas(window, bg=self.canvas_bg, height=self.canvas_h, width=self.canvas_w, highlightthickness = 0) def create_pixel_cells(self,a,b): # a,b值为偏差值,若地图大于窗口的话,用于调节起始坐标 """ 创建像素版的单元格 """ p = self.p # 小方格内一行像素的数量 s = self.s # 单像素的宽度 gy = len(game_map[self.game_stage-1]) gx = len(game_map[self.game_stage-1][0]) for y in range(0,gy*p-b): for x in range(0,gx*p-a): if x % p != 0: xp = 0 else: xp = self.cell_gap # 每8个单元格间隔一个gap(p = 8) if y % p != 0: yp = 0 else: yp = self.cell_gap x1 = self.frame_x + s * x + xp y1 = self.frame_y + s * y + yp x2 = self.frame_x + s * (x+1) y2 = self.frame_y + s * (y+1) canvas.itemconfig(canvas.create_rectangle(x1,y1,x2,y2, fill = self.color_dict[pixel_map[y+b][x+a]], outline = self.canvas_bg, width = 0), fill = self.color_dict[pixel_map[y+b][x+a]]) canvas.place(x=0,y=0) def load_pixel_boxman(self,boxman_y,boxman_x): """ 加载像素版人物 """ p = self.p for y in range(0,p): for x in range(0,p): pixel_map[y + p*(boxman_y)][x + p*boxman_x] = cell[game_map[self.game_stage-1][boxman_y][boxman_x]][y][x] def pixel_gif(self): """ 像素动画效果 """ # 2张像素图反复切换做出动画效果 global p_gif p_gif = p_gif + 1 if p_gif > 1: p_gif = 0 cell[2] = pe2[p_gif] cell[6] = pe6[p_gif] Boxman(self.game_stage).load_pixel_boxman(boxman_y + 0,boxman_x + 0) def game_loop(self): """ 刷新游戏画面 """ global loop if remake == 0: # 简易模式 pass # 静止画面不需要自动刷新 elif remake == 1: # 图片模式 canvas.delete('all') # 清除canvas,不清除的话时间久了有BUG Boxman(self.game_stage).create_pic_cells(px,py) else: # 像素模式 canvas.delete('all') # 清除canvas Boxman(self.game_stage).pixel_gif() Boxman(self.game_stage).create_pixel_cells(px*self.p,py*self.p) loop = window.after(100, Boxman(self.game_stage).game_loop) def window_center(self,window,w_size,h_size): """ 窗口居中 """ screenWidth = window.winfo_screenwidth() # 获取显示区域的宽度 screenHeight = window.winfo_screenheight() # 获取显示区域的高度 left = (screenWidth - w_size) // 2 top = (screenHeight - h_size) // 2 window.geometry("%dx%d+%d+%d" % (w_size, h_size, left, top)) def create_game_cells(self,a,b): # a,b值为偏差值,若地图大于窗口的话,用于调节起始坐标 """ 创建初始版的游戏单元格 """ # 通过game_map列表,对应字典color_dict里的颜色画出地图 for y in range(0,len(game_map[self.game_stage-1])-b): for x in range(0,len(game_map[self.game_stage-1][0])-a): canvas.itemconfig(canvas.create_rectangle( self.frame_x + self.cell_size * x + self.cell_gap, self.frame_y + self.cell_size * y + self.cell_gap, self.frame_x + self.cell_size * (x+1), self.frame_y + self.cell_size * (y+1), fill = self.color_dict[game_map[self.game_stage-1][y+b][x+a]], outline = self.canvas_bg, width = 0), fill = self.color_dict[game_map[self.game_stage-1][y+b][x+a]]) canvas.place(x=0,y=0) def create_pic_cells(self,a,b): """ 创建图片模式的单元格 """ global man_gif,pic_dict man_2 = ["2.png","22.png"] # 两张空地上人的图片 man_6 = ["6.png","66.png"] # 两张终点上人的图片 man_gif = man_gif + 1 if man_gif > 1: man_gif = 0 man_2[0] = man_2[man_gif] man_6[0] = man_6[man_gif] pic_dict = {0: tk.PhotoImage(file = '0.png'), 1: tk.PhotoImage(file = '1.png'), 2: tk.PhotoImage(file = man_2[0]), 3: tk.PhotoImage(file = '3.png'), 4: tk.PhotoImage(file = '4.png'), 5: tk.PhotoImage(file = '5.png'), 6: tk.PhotoImage(file = man_6[0]) } for y in range(0,len(game_map[self.game_stage-1])-b): for x in range(0,len(game_map[self.game_stage-1][0])-a): n = game_map[self.game_stage-1][y+b][x+a] # 单元格的值 canvas.create_image(self.frame_x*2 + self.cell_size*x + x*self.cell_gap, self.frame_y*2 + self.cell_size*y + y*self.cell_gap, image = pic_dict[n]) canvas.place(x=0,y=0) def boxman_xy(self): """ 获取人物坐标 """ global boxman_x, boxman_y xy = [] for i in range(0,len(game_map[self.game_stage-1])): try: # 查找数值为2的坐标,没有就返回0。为防止人物出现在0列,先加上1,最后再减去。 x = game_map[self.game_stage-1][i].index(2) + 1 except: x = 0 xy.append(x) boxman_x = max(xy) boxman_y = xy.index(boxman_x) boxman_x = boxman_x - 1 # 之前加1,现在减回 if boxman_x == -1: # 没有数值为2,说明人物在终点,即数值等于6 xy = [] for i in range(0,len(game_map[self.game_stage-1])): try: x = game_map[self.game_stage-1][i].index(6) + 1 except: x = 0 xy.append(x) boxman_x = max(xy) boxman_y = xy.index(boxman_x) boxman_x = boxman_x - 1 def move_action(self,event): """ 按键控制 """ def Boxman_move(event,key,x,y): """ 操控人物 """ # 0表示空白, 1表示墙, 2表示人, 3表示箱子, 4表示终点, 5表示已完成的箱子, 6表示在终点上的人 def operation_forward_none(f1,f2,f3,f4,f5): """ 前方是空地或是终点 """ if game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1] == f1: ### 前方是空地或是终点 if game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0] == 2: # 人站在空地上 game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0] = f2 ### 人离开后是空地 game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1] = f3 ### 前方是空地或是终点 else: # 人站在终点上 game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0] = f4 ### 身后是终点 game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1] = f5 ### 前方是空地或是终点 def operation_forward_box(f1,f2,f3,f4,f5,f6,f7): """ 前方是空地上的箱子或是已完成的箱子 """ if game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1] == f1: ### 前方是空地上的箱子或是已完成的箱子 if game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0] == 2: # 人站在空地 if game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2] == f2: ### 箱子的前方是空地或终点 game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0] = 0 # 人离开后是空地 game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1] = f3 ### 前方是空地或是终点 game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2] = f4 ### 前方是空地上的箱子或是已完成的箱子 else: ### 人站在终点上 if game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2] == f5: ### 箱子的前方是空地或是终点 game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0] = 4 # 身后是终点 game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1] = f6 ### 前方是空地或是终点 game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2] = f7 ### 箱子的前方是空地或是终点 if remake == 2: Boxman(self.game_stage).load_pixel_boxman(boxman_y + y,boxman_x + x) direction = event.keysym if(direction == key): operation_forward_none(0,0,2,4,2) operation_forward_none(4,0,6,4,6) operation_forward_box(3,0,2,3,0,2,3) operation_forward_box(3,4,2,5,4,2,5) operation_forward_box(5,0,6,3,0,6,3) operation_forward_box(5,4,6,5,4,6,5) Boxman(self.game_stage).boxman_xy() # 刷新坐标 temp = [] # 记录每步的操作,供撤消使用。 temp.append(boxman_y) # 保存XY轴坐标值,即人物所在单元格的坐标 temp.append(boxman_x) # 后面6个分别是中,上,下,左,右和前方单元格的值 temp.append(game_map[self.game_stage-1][boxman_y + 0][boxman_x + 0]) temp.append(game_map[self.game_stage-1][boxman_y - 1][boxman_x + 0]) temp.append(game_map[self.game_stage-1][boxman_y + 1][boxman_x + 0]) temp.append(game_map[self.game_stage-1][boxman_y + 0][boxman_x - 1]) temp.append(game_map[self.game_stage-1][boxman_y + 0][boxman_x + 1]) temp.append(game_map[self.game_stage-1][boxman_y + y][boxman_x + x]) record_list.append(temp) if len(record_list) > 1: if record_list[-1] == record_list[-2]: del record_list[-1] # 删除连续相同的数据 def game_select_stage(event,key): """ 返回游戏主页面 """ global game_map direction = event.keysym if(direction == key): game_map = copy.deepcopy(backup_map) window.after_cancel(loop) window.destroy() # 窗口关闭并启动起始窗口 Boxman(self.game_stage).index_game() def reset_stage(event,key): """ 重置关卡 """ global game_map direction = event.keysym if(direction == key): del record_list[:] game_map = copy.deepcopy(backup_map) Boxman(self.game_stage).boxman_xy() # 重置大地图时需要重新定位人物坐标 Boxman(self.game_stage).big_map_check() if remake == 2: Boxman(self.game_stage).load_pixel_map() # 像素模式下重新装载下地图 def to_stage(event,key,stage): # 直接按数字键选关 """ 选择游戏关卡 """ global game_map direction = event.keysym if(direction == key): del record_list[:] game_map = copy.deepcopy(backup_map) window.after_cancel(loop) window.destroy() Boxman(stage).window_open() def restore_stage(event,key): """ 撤销功能 """ direction = event.keysym if(direction == key): def restore(): """ 撤销步骤的函数 """ m = game_map[self.game_stage-1] before_forward = 0 # 之前所面对的(0是临时值) before_stand = record_list[-2][2] # 之前所站的单元格的值 now_stand = record_list[-1][2] # 当前所站的单元格的值 now_forward = record_list[-1][7] # 当前所面对的单元格的值 before_x = record_list[-2][1] # 之前所站的X轴坐标 before_y = record_list[-2][0] # 之前所站的Y轴坐标 now_x = record_list[-1][1] # 当前所站的X轴坐标 now_y = record_list[-1][0] # 当前所站的Y轴坐标 b_up = record_list[-2][3] # 之前上方单元格的值 b_dw = record_list[-2][4] # 之前下方单元格的值 b_lf = record_list[-2][5] # 之前左方单元格的值 b_rg = record_list[-2][6] # 之前右方单元格的值 # 推断出之前所面对的单元格的值 if before_x > now_x: next_x = now_x - 1 before_forward = b_lf elif before_x < now_x: next_x = now_x + 1 before_forward = b_rg else: next_x = now_x if before_y > now_y: next_y = now_y - 1 before_forward = b_up elif before_y < now_y: next_y = now_y + 1 before_forward = b_dw else: next_y = now_y # 0表示空白, 1表示墙, 2表示人, 3表示箱子, 4表示终点, 5表示已完成的箱子, 6表示在终点上的人 m[before_y][before_x] = before_stand # 人退回之前的状态,2或者6 m[ now_y][ now_x] = before_forward # 推断出当前面对的单元格的值 if before_forward == 3: if now_forward == 3: if now_stand == 2: m[next_y][next_x] = 0 elif now_stand == 6: m[next_y][next_x] = 0 elif now_forward == 5: if now_stand == 2: m[next_y][next_x] = 4 elif now_stand == 6: m[next_y][next_x] = 0 elif before_forward == 5: if now_forward == 3: if now_stand == 2: m[next_y][next_x] = 0 elif now_stand == 6: m[next_y][next_x] = 0 elif now_forward == 5: if now_stand == 2: m[next_y][next_x] = 0 elif now_stand == 6: m[next_y][next_x] = 4 restore() del record_list[-1] # 每撤消一步就删除最后一组列表 if remake == 2: Boxman(self.game_stage).load_pixel_map() # 像素模式下重新装载下地图 def game_pass(): # 通关条件为箱子数为0 """ 获取箱子数量,等于0的话过关 """ xy = [] for i in range(0,len(game_map[self.game_stage-1])): x = game_map[self.game_stage-1][i].count(3) xy.append(x) box_number = sum(xy) if box_number == 0: pass_win() def pass_win(): """ 箱子为零时,显示过关窗口 """ global game_map showinfo('恭喜过关','返回首页') window.destroy() game_map = copy.deepcopy(backup_map) Boxman(0).index_game() def move_map_ws(event,key,a,b): """ 若是大地图,则上下移动地图 """ global py direction = event.keysym if(direction == key): my = len(game_map[self.game_stage-1]) if self.big_map == 1: if boxman_y >= int(self.max_cells//2) + a: if boxman_y < my - int(self.max_cells//2): if game_map[self.game_stage-1][boxman_y + b*1][boxman_x] not in [1] and \ game_map[self.game_stage-1][boxman_y + b*2][boxman_x] not in [1,3,5]: py = boxman_y - int(self.max_cells//2) + b elif boxman_y >= my - int(self.max_cells//2): py = my - self.max_cells else: py = 0 else: py = 0 def move_map_ad(event,key,a,b): """ 若是大地图,则左右移动地图 """ global px direction = event.keysym if(direction == key): mx = len(game_map[self.game_stage-1][0]) if self.big_map == 1: if boxman_x >= int(self.max_cells/2) + a: if boxman_x < mx - int(self.max_cells//2): if game_map[self.game_stage-1][boxman_y][boxman_x + b*1] not in [1] and \ game_map[self.game_stage-1][boxman_y][boxman_x + b*2] not in [1,3,5] : px = boxman_x - int(self.max_cells//2) + b elif boxman_x >= mx - int(self.max_cells//2): px = mx - self.max_cells else: px = 0 else: px = 0 def change_map(event,key): """ 三个游戏版本的切换 """ # 版本切换 0为原始模式,1为图片模式,2为像素动画模式 global remake direction = event.keysym if(direction == key): window.after_cancel(loop) window.destroy() remake = remake + 1 if remake > 2: remake = 0 Boxman(self.game_stage).window_open() change_map(event, '0') move_map_ws(event, 'w', 1, -1) move_map_ws(event, 's', 0, 1) move_map_ad(event, 'a', 1, -1) move_map_ad(event, 'd', 0, 1) move_map_ws(event, 'W', 1, -1) move_map_ws(event, 'S', 0, 1) move_map_ad(event, 'A', 1, -1) move_map_ad(event, 'D', 0, 1) move_map_ws(event, 'Up', 1, -1) move_map_ws(event, 'Down', 0, 1) move_map_ad(event, 'Left', 1, -1) move_map_ad(event,'Right', 0, 1) Boxman_move(event, 'w', 0, -1) Boxman_move(event, 's', 0, 1) Boxman_move(event, 'a', -1, 0) Boxman_move(event, 'd', 1, 0) Boxman_move(event, 'W', 0, -1) Boxman_move(event, 'S', 0, 1) Boxman_move(event, 'A', -1, 0) Boxman_move(event, 'D', 1, 0) Boxman_move(event, 'Up', 0, -1) Boxman_move(event, 'Down', 0, 1) Boxman_move(event, 'Left', -1, 0) Boxman_move(event,'Right', 1, 0) game_select_stage(event, 'p') # 返回主页面 game_select_stage(event, 'P') reset_stage(event, 'j') # 重置该关卡 reset_stage(event, 'J') to_stage(event, '1', 1) to_stage(event, '2', 2) to_stage(event, '3', 3) to_stage(event, '4', 4) to_stage(event, '5', 5) to_stage(event, '6', 6) if len(record_list) <= 1: reset_stage(event, 'r') reset_stage(event, 'R') else: restore_stage(event, 'r') restore_stage(event, 'R') move_map_ws(event, 'r', 0, 0) move_map_ad(event, 'r', 0, 0) move_map_ws(event, 'R', 0, 0) move_map_ad(event, 'R', 0, 0) if remake == 0: Boxman(self.game_stage).create_canvas() # 不刷新的话在大地图中有小BUG canvas.delete('all') Boxman(self.game_stage).create_game_cells(px,py) Boxman(self.game_stage).boxman_xy() # 重新刷新人物坐标,不然会有BUG game_pass() def big_map_check(self): """ 大地图判断 """ global px,py # 如果是大地图,更换起始坐标,以人物为中心建立地图 mx = len(game_map[self.game_stage-1][0]) my = len(game_map[self.game_stage-1]) if self.big_map == 1: if boxman_x > int(self.max_cells//2): if boxman_x < mx - int(self.max_cells//2): px = boxman_x - int(self.max_cells//2) elif boxman_x >= mx - int(self.max_cells//2): px = mx - self.max_cells else: px = 0 else: px = 0 if self.big_map == 1: if boxman_y > int(self.max_cells//2): if boxman_y < my - int(self.max_cells//2): py = boxman_y - int(self.max_cells//2) elif boxman_y >= my - int(self.max_cells//2): py = my - self.max_cells else: py = 0 else: py = 0 def window_open(self): """ 开启游戏窗口 """ global window,txt_lable,px,py,p_gif,man_gif window = tk.Tk() window.focus_force() window.title('Boxman') Boxman(self.game_stage).window_center(window,self.win_w_size,self.win_h_size) p_gif = 0 man_gif = 0 if self.game_stage == 0: # 若等于0,代表是最后一关 n = len(game_map) else: n = self.game_stage mode = { 0:'简易模式', 1:'图片模式', 2:'像素模式'} txt_lable = tk.Label(window, text= mode[remake] +"\n" +"\n当前为第" + str(n) + "关" +"\n白色单元格为空地" +"\n灰色单元格为墙" +"\n黄色单元格为打工人" +"\n绿色单元格为箱子" +"\n粉色单元格为终点" +"\n红色单元格为已完成的箱子" +"\n橘色单元格为站在终点上的人" +"\n" +"\n字母ADSW或方向键移动" +"\n字母键P返回主页面" +"\n字母键J重置本关" +"\n字母键R为撤消" +"\n数字键0为切换游戏界面模式" +"\n数字键1~6直接选择关卡" +"\n(第六关为测试关)" +"\n" +"\n" +"\nBy Juni Zhu" +"\n微信: znix1116" +"\n2022.06.08" , font=('Arial', 11),anchor="ne", justify="left") txt_lable.pack(side='right') Boxman(self.game_stage).boxman_xy() Boxman(self.game_stage).big_map_check() Boxman(self.game_stage).create_canvas() if remake == 0: Boxman(self.game_stage).create_game_cells(px,py) elif remake == 1: Boxman(self.game_stage).create_pic_cells(px,py) else: Boxman(self.game_stage).create_pixel_map() Boxman(self.game_stage).load_pixel_map() Boxman(self.game_stage).create_pixel_cells(px*self.p,py*self.p) window.bind('<Key>', Boxman(self.game_stage).move_action) Boxman(self.game_stage).game_loop() def close_w(): window.after_cancel(loop) window.destroy() window.protocol('WM_DELETE_WINDOW', close_w) window.mainloop() def run_game(self): """ Run Game """ global backup_map,record_list record_list = [] # 记录每步的操作,供撤消用 backup_map = [] # 备份地图,供恢复用 backup_map = copy.deepcopy(game_map) # 备份地图,需要用深度复制 Boxman(self.game_stage).window_open() def index_game(self): """ 起始界面 """ # 窗口大小会根据按键的数量自动调整 for i in range(1,len(game_map)+1): # 批量生成函数,to_game_1,2,3,4,5... exec("def to_game_" + str(i) + "():\n\ index_win.destroy()\n\ Boxman(" + str(i) + ").run_game()") global index_win index_win = tk.Tk() index_win.focus_force() index_win.title('Welcome') Boxman(self.game_stage).window_center(index_win,200,(len(game_map)+1)*30) for i in range(1,len(game_map)+1): # 批量添加按键 exec("b" + str(i) + " = tk.Button(index_win, text='" + str(i) + "', font=('Arial', 12), width=10, height=1, command=to_game_" + str(i) + ")") exec("b" + str(i) + ".pack()") index_win.mainloop() if __name__ == '__main__': Boxman(0).index_game()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。