XuYangYu233
文章10
标签5
分类0
python技巧1

python技巧1

一点点python编程方面的小技巧,yield的小用法

1. 使用效果预览

gif_try2.gif

背景图片作者pixiv id=18075074,人物是b站2020拜年祭网页上弄下来的,gif没做好可能脸看着有点扁。。。

gif有点大,网页加载起来可能比较慢

2. 缘由

pygame是一款轻量级的游戏引擎,其主要目的就是制作一些看起来比较廉价的2d游戏,以供python新手练手,以至于在很多方面其并不完善。而对于大部分avg来说,渐变式的转场是其标配。pygame显然不会专门为此做一个接口,故这个问题需要开发者自己解决。

pygame由于其自身特点,对于透明度的支持都不太方便,因此我们对于其原生的显示透明度图片的函数还要再加一点东西打包成新函数:


def blit_alpha(target, source, location, opacity):
    x = location[0]
    y = location[1]
    temp = pygame.Surface((source.get_width(), source.get_height())).convert()
    temp.blit(target, (-x, -y))
    temp.blit(source, (0, 0))
    temp.set_alpha(opacity)
    target.blit(temp, location)

因为这不是重点,我们在此就不解释里面这些参数的意义了,此处的重点是这个函数是在一帧中绘制一个固定透明度的图片,想要形成开头那种渐变的效果,我们就需要随着帧数的增加缓慢增大或减小图片的透明度,而这正是问题的所在。

为了保证代码的可读性,我们需要把整个切换背景的过程写成一个函数或方法。而由于这个特性,“随着帧数进行缓慢变化”这个要点就会变得比较困难。举个例子,当游戏主循环接收到要切换背景的信号后,会调用切换背景的函数,而该函数内部有一个循环,用来不断调整透明度,而此时游戏主循环就会阻塞在这里。往抽象了说,就是要解决一个双循环同步进行的问题。

一般来说这个问题使用一个双线程会比较好解决,尤其是带线程锁的双线程。但pygame毕竟不是一般的游戏引擎,如果在这个地方使用双线程会出现显著掉帧,视觉上来说就是透明度变化的时候一卡一卡的,观感属实不行。

为了解决这个问题,我们就需要使用一个Python特性

3. 解决方案

先上代码:

def switch_bg_proc(screen, after, before):
    global ticks
    ticks = 0
    while ticks < 255:
        blit_alpha(screen, before, (0, 0), 260 - ticks)
        blit_alpha(screen, after, (0, 0), ticks)
        yield
    blit_alpha(screen, after, (0, 0), 256)
    ticks = 0

def chg_bg(screen, msg, before, sbp):
    global ticks, sbp_flag
    after = pygame.image.load('imgs/{}'.format(msg)).convert()
    if sbp_flag:
        ticks = 254
    else:
        sbp = switch_bg_proc(screen, after, before)
        sbp_flag = True

    return after, sbp

主循环部分:

    while True:
        for in_event in pygame.event.get():
            if in_event.type == pygame.QUIT:        # 退出
                pygame.display.quit()
                return
            elif in_event.type == pygame.MOUSEBUTTONDOWN:       # 左键点击
                if sbp_flag:            # 渐变转场过程中如果玩家按下鼠标左键,则快速转场完成
                    ticks = 254
                    continue

        # ----Snip---- 省略

                name, msg, game_event = read_dialog()       # 读取游戏事件
                if game_event == 0:                  # 判断游戏事件
                    chat.update(name, msg)
                elif game_event == -1:
                    pygame.event.post(pygame.event.Event(pygame.QUIT))
                elif game_event == 1:
                    pass
                elif game_event == 2:
                    background, sbp = chg_bg(screen, msg, background, sbp)

        # ----Snip---- 省略

        if sbp_flag:
            ticks += 5
            try:
                next(sbp)
            except StopIteration:
                sbp_flag = False
        else:
            blit_alpha(screen, background, (0, 0), 256)

        # ----Snip---- 省略

部分解释一下,ticks和sbp_flag是全局变量,前者用来记录透明度的变化,其随帧数而变化,后者是switch_bg_proc的缩写,用来判断是否处于背景切换的过程中。

可以看到,在switch_bg_proc函数的循环中存在一个yield,这使其变成了一个生成器,在chg_bg函数中“调用”了该函数,实际是产生了一个生成器对象。

简单来说,就是在读取到转场的信号的时候调用chg_bg函数,该函数根据传入的图片位置信息生成一个生成器对象sbp,将其返回至主函数,主函数在循环的过程中根据sbp_flag来判断是否进行转场,若是,则next(sbp),使switch_bg_proc函数内部的循环进行一轮,然后再次停在yield的地方,等待下一次的next(sbp),由此确保了其随主循环同步进行

4. 总结

平时写一些小的脚本的时候基本没碰过yield,当时学的时候也是一脸懵逼。不过由这个小例子可以了解到一些yield以及python生成器的妙用,即在涉及到两个循环同步进行的时候可以用这个方法来避免一些繁琐的多线程操作,这也再次提醒了我python和C/C++以及其它语言在一些细节上可能会有各自完全不相同的解决办法