赛博红兔的科技博客

CyberHongTu shares news, insights, and musings on fascinating technology subjects.


和我一起做3A游戏《归乡之路》(三)

大家好,我是赛博红兔。欢迎来到《和我一起做3A游戏》第三集!这个系列是对pygame这个Python的游戏库的所有主要功能的介绍。通过这个教程,你应该能掌握制作任何2D游戏的工具,今天我们继续来做《归乡之路》这个打字游戏。先来回顾一下上一集的内容,我们设计了小猫的动画音效和打字的核心玩法,还有分数统计和显示。主要是了解了用矩形绘画,精灵这个pygame核心的类,还有游戏事件的触发和监听。那么今天,我们就在这个代码基础上继续做游戏。我们最后要完成的任务是创建背景的花草树木Trees和家House的类,毕竟小猫还需要回家。最后还要加上游戏BGM一首轻松愉快的背景音乐。今天,我们最主要要学习的核心是处理精灵之间的碰撞。

Trees

class Trees(pg.sprite.Sprite):
    def __init__(self, treeType):
        super().__init__()
        # Load Tree Image
        file_path = TREE_TYPE.get(treeType)
        tree_image = pg.image.load(file_path).convert_alpha()
        if treeType not in ['grass_tree1', 'grass_tree2']:
            tree_image = pg.transform.scale(tree_image, (TREE_WIDTH, TREE_HEIGHT))
        self.image = tree_image
        self.rect = self.image.get_rect(midbottom=(randint(WIDTH, WIDTH * 2), GROUND_HEIGHT))

    def animation(self):
        self.rect.x -= MOVING_SPEED

    def destroy(self):
        if self.rect.x <= -WIDTH:
            self.kill()

    def update(self):
        self.animation()
        self.destroy()
  1. 构造函数 (__init__):
    • super().__init__() 调用父类的构造函数,为创建的树实例初始化所有的pygame精灵组件。
    • file_path = TREE_TYPE.get(treeType)TREE_TYPE 字典中获取特定树型的文件路径。treeType 参数决定树的种类。
    • tree_image = pg.image.load(file_path).convert_alpha() 加载树的图像文件,并转换格式以支持透明层(如果有的话),这使得树图像的边缘更加平滑。
    • if treeType not in ['grass_tree1', 'grass_tree2']: 检查树的类型,如果不是特定的两种草地树,则执行缩放。
    • tree_image = pg.transform.scale(tree_image, (TREE_WIDTH, TREE_HEIGHT)) 缩放图像到指定的宽和高。
    • self.image = tree_image 将加载和可能缩放后的图像赋值给精灵的图像属性。
    • self.rect = self.image.get_rect(midbottom=(randint(WIDTH, WIDTH * 2), GROUND_HEIGHT)) 设置精灵的矩形位置。这里midbottom属性表示图像底部中心的位置,randint(WIDTH, WIDTH * 2)生成一个随机的x坐标,使得树木可以在屏幕右侧的不同位置出现。
  2. 动画 (animation):
    • self.rect.x -= MOVING_SPEED 更新树木的横向位置,通过每帧减去设定的移动速度,使树木向左移动。
  3. 销毁 (destroy):
    • if self.rect.x <= -WIDTH: 检查树木是否移动到了屏幕左侧之外(即完全不可见)。
    • self.kill() 如果树木已经移出屏幕,从所有pygame精灵组和所有组中删除该树精灵,释放其资源。
  4. 更新 (update):
    • self.animation() 调用动画方法,处理位置更新。
    • self.destroy() 调用销毁方法,检查是否需要从游戏中移除树木精灵。

House类

class House(pg.sprite.Sprite):
    def __init__(self):
        super().__init__()

        house_image = pg.image.load(HOUSE).convert_alpha()
        self.image = pg.transform.scale(
            house_image, (HOUSE_WIDTH, HOUSE_HEIGHT))
        self.rect = self.image.get_rect(midbottom=(
            WIDTH + HOUSE_WIDTH, GROUND_HEIGHT + HOUSE_GROUND_OFFSET))

    def animation(self):
        self.rect.x -= MOVING_SPEED

    def update(self):
        self.animation()
  1. 构造函数 (__init__):
    • super().__init__() 同样调用pygame精灵的构造函数,初始化精灵基础设置。
    • house_image = pg.image.load(HOUSE).convert_alpha() 加载房屋图像,并使其支持透明度。
    • self.image = pg.transform.scale(house_image, (HOUSE_WIDTH, HOUSE_HEIGHT)) 将房屋图像缩放到指定尺寸。
    • self.rect = self.image.get_rect(midbottom=(WIDTH + HOUSE_WIDTH, GROUND_HEIGHT + HOUSE_GROUND_OFFSET)) 设置房屋的位置,确保其出现在屏幕右侧,并调整y坐标以考虑地面高度偏移。
  2. 动画 (animation):
    • self.rect.x -= MOVING_SPEED 更新房屋位置,使其与树木类似向左移动。
  3. 更新 (update):
    • self.animation() 在每帧调用动画函数更新房屋的位置。

碰撞

def collision(self):
        if collided_houses := pg.sprite.spritecollide(cat.sprite, house, False):
            for collided_house in collided_houses:
                if collided_house.rect.centerx <= cat.sprite.rect.centerx:
                    self.win_sound.play()
                    trees.empty()
                    house.empty()
                    return False
                else:
                    return True
        else:
            return True
  1. 定义碰撞检测方法:
    • def collision(self): 这一行定义了 Game 类中的 collision 方法。这个方法将用于检测游戏中的碰撞,并根据碰撞结果决定游戏是否继续。
  2. 检测碰撞:
    • if collided_houses := pg.sprite.spritecollide(cat.sprite, house, False): 这行代码使用 pg.sprite.spritecollide 函数来检查 cat.sprite(猫的精灵)和 house(房屋的精灵组)之间是否发生了碰撞。这个函数返回一个列表,包含所有与猫发生碰撞的房屋精灵。参数 False 表示不使用像素级碰撞检测,即使用边界框(bounding boxes)进行碰撞检测。
  3. 处理碰撞结果:
    • for collided_house in collided_houses: 这行代码遍历所有与猫发生碰撞的房屋精灵。对于每一个碰撞的房屋,执行以下判断和操作。
    • if collided_house.rect.centerx <= cat.sprite.rect.centerx: 检查碰撞的房屋的中心 x 坐标是否小于或等于猫的中心 x 坐标。这个条件通常用来判断房屋是否在猫的前方或正与猫重合。
    • self.win_sound.play() 如果上述条件为真,即碰撞的房屋位于猫的前方或重合,播放胜利的声音效果。
    • trees.empty() 清空所有树木精灵。empty() 方法会移除所有精灵组中的精灵。
    • house.empty() 清空所有房屋精灵。同样使用 empty() 方法。
    • return False 返回 False,表示游戏应该结束,因为发生了有效的碰撞。
    • else: 如果检查的房屋中心 x 坐标大于猫的中心 x 坐标,即房屋在猫的后方。
    • return True 返回 True,表示游戏可以继续进行。
  4. 如果没有发生碰撞:
    • else: 如果 pg.sprite.spritecollide 函数没有检测到任何碰撞,即 collided_houses 为空。
    • return True 同样返回 True,表示游戏可以继续进行,因为没有发生碰撞。

Game类中的main_loop方法扩展

# Generate Tree
if event.type == self.tree_timer:
    tree_type = choice(list(TREE_TYPE.keys()))
    trees.add(Trees(tree_type))

# Generate House
if event.type == self.house_timer:
    house.add(House())

# Reset Game Parameters and Timers
if event.type == pg.KEYDOWN and event.key == pg.K_SPACE:
    self.game_active = True
    text_target.sprite.score = 0
    text_target.sprite.letter_count = 0
    text_target.sprite.candidate = choice(WORDBANK)
    text_target.sprite.update_text()
    pg.time.set_timer(self.tree_timer, TREE_SPAWN_FREQ)
    pg.time.set_timer(self.house_timer, HOUSE_SPAWN_FREQ)

    # Play In-game Music
    pg.mixer.music.stop()
    pg.mixer.music.set_volume(0.3)
    pg.mixer.music.play()

# Active/Deactivate Game Based on Collision Event
self.game_active = self.collision()
  1. 树木生成:
    • 在游戏事件循环中,当检测到特定的定时器事件(self.tree_timer),随机选择一个树木类型,并创建一个新的树木精灵,然后将其添加到树木组中。
  2. 房屋生成:
    • 类似地,当房屋的定时器事件触发时(self.house_timer),创建一个新的房屋精灵并添加到房屋组中。
  3. 游戏重置:
    • 如果玩家按下空格键,游戏的活动状态被重置为激活状态,同时重置得分和打字目标,重新设置树木和房屋的生成定时器,并重启背景音乐。
  4. 碰撞检测:
    • 游戏会检查猫精灵和房屋精灵之间是否发生碰撞。如果检测到碰撞,根据碰撞的位置可能会结束游戏或继续进行。

好了,随着《归乡之路》的打字游戏的完成,我们的pygame基础教学就该一段落了,我们已经学到了pygame大部分的游戏制作功能。当然,大家可以看到,在我放出的这个完整版游戏里还有更多的内容,比如有游戏开始画面,有三首背景音乐循环播放,还有藏着一个彩蛋不知道到今天大家发现没有。这个彩蛋的触发也是用到了事件触发器。大家可以去研究我的源文件学习这些内容。不知道大家一路跟下来学到了多少,谢谢大家的陪伴,你有什么问题请给我留言。我还会继续放出其他的3A游戏,咱们一起继续边玩边学一些pygame和制作游戏相关高阶的内容。那就这样,咱们下期再见!



Leave a comment