Pygame入门4——使用精灵类重构

2022-12-06 14:26:07 浏览数 (1)

前面3章已经完成了游戏,这章使用OOP风格重构游戏,然后给游戏添加一点音乐。

用精灵类重构

如果你完成了前3章的代码,应该会发现代码很乱。想更改某个代码?查找困难、修改起来更困难!不断添加的新功能让我们的代码越来越复杂,难以阅读。

考虑使用OOP重构代码,将游戏元素用类组织起来。Pygame提供了Sprite,便于我们控制游戏中的元素:

Sprite.png

Sprite(精灵)类,可以看成是surface rectangle的组合,并且绘制和更新起来非常容易。

精灵绘制.png

想要绘制一个精灵,只需要:

1.创建sprite;

2.将sprite放到Group或GroupSingle中;

3.通过group进行 draw/update (和把大象放进冰箱里一样简单)

group_or_single.png

Group,是用于装精灵的容器。GroupSingle,只能装一个精灵。

下面,我们用精灵重构玩家的属性和方法:继承精灵类,并重写其方法。

__init__(self) 在初始化方法中,初始化素材,初始image和rect。注意,这里image和rect是固定写法,Sprite更新时会用到这两个名称。另外初始化方法中要调用父类的初始化方法。

update(self): 角色更新的逻辑。

代码语言:javascript复制
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        walk_1 = pygame.image.load('graphics/Player/player_walk_1.png').convert_alpha()
        walk_2 = pygame.image.load('graphics/Player/player_walk_2.png').convert_alpha()
        self.walk = [walk_1, walk_2]
        self.index = 0
        self.jump = pygame.image.load('graphics/Player/jump.png').convert_alpha()

        self.image = self.walk[self.index]
        self.rect = self.image.get_rect(midbottom=(80, 300))
        self.gravity = 0

        self.jump_sound = pygame.mixer.Sound('audio/jump.mp3')
        self.jump_sound.set_volume(0.5)

    def input(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_SPACE] and self.rect.bottom >= 300:
            self.jump_sound.play()
            self.gravity -= 20

    def apply_gravity(self):
        self.gravity  = 1
        self.rect.y  = self.gravity
        if self.rect.bottom >= 300:
            self.rect.bottom = 300
            self.gravity = 0

    def animation_state(self):
        if self.rect.bottom < 300:
            self.image = self.jump
        else:
            self.index  = 0.1
            if self.index >= len(self.walk):
                self.index = 0
            self.image = self.walk[int(self.index)]

    def update(self):
        self.input()
        self.apply_gravity()
        self.animation_state()

然后需要将其放到Group/GroupSingle中:

代码语言:javascript复制
player = pygame.sprite.GroupSingle()
player.add(Player())

最后在游戏的主循环中,调用Group的draw和update方法。

代码语言:javascript复制
player.draw(screen)
player.update()

类似地,可以创建蜗牛和苍蝇。它们都是一种障碍,可以创建一个类Obstacle来表示。然后放到Group中:

代码语言:javascript复制
obstacle_group = pygame.sprite.Group()
...
if event.type == obstacle_timer:
    obstacle_group.add(Obstacle(random.choice(['fly', 'snail', 'snail', 'snail'])))

在游戏的主循环中,调用Group的draw和update方法。

代码语言:javascript复制
obstacle_group.draw(screen)
obstacle_group.update()

使用精灵后,检测它们之间的碰撞很简单,只需要使用pygame.sprite中的碰撞检测即可:

代码语言:javascript复制
def collision_sprite():
    if pygame.sprite.spritecollide(player.sprite, obstacle_group, False):
        obstacle_group.empty()
        return False
    return True

添加音乐

播放音乐相当简单,加载、播放。这里我们播放一个背景音乐。

代码语言:javascript复制
bg_music = pygame.mixer.Sound('audio/张震岳 - 迷途羔羊.mp3')
bg_music.set_volume(0.5) # 设置音量50%
bg_music.play(loops=-1)  # loops为播放重复的次数,-1 表示永远循环播放。

可以在玩家跳跃时添加一个音效。

代码语言:javascript复制
# in __init__()
self.jump_sound = pygame.mixer.Sound('audio/jump.mp3')
# in input()
if keys[pygame.K_SPACE] and self.rect.bottom >= 300:
    self.jump_sound.play()

完整代码:

代码语言:javascript复制
import random
import pygame
from sys import exit


class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        walk_1 = pygame.image.load('graphics/Player/player_walk_1.png').convert_alpha()
        walk_2 = pygame.image.load('graphics/Player/player_walk_2.png').convert_alpha()
        self.walk = [walk_1, walk_2]
        self.index = 0
        self.jump = pygame.image.load('graphics/Player/jump.png').convert_alpha()

        self.image = self.walk[self.index]
        self.rect = self.image.get_rect(midbottom=(80, 300))
        self.gravity = 0

        self.jump_sound = pygame.mixer.Sound('audio/jump.mp3')
        self.jump_sound.set_volume(0.5)

    def input(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_SPACE] and self.rect.bottom >= 300:
            self.jump_sound.play()
            self.gravity -= 20

    def apply_gravity(self):
        self.gravity  = 1
        self.rect.y  = self.gravity
        if self.rect.bottom >= 300:
            self.rect.bottom = 300
            self.gravity = 0

    def animation_state(self):
        if self.rect.bottom < 300:
            self.image = self.jump
        else:
            self.index  = 0.1
            if self.index >= len(self.walk):
                self.index = 0
            self.image = self.walk[int(self.index)]

    def update(self):
        self.input()
        self.apply_gravity()
        self.animation_state()


class Obstacle(pygame.sprite.Sprite):
    def __init__(self, type):
        super().__init__()
        if type == 'fly':
            fly_frame_1 = pygame.image.load('graphics/Fly/Fly1.png').convert_alpha()
            fly_frame_2 = pygame.image.load('graphics/Fly/Fly2.png').convert_alpha()
            self.frames = [fly_frame_1, fly_frame_2]
            y_pos = 210
        elif type == 'snail':
            snail_frame_1 = pygame.image.load('graphics/snail/snail1.png').convert_alpha()
            snail_frame_2 = pygame.image.load('graphics/snail/snail2.png').convert_alpha()
            self.frames = [snail_frame_1, snail_frame_2]
            y_pos = 300
        self.index = 0
        self.image = self.frames[self.index]
        self.rect = self.image.get_rect(midbottom=(random.randint(900, 1100), y_pos))

    def animation_state(self):
        self.index  = 0.1
        if self.index >= len(self.frames):
            self.index = 0
        self.image = self.frames[int(self.index)]

    def update(self):
        self.animation_state()
        self.rect.x -= 6
        self.destroy()

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


def display_score():
    # 显示分数
    current = pygame.time.get_ticks() // 1000 - start_time
    score_surf = test_font.render(f"Score:{current}", False, (64, 64, 64))
    screen.blit(score_surf, score_surf.get_rect(center=(400, 50)))
    return current

def collision_sprite():
    if pygame.sprite.spritecollide(player.sprite, obstacle_group, False):
        obstacle_group.empty()
        return False
    return True


# 初始化 引擎
pygame.init()
# 设置屏幕
screen = pygame.display.set_mode((800, 400))  # 宽度800,高度400
pygame.display.set_caption('Runner')  # 设置标题
# 时钟
clock = pygame.time.Clock()
# 字体
test_font = pygame.font.Font('font/Pixeltype.ttf', 50)
# 背景
sky_surf = pygame.image.load('graphics/Sky.png').convert()
ground_surf = pygame.image.load('graphics/ground.png').convert()
# 得分
score_surf = test_font.render("My game", False, 'Black')
score_rect = score_surf.get_rect(center=(400, 50))
# 游戏简介
game_name_surf = test_font.render("Runner", False, 'Black')
game_name_rect = game_name_surf.get_rect(center=(400, 80))
game_message_surf = test_font.render("Press space to run", False, (111, 196, 169))
game_message_rect = game_message_surf.get_rect(center=(400, 320))
# 人物画
player_stand_surf = pygame.image.load('graphics/Player/player_stand.png').convert_alpha()
player_stand_surf = pygame.transform.rotozoom(player_stand_surf, 0, 2)
player_stand_rect = player_stand_surf.get_rect(midbottom=(400, 300))


# Timer
obstacle_timer = pygame.USEREVENT   1
pygame.time.set_timer(obstacle_timer, 800)


player = pygame.sprite.GroupSingle()
player.add(Player())
obstacle_group = pygame.sprite.Group()


bg_music = pygame.mixer.Sound('audio/张震岳 - 迷途羔羊.mp3')
bg_music.set_volume(0.5)
bg_music.play(loops=-1)

start_time = 0
score = 0
game_activate = False
while True:
    # 获取用户输入
    for event in pygame.event.get():
        # 用户点击退出,关闭游戏
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
        if game_activate:
            if event.type == obstacle_timer:
                obstacle_group.add(Obstacle(random.choice(['fly', 'snail', 'snail', 'snail'])))
        else:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                game_activate = True
                start_time = pygame.time.get_ticks() // 1000
    # 游戏运行:绘图,更新
    if game_activate:
        pygame.draw.rect(screen, '#c0e8ec', score_rect)
        pygame.draw.rect(screen, '#c0e8ec', score_rect, 10)
        screen.blit(sky_surf, (0, 0))  # 将test_surface放到screen上。(0,0):放置后test_surface的左上角位于screen的(0,0)处
        screen.blit(ground_surf, (0, 300))
        score = display_score()
        player.draw(screen)
        player.update()
        obstacle_group.draw(screen)
        obstacle_group.update()
        game_activate = collision_sprite()
    # 游戏结束:
    else:
        screen.fill((94, 129, 162))
        screen.blit(player_stand_surf, player_stand_rect)
        screen.blit(game_name_surf, game_name_rect)
        if score == 0:
            screen.blit(game_message_surf, game_message_rect)
        else:
            score_message = test_font.render(f'Your score:{score}', False, (111, 196, 169))
            score_message_rect = score_message.get_rect(center=(400, 320))
            screen.blit(score_message, score_message_rect)

    pygame.display.update()
    clock.tick(60)  # 不超过60 fps

0 人点赞