基於Python實現炸彈人小遊戲

前言

今天用Python實現的是一個炸彈人小遊戲,廢話不多說,讓我們愉快地開始吧~

效果展示

開發工具

Python版本: 3.6.4

相關模塊:

pygame模塊;

以及一些Python自帶的模塊。

環境搭建

安裝Python並添加到環境變量,pip安裝需要的相關模塊即可。

原理簡介

遊戲規則:

玩傢通過↑↓←→鍵控制角色zelda(綠色)行動,當玩傢按下空格鍵時,則可以在當前位置放置炸彈。其他角色(dk和batman)則由電腦控制進行隨機行動。所有角色被炸彈產生的火焰灼燒時(包括自己放置的炸彈),都將損失一定生命值;所有角色吃到水果時,均可恢復一定數值的生命值。另外,墻可以阻止炸彈產生的火焰進一步擴散。

當我方角色zelda生命值為0時,遊戲失敗;當電腦方所有角色生命值為0時,遊戲勝利,進入下一關。

逐步實現:

首先,我們來明確一下該遊戲包含哪些遊戲精靈類:

  • 炸彈類
  • 角色類
  • 墻類
  • 背景類
  • 水果類

主要代碼

墻類和背景類很好定義,隻需要可以導入圖片,然後把圖片綁定到指定位置就行瞭:

'''墻類'''
class Wall(pygame.sprite.Sprite):
  def __init__(self, imagepath, coordinate, blocksize, **kwargs):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.image.load(imagepath)
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.rect = self.image.get_rect()
    self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize
    self.coordinate = coordinate
    self.blocksize = blocksize
  '''畫到屏幕上'''
  def draw(self, screen):
    screen.blit(self.image, self.rect)
    return True


'''背景類'''
class Background(pygame.sprite.Sprite):
  def __init__(self, imagepath, coordinate, blocksize, **kwargs):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.image.load(imagepath)
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.rect = self.image.get_rect()
    self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize
    self.coordinate = coordinate
    self.blocksize = blocksize
  '''畫到屏幕上'''
  def draw(self, screen):
    screen.blit(self.image, self.rect)
    return True 

水果類定義其實也差不多,但是不同的水果可以幫助角色恢復不同數值的生命值:

'''水果類'''
class Fruit(pygame.sprite.Sprite):
  def __init__(self, imagepath, coordinate, blocksize, **kwargs):
    pygame.sprite.Sprite.__init__(self)
    self.kind = imagepath.split('/')[-1].split('.')[0]
    if self.kind == 'banana':
      self.value = 5
    elif self.kind == 'cherry':
      self.value = 10
    else:
      raise ValueError('Unknow fruit <%s>...' % self.kind)
    self.image = pygame.image.load(imagepath)
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.rect = self.image.get_rect()
    self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize
    self.coordinate = coordinate
    self.blocksize = blocksize
  '''畫到屏幕上'''
  def draw(self, screen):
    screen.blit(self.image, self.rect)
    return True

炸彈類和角色類的定義就稍稍復雜一些瞭。角色類需要根據玩傢或者電腦的指示上下左右移動,同時可以在自己的位置上產生炸彈以及吃水果之後恢復一定數值的生命值:

'''角色類'''
class Hero(pygame.sprite.Sprite):
  def __init__(self, imagepaths, coordinate, blocksize, map_parser, **kwargs):
    pygame.sprite.Sprite.__init__(self)
    self.imagepaths = imagepaths
    self.image = pygame.image.load(imagepaths[-1])
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.rect = self.image.get_rect()
    self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize
    self.coordinate = coordinate
    self.blocksize = blocksize
    self.map_parser = map_parser
    self.hero_name = kwargs.get('hero_name')
    # 生命值
    self.health_value = 50
    # 炸彈冷卻時間
    self.bomb_cooling_time = 5000
    self.bomb_cooling_count = 0
    # 隨機移動冷卻時間(僅AI電腦用)
    self.randommove_cooling_time = 100
    self.randommove_cooling_count = 0
  '''角色移動'''
  def move(self, direction):
    self.__updateImage(direction)
    if direction == 'left':
      if self.coordinate[0]-1 < 0 or self.map_parser.getElemByCoordinate([self.coordinate[0]-1, self.coordinate[1]]) in ['w', 'x', 'z']:
        return False
      self.coordinate[0] = self.coordinate[0] - 1
    elif direction == 'right':
      if self.coordinate[0]+1 >= self.map_parser.width or self.map_parser.getElemByCoordinate([self.coordinate[0]+1, self.coordinate[1]]) in ['w', 'x', 'z']:
        return False
      self.coordinate[0] = self.coordinate[0] + 1
    elif direction == 'up':
      if self.coordinate[1]-1 < 0 or self.map_parser.getElemByCoordinate([self.coordinate[0], self.coordinate[1]-1]) in ['w', 'x', 'z']:
        return False
      self.coordinate[1] = self.coordinate[1] - 1
    elif direction == 'down':
      if self.coordinate[1]+1 >= self.map_parser.height or self.map_parser.getElemByCoordinate([self.coordinate[0], self.coordinate[1]+1]) in ['w', 'x', 'z']:
        return False
      self.coordinate[1] = self.coordinate[1] + 1
    else:
      raise ValueError('Unknow direction <%s>...' % direction)
    self.rect.left, self.rect.top = self.coordinate[0] * self.blocksize, self.coordinate[1] * self.blocksize
    return True
  '''隨機行動(AI電腦用)'''
  def randomAction(self, dt):
    # 冷卻倒計時
    if self.randommove_cooling_count > 0:
      self.randommove_cooling_count -= dt
    action = random.choice(['left', 'left', 'right', 'right', 'up', 'up', 'down', 'down', 'dropbomb'])
    flag = False
    if action in ['left', 'right', 'up', 'down']:
      if self.randommove_cooling_count <= 0:
        flag = True
        self.move(action)
        self.randommove_cooling_count = self.randommove_cooling_time
    elif action in ['dropbomb']:
      if self.bomb_cooling_count <= 0:
        flag = True
        self.bomb_cooling_count = self.bomb_cooling_time
    return action, flag
  '''生成炸彈'''
  def generateBomb(self, imagepath, digitalcolor, explode_imagepath):
    return Bomb(imagepath=imagepath, coordinate=copy.deepcopy(self.coordinate), blocksize=self.blocksize, digitalcolor=digitalcolor, explode_imagepath=explode_imagepath)
  '''畫到屏幕上'''
  def draw(self, screen, dt):
    # 冷卻倒計時
    if self.bomb_cooling_count > 0:
      self.bomb_cooling_count -= dt
    screen.blit(self.image, self.rect)
    return True
  '''吃水果'''
  def eatFruit(self, fruit_sprite_group):
    eaten_fruit = pygame.sprite.spritecollide(self, fruit_sprite_group, True, None)
    for fruit in eaten_fruit:
      self.health_value += fruit.value
  '''更新角色朝向'''
  def __updateImage(self, direction):
    directions = ['left', 'right', 'up', 'down']
    idx = directions.index(direction)
    self.image = pygame.image.load(self.imagepaths[idx])
    self.image = pygame.transform.scale(self.image, (self.blocksize, self.blocksize))

炸彈類則需要有倒計時提示功能,以及倒計時結束之後在炸彈殺傷范圍內產生火焰特效(窮,估計隻值1毛錢的特效T_T,大傢多擔待):

'''炸彈類'''
class Bomb(pygame.sprite.Sprite):
  def __init__(self, imagepath, coordinate, blocksize, digitalcolor, explode_imagepath, **kwargs):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.image.load(imagepath)
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.explode_imagepath = explode_imagepath
    self.rect = self.image.get_rect()
    # 像素位置
    self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize
    # 坐標(元素塊為單位長度)
    self.coordinate = coordinate
    self.blocksize = blocksize
    # 爆炸倒計時
    self.explode_millisecond = 6000 * 1 - 1
    self.explode_second = int(self.explode_millisecond / 1000)
    self.start_explode = False
    # 爆炸持續時間
    self.exploding_count = 1000 * 1
    # 炸彈傷害能力
    self.harm_value = 1
    # 該炸彈是否還存在
    self.is_being = True
    self.font = pygame.font.SysFont('Consolas', 20)
    self.digitalcolor = digitalcolor
  '''畫到屏幕上'''
  def draw(self, screen, dt, map_parser):
    if not self.start_explode:
      # 爆炸倒計時
      self.explode_millisecond -= dt
      self.explode_second = int(self.explode_millisecond / 1000)
      if self.explode_millisecond < 0:
        self.start_explode = True
      screen.blit(self.image, self.rect)
      text = self.font.render(str(self.explode_second), True, self.digitalcolor)
      rect = text.get_rect(center=(self.rect.centerx-5, self.rect.centery+5))
      screen.blit(text, rect)
      return False
    else:
      # 爆炸持續倒計時
      self.exploding_count -= dt
      if self.exploding_count > 0:
        return self.__explode(screen, map_parser)
      else:
        self.is_being = False
        return False
  '''爆炸效果'''
  def __explode(self, screen, map_parser):
    explode_area = self.__calcExplodeArea(map_parser.instances_list)
    for each in explode_area:
      image = pygame.image.load(self.explode_imagepath)
      image = pygame.transform.scale(image, (self.blocksize, self.blocksize))
      rect = image.get_rect()
      rect.left, rect.top = each[0] * self.blocksize, each[1] * self.blocksize
      screen.blit(image, rect)
    return explode_area
  '''計算爆炸區域'''
  def __calcExplodeArea(self, instances_list):
    explode_area = []
    # 區域計算規則為墻可以阻止爆炸擴散, 且爆炸范圍僅在遊戲地圖范圍內
    for ymin in range(self.coordinate[1], self.coordinate[1]-5, -1):
      if ymin < 0 or instances_list[ymin][self.coordinate[0]] in ['w', 'x', 'z']:
        break
      explode_area.append([self.coordinate[0], ymin])
    for ymax in range(self.coordinate[1]+1, self.coordinate[1]+5):
      if ymax >= len(instances_list) or instances_list[ymax][self.coordinate[0]] in ['w', 'x', 'z']:
        break
      explode_area.append([self.coordinate[0], ymax])
    for xmin in range(self.coordinate[0], self.coordinate[0]-5, -1):
      if xmin < 0 or instances_list[self.coordinate[1]][xmin] in ['w', 'x', 'z']:
        break
      explode_area.append([xmin, self.coordinate[1]])
    for xmax in range(self.coordinate[0]+1, self.coordinate[0]+5):
      if xmax >= len(instances_list[0]) or instances_list[self.coordinate[1]][xmax] in ['w', 'x', 'z']:
        break
      explode_area.append([xmax, self.coordinate[1]])
    return explode_area

因為炸彈類和角色類每幀都要綁定到遊戲屏幕上,所以一些倒計時操作就合並地寫到draw函數裡瞭,當然最好是重新寫一個函數來實現該功能,那樣代碼結構看起來會更清晰一些。

接下來,我們在.map文件中設計我們的遊戲地圖:

然後通過一個地圖解析類來解析.map文件,這樣每次切換關卡時隻需要重新導入一個新的.map文件就行瞭,同時這樣也方便遊戲後續進行擴展:

'''.map文件解析器'''
class mapParser():
  def __init__(self, mapfilepath, bg_paths, wall_paths, blocksize, **kwargs):
    self.instances_list = self.__parse(mapfilepath)
    self.bg_paths = bg_paths
    self.wall_paths = wall_paths
    self.blocksize = blocksize
    self.height = len(self.instances_list)
    self.width = len(self.instances_list[0])
    self.screen_size = (blocksize * self.width, blocksize * self.height)
  '''地圖畫到屏幕上'''
  def draw(self, screen):
    for j in range(self.height):
      for i in range(self.width):
        instance = self.instances_list[j][i]
        if instance == 'w':
          elem = Wall(self.wall_paths[0], [i, j], self.blocksize)
        elif instance == 'x':
          elem = Wall(self.wall_paths[1], [i, j], self.blocksize)
        elif instance == 'z':
          elem = Wall(self.wall_paths[2], [i, j], self.blocksize)
        elif instance == '0':
          elem = Background(self.bg_paths[0], [i, j], self.blocksize)
        elif instance == '1':
          elem = Background(self.bg_paths[1], [i, j], self.blocksize)
        elif instance == '2':
          elem = Background(self.bg_paths[2], [i, j], self.blocksize)
        else:
          raise ValueError('instance parse error in mapParser.draw...')
        elem.draw(screen)
  '''隨機獲取一個空地'''
  def randomGetSpace(self, used_spaces=None):
    while True:
      i = random.randint(0, self.width-1)
      j = random.randint(0, self.height-1)
      coordinate = [i, j]
      if used_spaces and coordinate in used_spaces:
        continue
      instance = self.instances_list[j][i]
      if instance in ['0', '1', '2']:
        break
    return coordinate
  '''根據坐標獲取元素類型'''
  def getElemByCoordinate(self, coordinate):
    return self.instances_list[coordinate[1]][coordinate[0]]
  '''解析.map文件'''
  def __parse(self, mapfilepath):
    instances_list = []
    with open(mapfilepath) as f:
      for line in f.readlines():
        instances_line_list = []
        for c in line:
          if c in ['w', 'x', 'z', '0', '1', '2']:
            instances_line_list.append(c)
        instances_list.append(instances_line_list)
    return instances_list

OK,做完這些準備工作,就可以開始寫遊戲主循環啦: 

'''遊戲主程序'''
def main(cfg):
  # 初始化
  pygame.init()
  pygame.mixer.init()
  pygame.mixer.music.load(cfg.BGMPATH)
  pygame.mixer.music.play(-1, 0.0)
  screen = pygame.display.set_mode(cfg.SCREENSIZE)
  pygame.display.set_caption('Bomber Man - 🛰️: apython68')
  # 開始界面
  Interface(screen, cfg, mode='game_start')
  # 遊戲主循環
  font = pygame.font.SysFont('Consolas', 15)
  for gamemap_path in cfg.GAMEMAPPATHS:
    # -地圖
    map_parser = mapParser(gamemap_path, bg_paths=cfg.BACKGROUNDPATHS, wall_paths=cfg.WALLPATHS, blocksize=cfg.BLOCKSIZE)
    # -水果
    fruit_sprite_group = pygame.sprite.Group()
    used_spaces = []
    for i in range(5):
      coordinate = map_parser.randomGetSpace(used_spaces)
      used_spaces.append(coordinate)
      fruit_sprite_group.add(Fruit(random.choice(cfg.FRUITPATHS), coordinate=coordinate, blocksize=cfg.BLOCKSIZE))
    # -我方Hero
    coordinate = map_parser.randomGetSpace(used_spaces)
    used_spaces.append(coordinate)
    ourhero = Hero(imagepaths=cfg.HEROZELDAPATHS, coordinate=coordinate, blocksize=cfg.BLOCKSIZE, map_parser=map_parser, hero_name='ZELDA')
    # -電腦Hero
    aihero_sprite_group = pygame.sprite.Group()
    coordinate = map_parser.randomGetSpace(used_spaces)
    aihero_sprite_group.add(Hero(imagepaths=cfg.HEROBATMANPATHS, coordinate=coordinate, blocksize=cfg.BLOCKSIZE, map_parser=map_parser, hero_name='BATMAN'))
    used_spaces.append(coordinate)
    coordinate = map_parser.randomGetSpace(used_spaces)
    aihero_sprite_group.add(Hero(imagepaths=cfg.HERODKPATHS, coordinate=coordinate, blocksize=cfg.BLOCKSIZE, map_parser=map_parser, hero_name='DK'))
    used_spaces.append(coordinate)
    # -炸彈bomb
    bomb_sprite_group = pygame.sprite.Group()
    # -用於判斷遊戲勝利或者失敗的flag
    is_win_flag = False
    # -主循環
    screen = pygame.display.set_mode(map_parser.screen_size)
    clock = pygame.time.Clock()
    while True:
      dt = clock.tick(cfg.FPS)
      for event in pygame.event.get():
        if event.type == pygame.QUIT:
          pygame.quit()
          sys.exit(-1)
        # --↑↓←→鍵控制上下左右, 空格鍵丟炸彈
        elif event.type == pygame.KEYDOWN:
          if event.key == pygame.K_UP:
            ourhero.move('up')
          elif event.key == pygame.K_DOWN:
            ourhero.move('down')
          elif event.key == pygame.K_LEFT:
            ourhero.move('left')
          elif event.key == pygame.K_RIGHT:
            ourhero.move('right')
          elif event.key == pygame.K_SPACE:
            if ourhero.bomb_cooling_count <= 0:
              bomb_sprite_group.add(ourhero.generateBomb(imagepath=cfg.BOMBPATH, digitalcolor=cfg.YELLOW, explode_imagepath=cfg.FIREPATH))
      screen.fill(cfg.WHITE)
      # --電腦Hero隨機行動
      for hero in aihero_sprite_group:
        action, flag = hero.randomAction(dt)
        if flag and action == 'dropbomb':
          bomb_sprite_group.add(hero.generateBomb(imagepath=cfg.BOMBPATH, digitalcolor=cfg.YELLOW, explode_imagepath=cfg.FIREPATH))
      # --吃到水果加生命值(隻要是Hero, 都能加)
      ourhero.eatFruit(fruit_sprite_group)
      for hero in aihero_sprite_group:
        hero.eatFruit(fruit_sprite_group)
      # --遊戲元素都綁定到屏幕上
      map_parser.draw(screen)
      for bomb in bomb_sprite_group:
        if not bomb.is_being:
          bomb_sprite_group.remove(bomb)
        explode_area = bomb.draw(screen, dt, map_parser)
        if explode_area:
          # --爆炸火焰范圍內的Hero生命值將持續下降
          if ourhero.coordinate in explode_area:
            ourhero.health_value -= bomb.harm_value
          for hero in aihero_sprite_group:
            if hero.coordinate in explode_area:
              hero.health_value -= bomb.harm_value
      fruit_sprite_group.draw(screen)
      for hero in aihero_sprite_group:
        hero.draw(screen, dt)
      ourhero.draw(screen, dt)
      # --左上角顯示生命值
      pos_x = showText(screen, font, text=ourhero.hero_name+'(our):'+str(ourhero.health_value), color=cfg.YELLOW, position=[5, 5])
      for hero in aihero_sprite_group:
        pos_x, pos_y = pos_x+15, 5
        pos_x = showText(screen, font, text=hero.hero_name+'(ai):'+str(hero.health_value), color=cfg.YELLOW, position=[pos_x, pos_y])
      # --我方玩傢生命值小於等於0/電腦方玩傢生命值均小於等於0則判斷遊戲結束
      if ourhero.health_value <= 0:
        is_win_flag = False
        break
      for hero in aihero_sprite_group:
        if hero.health_value <= 0:
          aihero_sprite_group.remove(hero)
      if len(aihero_sprite_group) == 0:
        is_win_flag = True
        break
      pygame.display.update()
      clock.tick(cfg.FPS)
    if is_win_flag:
      Interface(screen, cfg, mode='game_switch')
    else:
      break
  Interface(screen, cfg, mode='game_end') 

以上就是基於Python實現炸彈人小遊戲 的詳細內容,更多關於Python炸彈人遊戲 的資料請關註WalkonNet其它相關文章!

推薦閱讀: