这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。
为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是
基于python的谷歌恐龙小游戏设计与实现
学长这里给一个题目综合评分(每项满分5分)
选题指导, 项目分享:
https://gitee.com/dancheng-senior/project-sharing-1/blob/master/%E6%AF%95%E8%AE%BE%E6%8C%87%E5%AF%BC/README.md
几年前,Google 给 Chrome 浏览器加了一个有趣的彩蛋:如果你在未联网的情况下访问网页,会看到 “Unable to connect to the Internet” 或 “No internet” 的提示,旁边是一只像素恐龙。
许多人可能觉得这只恐龙只是一个可爱的小图标,在断网的时候陪伴用户。但是后来有人按下空格键,小恐龙开始奔跑!
这个小彩蛋成为深受人们喜爱的小游戏,风靡至今。
今天我们用pygame做一个仿谷歌浏览器小恐龙游戏,游戏规则如下:
玩家通过↑↓控制小恐龙,如果玩家按下了空格键或者↑键,则小恐龙跳跃,如果玩家按下了↓键,则小恐龙低头,否则小恐龙正常向前冲。当小恐龙碰到这些障碍物时,小恐龙就死掉了,本局游戏也随之结束。
简介
Pygame是一系列专门为编写电子游戏而设计的Python模块(modules)。Pygame在已经非常优秀的SDL库的基础上增加了许多功能。这让你能够用Python语言编写出丰富多彩的游戏程序。
Pygame可移植性高,几乎能在任何平台和操作系统上运行。
Pygame已经被下载过数百万次。
Pygame免费开源。它在LGPL许可证(Lesser General Public License,GNU宽通用公共许可证)下发行。使用Pygame,你可以创造出免费开源,可共享,或者商业化的游戏。详情请见LGPL许可证。
优点
能够轻松使用多核CPU(multi core CPUs) :如今双核CPU很常用,8核CPU在桌面系统中也很便宜,而利用好多核系统,能让你在你的游戏中实现更多东西。特定的pygame函数能够释放令人生畏的python GIL(全局解释器锁),这几乎是你用C语言才能做的事。
核心函数用最优化的C语言或汇编语言编写:C语言代码通常比Python代码运行速度快10-20倍。而汇编语言编写的代码(assembly code)比Python甚至快到100多倍。
安装便捷:一般仅需包管理程序或二进制系统程序便能安装。
真正地可移植:支持Linux (主要发行版), Windows (95, 98, ME, 2000, XP, Vista, 64-bit Windows,), Windows CE, BeOS, MacOS, Mac OS X, FreeBSD, NetBSD, OpenBSD, BSD/OS, Solaris, IRIX, and QNX等操作系统.也能支持AmigaOS, Dreamcast, Atari, AIX, OSF/Tru64, RISC OS, SymbianOS and OS/2,但是还没有受到官方认可。你也可以在手持设备,游戏控制台, One Laptop Per Child (OLPC) computer项目的电脑等设备中使用pygame.
用法简单:无论是小孩子还是大人都能学会用pygame来制作射击类游戏。
很多Pygame游戏已发行:其中包括很多游戏大赛入围作品、非常受欢迎的开源可分享的游戏。
由你来控制主循环:由你来调用pygame的函数,pygame的函数并不需要调用你的函数。当你同时还在使用其他库来编写各种各种的程序时,这能够为你提供极大的掌控权。
不需要GUI就能使用所有函数:仅在命令行中,你就可以使用pygame的某些函数来处理图片,获取游戏杆输入,播放音乐……
对bug反应迅速:很多bug在被上报的1小时内就能被我们修复。虽然有时候我们确实会卡在某一个bug上很久,但大多数时候我们都是很不错的bug修复者。如今bug的上报已经很少了,因为许多bug早已被我们修复。
代码量少:pygame并没有数以万计的也许你永远用不到的冗杂代码。pygame的核心代码一直保持着简洁特点,其他附加物诸如GUI库等,都是在核心代码之外单独设计研发的。
模块化:你可以单独使用pygame的某个模块。想要换着使用一个别的声音处理库?没问题。pygame的很多核心模块支持独立初始化与使用。
最小开发框架
import pygame,sys #sys是python的标准库,提供Python运行时环境变量的操控
pygame.init() #内部各功能模块进行初始化创建及变量设置,默认调用
size = width,height = 800,600 #设置游戏窗口大小,分别是宽度和高度
screen = pygame.display.set_mode(size) #初始化显示窗口
pygame.display.set_caption("小游戏程序") #设置显示窗口的标题内容,是一个字符串类型
while True: #无限循环,直到Python运行时退出结束
for event in pygame.event.get(): #从Pygame的事件队列中取出事件,并从队列中删除该事件
if event.type == pygame.QUIT: #获得事件类型,并逐类响应
sys.exit() #用于退出结束游戏并退出
pygame.display.update() #对显示窗口进行更新,默认窗口全部重绘
代码执行流程
# 游戏初始化
pygame.init()
screen = pygame.display.set_mode(cfg.SCREENSIZE)
pygame.display.set_caption('T-Rex Rush —— Charles的皮卡丘')
# 导入所有声音文件
sounds = {}
for key, value in cfg.AUDIO_PATHS.items():
sounds[key] = pygame.mixer.Sound(value)
首先,我们来明确一下该游戏包含哪些游戏精灵类:
小恐龙:由玩家控制以躲避路上的障碍物;
路面:游戏的背景;
云:游戏的背景;
飞龙:路上的障碍物之一,小恐龙碰上就会死掉;
仙人掌:路上的障碍物之一,小恐龙碰上就会死掉;
记分板:记录当前的分数和历史最高分。
云,路面以及仙人掌来说,定义起来很简单,我们只需要加载对应的游戏元素图片:
'''地板'''
class Ground(pygame.sprite.Sprite):
def __init__(self, imagepath, position, **kwargs):
pygame.sprite.Sprite.__init__(self)
# 导入图片
self.image_0 = pygame.image.load(imagepath)
self.rect_0 = self.image_0.get_rect()
self.rect_0.left, self.rect_0.bottom = position
self.image_1 = pygame.image.load(imagepath)
self.rect_1 = self.image_1.get_rect()
self.rect_1.left, self.rect_1.bottom = self.rect_0.right, self.rect_0.bottom
# 定义一些必要的参数
self.speed = -10
'''更新地板'''
def update(self):
self.rect_0.left += self.speed
self.rect_1.left += self.speed
if self.rect_0.right < 0:
self.rect_0.left = self.rect_1.right
if self.rect_1.right < 0:
self.rect_1.left = self.rect_0.right
'''将地板画到屏幕'''
def draw(self, screen):
screen.blit(self.image_0, self.rect_0)
screen.blit(self.image_1, self.rect_1)
'''云'''
class Cloud(pygame.sprite.Sprite):
def __init__(self, imagepath, position, **kwargs):
pygame.sprite.Sprite.__init__(self)
# 导入图片
self.image = pygame.image.load(imagepath)
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
# 定义一些必要的参数
self.speed = -1
'''将云画到屏幕上'''
def draw(self, screen):
screen.blit(self.image, self.rect)
'''更新云'''
def update(self):
self.rect = self.rect.move([self.speed, 0])
if self.rect.right < 0:
self.kill()
'''仙人掌'''
class Cactus(pygame.sprite.Sprite):
def __init__(self, imagepaths, position=(600, 147), sizes=[(40, 40), (40, 40)], **kwargs):
pygame.sprite.Sprite.__init__(self)
# 导入图片
self.images = []
image = pygame.image.load(imagepaths[0])
for i in range(3):
self.images.append(pygame.transform.scale(image.subsurface((i*101, 0), (101, 101)), sizes[0]))
image = pygame.image.load(imagepaths[1])
for i in range(3):
self.images.append(pygame.transform.scale(image.subsurface((i*68, 0), (68, 70)), sizes[1]))
self.image = random.choice(self.images)
self.rect = self.image.get_rect()
self.rect.left, self.rect.bottom = position
self.mask = pygame.mask.from_surface(self.image)
# 定义一些必要的变量
self.speed = -10
'''画到屏幕上'''
def draw(self, screen):
screen.blit(self.image, self.rect)
'''更新'''
def update(self):
self.rect = self.rect.move([self.speed, 0])
if self.rect.right < 0:
self.kill()
记分板的定义类似,只不过它不需要移动,但是需要实时地更新当前 的分数:
'''记分板'''
class Scoreboard(pygame.sprite.Sprite):
def __init__(self, imagepath, position, size=(11, 13), is_highest=False, bg_color=None, **kwargs):
pygame.sprite.Sprite.__init__(self)
# 导入图片
self.images = []
image = pygame.image.load(imagepath)
for i in range(12):
self.images.append(pygame.transform.scale(image.subsurface((i*20, 0), (20, 24)), size))
if is_highest:
self.image = pygame.Surface((size[0]*8, size[1]))
else:
self.image = pygame.Surface((size[0]*5, size[1]))
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
# 一些必要的变量
self.is_highest = is_highest
self.bg_color = bg_color
self.score = '00000'
'''设置得分'''
def set(self, score):
self.score = str(score).zfill(5)
'''画到屏幕上'''
def draw(self, screen):
self.image.fill(self.bg_color)
for idx, digital in enumerate(list(self.score)):
digital_image = self.images[int(digital)]
if self.is_highest:
self.image.blit(digital_image, ((idx+3)*digital_image.get_rect().width, 0))
else:
self.image.blit(digital_image, (idx*digital_image.get_rect().width, 0))
if self.is_highest:
self.image.blit(self.images[-2], (0, 0))
self.image.blit(self.images[-1], (digital_image.get_rect().width, 0))
screen.blit(self.image, self.rect)
上面代码用is_highest变量来区分该记分板是否用于记录游戏最高分,还是只是记录当前的分数,做该区分的原因是游戏最高分前面有HI标识,所以占的空间更大:
飞龙的定义就稍微复杂一些了,因为它不仅需要向左移动,还需要做出不停扇动翅膀的效果。具体而言,飞龙有两张图:
你需要做的就是每隔一段时间就切换一次当前的飞龙图片,以实现飞龙扇动翅膀的效果:
'''飞龙'''
class Ptera(pygame.sprite.Sprite):
def __init__(self, imagepath, position, size=(46, 40), **kwargs):
pygame.sprite.Sprite.__init__(self)
# 导入图片
self.images = []
image = pygame.image.load(imagepath)
for i in range(2):
self.images.append(pygame.transform.scale(image.subsurface((i*92, 0), (92, 81)), size))
self.image_idx = 0
self.image = self.images[self.image_idx]
self.rect = self.image.get_rect()
self.rect.left, self.rect.centery = position
self.mask = pygame.mask.from_surface(self.image)
# 定义一些必要的变量
self.speed = -10
self.refresh_rate = 10
self.refresh_counter = 0
'''画到屏幕上'''
def draw(self, screen):
screen.blit(self.image, self.rect)
'''更新'''
def update(self):
if self.refresh_counter % self.refresh_rate == 0:
self.refresh_counter = 0
self.image_idx = (self.image_idx + 1) % len(self.images)
self.loadImage()
self.rect = self.rect.move([self.speed, 0])
if self.rect.right < 0:
self.kill()
self.refresh_counter += 1
'''载入当前状态的图片'''
def loadImage(self):
self.image = self.images[self.image_idx]
rect = self.image.get_rect()
rect.left, rect.top = self.rect.left, self.rect.top
self.rect = rect
self.mask = pygame.mask.from_surface(self.image)
最后,我们需要定义一下小恐龙类,也就是最复杂的一个游戏精灵类。它有低头,跳跃,普通前进三种状态。对于低头来说:
你只需要和飞龙扇动翅膀一样,不断切换两张低头的图片以实现小恐龙跑动的效果就可以了。
对于普通状态也是类似的:
对于跳跃状态,我们则可以通过初中学的上抛和自由落体运动公式来建模,从而计算小恐龙在竖直方向上的位置。具体而言,代码实现如下:
'''小恐龙'''
class Dinosaur(pygame.sprite.Sprite):
def __init__(self, imagepaths, position=(40, 147), size=[(44, 47), (59, 47)], **kwargs):
pygame.sprite.Sprite.__init__(self)
# 导入所有图片
self.images = []
image = pygame.image.load(imagepaths[0])
for i in range(5):
self.images.append(pygame.transform.scale(image.subsurface((i*88, 0), (88, 95)), size[0]))
image = pygame.image.load(imagepaths[1])
for i in range(2):
self.images.append(pygame.transform.scale(image.subsurface((i*118, 0), (118, 95)), size[1]))
self.image_idx = 0
self.image = self.images[self.image_idx]
self.rect = self.image.get_rect()
self.rect.left, self.rect.bottom = position
self.mask = pygame.mask.from_surface(self.image)
# 定义一些必要的变量
self.init_position = position
self.refresh_rate = 5
self.refresh_counter = 0
self.speed = 11.5
self.gravity = 0.6
self.is_jumping = False
self.is_ducking = False
self.is_dead = False
self.movement = [0, 0]
'''跳跃'''
def jump(self, sounds):
if self.is_dead or self.is_jumping:
return
sounds['jump'].play()
self.is_jumping = True
self.movement[1] = -1 * self.speed
'''低头'''
def duck(self):
if self.is_jumping or self.is_dead:
return
self.is_ducking = True
'''不低头'''
def unduck(self):
self.is_ducking = False
'''死掉了'''
def die(self, sounds):
if self.is_dead:
return
sounds['die'].play()
self.is_dead = True
'''将恐龙画到屏幕'''
def draw(self, screen):
screen.blit(self.image, self.rect)
'''载入当前状态的图片'''
def loadImage(self):
self.image = self.images[self.image_idx]
rect = self.image.get_rect()
rect.left, rect.top = self.rect.left, self.rect.top
self.rect = rect
self.mask = pygame.mask.from_surface(self.image)
'''更新小恐龙'''
def update(self):
if self.is_dead:
self.image_idx = 4
self.loadImage()
return
if self.is_jumping:
self.movement[1] += self.gravity
self.image_idx = 0
self.loadImage()
self.rect = self.rect.move(self.movement)
if self.rect.bottom >= self.init_position[1]:
self.rect.bottom = self.init_position[1]
self.is_jumping = False
elif self.is_ducking:
if self.refresh_counter % self.refresh_rate == 0:
self.refresh_counter = 0
self.image_idx = 5 if self.image_idx == 6 else 6
self.loadImage()
else:
if self.refresh_counter % self.refresh_rate == 0:
self.refresh_counter = 0
if self.image_idx == 1:
self.image_idx = 2
elif self.image_idx == 2:
self.image_idx = 3
else:
self.image_idx = 1
self.loadImage()
self.refresh_counter += 1
最后写游戏主循环
游戏主循环的逻辑很简单,即每帧游戏画面,我们都需要检测一下玩家的操作,如果玩家按下了空格键或者↑键,则小恐龙跳跃,如果玩家按下了↓键,则小恐龙低头,否则小恐龙正常向前冲。
然后在游戏中,我们随机产生云,飞龙和仙人掌这些游戏场景和障碍物,并且和路面一起以相同的速度向左移动,从而实现小恐龙向右移动的视觉效果。在移动的过程中,我们需要对小恐龙和仙人掌,小恐龙和飞龙进行碰撞检测,当小恐龙碰到这些障碍物时,小恐龙就死掉了,本局游戏也随之结束。
需要注意的是我们应该使用collide_mask函数来进行更为精确的碰撞检测,而不是之前的collide_rect函数:
即当两个目标的最小外接矩形有重叠时,collide_rect就会判定两个目标有碰撞,这显然是不合理的,会给玩家带来较差的游戏体验。
另外,当分数每提高一千分,我们就和原版的游戏一样增加一点场景和障碍物向左移动的速度(也就是增加小恐龙向右移动的速度)。
最后,把当前所有的游戏元素绑定到屏幕上并更新当前的屏幕就ok了。
# 游戏主循环
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE or event.key == pygame.K_UP:
dino.jump(sounds)
elif event.key == pygame.K_DOWN:
dino.duck()
elif event.type == pygame.KEYUP and event.key == pygame.K_DOWN:
dino.unduck()
screen.fill(cfg.BACKGROUND_COLOR)
# --随机添加云
if len(cloud_sprites_group) < 5 and random.randrange(0, 300) == 10:
cloud_sprites_group.add(Cloud(cfg.IMAGE_PATHS['cloud'], position=(cfg.SCREENSIZE[0], random.randrange(30, 75))))
# --随机添加仙人掌/飞龙
add_obstacle_timer += 1
if add_obstacle_timer > random.randrange(50, 150):
add_obstacle_timer = 0
random_value = random.randrange(0, 10)
if random_value >= 5 and random_value <= 7:
cactus_sprites_group.add(Cactus(cfg.IMAGE_PATHS['cacti']))
else:
position_ys = [cfg.SCREENSIZE[1]*0.82, cfg.SCREENSIZE[1]*0.75, cfg.SCREENSIZE[1]*0.60, cfg.SCREENSIZE[1]*0.20]
ptera_sprites_group.add(Ptera(cfg.IMAGE_PATHS['ptera'], position=(600, random.choice(position_ys))))
# --更新游戏元素
dino.update()
ground.update()
cloud_sprites_group.update()
cactus_sprites_group.update()
ptera_sprites_group.update()
score_timer += 1
if score_timer > (cfg.FPS//12):
score_timer = 0
score += 1
score = min(score, 99999)
if score > highest_score:
highest_score = score
if score % 100 == 0:
sounds['point'].play()
if score % 1000 == 0:
ground.speed -= 1
for item in cloud_sprites_group:
item.speed -= 1
for item in cactus_sprites_group:
item.speed -= 1
for item in ptera_sprites_group:
item.speed -= 1
# --碰撞检测
for item in cactus_sprites_group:
if pygame.sprite.collide_mask(dino, item):
dino.die(sounds)
for item in ptera_sprites_group:
if pygame.sprite.collide_mask(dino, item):
dino.die(sounds)
# --将游戏元素画到屏幕上
dino.draw(screen)
ground.draw(screen)
cloud_sprites_group.draw(screen)
cactus_sprites_group.draw(screen)
ptera_sprites_group.draw(screen)
score_board.set(score)
highest_score_board.set(highest_score)
score_board.draw(screen)
highest_score_board.draw(screen)
# --更新屏幕
pygame.display.update()
clock.tick(cfg.FPS)
# --游戏是否结束
if dino.is_dead:
break