温馨提示
本教程源码素材已更新至公众号菜单栏【源码素材】
如果文章对您有一点点帮助,点
、点
,万分感谢
推箱子程序能跑起来,靠的不是什么高深的技术,就是把几个基本模块拼在一起:窗口怎么开、图片怎么加载、角色类怎么写、关卡怎么加载、移动怎么判断、键盘怎么绑定。听着多,代码其实不长,今天一段段讲清楚。
窗口设置
开头这段是固定套路,做 turtle 项目基本都要写:
ms = turtle.Screen()ms.setup(650, 650, 200, 0)ms.title('推箱子小游戏')ms.bgcolor("yellow")
Screen() 创建窗口,setup(宽, 高, x, y) 设置窗口大小和打开位置,这里是 650×650 像素。bgcolor("yellow") 把背景设成黄色。
接下来注册四张图片,turtle 只认 gif 格式:
ms.register_shape('wall.gif') # 墙ms.register_shape('o.gif') # 目标点ms.register_shape('p.gif') # 小人ms.register_shape('boxc.gif') # 箱子(推到目标点后显示的那张)
最后一行 ms.tracer(0) 关掉 turtle 的自动刷新,改成手动控制,画完再一次性显示出来。这个技巧在 turtle 动画里很常见,不开的话画面会一格一格跳。
Pen 类:角色的模板
class Pen(turtle.Turtle): def __init__(self, pic): super().__init__() self.shape(pic) self.penup()
所有角色(墙、目标点、小人、箱子)都用同一个类来创建。继承 turtle.Turtle 是为了直接拿画笔的功能,绑定图片用 shape(),然后 penup() 抬笔——抬笔之后 goto() 移动才不会画线。
move():移动判断的核心
这是整个程序最核心的部分,也是最难理解的。小人按方向键移动,到底能不能走,要分三种情况判断:
def move(self, x, gx, y, gy): gox = x + gx goy = y + gy has_box = False if (gox, goy) in list_space: for i in list_box: if i.pos() == (gox, goy): has_box = True gox2 = x + gx * 2 goy2 = y + gy * 2 if (gox2, goy2) in list_space: self.goto(gox, goy) i.goto(gox2, goy2) if has_box == False: self.goto(gox, goy)
第一步:算目标格子坐标。当前位置 (x, y) 加上方向增量 (gx, gy),得到 (gox, goy)。
第二步:目标格子在不在可通行列表里。不在的话就是撞墙,谁都不动。
第三步:目标格子上有没有箱子。遍历 list_box 里所有箱子,如果某个箱子的坐标等于目标格子,说明前面有箱子。
第四步:箱子前面还有没有空位。算箱子要去的格子 (gox2, goy2),就是当前位置再加一格。如果这个格子也在可通行列表里,就小人往前走一格、箱子被推到前面一格。
第五步:目标格子上没有箱子,直接走过去。
四个方向方法的区别只在于增量参数不同:
def go_up(self): self.move(self.xcor(), +0, self.ycor(), +50)def go_down(self): self.move(self.xcor(), +0, self.ycor(), -50)def go_left(self): self.move(self.xcor(), -50, self.ycor(), +0)def go_right(self): self.move(self.xcor(), +50, self.ycor(), +0)
往哪个方向走,就在对应的坐标上加或减 50(每个格子的像素宽度)。
Game 类:关卡加载
class Game(): def show(self): for i in range(8): for j in range(8): x = -175 + j * 50 y = 175 - i * 50 if level_1[i][j] == " ": list_space.append((x, y)) elif level_1[i][j] == 'X': wall.goto(x, y) wall.stamp() elif level_1[i][j] == 'O': list_space.append((x, y)) box.goto(x, y) box.stamp() elif level_1[i][j] == 'P': player.goto(x, y) list_space.append((x, y)) elif level_1[i][j] == 'B': box1 = Pen('boxc.gif') box1.goto(x, y) list_space.append((x, y)) list_box.append(box1)
遍历 8×8 的地图字符串列表,根据每个字符决定画什么。其中两件事是同步做的:
一是盖印章,wall.stamp() 和 box.stamp() 画墙和目标点。它们只画不动,用 stamp 省事。
二是记录可通行坐标,所有空地、目标点、小人位置、箱子初始位置都加进 list_space。这样后面小人走动时就能查这个列表判断能不能走。
遇到箱子字符 B 时,除了画箱子,还要把箱子对象本身加进 list_box,因为推箱子时要遍历这个列表找到箱子再让它移动。
关卡地图
level_1 = [ 'CCXXXCCC', 'CCXOXCCC', 'CCX XXXX', 'XXXB BOX', 'XO BPXXX', 'XXXXBXCC', 'CCCXOXCC', 'CCCXXXCC']
字符含义:
X 墙
O 目标点(箱子要去的地方)
B 箱子
P 小人
空格 游戏里的空地,可以走过去
C 边角背景色,不算空地
用字符来表示地图是个很实用的技巧,不需要写一堆坐标数据,直接看字符串就能想象出地图长什么样。
对象创建和主循环
list_box = []list_space = []wall = Pen('wall.gif')box = Pen('o.gif')player = Pen('p.gif')game = Game()game.show()ms.listen()ms.onkey(player.go_up, "Up")ms.onkey(player.go_left, "Left")ms.onkey(player.go_down, "Down")ms.onkey(player.go_right, "Right")while True: ms.update()ms.mainloop()
list_box 和 list_space 在创建角色之前要先定义成空列表,因为 Game.show() 里会往这两个列表里添加内容。
ms.listen() 让窗口开始监听键盘输入,onkey() 把四个方向函数绑定到方向键上。
最后 while 循环不断调用 update(),保证画面持续刷新、键盘响应一直在线。程序会一直运行,直到关闭窗口。
常见问题
运行后图片找不到
图片文件要和 Python 文件放在同一个文件夹里,从那个文件夹里双击运行,不要在其他目录调用。
方向键按了没反应
先在窗口里点一下鼠标,让窗口获得焦点,再按方向键。
推过去的箱子还能穿过去
list_space 加载时只记了一次,箱子推走后没有实时更新列表。完整版需要在每次推箱子成功后,把旧坐标从列表移除、把新坐标对应的逻辑改掉。先跑通,再优化。
推箱子这个程序,把基本模块拼在一起就能跑。把代码自己敲一遍,改一改关卡地图,再试着自己加个步数统计或者过关提示,就算真正掌握了。
【参考讲义】