前面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):
角色更新的逻辑。
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
中的碰撞检测即可:
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