今天继续:用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()