赛博红兔的科技博客

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


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

大家好,我是赛博红兔。欢迎来到《和我一起做3A游戏》第二集!这个系列是对pygame这个Python的游戏库的所有主要功能的介绍。通过这个教程,你应该能掌握制作任何2D游戏的工具,今天我们继续来做《归乡之路》这个打字游戏。先来回顾一下上一集的内容,我们设计了游戏的艺术概念,找好了资源,建立了项目文件夹。创建了主窗口,创建了游戏主循环,学习了控制游戏的帧率,如何正确关闭游戏处理,还有绘制颜色块、文字和图片画布。那么今天,我们就在这个代码基础上继续做游戏。我们主要要完成的任务是创建游戏主人公小猫的类,还有打字游戏要打的单词的类,和分数的统计和显示。

精灵Sprite是Pygame一大特色类,也是我们面向对象编程的核心。精灵用于集中创建和管理游戏中的各种对象,这些对象往往是一堆有相同属性的不同个体,比如说各种敌人、各种花草树木、各种武器子弹等等。首先,每个精灵都有画布(pygame.Surface)的属性,用来展示精灵的视觉外观,上一集我们已经介绍过了。精灵还有矩形(pygame.Rect)的属性,定义了精灵的位置和大小。矩形的两个核心功能是:第一,它帮助我们更高效、更精确地放置一个画布;第二,矩形能够帮助我们检测碰撞。上一集记不记得我们的画布只能通过它的左上角也就是起始点来进行定位。但是使用矩形来定位更加的方便精确。那么,矩形到底是怎么工作的呢?其实挺简单的,矩形就是一个四边形,它使用周围的点或者线进行定位。左边这种是用9个点来定位,每个点都可以用一对坐标就是X和Y的值来赋值,比如说topleft=(0, 0)就是左上角对齐(0,0)这个点,center=(10,10)就是中心点对齐(10,10)这个点,明白吧?右边是用上下左右四条线对齐的方式,比如top=0就是顶部对齐y=0这条线,left=10就是左边对齐x=10这条线,明白了吧?当然还有centerx和centery分别对应中心点x和y坐标。这样我们就能更精确地控制精灵的位置了。我们还可以在每一帧更新精灵的行为,如移动、更改状态、检测碰撞等。精灵最有价值的地方,就是碰撞检测。我们一会就会学如何用精灵的矩形检测碰撞,也可以用圆形或者更精确的像素来检测。精灵还有一个分组管理(pg.sprite.Group或者pg.sprite.GroupSingle),可以同时管理和绘制大量的精灵,主要用在角色控制,敌人和 AI 行为,道具和加成,粒子系统和特效。

引入额外的模块和变量

from random import choice, randint, random

引入了Python标准库random中的三个函数,分别是choice(从序列中随机选取一个元素),randint(生成一个指定范围内的随机整数),和random(生成一个[0.0, 1.0)范围内的随机浮点数)。这些函数可能用于游戏中的随机事件处理。

新增Cat

class Cat(pg.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.gravity = 0
        self.cat_index = 0
  • class Cat(pg.sprite.Sprite): 定义了一个名为Cat的新类,继承自Pygame的Sprite类。Sprite类是Pygame中用于表示所有可视化对象(比如游戏中的角色和物体)的基类。
  • super().__init__(): 调用了父类Sprite的构造函数,这是类继承中常用的初始化父类的方法。
  • self.gravity = 0: 初始化重力影响为0。这个变量可能用于模拟跳跃时的重力影响。
  • self.cat_index = 0: 初始化用于追踪当前使用的猫的图像索引的变量。
# Load Cat Stand Image
cat_image = pg.image.load(CAT_STAND).convert_alpha()
self.cat_stand = pg.transform.scale(
    cat_image, (CAT_WIDTH * 2, CAT_HEIGHT * 2))
self.cat_stand_rect = self.cat_stand.get_rect(
    center=(WIDTH // 2, HEIGHT // 2))

# Load Cat Walk Image
self.cat_walk = []
for i in range(len(CAT_WALK)):
    cat_image = pg.image.load(CAT_WALK[i]).convert_alpha()
    cat_image = pg.transform.scale(cat_image, (CAT_WIDTH, CAT_HEIGHT))
    self.cat_walk.append(cat_image)

# Load Cat Jump Image and Sound
self.cat_jump = []
for i in range(len(CAT_JUMP)):
    cat_image = pg.image.load(CAT_JUMP[i]).convert_alpha()
    cat_image = pg.transform.scale(cat_image, (CAT_WIDTH, CAT_HEIGHT))
    self.cat_jump.append(cat_image)

self.jump_sound = pg.mixer.Sound(JUMP_SOUND)
  • 这一部分代码加载了猫的站立、行走和跳跃的图像,并且调整了图像的大小以适应游戏窗口。同时,还加载了跳跃的声音效果。
  • pg.image.load(): 加载图像文件。
  • convert_alpha(): 转换图像格式以支持透明部分,这通常用于PNG图像包含透明度。
  • pg.transform.scale(): 调整图像的尺寸。
  • self.cat_walkself.cat_jump: 这些列表用于存储行走和跳跃时的一系列图像,以便于动画效果的实现。

Cat类中的方法

Cat类是为表示一个猫角色而创建的,继承自Pygame的Sprite基类。这个类通过提供多种图像和相关逻辑,实现了猫的不同动作表现,如行走和跳跃。

def jump(self):
    for event in pg.event.get():
        if event.type == CORRECT_TYPING and self.rect.bottom >= GROUND_HEIGHT:
            self.cat_index = 0
            self.gravity = GRAVITY
            self.rect.y += self.gravity
            self.jump_sound.play()
            return

    if self.rect.bottom < GROUND_HEIGHT:
        self.gravity += 1
    else:
        self.gravity = 0
        self.rect.bottom = GROUND_HEIGHT
    self.rect.y += self.gravity
  • def jump(self): 定义jump方法,处理跳跃逻辑。
  • for event in pg.event.get(): 遍历从Pygame事件队列中获取的所有事件。
  • if event.type == CORRECT_TYPING and self.rect.bottom >= GROUND_HEIGHT: 如果事件类型是CORRECT_TYPING(一种自定义事件,可能代表玩家打字正确)并且猫在地面上,则触发跳跃。
  • self.cat_index = 0: 重置动画帧索引。
  • self.gravity = GRAVITY: 设置重力效果,GRAVITY是一个预定义的常量,代表跳跃的初始速度。
  • self.rect.y += self.gravity: 更新猫的垂直位置。
  • self.jump_sound.play(): 播放跳跃声音。
  • return: 跳出方法,避免执行后续的重力逻辑。
  • if self.rect.bottom < GROUND_HEIGHT: 如果猫不在地面上,即在空中。
  • self.gravity += 1: 增加重力影响,使猫下落加速。
  • else: 如果猫在地面上。
  • self.gravity = 0: 重置重力影响。
  • self.rect.bottom = GROUND_HEIGHT: 确保猫在地面。
  • self.rect.y += self.gravity: 继续更新猫的垂直位置,应用重力效果。
def animation(self):
    self.cat_index += 0.2
    if self.rect.bottom < GROUND_HEIGHT:
        if self.cat_index >= len(self.cat_jump):
            self.cat_index = 0
        self.image = self.cat_jump[int(self.cat_index)]
    else:
        if self.cat_index >= len(self.cat_walk):
            self.cat_index = 0
        self.image = self.cat_walk[int(self.cat_index)]
  • def animation(self): 定义animation方法,处理动画帧切换。
  • self.cat_index += 0.2: 更新动画帧索引,0.2 代表每次调用方法时动画帧的递增幅度。
  • if self.rect.bottom < GROUND_HEIGHT: 如果猫在空中(跳跃中)。
  • if self.cat_index >= len(self.cat_jump): 如果动画帧索引超出了跳跃动画序列的长度。
  • self.cat_index = 0: 重置动画帧索引。
  • self.image = self.cat_jump[int(self.cat_index)]: 更新猫的图像为跳跃动画序列中对应的帧。
  • else: 如果猫在地面上。
  • if self.cat_index >= len(self.cat_walk): 如果动画帧索引超出了行走动画序列的长度。
  • self.cat_index = 0: 重置动画帧索引。
  • self.image = self.cat_walk[int(self.cat_index)]: 更新猫的图像为行走动画序列中对应的帧。
def update(self):
    self.jump()
    self.animation()
  • def update(self): 定义update方法,用于每个游戏帧更新猫的状态。
  • self.jump(): 调用jump方法,处理跳跃逻辑。
  • self.animation(): 调用animation方法,更新动画帧。

新增TextTarget

这个类处理游戏中的打字目标,包括从词库中选择单词,更新显示的文本,和处理打字的输入。

class TextTarget(pg.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.letter_count = 0
        self.score = 0

        # Choose text candidate from a wordbank
        self.candidate = choice(WORDBANK)
        self.update_text()
        self.hit_sound = pg.mixer.Sound(HIT_SOUND)
  • class TextTarget(pg.sprite.Sprite): 定义了一个名为TextTarget的新类,继承自Pygame的Sprite类,用于表示游戏中的打字目标。
  • super().__init__(): 调用父类的构造函数,初始化Sprite基类。
  • self.letter_count = 0: 初始化打过的字母计数为0。
  • self.score = 0: 初始化得分为0。
  • self.candidate = choice(WORDBANK): 从单词库(WORDBANK)中随机选择一个单词作为当前打字目标。
  • self.update_text(): 调用update_text方法来渲染选中的单词。
  • self.hit_sound = pg.mixer.Sound(HIT_SOUND): 加载打字正确时的声音效果。
def update_text(self):
    self.image = TARGET_FONT.render(
        self.candidate, False, 'mediumslateblue')
    self.rect = self.image.get_rect(center=(WIDTH // 2, TEXTTARGET_HEIGHT))
  • def update_text(self): 定义update_text方法,用于更新屏幕上显示的单词。
  • self.image = TARGET_FONT.render(self.candidate, False, 'mediumslateblue'): 使用Pygame的字体系统渲染文本,TARGET_FONT是预定义的字体,self.candidate是要渲染的文本内容,mediumslateblue是字体颜色。
  • self.rect = self.image.get_rect(center=(WIDTH // 2, TEXTTARGET_HEIGHT)): 获取渲染文本的矩形并设置其位置,使其水平居中且在预定义的高度显示。
def typing_check(self):
    if self.candidate:
        keys = pg.key.get_pressed()
        letter = self.candidate[0]
        letter_code = ord(letter)
        for key in range(len(keys)):
            if keys[key]:
                if key == letter_code:
                    self.candidate = self.candidate.replace(letter, '', 1)
                    self.letter_count += 1
                    self.hit_sound.play()
                    self.update_text()
                else:
                    break
    else:
        pg.event.post(pg.event.Event(CORRECT_TYPING))
        self.score = self.letter_count
        self.candidate = choice(WORDBANK)
        self.update_text()
  • def typing_check(self): 定义typing_check方法,处理玩家的打字输入。
  • if self.candidate: 如果当前有待打的单词。
  • keys = pg.key.get_pressed(): 获取当前按下的所有键。
  • letter = self.candidate[0]: 获取当前单词的第一个字母。
  • letter_code = ord(letter): 获取该字母的ASCII码。
  • for key in range(len(keys)): 遍历所有按键。
  • if keys[key]: 如果当前键被按下。
  • if key == letter_code: 如果按下的键与单词的第一个字母匹配。
  • self.candidate = self.candidate.replace(letter, '', 1): 移除单词中的第一个字母。
  • self.letter_count += 1: 字母计数加一。
  • self.hit_sound.play(): 播放击中声音。
  • self.update_text(): 更新显示的文本。
  • else: 如果按下的键不匹配。
  • break: 退出循环。
  • else: 如果当前没有待打的单词。
  • pg.event.post(pg.event.Event(CORRECT_TYPING)): 发送一个自定义事件,表示正确完成一个单词的打字。
  • self.score = self.letter_count: 更新得分为字母计数。
  • self.candidate = choice(WORDBANK): 重新从单词库中选择一个单词。
  • self.update_text(): 更新显示的文本。
def update(self):
    self.typing_check()
  • def update(self): 定义update方法,用于每个游戏帧更新打字目标的状态。
  • self.typing_check(): 调用typing_check方法,处理玩家的打字输入。

更新Game类和主循环

在更新的Game类和游戏的主循环中,添加了处理得分显示、处理键盘事件、更新屏幕显示等逻辑。

def display_score(self, score):
    score_text = SCORE_FONT.render(
        f'Score: {score}', False, 'white')
    score_rect = score_text.get_rect(right=WIDTH)
    self.screen.blit(score_text, score_rect)
  • def display_score(self, score):: 定义一个显示得分的方法。
  • score_text = SCORE_FONT.render(f'Score: {score}', False, 'white'): 使用预设字体渲染得分文本。
  • score_rect = score_text.get_rect(right=WIDTH): 获取文本的矩形区域,并设置其右边界与屏幕宽度对齐。
  • self.screen.blit(score_text, score_rect): 将得分文本绘制到屏幕上。
def main_loop(self):
    while True:
        self.clock.tick(FPS)
        score = text_target.sprite.score
        for event in pg.event.get():
            if event.type == pg.QUIT:
                pg.quit()
                exit()

        # Display Game Backgrounds, Text, Objects, and Score
        self.screen.blit(self.sky_background, (0, 0))
        self.screen.blit(self.ground_background, (0, GROUND_HEIGHT))
        self.display_score(score)
        text_target.draw(self.screen)
        cat.draw(self.screen)

        # Update Sprites
        text_target.update()
        cat.update()

        pg.display.update()
  • def main_loop(self):: 定义主循环方法,游戏的主逻辑都在这里处理。
  • while True:: 创建一个无限循环,游戏会在这个循环中运行。
  • self.clock.tick(FPS): 控制游戏的帧率。
  • score = text_target.sprite.score: 获取打字目标的得分。
  • for event in pg.event.get():: 遍历所有的事件。
  • if event.type == pg.QUIT:: 如果事件类型是退出,则关闭游戏窗口。
  • pg.quit(): 退出Pygame。
  • exit(): 退出程序。
  • self.screen.blit(...: 绘制背景、得分和其他对象。
  • text_target.draw(self.screen): 绘制打字目标。
  • cat.draw(self.screen): 绘制猫对象。
  • text_target.update(): 更新打字目标的状态。
  • cat.update(): 更新猫的状态。
  • pg.display.update(): 更新整个屏幕的显示。


Leave a comment