利用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 遊戲的資料請關註WalkonNet其它相關文章!

推薦閱讀: