pygame开发的炸弹人游戏(详细讲解)

前言

本次开发的游戏为炸弹人游戏,是一个经典的小游戏,4399、7k7k等上面就可以找到,本游戏基于python语言,基于pygame开发,pygame是 跨平台 Python模块,专为电子游戏设计。 包含图像、声音。建立在SDL基础上,允许实时电子游戏研发而无需被低阶语言,如C语言或是更低阶的组合语言束缚。

实现游戏概述

炸弹人游戏,一个主机玩家和两个AI玩家,在地图场景内主机玩家可按空格投放炸弹,主机玩家用‘↑’‘↓’‘←’‘→’四个方向键进行四个方位移动,AI玩家电脑随机移动,地图中有墙面进行格挡,炸弹爆炸不可透墙,每个玩家有50生命值,炸弹爆炸效果持续一秒,玩家在爆炸范围内会受到持续伤害,主机玩家炸死两个AI玩家即通关下一关,一共设置两个关卡,游戏右上角有暂停键可以暂停游戏,左上角显示三个玩家的生命值,游戏开始会随机投放水果,不同水果可以恢复不同血量。每个玩家的生命值都有上限,满血吃水果不补生命值。

游戏结构

系统结构
pygame开发的炸弹人游戏(详细讲解)_第1张图片
系统模块
1.选择模块
需要起码两个按钮,一个退出按钮,以及一个复用功能按钮,使用监听事件监听按钮是否被点击,按钮实现是使用pygame.draw绘制在屏幕上,所以监听事件监听的是整个按钮区域是否被点击。
外加一张美观的背景图
复用功能按钮:按钮位置不变,代表含义不同,“下一关”,“重生”,“开始”,具体
开始界面
pygame开发的炸弹人游戏(详细讲解)_第2张图片
下一关界面
pygame开发的炸弹人游戏(详细讲解)_第3张图片

重生界面
pygame开发的炸弹人游戏(详细讲解)_第4张图片
胜利界面
pygame开发的炸弹人游戏(详细讲解)_第5张图片
2.游戏模块
游戏屏幕里出现的所有一切都可以定义为一个个游戏元素,每一个游戏元素都有一个共性,所有的游戏元素都需要坐标,显示的高度和宽度,以及显示的图片,所以游戏元素定义一个游戏基类,所有共性可以提炼到游戏基类,游戏基类继承pygame.sprite.Sprite,所有的游戏元素继承游戏基类。

3.地图模块
解析.map文件,每张地图的每一行信息用一个列表存储,再把每一行列表信息加到一个列表中,可以理解为一个二维数组。解析完再将遍历整个列表,将列表中的每一个游戏元素,调用游戏元素自身的draw方法绘制到屏幕上,整个列表遍历完地图绘制完成。实现在地图上获取空地功能,可以方便水果的绘制以及游戏角色的降临。

第一关地图.map文件
pygame开发的炸弹人游戏(详细讲解)_第6张图片

第二关地图.map文件
pygame开发的炸弹人游戏(详细讲解)_第7张图片

4.资源模块
我觉得很有必要划为一个模块,程序的可移植性会变的更加可观,任何资源的地址都使用宏定义资源位置更改只需更改资源模块的定义即可。包括地图使用.map文件存储,map是一种图像数据调用文件,可以模拟场景。先将游戏元素设置标记,即给定代号,然后调用通过解析成对应的游戏元素就可以显示出来

主机玩家
pygame开发的炸弹人游戏(详细讲解)_第8张图片
AI玩家1
pygame开发的炸弹人游戏(详细讲解)_第9张图片
AI玩家2
pygame开发的炸弹人游戏(详细讲解)_第10张图片
背景块
pygame开发的炸弹人游戏(详细讲解)_第11张图片
墙块
pygame开发的炸弹人游戏(详细讲解)_第12张图片
水果
pygame开发的炸弹人游戏(详细讲解)_第13张图片
炸弹和爆炸效果
pygame开发的炸弹人游戏(详细讲解)_第14张图片

5.游戏主模块
游戏中所有的规定都在此模块实现,游戏一开始是选择画面,选择进入游戏或者退出,如果进行游戏,游戏地图使用一个循环遍历,一张地图过关再进入下一张地图,游戏开始循环刷新游戏面,每秒三十帧,每张地图开端会随机获取几块空地,投放水果以供恢复血量,玩家用方向键控制主机角色移动,空格投放炸弹,AI角色随机移动随机投放炸弹,玩家生命值为0失败,又是进入重生页面,两个AI生命值为0,玩家获胜选择下一关页面,所有地图过关胜利,显示获胜画面,获胜后可以退出或重新游戏。

实现代码

1 GameSprites.py
1.1 设计游戏基类

GameSprites继承pygame.sprite.Sprite
初始化方法:
用于获取图片,图片坐标以及图片大小

def __init__(self, imagepath, coordinate, blocksize, **kwargs):
    super().__init__(self)
    self.image = pygame.image.load(imagepath)
    #缩放函数,设置图片大小,按每个图片都缩小为30*30
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.rect = self.image.get_rect()
    self.rect.x, self.rect.y = coordinate[0] * blocksize, coordinate[1] * blocksize
    self.coordinate = coordinate
    self.blocksize = blocksize

1.2 基于游戏基类的屏幕元素
游戏屏幕中的元素都基于GameSprites
1.2.1 Wall
墙,作为游戏中的障碍物,可以隔断爆炸,阻拦角色移动,直接在初始化方法时候传入参数调用父类即可,draw绘制墙元素在屏幕上

class Wall(GameSprites):
    def __init__(self, imagepath, coordinate, blocksize, **kwargs):
        super().__init__(self, imagepath, coordinate, blocksize, **kwargs)
def draw(self, screen):
        screen.blit(self.image, self.rect)
        return True

1.2.2 Background
背景类Background,初始化调用父类初始化,有三种背景色供选择,所以背景是一小块一小块的绘制

class Background(GameSprites):
    def __init__(self, imagepath, coordinate, blocksize, **kwargs):
        super().__init__(imagepath, coordinate, blocksize)
    def draw(self, screen):
        screen.blit(self.image, self.rect)
        return True

1.2.3 Fruit
用于恢复生命值的水果类
在父类GameSprites的基础上加入属性分类,不同的水果恢复生命值不一样

class Fruit(GameSprites):
    def __init__(self, imagepath, coordinate, blocksize, **kwargs):
        super().__init__(imagepath, coordinate, blocksize)
        self.kind = imagepath.split('/')[-1].split('.')[0]
        if self.kind == 'peach':
            self.value = 5
        elif self.kind == 'pineapple':
            self.value = 10
        else:
            raise ValueError('Unknow fruit <%s>...' % self.kind)

    def draw(self, screen):
        screen.blit(self.image, self.rect)
        return True

1.2.4 Bomb
炸弹类:用定时器进行定时爆炸,由玩家与AI端持有技能,用于炸伤炸死敌人
功能:炸弹能进行倒计时,有一定范围的爆炸,不可穿墙,爆炸效果持续一秒
1.2.4.1 Bomb的初始化函数
在父类的基础上增加属性
explode_image:存放爆炸效果图
explode_millisecond:爆炸倒计时最大值,毫秒级
explode_second:显示于炸弹上的秒数
start_explode:爆炸效果持续时间
explode_count:爆炸持续时间
harm_value:伤害生命值
is_being:判断炸弹是否存在
font:倒计时显示的数字字体以及大小设置
digitalcolor:倒计时数字颜色

def __init__(self, imagepath, coordinate, blocksize, digitalcolor, explode_imagepath, **kwargs):
    super().__init__(self, imagepath, coordinate, blocksize, **kwargs)
    self.explode_image = explode_imagepath
    self.explode_millisecond = 6000 * 1 - 1
    self.explode_second = int(self.explode_millisecond / 1000)
    self.start_explode = False
    self.explode_count = 1000 * 1
    self.harm_value = 1
    self.is_being = True
    self.font = pygame.font.SysFont('my_font.ttf', 20)
    self.digitalcolor = digitalcolor

1.2.4.2 draw 函数将炸弹显示到屏幕上
dt:计时
map_parser:地图

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

1.2.4.3 __calcExplodeArea计算爆炸区域
区域计算规则为墙可以阻止爆炸扩散, 且爆炸范围仅在游戏地图范围内
instances_list:
ymin: 爆炸高端,不可超过屏幕顶端
ymax: 爆炸低端,不可超过屏幕底端
xmin: 爆炸左端,不可超过屏幕左端
xmax: 爆炸右端,不可超过屏幕右端
explode_area: 爆炸区域

def __calcExplodeArea(self, instances_list):
   explode_area = []
   for ymin in range(self.coordinate[1], self.coordinate[1]-5, -1):
      if ymin < 1 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

1.2.4.3 __explode爆炸效果
通过explode_area将爆炸效果图显示在该坐标上

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

1.2.5 Role
角色类:角色进行四个方位的移动上下左右,有角色名称用于区分玩家和AI,每个角色都有生命值,都可以获取水果加生命值,每个角色的四个方位移动会对应着四个朝向,可以生成炸弹,AI会进行随机方位的移动,玩家用‘↑’‘↓’‘←’‘→’方向键进行移动
3.1.2.5.1 Role的初始化函数
直接继承于pygame.sprite.Sprite,角色有四个动作,所以每个角色的绘制图片有四张,游戏基类的初始化方法有冲突
role: 角色名称
health_value: 角色生命值
max_value: 角色最大生命值
bomb_cooling_time: 炸弹冷却时间
bomb_cooling_count: 炸弹冷却时间计时
randommove_cooling_time: 随机移动冷却时间(AI专用)
randommove_cooling_count: 随机移动冷却时间计时(AI专用)
map_parser: 地图

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.role = kwargs.get('role_name')
    # 生命值
    self.health_value = 50
    self.max_value = 50
    # 炸弹冷却时间
    self.bomb_cooling_time = 5000
    self.bomb_cooling_count = 0
    # 随机移动冷却时间(仅AI电脑用)
    self.randommove_cooling_time = 100
    self.randommove_cooling_count = 0

1.2.5.2 move
玩家角色进行移动,分为上下左右四个方位,墙体阻挡角色的移动,以及在游戏屏幕界限内,不可以超过计分区域
实现具体方法,角色坐标限则在游戏屏幕内,特别的顶端限制在计分区域下,下一步到达墙类元素止步

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]-2 < 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

1.2.5.2 randomAction
该类提供AI进行随机的移动
使AI随机方位的移动并且小概率的投放炸弹,随机移动冷却时间为0.1秒,炸弹爆炸冷却时间为5秒
randommove_cooling_time: 随机移动冷却时间(AI专用)
randommove_cooling_count: 随机移动冷却时间计时(AI专用)
action: 接下来的动作

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
3.1.2.5.2 generateBomb
生成炸弹,调用Bomb类生成炸弹
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)

1.2.5.2 draw
将角色显示到屏幕上,并进行角色投放炸弹之后的倒计时

def draw(self, screen, dt):
   if self.bomb_cooling_count > 0:
      self.bomb_cooling_count -= dt
   screen.blit(self.image, self.rect)
   return True

1.2.5.2 eatFruit
角色进行吃水果恢复血量,角色覆盖水果位置,水果消失,角色类生命值增加,生命值达到上限则保持在生命值上限。

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
        if self.health_value > self.health_maxvalue:
            self.health_value = self.health_maxvalue

1.2.5.2 __updateImage
更新角色朝向

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))

2 choice.py
#选择模块
import sys
import pygame
2.1 showText
在屏幕指定位置显示文字
text: 要显示的文字内容
font.render:
参数一:显示的内容
参数二:是否开启抗锯齿,就是说True的话字体会比较平滑,不过相应的速度有一点点影响
参数三:字体颜色
position: 文字显示坐标

def showText(screen, font, text, color, position):
   text_render = font.render(text, True, color)
   rect = text_render.get_rect()
   rect.left, rect.top = position
   screen.blit(text_render, rect)
   return rect.right

2.2 Button
绘制按钮
linecolor: 按钮边框颜色,金色
buttoncolor: 按钮颜色,洋红色
textcolor:字体颜色,白色
四条线条宽度设置为5

def Button(screen, position, text, buttoncolor=(255, 0, 255), linecolor=(255, 215, 0), textcolor=(255, 255, 255), bwidth=200, bheight=50):
   left, top = position
   pygame.draw.line(screen, linecolor, (left, top), (left+bwidth, top), 5)
   pygame.draw.line(screen, linecolor, (left, top-2), (left, top+bheight), 5)
   pygame.draw.line(screen, linecolor, (left, top+bheight), (left+bwidth, top+bheight), 5)
   pygame.draw.line(screen, linecolor, (left+bwidth, top+bheight), (left+bwidth, top), 5)
   pygame.draw.rect(screen, buttoncolor, (left, top, bwidth, bheight))
   font = pygame.font.SysFont('Consolas', 30)
   text_render = font.render(text, 1, textcolor)
   rect = text_render.get_rect()
   rect.centerx, rect.centery = left + bwidth / 2, top + bheight / 2
   return screen.blit(text_render, rect)

2.3 choice_interface
绘制四个选择界面,可供选择开始,下一关,重生及成功,主要是背景图的不同,调用规则在主游戏模块实现

def choice_interface(screen, resources, mode='START'):
    pygame.display.set_mode(resources.SCREENSIZE)
    if mode == 'START' or mode == 'NEXT' or mode == 'RESTART' or mode == 'WIN':
        clock = pygame.time.Clock()
        while True:
            if mode == 'START':
                bg = pygame.image.load("resources/images/background1.png")
            elif mode == 'WIN':
                bg = pygame.image.load("resources/images/win.png")
            else:
                bg = pygame.image.load("resources/images/background2.png")
            screen.blit(bg, (0,0))
            button_1 = Button(screen, (220, 150), mode)
            button_2 = Button(screen, (220, 250), 'QUIT')
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit(-1)
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    if button_1.collidepoint(pygame.mouse.get_pos()):
                        return True
                    elif button_2.collidepoint(pygame.mouse.get_pos()):
                        pygame.quit()
                        sys.exit(-1)
            pygame.display.update()
            clock.tick(resources.FPS)
    else:
        raise ValueError('Interface.mode unsupport <%s>...' % mode)

3 map.py
游戏屏幕的初始布局
3.1 mapParser
屏幕地图解析
3.1.1 初始化

获取地图文件,背景块图片,墙图片,以及背景块尺寸

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)

3.1.2 draw
将地图上的元素画到屏幕上,三种不同的墙体,以及三种颜色的背景块

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)

3.1.3 randomGetSpace
随机获取一块空地,实现是获取地图上的背景块坐标

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

3.1.4 getElemByCoordinate
根据坐标获取元素类型

def getElemByCoordinate(self, coordinate):
   return self.instances_list[coordinate[1]][coordinate[0]]

3.1.5 __parse
解析地图.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

4 resources.py
所有图片,音频资源,包括一些宏定义

SCREENSIZE = (640, 480) 屏幕大小
BLOCKSIZE = 30 
FPS = 30 帧数
游戏地图路径
GAMEMAPPATHS = ['resources/maps/1.map', 'resources/maps/2.map']
代表墙块路径
WALLPATHS = ['resources/images/res/wall0.png', 'resources/images/res/wall1.png', 'resources/images/res/wall2.png']
角色的四个方位图片路径
ROLEAI1PATHS = ['resources/images/ming/left.png', 'resources/images/ming/right.png', 'resources/images/ming/up.png', 'resources/images/ming/down.png']
HEROROLEPATHS = ['resources/images/zuo/left.png', 'resources/images/zuo/right.png', 'resources/images/zuo/up.png', 'resources/images/zuo/down.png']
ROLEAI2PATHS = ['resources/images/jiu/left.png', 'resources/images/jiu/right.png', 'resources/images/jiu/up.png', 'resources/images/jiu/down.png']
水果的图片路径
FRUITPATHS = ['resources/images/res/peach.png', 'resources/images/res/pineapple.png']
暂停按钮
PAUSEDPATHS = ['resources/images/pause_nor.png','resources/images/pause_pressed.png','resources/images/resume_nor.png','resources/images/resume_pressed.png']
背景图片的路径
BACKGROUNDPATHS = ['resources/images/res/bg0.png', 'resources/images/res/bg1.png', 'resources/images/res/bg2.png', 'resources/images/res/bg3.png']
炸弹图片的路径
BOMBPATH = 'resources/images/res/bomb.png'
炸弹爆炸效果图片的路径
FIREPATH = 'resources/images/res/fire.png'
游戏背景音乐的路径
BGMPATH = 'resources/audio/bgm.mp3'
屏幕上调用的色彩
YELLOW = (255, 255, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
BLACK = (0, 0, 0)
ORANGE = (255, 253, 0)
WHITE = (255, 255, 255)

5 bomberman.py

import sys
import random
import pygame

from modules import res
from modules.map import *
from modules.choice import *
from modules.GameSprites import *
from modules.res import *

游戏主程序
def main(resources):
   # 初始化
   pygame.init()
	# 游戏音频初始化,游戏开始bgm响起
   pygame.mixer.init()
   pygame.mixer.music.load(resources.BGMPATH)
	#一直循环
   pygame.mixer.music.play(-1, 0.0)
	#初始化一个准备显示的屏幕
   screen = pygame.display.set_mode(resources.SCREENSIZE)
#设置图标
screen = pygame.display.set_mode(res.SCREENSIZE)
icon = pygame.image.load("resources/pika.png")

	#窗口标题
   pygame.display.set_caption('Bomber Man ')
   # 开始界面
   choice_interface(screen, resources, mode='START')
   # 游戏主循环
	#选择字体类型及大小
   font = pygame.font.SysFont('Consolas', 15)
	#记录过关数
	count = 1
	#遍历游戏地图 
   for gamemap_path in resources.GAMEMAPPATHS:
      # -解析.map生成地图
      map_parser = mapParser(gamemap_path, bg_paths= resources.BACKGROUNDPATHS, wall_paths= resources.WALLPATHS, blocksize= resources.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(resources.FRUITPATHS), coordinate=coordinate, blocksize=cfg.BLOCKSIZE))
      # -我方Role 随机获取一块空地降临
      coordinate = map_parser.randomGetSpace(used_spaces)
      used_spaces.append(coordinate)
      ourhero = Role(imagepaths=resources.HEROROLEPATHS, coordinate=coordinate, blocksize= resources.BLOCKSIZE, map_parser=map_parser, role='zuo')
      # -电脑 Role 随机获取一块空地降临
      aihero_sprite_group = pygame.sprite.Group()
      coordinate = map_parser.randomGetSpace(used_spaces)
      aihero_sprite_group.add(Role(imagepaths=resources.ROLEAI1PATHS, coordinate=coordinate, blocksize= resources.BLOCKSIZE, map_parser=map_parser, role ='jiu'))
      used_spaces.append(coordinate)
      coordinate = map_parser.randomGetSpace(used_spaces)
      aihero_sprite_group.add(Role(imagepaths= resources. ROLEAI2PATHS, coordinate=coordinate, blocksize= resources.BLOCKSIZE, map_parser=map_parser, role ='ming'))
      used_spaces.append(coordinate)
      # -炸弹精灵组 
      bomb_sprite_group = pygame.sprite.Group()
      # -用于判断游戏胜利或者失败的flag
      is_win_flag = False
      # -主循环
      screen = pygame.display.set_mode(map_parser.screen_size)
		# -clock创建时钟对象(可以控制游戏循环频率) 
      clock = pygame.time.Clock()
		
		# 标志是否暂停游戏
paused = False
#暂停按钮
		paused_nor_image = pygame.image.load(res.PAUSEDPATHS[0])
paused_nor_image = pygame.transform.scale(paused_nor_image, (30, 30))
		#暂停按钮(鼠标停留在上方时显示),使颜色加深
pause_pressed_image = pygame.image.load(res.PAUSEDPATHS[1])
pause_pressed_image = pygame.transform.scale(pause_pressed_image, (30, 30))
		#启动按钮
resume_nor_image = pygame.image.load(res.PAUSEDPATHS[2])
resume_nor_image = pygame.transform.scale(resume_nor_image, (30, 30))
		#启动按钮(鼠标停留在上方时显示) ,使颜色加深
resume_pressed_image = pygame.image.load(res.PAUSEDPATHS[3])
resume_pressed_image = pygame.transform.scale(resume_pressed_image, (30, 30))
paused_rect = paused_nor_image.get_rect()

		#暂停按钮位置设置,两张地图位置右上角
if order == 0:
   paused_rect.left, paused_rect.top = 12 * 30, 0
elif order == 1:
   paused_rect.left, paused_rect.top = 24 * 30, 0
paused_image = paused_nor_image


     		while True:
			#设置帧率
			dt = clock.tick(res.FPS)
			#监听暂停按钮和关闭程序
			for event in pygame.event.get():
				if event.type == pygame.QUIT:
					pygame.quit()
					sys.exit(-1)
				elif event.type == pygame.MOUSEBUTTONDOWN:
					if event.button == 1 and paused_rect.collidepoint(event.pos):
						paused = not paused
						#暂停切换图片,关掉BGM
						if paused:
						pygame.time.set_timer(pygame.USEREVENT, 0)
							pygame.mixer.music.pause()
							pygame.mixer.pause()
							paused_image = resume_pressed_image
						else:
										pygame.time.set_timer(pygame.USEREVENT, 30 * 1000)
							pygame.mixer.music.unpause()
							pygame.mixer.unpause()
							paused_image = pause_pressed_image
			screen.fill(res.WHITE)
			#如果当前是否运行状态
			if not paused:
				#为保证按钮连贯,不松开按钮亦可响应,方向键不用监听事件监听
				# --↑↓←→键控制上下左右, 空格键丢炸弹
				keys_pressed = pygame.key.get_pressed()
				if keys_pressed[pygame.K_RIGHT]:
					ourhero.move('right')
				elif keys_pressed[pygame.K_LEFT]:
					ourhero.move('left')
				elif keys_pressed[pygame.K_UP]:
					ourhero.move('up')
				elif keys_pressed[pygame.K_DOWN]:
					ourhero.move('down')
				#空格投放炸弹
				elif keys_pressed[pygame.K_SPACE]:
					if ourhero.bomb_cooling_count <= 0:
						bomb_sprite_group.add(
							ourhero.generateBomb(imagepath=res.BOMBPATH, digitalcolor=res.ORANGE,
												 explode_imagepath=res.FIREPATH))
				# --电脑AI随机行动
				for hero in aihero_sprite_group:
					action, flag = hero.randomAction(dt)
					if flag and action == 'dropbomb':
						bomb_sprite_group.add(hero.generateBomb(imagepath=res.BOMBPATH, digitalcolor=res.ORANGE, explode_imagepath=res.FIREPATH))
				# --吃到水果加生命值(只要是Role, 都能加),角色生命值在Role类已设#置上限,进行上限处理
				ourhero.eatFruit(fruit_sprite_group)
				for hero in aihero_sprite_group:
					hero.eatFruit(fruit_sprite_group)
				# --游戏元素都绑定到屏幕上
				map_parser.draw(screen)
					#绘制暂停按钮
				screen.blit(paused_image, paused_rect)
				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:
						# --爆炸火焰范围内的Role生命值将持续下降
						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)
			else:
				map_parser.draw(screen)
				screen.blit(paused_image, paused_rect)
				for bomb in bomb_sprite_group:
					if not bomb.is_being:
						bomb_sprite_group.remove(bomb)
					explode_area = bomb.draw(screen, dt, map_parser)
				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:
	#暂停按钮位置标志
      	order = 1
	#游戏结束
      	if count == 2:
            break
      	else:
            choice_interface(screen, res, mode='NEXT')
	#过关统计
      count += 1
   else:
      break
#过关失败重生
if is_win_flag == False:
   choice_interface(screen, res, mode='RESTART')
#过关成功WIN
else:
   choice_interface(screen, res, mode='WIN')

运行:
if __name__ == '__main__':
   while True:
      main(res)

实验效果

1起始选择页面
开始页面
pygame开发的炸弹人游戏(详细讲解)_第15张图片

2游戏页面
1第一关
第一关游戏画面
pygame开发的炸弹人游戏(详细讲解)_第16张图片

过关后进入下一关
pygame开发的炸弹人游戏(详细讲解)_第17张图片
2第二关
第二关游戏画面
pygame开发的炸弹人游戏(详细讲解)_第18张图片

顺利通关画面
pygame开发的炸弹人游戏(详细讲解)_第19张图片

3游戏页面元素介绍

游戏元素展示图
pygame开发的炸弹人游戏(详细讲解)_第20张图片

启动按钮

pygame开发的炸弹人游戏(详细讲解)_第21张图片

游戏中爆炸效果
pygame开发的炸弹人游戏(详细讲解)_第22张图片

失败可选重新游戏
pygame开发的炸弹人游戏(详细讲解)_第23张图片

展望

后续程序还可以接着改造,包括炸弹威力的等级可以分为多个等级,根据级别分类炸弹,水果加多种类,可以给角色提供高等级炸弹,以及移动加速,角色在爆炸范围内可以做出相应的反应等等。整个项目还可以优化,我只是作为初学者进行一个初步的开发,后继会接着添加功能。

你可能感兴趣的:(pygame开发的炸弹人游戏(详细讲解))