利用Python模拟谷歌的小恐龙游戏

 更新时间:2021年12月03日 15:11:35   作者:酸菜鱼编程  
谷歌流量器中有个很有名的彩蛋:当你网络出现问题时,就会出现一个“小恐龙游戏”。本文就主要为大家介绍了如何用Python模拟实现这一小游戏,感兴趣的同学可以学习一下

前言

本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理。

PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取

谷歌流量器中有个很有名的彩蛋:当你网络出现问题时,就会出现一个“小恐龙游戏”。

(如果想要直接进行游戏,可以在地址栏输入:chrome://dino)

今天我们就来给大家演示下,用Python来自己做一个仿制的“小恐龙游戏”!

废话不多说,让我们愉快地开始吧~

开发工具

Python版本:3.6.4

相关模块:

pygame模块;以及一些python自带的模块。

环境搭建

安装Python并添加到环境变量,pip安装需要的相关模块即可。

先睹为快

在终端运行如下命令即可:

python Game7.py

效果如下:

代码介绍

这里介绍一下游戏的实现原理。

首先,我们对游戏进行一些必要的初始化工作:

 
# 游戏初始化
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)

接着,我们来考虑一下,游戏中有哪些游戏元素:

小恐龙:由玩家控制以躲避路上的障碍物;

路面:游戏的背景;

云:游戏的背景;

飞龙:路上的障碍物之一,小恐龙碰上就会死掉;

仙人掌:路上的障碍物之一,小恐龙碰上就会死掉;

记分板:记录当前的分数和历史最高分。

让我们来依次定义一下这些游戏元素类。对于云,路面以及仙人掌来说,定义起来很简单,我们只需要加载对应的游戏元素图片:

然后写两个类内部方法update和draw就ok了。两个方法分别用于将场景不断向左移动以实现小恐龙不断向前移动的动画效果和将场景显示在游戏界面的对应位置上。具体而言,代码实现如下:

 
'''地板'''
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

定义完游戏精灵类,我们就可以实例化他们:

 
# 定义一些游戏中必要的元素和变量
score = 0
score_board = Scoreboard(cfg.IMAGE_PATHS['numbers'], position=(534, 15), bg_color=cfg.BACKGROUND_COLOR)
highest_score = highest_score
highest_score_board = Scoreboard(cfg.IMAGE_PATHS['numbers'], position=(435, 15), bg_color=cfg.BACKGROUND_COLOR, is_highest=True)
dino = Dinosaur(cfg.IMAGE_PATHS['dino'])
ground = Ground(cfg.IMAGE_PATHS['ground'], position=(0, cfg.SCREENSIZE[1]))
cloud_sprites_group = pygame.sprite.Group()
cactus_sprites_group = pygame.sprite.Group()
ptera_sprites_group = pygame.sprite.Group()
add_obstacle_timer = 0
score_timer = 0

然后写游戏主循环啦:

 
# 游戏主循环
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

游戏主循环的逻辑很简单,即每帧游戏画面,我们都需要检测一下玩家的操作,如果玩家按下了空格键或者↑键,则小恐龙跳跃,如果玩家按下了↓键,则小恐龙低头,否则小恐龙正常向前冲。

然后在游戏中,我们随机产生云,飞龙和仙人掌这些游戏场景和障碍物,并且和路面一起以相同的速度向左移动,从而实现小恐龙向右移动的视觉效果。在移动的过程中,我们需要对小恐龙和仙人掌,小恐龙和飞龙进行碰撞检测,当小恐龙碰到这些障碍物时,小恐龙就死掉了,本局游戏也随之结束。

需要注意的是我们应该使用collide_mask函数来进行更为精确的碰撞检测,而不是之前的collide_rect函数:

即当两个目标的最小外接矩形有重叠时,collide_rect就会判定两个目标有碰撞,这显然是不合理的,会给玩家带来较差的游戏体验。

另外,当分数每提高一千分,我们就和原版的游戏一样增加一点场景和障碍物向左移动的速度(也就是增加小恐龙向右移动的速度)。

最后,把当前所有的游戏元素绑定到屏幕上并更新当前的屏幕就ok了。

大概就是这样,大功告成完整源代码详见相关文件呗

GitHub地址

以上就是利用Python模拟谷歌的小恐龙游戏的详细内容,更多关于Python 游戏的资料请关注脚本之家其它相关文章!

相关文章

  • Python 结巴分词实现关键词抽取分析

    Python 结巴分词实现关键词抽取分析

    这篇文章主要介绍了Python 结巴分词实现关键词抽取分析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • Python Merge函数原理及用法解析

    Python Merge函数原理及用法解析

    这篇文章主要介绍了Python Merge函数原理及用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • 利用Python第三方库xlwt写入数据到Excel工作表实例代码

    利用Python第三方库xlwt写入数据到Excel工作表实例代码

    大家应该都知道xlwt是python中写入到excel的库,下面这篇文章主要给大家介绍了关于利用Python第三方库xlwt写入数据到Excel工作表的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • 五分钟学会怎么用python做一个简单的贪吃蛇

    五分钟学会怎么用python做一个简单的贪吃蛇

    这篇文章主要介绍了五分钟学会怎么用python做一个简单的贪吃蛇,帮助大家更好的理解和使用python,感兴趣的朋友可以了解下
    2021-01-01
  • Python生成不重复随机值的方法

    Python生成不重复随机值的方法

    这篇文章主要介绍了Python生成不重复随机值的方法,实例分析了Python算法实现与Python自带方法的实现技巧,非常简单实用,需要的朋友可以参考下
    2015-05-05
  • Python直接赋值、浅拷贝与深度拷贝实例分析

    Python直接赋值、浅拷贝与深度拷贝实例分析

    这篇文章主要介绍了Python直接赋值、浅拷贝与深度拷贝,结合实例形式分析了Python直接赋值、浅拷贝与深度拷贝的概念、原理、用法及相关操作注意事项,需要的朋友可以参考下
    2019-06-06
  • 解密Python中的作用域与名字空间

    解密Python中的作用域与名字空间

    名字空间对于 Python 来说是一个非常重要的概念,并且与名字空间这个概念紧密联系在一起的还有名字、作用域这些概念,下面就来剖析这些概念是如何体现的
    2023-02-02
  • Python datacompy 找出两个DataFrames不同的地方

    Python datacompy 找出两个DataFrames不同的地方

    本文主要介绍了Python datacompy 找出两个DataFrames不同的地方,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧<BR>
    2022-05-05
  • 详解Python yaml模块

    详解Python yaml模块

    这篇文章主要介绍了Python yaml模块的相关资料,帮助大家更好的理解和学习python,感兴趣的朋友可以了解下
    2020-09-09
  • pycharm2021激活码使用教程(永久激活亲测可用)

    pycharm2021激活码使用教程(永久激活亲测可用)

    pycharm2021激活码是一个可以轻松帮助用户免费激活pycharm2021.1软件的文件,虽然说pycharm现在只是推出了2021.1的EAP版,但是如果你想先率先体验一波,那么就可以利用小编提供的这个激活码来进行使用啦,并这个激活码是永久有效的
    2021-03-03

最新评论