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

前言

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

实现游戏概述

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

游戏结构

系统结构


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


下一关界面

重生界面


胜利界面


2.游戏模块
游戏屏幕里出现的所有一切都可以定义为一个个游戏元素,每一个游戏元素都有一个共性,所有的游戏元素都需要坐标,显示的高度和宽度,以及显示的图片,所以游戏元素定义一个游戏基类,所有共性可以提炼到游戏基类,游戏基类继承pygame.sprite.Sprite,所有的游戏元素继承游戏基类。

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

第一关地图.map文件

第二关地图.map文件

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

主机玩家


AI玩家1


AI玩家2


背景块


墙块


水果


炸弹和爆炸效果

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, flag3.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 sysimport randomimport pygamefrom modules import resfrom 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, 0elif order == 1: paused_rect.left, paused_rect.top = 24 * 30, 0paused_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#暂停切换图片,关掉BGMif paused:pygame.time.set_timer(pygame.USEREVENT, 0)pygame.mixer.music.pause()pygame.mixer.pause()paused_image = resume_pressed_imageelse:pygame.time.set_timer(pygame.USEREVENT, 30 * 1000)pygame.mixer.music.unpause()pygame.mixer.unpause()paused_image = pause_pressed_imagescreen.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_valuefor hero in aihero_sprite_group:if hero.coordinate in explode_area:hero.health_value -= bomb.harm_valuefruit_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')#过关成功WINelse: choice_interface(screen, res, mode='WIN')运行:if __name__ == '__main__': while True: main(res)

实验效果

1起始选择页面
开始页面

2游戏页面
1第一关
第一关游戏画面

过关后进入下一关


2第二关
第二关游戏画面

顺利通关画面

3游戏页面元素介绍

游戏元素展示图

启动按钮

游戏中爆炸效果

失败可选重新游戏

展望

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

相关推荐

相关文章