这个也是和弹球游戏一样的环境,python3+tkinter,所以环境配置可以参考前一篇。火柴人比前一篇就是多了个一个动画效果,已经更加麻烦的体积碰撞,还有上升下落的物理模拟,还有一个就是游戏成功的检测。
github连接在这:https://github.com/finalObject/stickman
动画效果
不像之前的弹球游戏,只有长方形和圆心,直接在程序中绘制就行,这个火柴人里的一些元素图片都需要自己提前准备的。然后image里就是自己绘制的元素,有点简陋。
承载小人的平台是设定之后就不会变更了的,但是小人和门是存在一个动画效果的。不过门的动画比较特殊,只有在游戏结束的时候才会触发一次,所以比较简单。而小人是需要不断变换图像,从而显示出奔跑状态的。
下面就是小人的动画函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
def animate(self): if self.x != 0 and self.y ==0: #每0.1s更换一下图片,刷新自身图片 #不把刷新做在整体的update里面,可以让不同精灵拥有不同的刷新频率 if time.time() - self.last_time >0.1: self.last_time = time.time() self.current_image += self.current_image_add #图片更换的顺序为1->2->3->2->1->2...这样连贯一点 if self.current_image >=2: self.current_image_add =-1 if self.current_image <=0: self.current_image_add =1 # 如果速度方向向左 if self.x<0: # 但是如果在起跳状态下,直接是固定的起跳图片 if self.y!=0: self.game.canvas.itemconfig(self.image,image=self.images_left[2]) # 不是的话,那就在朝左的图片中依次变换 else: self.game.canvas.itemconfig(self.image,image=self.images_left[self.current_image]) # 和速度向左同理 elif self.x>0: if self.y!=0: self.game.canvas.itemconfig(self.image,image=self.images_right[2]) else: self.game.canvas.itemconfig(self.image,image=self.images_right[self.current_image]) |
游戏中一个对应的元素(Sprite对象),在画布中创建图像时,会返回一个对象,然后就这个对象储存在self.image中。
1 |
self.image = game.canvas.create_image(x,y,image = self.photo_image1,anchor='nw') |
如果需要更改图像,需要
1 |
self.game.canvas.itemconfig(self.image,image=self.photo_image2) |
如果直接重新创建,会导致原画和新画都出现在屏幕上,而使用itemconfig函数,可以抹出原画,只留下新画。
体积碰撞
体积碰撞基础函数写在coords.py里,包含左右上下坐标是否重叠的检测,在class StickSprite的move()函数里,就基于简单的检测,实现了和画布边缘以及其他Sprite的碰撞检测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
def move(self): #首先根据现有状态更新动画,可以放在开头,也可以放在函数最后 self.animate() # 如果小人处于上升状态,那么会不停的计数,等到一定时间之后,小人的速度会向下,模拟重力嘛,但是速度不是连续变化的 if self.y <0: self.jump_count += 1 if self.jump_count >20: self.y =4 # 下面这个判断之前代码的这么写的,但是今天写备注的时候发现没啥意义,注释之后同样正常工作 # if self.y >0: # self.jump_count -= 1 # 获取自身坐标,为接下来的碰撞检测做准备 co = self.coords() # 四个方向是否可以继续走! # 为True表示这个方向没有碰到边界,为False表示触碰到了边界,可以是画布边界,也可以是其他精灵的边界 left = True right = True top = True bottom = True # 是否正在下落 falling = True # 下面四个if是用来判断和画布边缘碰撞的 if self.y >0 and co.y2 >=self.game.canvas_height: self.y = 0 bottom = False elif self.y <0 and co.y1 <=0: self.y=0 top = False if self.x >0 and co.x2 >= self.game.canvas_width: self.x =0 right = False elif self.x <0 and co.x1 <=0: self.x =0 left = False # for循环开始判断和其他精灵的碰撞 for sprite in self.game.sprites: if sprite == self: continue sprite_co = sprite.coords() if top and self.y<0 and collided_top(co,sprite_co): self.y=-self.y top=False if bottom and self.y >0 and collided_bottom(self.y,co,sprite_co): self.y=sprite_co.y1-co.y2 if self.y<0: self.y=0 bottom=False top=False if bottom and falling and self.y ==0 \ and co.y2<self.game.canvas_height \ and collided_bottom(1,co,sprite_co): falling = False if left and self.x <0 and collided_left(co,sprite_co): self.x=0 left = False # 这个是判断游戏标志,如果碰触到了endgame为True的,running就会呗设置为False,小人就不能跑了,认为游戏结束。同时,对应的这个精灵,其实就是门,也需要执行changeEndImage,修改为开门状态。 if sprite.endgame: self.game.running = False sprite.changeEndImage() if right and self.x >0 and collided_right(co,sprite_co): self.x=0 right = False if falling and bottom and self.y ==0 \ and co.y2 < self.game.canvas_height: self.y=4 # 根据速度移动位置 self.game.canvas.move(self.image,self.x,self.y) |
物理模拟
主要就是一个小人跳跃过程中的模拟,这里其实很简单地对跳跃进行计时,到达一定时间后就将速度朝小。比较机械,只是很离散的模拟。
1 2 3 4 |
if self.y <0: self.jump_count += 1 if self.jump_count >20: self.y =4 |
然后在检测到碰撞后(上下左右的碰撞),会把对应方向速度置0。
另外,当小人没有产生向下的碰撞时,小人会产生朝下的速度。这样就解决了小人漂浮在空中的bug。
1 2 3 |
if falling and bottom and self.y ==0 \ and co.y2 < self.game.canvas_height: self.y=4 |
成功检测
在游戏初始化的时候,部分sprite的endgame属性为True,也就意味它有能力结束游戏,这个游戏里只有顶端的门有这个True。
1 2 3 4 5 6 7 |
if left and self.x <0 and collided_left(co,sprite_co): self.x=0 left = False # 这个是判断游戏标志,如果碰触到了endgame为True的,running就会呗设置为False,小人就不能跑了,认为游戏结束。同时,对应的这个精灵,其实就是门,也需要执行changeEndImage,修改为开门状态。 if sprite.endgame: self.game.running = False sprite.changeEndImage() |
如果小人和门发生了碰撞,就会将running设置位False,然后执行门的结束游戏图像修改的函数,其实就是把门的图像由关变成开。running在Game类的mainloop()函数里控制着整个游戏的进行,如果变成False,小人就不能移动。
1 2 3 4 5 6 7 8 9 |
# 运行的时候就执行这个主循环,只要running为True,小人就会不停移动下去 def mainloop(self): while 1: if self.running == True: for sprite in self.sprites: sprite.move() self.tk.update_idletasks() self.tk.update() time.sleep(0.01) |
之前尝试直接把while 1里面所有的东西放放入判断语句中,也就意味着如果running为False,连update和sleep函数都不会执行,但是这样做会导致程序卡死。
另外还有一个不足之处时,我的游戏检测只会在向左碰撞的函数里触发,因为这个场景下小人只可能从右往左抵达门,更加合理的方式,是应该保证发生碰撞一定会执行这个成功检测。
以上基本就是代码的全部内容了,代码已经贴的差不多了,再把全部代码贴上来会显得有点冗长,需要的请移步github。