在上一期中我使用了PixiJS开发了网页版的谷歌小恐龙,这一期我使用python也实现一下
介绍
Pygame是一个开放源代码的跨平台Python库,它使得多媒体应用程序(尤其是视频游戏)的开发变得简单易行。Pygame基于Simple DirectMedia Layer(SDL)库和多个流行的库来抽象最常见的功能,使编写程序变得更直观。
Pygame的主要特点和功能包括:
- 跨平台:Pygame支持在大部分操作系统上运行,包括Windows、Mac OS和Linux等,并且可以在经过编译后在Android手机和网页上运行。
- 适合新手:与一些类似框架相比,Pygame更加适合新手入门游戏开发。
- 功能全面:Pygame支持的功能包括图片、文字、绘图、OpenGL 3D、音频、摄像头、游戏手柄等。
- 易于使用:Pygame提供了直观的API,使得开发者可以简单地创建游戏窗口、绘制图像、形状和文字,以及添加音效和背景音乐到游戏中。
- 游戏开发框架:Pygame游戏开发框架共包含四个部分:引入pygame和sys、初始化pygame游戏引擎并完成游戏窗体设置、在一个无限循环中获取pygame事件并逐类响应、在该无限循环中不断刷新游戏屏幕。
Pygame的原作者是Pete Shinners,其协议为GNU Lesser General Public License。它原本是为了代替突然停止的pySDL和其他一些游戏制作竞赛中的工具而开发的。
要使用Pygame,你需要先将它安装到你的机器上。最简单的安装方法是使用pip:pip install pygame
或pip install pygame-ce
(推荐使用pygame官方的社区编辑版)。
封装动画精灵类
#创建动画精灵父类,继承pygame的Sprite
class AnimatedSprite(pygame.sprite.Sprite):
def __init__(self, animation_frames):
super().__init__()
#传入动画每一帧的列表
self.frames = animation_frames
#当前帧索引
self.frame_index = 0
self.image, self.rect = self.frames[self.frame_index]
self.animation_speed = 0.1 # 动画速度,单位是秒
self.last_update = pygame.time.get_ticks()
def update(self, current_time):
# 检查是否需要更新帧
if current_time - self.last_update > int(self.animation_speed * 1000):
self.frame_index = (self.frame_index + 1) % len(self.frames)
self.image, self.rect = self.frames[self.frame_index]
self.last_update = current_time
该部分编写了动画精灵类,用于实现精灵的动画效果
编写恐龙精灵类
#编写恐龙类,继承我们刚刚编写的动画精灵类
class PinoSprite(AnimatedSprite):
def __init__(self):
self.load_animation_frames()
#调用父类构造方法,将帧列表传入
super().__init__(self.run_animation_frames)
self.is_jump = False
self.speed = 7
#获取动画帧列表
def load_animation_frames(self):
#加载总素材图片
base_frame = pygame.image.load("./googlepino.png").convert_alpha()
self.run_animation_frames = []
for i in range(2):
#切割出每一帧的奔跑动作
frame = base_frame.subsurface(pygame.Rect(935 + (i * 45), 0, 45, 50))
frame_rect = frame.get_rect()
#设置位置
frame_rect.bottom = SCREEN_HEIGHT
frame_rect.x = 50
self.run_animation_frames.append((frame, frame_rect))
self.squat_animation_frames = []
for i in range(2):
#切割出每一帧的蹲下动作
frame = base_frame.subsurface(pygame.Rect(1110 + (i * 60), 20, 60, 30))
frame_rect = frame.get_rect()
#设置位置
frame_rect.bottom = SCREEN_HEIGHT
frame_rect.x = 50
self.squat_animation_frames.append((frame, frame_rect))
#切换蹲下动作
def squat(self):
self.frames = self.squat_animation_frames
#切换奔跑动作
def run(self):
self.frames = self.run_animation_frames
#切换跳跃动作
def jump(self):
self.is_jump = True
self.is_up = True
def update(self, current_time):
super().update(current_time)
if self.is_jump:
#如果跳跃的话,设置每一个动作的位置
for frame in self.frames:
rect = frame[1]
if self.is_up:
#向上的话y坐标减小
rect.bottom -= self.speed
#如果y坐标已经达到跳跃最高处,则向下
if rect.bottom <= SCREEN_HEIGHT - rect.height * 2:
self.is_up = False
else:
#向下的话y坐标增加
rect.bottom += self.speed
#如果y坐标已经到地面了,则跳跃结束
if rect.bottom >= SCREEN_HEIGHT:
self.is_jump = False
#如果已经完成跳跃动作了,则设置每一个动作的位置都到地面
if not self.is_jump:
for frame in self.frames:
rect = frame[1]
rect.bottom = SCREEN_HEIGHT
该部分我们实现了恐龙精灵的编写,完成了恐龙的跳跃,奔跑,蹲下动作
编写飞鸟精灵类
#编写飞鸟类,继承动画精灵类
class FlyBirdSprite(AnimatedSprite):
def __init__(self):
self.load_animation_frames()
super().__init__(self.animation_frames)
#获取飞鸟动画帧列表
def load_animation_frames(self):
self.animation_frames = []
base_frame = pygame.image.load("./googlepino.png").convert_alpha()
#设置x坐标随机位置
x = random.randint(1400, 1500)
#切割出每一个动画动作帧
for i in range(2):
frame = base_frame.subsurface(pygame.Rect(135 + (i * 45), 0, 45, 30))
frame_rect = frame.get_rect()
#设置位置
frame_rect.bottom = SCREEN_HEIGHT - 35
frame_rect.x = x
self.animation_frames.append((frame, frame_rect))
def update(self, current_time):
super().update(current_time)
for frame in self.frames:
rect = frame[1]
#设置每次向左移动10
rect.x -= 10
#如果已经超出了屏幕,则重新设置飞鸟的位置
if rect.x <= 0:
x = random.randint(1400, 1500)
for frame in self.frames:
frame[1].x = x
该部分我们完成了飞鸟类的编写,实现了飞鸟的移动,刷新
封装自己的精灵类
#封装自己的精灵类
class GameSprite(pygame.sprite.Sprite):
def __init__(self, rect):
super().__init__()
base_frame = pygame.image.load("./googlepino.png").convert_alpha()
#设置图片切割的位置
self.image = base_frame.subsurface(rect)
self.rect = self.image.get_rect()
def update(self):
#每次向左移动10
self.rect.x -= 10
该部分我们封装了自己的精灵类,用于继承
编写地面精灵类
#编写仙人掌精灵类,继承我们编写的精灵父类
class GroundSprite(GameSprite):
def __init__(self, is_alt=False):
#调用父类构造方法,切割出仙人掌的图片
super().__init__(pygame.Rect(10, 52, 1200, 15))
#设置位置
self.rect.bottom = SCREEN_HEIGHT
#如果是第二张图片,则放到后面
if is_alt:
self.rect.x = SCREEN_WIDTH
def update(self):
super().update()
#判断是否滚动完一张,滚动完设置到后面,循环滚动
if self.rect.right <= 0:
self.rect.x = SCREEN_WIDTH
该部分完成了地面精灵类的编写,实现地面循环滚动
编写仙人掌精灵类
#编写仙人掌精灵类,继承父类精灵
class CactusSprite(GameSprite):
def __init__(self):
#切割仙人掌图片
super().__init__(pygame.Rect(356, 0, 25, 55))
#位置设置到中间
self.rect.x = SCREEN_WIDTH / 2
self.rect.bottom = SCREEN_HEIGHT
def update(self):
super().update()
#超出屏幕左侧时设置到右侧
if self.rect.x <= 0:
self.rect.x = SCREEN_WIDTH
该部分完成了仙人掌精灵类的编写,实现仙人掌的移动,刷新
编写游戏类
class Game:
#初始化
def __init__(self):
pygame.init()
#设置游戏窗口
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
self.clock = pygame.time.Clock()
#设置分数
self.score = 0
#设置游戏状态
self.game_play = False
#设置字体
self.game_font = pygame.font.Font("./中文像素字体.ttf", 25)
该部分我们完成了游戏类的构造方法
#设置字体
def set_text(self, text):
if text != "开始游戏":
#清除掉原来的文字区域内容
pygame.draw.rect(self.screen, (255, 255, 255), self.text_rect)
self.text_info = text
self.text = self.game_font.render(text, True, (0, 0, 0))
self.text_rect = self.text.get_rect()
#设置位置
self.text_rect.center = self.screen.get_rect().center
#显示到屏幕
self.screen.blit(self.text, self.text_rect)
编写了游戏字体的设置
#创建精灵
def create_sprites(self):
self.pino = PinoSprite()
self.pino_group = pygame.sprite.Group(self.pino)
ground1 = GroundSprite()
ground2 = GroundSprite(True)
self.ground_group = pygame.sprite.Group(ground1, ground2)
self.cactus = CactusSprite()
self.cactus_group = pygame.sprite.Group(self.cactus)
self.fly_brid_group = pygame.sprite.Group()
该部分我们编写了精灵的创建方法,创建了恐龙精灵,地面精灵,仙人掌精灵和组,添加到组中
#精灵状态的更新
def update_sprites(self):
#填充为白色
self.screen.fill((255, 255, 255))
current_time = pygame.time.get_ticks()
self.pino_group.update(current_time)
self.pino_group.draw(self.screen)
self.fly_brid_group.update(current_time)
self.fly_brid_group.draw(self.screen)
self.ground_group.update()
self.ground_group.draw(self.screen)
self.cactus_group.update()
self.cactus_group.draw(self.screen)
该部分编写了所有精灵的更新方法
#检测碰撞
def check_bump(self):
#如果恐龙和仙人掌碰撞,则游戏结束
group = pygame.sprite.spritecollide(self.pino, self.cactus_group, False)
if len(group) > 0:
self.game_over()
#如果恐龙和飞鸟碰撞,则游戏结束
group = pygame.sprite.spritecollide(self.pino, self.fly_brid_group, False)
if len(group) > 0:
self.game_over()
def game_over(self):
self.game_play = False
self.set_text("游戏结束,分数为: {},点击重新开始游戏".format(self.score))
该部分我们编写了恐龙的碰撞检测方法,检测恐龙是否与障碍物碰撞了,如果发生碰撞,游戏结束,spritecollide方法是pygame提供的一个检测碰撞的方法,如果发生碰撞了会返回碰撞到的精灵组
#计算分数
def calculate_scroe(self):
#如果仙人掌已经到屏幕左侧,则分数加1
if self.cactus.rect.x <= 10:
self.score += 1
#分数到5时出现飞鸟
if self.score == 5:
self.fly_brid = FlyBirdSprite()
self.fly_brid_group.add(self.fly_brid)
if len(self.fly_brid_group) > 0:
#如果飞鸟已经到屏幕左侧,则分数加1
for frame in self.fly_brid.frames:
rect = frame[1]
if rect.x <= 10:
self.score += 1
break
#设置当前分数信息
self.set_text("当前分数为: {}".format(self.score))
该部分完成了分数的计算
#编写事件判断
def event_handle(self):
#遍历所有事件
for event in pygame.event.get():
#如果是关闭窗口,则直接停止程序
if event.type == pygame.QUIT:
pygame.quit()
exit()
#鼠标点击文字开始游戏
elif event.type == pygame.MOUSEBUTTONDOWN:
if self.text_rect.collidepoint(event.pos):
if self.text_info == "开始游戏":
self.game_play = True
self.set_text("当前分数为: {}".format(self.score))
else:
#游戏重新开始,初始化游戏状态
self.score = 0
self.cactus.rect.x = SCREEN_WIDTH / 2
self.fly_brid_group.empty()
self.game_play = True
self.set_text("当前分数为: {}".format(self.score))
#如果按下了空格,则恐龙跳跃
key_pressed = pygame.key.get_pressed()
if key_pressed[pygame.K_SPACE]:
self.pino.jump()
#如果按下下箭头,则恐龙蹲下
elif key_pressed[pygame.K_DOWN]:
#判断当前状态是否跳跃,跳跃时不能蹲下
if not self.pino.is_jump:
self.pino.squat()
else:
#其他状态恐龙恢复奔跑状态
self.pino.run()
该部分完成了游戏的操作,键盘控制恐龙的状态以及开始游戏和重新开始
if __name__ == "__main__":
#游戏运行
game = Game()
game.start_game()
最后我们可以运行游戏了!
最终效果
游戏完整代码
项目文件
game_sprite.py文件
import random
import pygame
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 300
class AnimatedSprite(pygame.sprite.Sprite):
def __init__(self, animation_frames):
super().__init__()
self.frames = animation_frames
self.frame_index = 0
self.image, self.rect = self.frames[self.frame_index]
self.animation_speed = 0.1 # 动画速度,单位是秒
self.last_update = pygame.time.get_ticks()
def update(self, current_time):
# 检查是否需要更新帧
if current_time - self.last_update > int(self.animation_speed * 1000):
self.frame_index = (self.frame_index + 1) % len(self.frames)
self.image, self.rect = self.frames[self.frame_index]
self.last_update = current_time
class PinoSprite(AnimatedSprite):
def __init__(self):
self.load_animation_frames()
super().__init__(self.run_animation_frames)
self.is_jump = False
self.speed = 7
def load_animation_frames(self):
base_frame = pygame.image.load("./googlepino.png").convert_alpha()
self.run_animation_frames = []
for i in range(2):
frame = base_frame.subsurface(pygame.Rect(935 + (i * 45), 0, 45, 50))
frame_rect = frame.get_rect()
frame_rect.bottom = SCREEN_HEIGHT
frame_rect.x = 50
self.run_animation_frames.append((frame, frame_rect))
self.squat_animation_frames = []
for i in range(2):
frame = base_frame.subsurface(pygame.Rect(1110 + (i * 60), 20, 60, 30))
frame_rect = frame.get_rect()
frame_rect.bottom = SCREEN_HEIGHT
frame_rect.x = 50
self.squat_animation_frames.append((frame, frame_rect))
def squat(self):
self.frames = self.squat_animation_frames
def run(self):
self.frames = self.run_animation_frames
def jump(self):
self.is_jump = True
self.is_up = True
def update(self, current_time):
super().update(current_time)
if self.is_jump:
for frame in self.frames:
rect = frame[1]
if self.is_up:
rect.bottom -= self.speed
if rect.bottom <= SCREEN_HEIGHT - rect.height * 2:
self.is_up = False
else:
rect.bottom += self.speed
if rect.bottom >= SCREEN_HEIGHT:
self.is_jump = False
if not self.is_jump:
for frame in self.frames:
rect = frame[1]
rect.bottom = SCREEN_HEIGHT
class FlyBirdSprite(AnimatedSprite):
def __init__(self):
self.load_animation_frames()
super().__init__(self.animation_frames)
def load_animation_frames(self):
self.animation_frames = []
base_frame = pygame.image.load("./googlepino.png").convert_alpha()
x = random.randint(1400, 1500)
for i in range(2):
frame = base_frame.subsurface(pygame.Rect(135 + (i * 45), 0, 45, 30))
frame_rect = frame.get_rect()
frame_rect.bottom = SCREEN_HEIGHT - 35
frame_rect.x = x
self.animation_frames.append((frame, frame_rect))
def update(self, current_time):
super().update(current_time)
for frame in self.frames:
rect = frame[1]
rect.x -= 10
if rect.x <= 0:
x = random.randint(1400, 1500)
for frame in self.frames:
frame[1].x = x
class GameSprite(pygame.sprite.Sprite):
def __init__(self, rect):
super().__init__()
base_frame = pygame.image.load("./googlepino.png").convert_alpha()
self.image = base_frame.subsurface(rect)
self.rect = self.image.get_rect()
def update(self):
self.rect.x -= 10
class GroundSprite(GameSprite):
def __init__(self, is_alt=False):
super().__init__(pygame.Rect(10, 52, 1200, 15))
self.rect.bottom = SCREEN_HEIGHT
if is_alt:
self.rect.x = SCREEN_WIDTH
def update(self):
super().update()
if self.rect.right <= 0:
self.rect.x = SCREEN_WIDTH
class CactusSprite(GameSprite):
def __init__(self):
super().__init__(pygame.Rect(356, 0, 25, 55))
self.rect.x = SCREEN_WIDTH / 2
self.rect.bottom = SCREEN_HEIGHT
def update(self):
super().update()
if self.rect.x <= 0:
self.rect.x = SCREEN_WIDTH
game.py文件
import pygame
from game_sprite import *
class Game:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
self.clock = pygame.time.Clock()
self.score = 0
self.game_play = False
self.game_font = pygame.font.Font("./中文像素字体.ttf", 25)
def start_game(self):
self.create_sprites()
self.update_sprites()
self.set_text("开始游戏")
while True:
self.event_handle()
if self.game_play:
self.clock.tick(60)
self.update_sprites()
self.calculate_scroe()
self.check_bump()
pygame.display.update()
def set_text(self, text):
if text != "开始游戏":
pygame.draw.rect(self.screen, (255, 255, 255), self.text_rect)
self.text_info = text
self.text = self.game_font.render(text, True, (0, 0, 0))
self.text_rect = self.text.get_rect()
self.text_rect.center = self.screen.get_rect().center
self.screen.blit(self.text, self.text_rect)
def create_sprites(self):
self.pino = PinoSprite()
self.pino_group = pygame.sprite.Group(self.pino)
ground1 = GroundSprite()
ground2 = GroundSprite(True)
self.ground_group = pygame.sprite.Group(ground1, ground2)
self.cactus = CactusSprite()
self.cactus_group = pygame.sprite.Group(self.cactus)
self.fly_brid_group = pygame.sprite.Group()
def update_sprites(self):
self.screen.fill((255, 255, 255))
current_time = pygame.time.get_ticks()
self.pino_group.update(current_time)
self.pino_group.draw(self.screen)
self.fly_brid_group.update(current_time)
self.fly_brid_group.draw(self.screen)
self.ground_group.update()
self.ground_group.draw(self.screen)
self.cactus_group.update()
self.cactus_group.draw(self.screen)
def check_bump(self):
group = pygame.sprite.spritecollide(self.pino, self.cactus_group, False)
if len(group) > 0:
self.game_over()
group = pygame.sprite.spritecollide(self.pino, self.fly_brid_group, False)
if len(group) > 0:
self.game_over()
def calculate_scroe(self):
if self.cactus.rect.x <= 10:
self.score += 1
if self.score == 5:
self.fly_brid = FlyBirdSprite()
self.fly_brid_group.add(self.fly_brid)
if len(self.fly_brid_group) > 0:
for frame in self.fly_brid.frames:
rect = frame[1]
if rect.x <= 10:
self.score += 1
break
self.set_text("当前分数为: {}".format(self.score))
def game_over(self):
self.game_play = False
self.set_text("游戏结束,分数为: {},点击重新开始游戏".format(self.score))
def event_handle(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if self.text_rect.collidepoint(event.pos):
if self.text_info == "开始游戏":
self.game_play = True
self.set_text("当前分数为: {}".format(self.score))
else:
self.score = 0
self.cactus.rect.x = SCREEN_WIDTH / 2
self.fly_brid_group.empty()
self.game_play = True
self.set_text("当前分数为: {}".format(self.score))
key_pressed = pygame.key.get_pressed()
if key_pressed[pygame.K_SPACE]:
self.pino.jump()
elif key_pressed[pygame.K_DOWN]:
if not self.pino.is_jump:
self.pino.squat()
else:
self.pino.run()
if __name__ == "__main__":
game = Game()
game.start_game()
游戏素材