原文在finalObject

这个也是和弹球游戏一样的环境,python3+tkinter,所以环境配置可以参考前一篇。火柴人比前一篇就是多了个一个动画效果,已经更加麻烦的体积碰撞,还有上升下落的物理模拟,还有一个就是游戏成功的检测。

github连接在这:https://github.com/finalObject/stickman

动画效果

不像之前的弹球游戏,只有长方形和圆心,直接在程序中绘制就行,这个火柴人里的一些元素图片都需要自己提前准备的。然后image里就是自己绘制的元素,有点简陋。

承载小人的平台是设定之后就不会变更了的,但是小人和门是存在一个动画效果的。不过门的动画比较特殊,只有在游戏结束的时候才会触发一次,所以比较简单。而小人是需要不断变换图像,从而显示出奔跑状态的。

下面就是小人的动画函数

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中。

self.image = game.canvas.create_image(x,y,image = self.photo_image1,anchor='nw')

如果需要更改图像,需要

self.game.canvas.itemconfig(self.image,image=self.photo_image2)

如果直接重新创建,会导致原画和新画都出现在屏幕上,而使用itemconfig函数,可以抹出原画,只留下新画。

体积碰撞

体积碰撞基础函数写在coords.py里,包含左右上下坐标是否重叠的检测,在class StickSprite的move()函数里,就基于简单的检测,实现了和画布边缘以及其他Sprite的碰撞检测。

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)

物理模拟

主要就是一个小人跳跃过程中的模拟,这里其实很简单地对跳跃进行计时,到达一定时间后就将速度朝小。比较机械,只是很离散的模拟。

if self.y <0:
    self.jump_count += 1
    if self.jump_count >20:
        self.y =4

然后在检测到碰撞后(上下左右的碰撞),会把对应方向速度置0。

另外,当小人没有产生向下的碰撞时,小人会产生朝下的速度。这样就解决了小人漂浮在空中的bug。

if falling and bottom and self.y ==0 \
   and co.y2 < self.game.canvas_height:
    self.y=4

成功检测

在游戏初始化的时候,部分sprite的endgame属性为True,也就意味它有能力结束游戏,这个游戏里只有顶端的门有这个True。

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,小人就不能移动。

# 运行的时候就执行这个主循环,只要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

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐