拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Python游戏开发,pygame模块,Python实作炸弹人小游戏

Python游戏开发,pygame模块,Python实作炸弹人小游戏

白鹭 - 2022-01-25 2101 0 0

前言

今天用Python实作的是一个炸弹人小游戏,废话不多说,让我们愉快地开始吧~

效果展示

image.png

开发工具

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 = https://www.cnblogs.com/daimubai/p/5
    elif self.kind =='cherry':
      self.value = https://www.cnblogs.com/daimubai/p/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 = https://www.cnblogs.com/daimubai/p/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 = https://www.cnblogs.com/daimubai/p/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')

文章到这里就结束了,感谢你的观看,Python24个小游戏系列,下篇文章分享过迷宫小游戏

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *