目录
一、首先明白编写坦克大战有哪些需求
二、细节编写
1、完成游戏的主要逻辑
2、完成坦克
1)完成我方坦克
2)完成敌方坦克
3、完成子弹
1)我方坦克射击
2)敌方坦克
3)我方子弹和敌方坦克碰撞
?4)敌方子弹与我方坦克的碰撞
5)实现我方坦克的重生
4、爆炸效果
1)敌方坦克
2)我方坦克
?5、墙壁实现
1)子弹与墙壁碰撞
2)坦克不能穿墙
3)实现坦克之间的碰撞检测
6、音效处理
三、代码
1、成果
2、缺点
坦克大战大量应用pygame包,https://www.pygame.org/docs/,可以阅读pygame文档熟悉相关的功能
一、首先明白编写坦克大战有哪些需求
???????应该思考有哪些类,以及其功能有什么。
- 主逻辑类:开始游戏,结束游戏,获取事件(比如鼠标事件、键盘事件)并处理
- 坦克类(有我方坦克,和敌方坦克):移动,射击,坦克的展示
- 子弹类:移动,子弹的展示
- 爆炸效果类:爆炸效果的展示
- 障碍物类(该项目只考虑墙壁):属性(是否可以通过),墙的展示
- 音效类:播放音乐
先开始编写框架
import pygame
class MainGame():
def __init__(self):
pass
def startGame(self):#开始游戏
pass
def getEvent(self):#获取事件,比如鼠标事件、键盘事件
pass
def endGame(self):#结束游戏
pass
class Tank():
def __init__(self):
pass
def move(self):#坦克移动
pass
def shot(self):#坦克射击
pass
def displayTank(self):#坦克进行显示
pass
class MyTank(Tank):
def __init__(self):
pass
class EnemyTank(Tank):
def __init__(self):
pass
class Bullet():
def __init__():
pass
def bulletMove(self):#子弹移动
pass
def dispalyBullet(self):#子弹显示
pass
class Explode():
def __init__(self):
pass
def displayExplode(self):#展示子弹
pass
class Wall():
def __init__(self):
pass
def dispalyWall(self):#展示墙壁
pass
class Music():
def __init__(self):
pass
def play(self):#开始播放音乐
pass
二、细节编写
1、完成游戏的主要逻辑
import pygame
COLOR_BLACK = pygame.COLOR(0,0,0)
class MainGame():
window = None#游戏主窗口
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 500#窗口的宽和高
def __init__(self):
pass
def startGame(self):#开始游戏
pygame.display.init()
MainGame.window = pygame.display.set_mode([MainGame.SCREEN_WIDTH,MainGame.SCREEN_HEIGHT])#加载游戏窗口(看文档)
pygame.display.set_caption('坦克大战')#设置标题
while True:#让窗口持续刷新
MainGame.window.fill(COLOR_BLACK)#填充为黑色
pygame.display.update()
def getEvent(self):#获取事件,比如鼠标事件、键盘事件
pass
def endGame(self):#结束游戏
print('古德拜拜')
exit(0)#结束Python解释器
在startGame里加入死循环,则可以一直显示窗口,但无法退出,所以要加入事件处理代码为:
def startGame(self):#开始游戏
pygame.display.init()
MainGame.window = pygame.display.set_mode([MainGame.SCREEN_WIDTH,MainGame.SCREEN_HEIGHT])#加载游戏窗口(看文档)
pygame.display.set_caption('坦克大战')#设置标题
while True:#让窗口持续刷新
MainGame.window.fill(COLOR_BLACK)#填充为黑色
self.getEvent()
pygame.display.update()
def getEvent(self):#获取事件,比如鼠标事件、键盘事件
eventList = pygame.event.get()#获取
for event in eventList:
if event.type == pygame.QUIT:#判断是否点击退出
self.endGame()
if event.type == pygame.KEYDOWN:#判断按键按下,并判断是哪个键
if event.key == pygame.K_LEFT:#左方向键
print('左')
elif event.key == pygame.K_RIGHT:
print('右')
elif event.key == pygame.K_UP:
print('上')
elif event.key == pygame.K_DOWN:
print('下')
elif event.key == pygame.K_SPACE:
print('射击')
此后,运行时,按方向键会显示方向,鼠标点击叉号会退出。
为在屏幕中显示剩余坦克的文本,所以在MainGame中加入新方法:
COLOR_RED = pygame.Color(255,0,0) #全局变量
def getTextSurface(self,text):
pygame.font.init()#初始化字体模块
font = pygame.font.SysFont('kaiti',18)#选中一个字体
textSurface = font.render(text,True,COLOR_RED)#进行绘制
return textSurface
在死循环中加入:(两个都是Surface,所以用blit函数,将一个Surface中的内容画到另一个)
MainGame.window.blit(self.getTextSurface('剩余坦克%d'%5),(5,5))#将绘制文字的小画布粘贴到窗口中
2、完成坦克
1)完成我方坦克
在这里我们用到了坦克的照片,可以将这些照片先添加到项目中
?需要的人儿可以下载链接:https://pan.baidu.com/s/1ueXXyqn_doGcRw3g9FiG1w? 提取码:jjgj
首先完成坦克的一个初始化
class Tank():
def __init__(self,left,top):
self.images = {'U':pygame.image.load('img/p1tankU.gif'),
'D':pygame.image.load('img/p1tankD.gif'),
'L':pygame.image.load('img/p1tankL.gif'),
'R':pygame.image.load('img/p1tankR.gif')
}
self.direction = 'U'
self.image = self.images[self.direction]#根据坦克的方向选择照片
self.rect = self.image.get_rect()#坦克所在的区域
self.rect.left = left
self.rect.top = top#指定坦克的初始化位置,分别距离x,y轴的距离
def move(self):#坦克移动
pass
def shot(self):#坦克射击
pass
def displayTank(self):#坦克进行显示(将坦克这个Surface绘制到窗口中,用blit())
self.image = self.images[self.direction]#重置坦克的图像
MainGame.window.blit(self.image,self.rect)#将坦克加入到窗口中
在MainGame中加入变量
TANK_P1 = None
?为使项目结构更清晰,在MainGame中定义一个创建我方坦克的函数:
def creatMyTank(self):
MainGame.TANK_P1 = Tank(650,600)#创建我方坦克
在MainGame的startGame修改为
def startGame(self):#开始游戏
pygame.display.init()
MainGame.window = pygame.display.set_mode([MainGame.SCREEN_WIDTH,MainGame.SCREEN_HEIGHT])#加载游戏窗口(看文档)
self.creatMyTank#创建我方坦克
pygame.display.set_caption('坦克大战')#设置标题
while True:#让窗口持续刷新
MainGame.window.fill(COLOR_BLACK)#填充为黑色
self.getEvent()
MainGame.window.blit(self.getTextSurface('剩余坦克%d'%5),(5,5))#将绘制文字的小画布粘贴到窗口中
MainGame.TANK_P1.displayTank()#将我方坦克加入窗口
pygame.display.update()
接下来实现移动,移动的原理:坦克距离窗口左侧为left值,距离窗口上侧为top值,坦克的移动实际就是两个值的变化,而坦克的调头就是新的图片。所以速度也是一个属性,在Tank的init加入
self.speed = 5
坦克的move函数:
def move(self):#坦克移动
if self.direction == 'L':
self.rect.left -= self.speed
elif self.direction == 'R':
self.rect.left += self.speed
elif self.direction == 'U':
self.rect.top -= self.speed
elif self.direction == 'D':
self.rect.top += self.speed
同时还应在MainGame的按键事件处理改为
for event in eventList:
if event.type == pygame.QUIT:#判断是否点击退出
self.endGame()
if event.type == pygame.KEYDOWN:#判断按键按下,并判断是哪个键
if event.key == pygame.K_LEFT:#左方向键
print('左')
MainGame.TANK_P1.direction = 'L'#改变坦克的方向
MainGame.TANK_P1.move()#实现移动
elif event.key == pygame.K_RIGHT:
print('右')
MainGame.TANK_P1.direction = 'R'#改变坦克的方向
MainGame.TANK_P1.move()
elif event.key == pygame.K_UP:
print('上')
MainGame.TANK_P1.direction = 'U'#改变坦克的方向
MainGame.TANK_P1.move()
elif event.key == pygame.K_DOWN:
print('下')
MainGame.TANK_P1.direction = 'D'#改变坦克的方向
MainGame.TANK_P1.move()
elif event.key == pygame.K_SPACE:
print('射击')
但是此时坦克可以移出边界,所以在坦克的move函数增加判断条件:
if self.direction == 'L':
if self.rect.left > 0:
self.rect.left -= self.speed
elif self.direction == 'R':
if self.rect.left + self.rect.height <MainGame.SCREEN_WIDTH:
self.rect.left += self.speed
elif self.direction == 'U':
if self.rect.top > 0:
self.rect.top -= self.speed
elif self.direction == 'D':
if self.rect.top + self.rect.height <MainGame.SCREEN_HEIGHT:
self.rect.top += self.speed
此时的坦克只有在按下方向键时才能移动,所以做优化,给坦克加一个开关,按键时打开开关,松开键时关闭,使得按下键就可一直移动:
在坦克的初始化中加入:
self.stop = True
在死循环中加入
if MainGame.TANK_P1 and not MainGame.TANK_P1.stop:#坦克存在并且打开开关时允许移动
MainGame.TANK_P1.move()
将MainGame中的getEvent函数中的move调用改为
MainGame.TANK_P1.stop = False
此外,还需要增加按键抬起,停止运动,所以在getEvent函数中增加新的条件判断:
if event.type == pygame.KEYUP:#将坦克的移动状态修改
MainGame.TANK_P1.stop = True
此时出现新的问题,就是移动时,按空格射击时,坦克会停止,因为空格抬起时,触发了条件,所以对抬起的按键加以判断
if event.type == pygame.KEYUP:#将坦克的移动状态修改
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN:
MainGame.TANK_P1.stop = True
坦克移动太快的话,除了改变speed参数,还可以引入time库,在死循环的任意地方加入
time.sleep(0.02)
为了使移动更加纵享丝滑,对手感进行优化:
MainGame中加入属性Num_Key = 0、getEvent的按键按下判断中添加:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN:
MainGame.Num_Key += 1
按键抬起判断改为:
if event.type == pygame.KEYUP:#将坦克的移动状态修改
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN:
MainGame.Num_Key -= 1
if ((event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN) and MainGame.Num_Key == 0):
MainGame.TANK_P1.stop = True
2)完成敌方坦克
将Tank的初始化复制到EnemyTank,并做修改
class EnemyTank(Tank):
def __init__(self,left,top,speed):
self.images = {'U':pygame.image.load('img/enemy1U.gif'),
'D':pygame.image.load('img/enemy1D.gif'),
'L':pygame.image.load('img/enemy1L.gif'),
'R':pygame.image.load('img/enemy1R.gif')
}
self.direction = self.randDirection()
self.image = self.images[self.direction]#根据坦克的方向选择照片
self.rect = self.image.get_rect()#坦克所在的区域
self.rect.left = left
self.rect.top = top#指定坦克的初始化位置,分别距离x,y轴的距离
self.speed = speed
self.stop = True
def randDirection(self):#用来随机生成坦克的方向
num = random.randint(1,4)
if num == 1:
return 'U'
elif num == 2:
return 'D'
elif num == 3:
return 'L'
elif num == 4:
return 'R'
用到了随机模块,所以引入随机random库 ,在MainGame中添加参数:
EnemyTank_list = [] #敌方坦克列表
EnemyTank_count = 5#创建的坦克数量
在MainGame中添加新方法(用于产生敌方坦克和进行显示):
def creatEnemyTank(self):#创建敌方坦克
top = 100
for i in range(MainGame.EnemyTank_count):
left = random.randint(1,int(self.SCREEN_WIDTH/100)-1)#每次都随机生成一个数值
speed = random.randint(3,4)
eTtank = EnemyTank(left*100,top,speed)
MainGame.EnemyTank_list.append(eTtank)
def blitEnemyTank(self):#将坦克加入到窗口中
for eTank in MainGame.EnemyTank_list:
eTank.displayTank()
在startGame中调用
self.creatEnemyTank()
在其中的死循环中加入
self.blitEnemyTank()#将敌方坦克加入窗口
优化剩余坦克数量,将原来的显示剩余数量修改为:
MainGame.window.blit(self.getTextSurface('剩余坦克%d'%len(MainGame.EnemyTank_list)),(5,5))#将绘制文字的小画布粘贴到窗口中
?如果敌方坦克直接调用move函数,则坦克会向着一个方向一直移动,所以在EnemyTank新增步数属性:
self.step = 40#新增步数属性,用来控制敌方坦克随机移动
并在其中定义新的方法:
def randMove(self):
if self.step <= 0:
self.direction = self.randDirection()#一定距离后改变方向
self.step = 40#重置步数
else:
self.move()
self.step -= 1
?将MainGame中的blitEnemyTank修改为:
def blitEnemyTank(self):#将坦克加入到窗口中
for eTank in MainGame.EnemyTank_list:
eTank.displayTank()
eTank.randMove()
3、完成子弹
1)我方坦克射击
完成子弹需要哪些东西:图片,方向,起始位置,速度
class Bullet():
def __init__(self,tank):
self.image = pygame.image.load('img/enemymissile.gif')#图像
self.direction = tank.direction#方向
self.rect = self.image.get_rect()
self.speed = 7#速度
if self.direction == 'U':#起始位置
self.rect.left = tank.rect.left + tank.rect.width/2 - self.rect.width/2
self.rect.top = tank.rect.top - self.rect.height
elif self.direction == 'D':
self.rect.left = tank.rect.left + tank.rect.width/2 - self.rect.width/2
self.rect.top = tank.rect.top + tank.rect.height
elif self.direction == 'L':
self.rect.left = tank.rect.left - self.rect.width/2 - self.rect.width/2
self.rect.top = tank.rect.top + tank.rect.width/2 - self.rect.width/2
elif self.direction == 'R':
self.rect.left = tank.rect.left + tank.rect.width
self.rect.top = tank.rect.top - self.rect.width/2 + tank.rect.width/2
def dispalyBullet(self):#子弹显示
MainGame.window.blit(self.image,self.rect)
原理:坦克距离窗口左侧为left值,距离窗口上侧为top值,left和top都是图片的左上角相对于窗口
接下来的主逻辑:坦克发射一个子弹,则一个子弹加入一个子弹列表,主逻辑中完成所有子弹的一个显示。完善Tank里面的shot函数:
def shot(self):#坦克射击,因为Bullet需要一个Tank参数,此时将自己传进去
return Bullet(self)
我方发射子弹需要按键,所以在事件处理函数改为:
elif event.key == pygame.K_SPACE:
m = Bullet(MainGame.TANK_P1)#产生一个子弹
MainGame.Bullet_list.append(m)
print('射击')
同时需要在MainGame中添加新属性:
Bullet_list = []#存储我方子弹的列表
接下来需要遍历所有子弹,进行显示,可以借鉴敌方坦克的逻辑,在MainGame定义新的方法
def blitBullet(self):
for bullet in MainGame.Bullet_list:
bullet.displayBullet()
并在死循环里合适位置调用该函数:
self.blitBullet()#调用渲染子弹列表的方法
然后,实现子弹的移动,向左移动,则left减少,向上移动,则top减少
def bulletMove(self):#子弹移动
if self.direction == 'U':
if self.rect.top > 0:
self.rect.top -= self.speed
elif self.direction == 'D':
if self.rect.top < MainGame.SCREEN_HEIGHT - self.rect.height:
self.rect.top += self.speed
elif self.direction == 'L':
if self.rect.left > 0:
self.rect.left -= self.speed
elif self.direction == 'R':
if self.rect.left < MainGame.SCREEN_WIDTH - self.rect.width:
self.rect.left += self.speed
在MainGame的blitBullet函数中的for循环加入:
bullet.bulletMove()
此时,我们的坦克可以无限发射,跟开挂一样,并且子弹不能消失,所以我们让子弹出界后消失,屏幕同一时间只能出现最多出现5发我方的子弹。可以给子弹加一个标签,出界后修改该标签,根据标签判断是不是要展示,则咋子弹的属性中加入:
self.live = True#判断子弹是否应存在
将bulletMove函数修改为:
def bulletMove(self):#子弹移动
if self.direction == 'U':
if self.rect.top > 0:
self.rect.top -= self.speed
else:
self.live = False
elif self.direction == 'D':
if self.rect.top < MainGame.SCREEN_HEIGHT - self.rect.height:
self.rect.top += self.speed
else:
self.live = False
elif self.direction == 'L':
if self.rect.left > 0:
self.rect.left -= self.speed
else:
self.live = False
elif self.direction == 'R':
if self.rect.left < MainGame.SCREEN_WIDTH - self.rect.width:
self.rect.left += self.speed
else:
self.live = False
将MainGame的blitBullet修改为:
def blitBullet(self):
for bullet in MainGame.Bullet_list:
if bullet.live:#如果子弹还存在,则展示,否则,移除子弹
bullet.displayBullet()
bullet.bulletMove()
else:
MainGame.Bullet_list.remove(bullet)
将MainGame的getEvent函数中的射击修改为:
elif event.key == pygame.K_SPACE:
if len(MainGame.Bullet_list) < 5:
m = Bullet(MainGame.TANK_P1)#产生一个子弹
MainGame.Bullet_list.append(m)
print('射击')
2)敌方坦克
在MainGame中加入敌方子弹列表:
Enemy_bullet_list = []#存储敌方坦克
?并将blitEnemyTank修改为
def blitEnemyTank(self):#将坦克加入到窗口中
for eTank in MainGame.EnemyTank_list:
eTank.displayTank()
eTank.randMove()#坦克移动
eBullet = eTank.shot()#敌方坦克射击,因为继承关系,所以有shot函数
MainGame.Enemy_bullet_list.append(eBullet)#将敌方坦克加入列表
在MainGame中写一个展示敌方子弹的函数:
def blitEnemyBullet(self):#将敌方子弹加入窗口
for ebullet in MainGame.Enemy_bullet_list:
if ebullet.live:#如果子弹还存在,则展示,否则,移除子弹
ebullet.displayBullet()
ebullet.bulletMove()#让子弹移动
else:
MainGame.Enemy_bullet_list.remove(ebullet)
然后在死循环中进行调用
self.blitEnemyBullet()#敌方坦克的子弹
此时会发现,移动时一直发射子弹,所以在EnemyTank类中重写shot函数,让其一定概率可以发射
def shot(self):
num = random.randint(1,50)
if num == 1:
return Bullet(self)
此时会有两种返回值,当不产生子弹,就会返回None,如果产生子弹,会返回Bullet,应在不产生子弹时,返回值不加入列表,否则会将产生的子弹顶替,blitEnemyTank修改为
def blitEnemyTank(self):#将坦克加入到窗口中
for eTank in MainGame.EnemyTank_list:
eTank.displayTank()
eTank.randMove()#坦克移动
eBullet = eTank.shot()#敌方坦克射击,因为继承关系,所以有shot函数
if eBullet:
MainGame.Enemy_bullet_list.append(eBullet)#将敌方坦克加入列表
3)我方子弹和敌方坦克碰撞
要使用pygame的精灵类碰撞,所以要继承精灵类,项目中的Tank和Bullet都需要继承,则可以让他们先继承一个基础类,该基础类继承精灵类(在pygame的官方文档有使用案例)
class BaseItem(pygame.sprite.Sprite):
def __init__():
pygame.sprite.Sprite.__init__(self)
#将两个类添加父类
class Tank(BaseItem)
class Bullet(BaseItem)
在Tank中新增属性
self.live = True
?父类定义的属性,子类要调用的话需要用父类的初始化方式,则在初始化加入
def __init__(self,left,top,speed):
super(EnemyTank,self).__init__(left,top)
在Bullet中新增函数:
def hitEnemyTank(self):#新增我方子弹碰撞敌方坦克的方法
for eTank in MainGame.EnemyTank_list:
if pygame.sprite.collide_rect(eTank,self):
self.live = False#子弹不再存活
eTank.live = False#坦克不存活
MainGame的blitEnemyTank应加上条件:
def blitEnemyTank(self):#将坦克加入到窗口中
for eTank in MainGame.EnemyTank_list:
if eTank.live:
eTank.displayTank()
eTank.randMove()#坦克移动
eBullet = eTank.shot()#敌方坦克射击,因为继承关系,所以有shot函数
if eBullet:
MainGame.Enemy_bullet_list.append(eBullet)#将敌方坦克加入列表
else:
MainGame.EnemyTank_list.remove(eTank)
MainGame的blitBullet的if中加入
bullet.hitEnemyTank()#调用我方子弹和敌方坦克的碰撞方法
?4)敌方子弹与我方坦克的碰撞
在Bullet类中定义新的方法:
def hitMyTank(self):#新增敌方子弹和我方坦克
#因为前面已经遍历了敌方子弹,所以这里只需要判断一个子弹
if pygame.sprite.collide_rect(MainGame.TANK_P1,self):
self.live = False#修改子弹状态
MainGame.TANK_P1.live = False#修改我方坦克状态
?在MainGame的blitEnemyBullet中if下判断并调用:
if MainGame.TANK_P1 and MainGame.TANK_P1.live:#坦克存在并活着才能调用
ebullet.hitMyTank()
我方坦克被打中后,子弹和我方坦克状态改变,接下来对我方坦克的展示进行逻辑处理,在死循环中的我方坦克显示加上条件判断,改为:
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
MainGame.TANK_P1.displayTank()#将我方坦克加入窗口
else:
del MainGame.TANK_P1
MainGame.TANK_P1 = None
?我方坦克被击中后,在动按键会报错,所以在按键KEYDOWN加上条件判断:
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
?将KEYUP的第二个判断改为:
if ((event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN) and MainGame.Num_Key == 0):
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
MainGame.TANK_P1.stop = True
5)实现我方坦克的重生
通过按ESC进行重生,所以修改getEvent方法,在KEYDOWN判断中增加:
if event.key == pygame.K_ESCAPE and not MainGame.TANK_P1:#ESC按键重生
self.creatMyTank()
4、爆炸效果
1)敌方坦克
多个图片的切换实现爆炸效果的从小到大
class Explode():
def __init__(self,tank):
self.rect = tank.rect
self.step = 0
self.images = [
pygame.image.load('img/blast0.gif'),
pygame.image.load('img/blast1.gif'),
pygame.image.load('img/blast2.gif'),
pygame.image.load('img/blast3.gif'),
pygame.image.load('img/blast4.gif')]
self.image = self.images[self.step]
self.live = True#一开始要进行显示
def displayExplode(self):#展示子弹
if self.step < len(self.images):
MainGame.window.blit(self.image,self.rect)
self.image = self.images[self.step]
self.step += 1
else:
self.step = 0
self.live = False#不再进行显示
在MainGame中新加属性:
Explode_list = []
在Bullet的hitEnemyTank中的if中调用,并且存放爆炸列表
explode = Explode(eTank)#产生一个爆炸效果
MainGame.Explode_list.append(explode)#将爆炸效果加入到爆炸效果列表
在MainGame中定义新方法:
def displayExplodes(self):#新增方法,展示爆炸效果列表
for explode in MainGame.Enemy_bullet_list:
if explode.live:
explode.displayExplode()
else:
MainGame.Explode_list.remove(explode)
接着在死循环中调用
self.displayExplodes()
2)我方坦克
在Bullet的hitMyTank中的if增加? ?
explode = Explode(MainGame.TANK_P1)#产生爆炸效
MainGame.Explode_list.append(explode)
?5、墙壁实现
class Wall():
def __init__(self,left,top):
self.image = pygame.image.load('img/steels.gif')
self.rect = self.image.get_rect()
self.rect.left = left
self.rect.top = top
self.live = True#因为子弹打墙壁要是否消失,所以定义该属性
self.hp = 3 #墙壁生命值
def dispalyWall(self):#展示墙壁
MainGame.window.blit(self.iamge,self.rect)
墙壁类似敌方坦克,打掉后消失,所以在MainGame中定义新的列表:
Wall_list = []#墙壁列表
并在MainGame定义新的方法用来生成墙:
def creatWalls(self): #创建墙壁的方法
for i in range(1,7):
wall = Wall(130*i,400)
MainGame.Wall_list.append(wall)
在startGame中调用:
self.creatWalls()#调用展示墙壁的方法
并定义新的展示墙的方法:
def blitWalls(self):
for wall in MainGame.Wall_list:
if wall.live:
wall.dispalyWall()
else:
MainGame.Wall_list.remove(wall)
在死循环中进行调用:
self.blitWalls()
1)子弹与墙壁碰撞
在子弹类中新增方法
def hitWalls(self):#子弹与墙壁碰撞
for wall in MainGame.Wall_list:
if pygame.sprite.collide_rect(wall,self):
self.live = False#修改属性
wall.hp -= 1
if wall.hp <= 0:
wall.live = False
在MainGame的blitBullet和blitEnemyBullet中进行调用
2)坦克不能穿墙
思路为:记录坦克之前的坐标,如果坦克和墙壁碰撞,则还原为原来的坐标
Tank里新增属性
self.oldLeft = self.rect.left#新增属性,记录坦克之前的坐标
self.oldTop = self.rect.top
Tank的move里开头增加一样的,并在Tank中创建新方法:
def stay(self):
self.rect.left = self.oldLeft
self.rect.top = self.oldTop
def hitWalls(self):#新增坦克和墙壁碰撞方法
for wall in MainGame.Wall_list:
if pygame.sprite.collide_rect(wall,self):
self.stay()
?在死循环中对我方坦克进行调用,在允许移动的条件判断中加入:
MainGame.TANK_P1.hitWalls()
在MainGame的blitEnemyTank的if中加入
eTank.hitWalls()#调用敌方坦克与墙壁的碰撞方法
3)实现坦克之间的碰撞检测
我方坦克主动撞击敌方,则stay(),敌方主动撞击我方坦克,则敌方stay(),编写MyTank为
class MyTank(Tank):
def __init__(self,left,top):
super(MyTank,self).__init__(left,top)
def hitEnemyTank(self):
for eTank in MainGame.EnemyTank_list:
if pygame.sprite.collide_rect(eTank,self):
self.stay()
在MainGame的blitEnemyTank的if中加入
MainGame.TANK_P1.hitEnemyTank()
在敌方坦克也定义一个
def hitMyTank(self):
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
if pygame.sprite.collide_rect(self,MainGame.TANK_P1):
self.stay()
并在MainGame的blitEnemyTank进行调用
eTank.hitMyTank()
6、音效处理
编写类
class Music():
def __init__(self,fileName):
self.fileName = fileName
pygame.mixer.init()
pygame.mixer.music.load(self.fileName)
def play(self):#开始播放音乐
pygame.mixer.music.play()
在MainGame的creatMyTank进行调用:
def creatMyTank(self):
MainGame.TANK_P1 = MyTank(650,600)#创建我方坦克
music = Music('img/start.wav')
music.play()
在MainGame的getEvent进行调用:
elif event.key == pygame.K_SPACE:
if len(MainGame.Bullet_list) < 5:
m = Bullet(MainGame.TANK_P1)#产生一个子弹
MainGame.Bullet_list.append(m)
print('射击')
music = Music('img/fire.wav')
music.play()
在MainGame的hitEnemyTank和hitMyTank调用:
music = Music('img/hit.wav')
music.play()
三、代码
1、成果
介个时候!!全部功能大工告成!!
再经过对代码的优化、改变,最终代码如下
import pygame,time,random
COLOR_BLACK = pygame.Color(0,0,0)
COLOR_RED = pygame.Color(255,0,0)
class MainGame():
window = None#游戏主窗口
SCREEN_WIDTH = 1300
SCREEN_HEIGHT = 700
TANK_P1 = None
FLAG = None
EnemyTank_list = [] #敌方坦克列表
EnemyTank_count = 5#创建的坦克数量
Bullet_list = []#存储我方子弹的列表
Enemy_bullet_list = []#存储敌方坦克
Num_Key = 0
Explode_list = []#墙壁列表
Wall_list = []#墙壁列表
defeatNum = 10
remainLive = 3
def __init__(self):
pass
def startGame(self):#开始游戏
pygame.display.init()
MainGame.window = pygame.display.set_mode([MainGame.SCREEN_WIDTH,MainGame.SCREEN_HEIGHT])#加载游戏窗口(看文档)
self.creatMyTank()#创建我方坦克
self.creatFlag()
start = Music('img/start.wav')
start.play()
pygame.display.set_caption('坦克大战')#设置标题
self.creatEnemyTank(MainGame.EnemyTank_count)
self.creatWalls()#调用展示墙壁的方法
while True:#让窗口持续刷新
MainGame.window.fill(COLOR_BLACK)#填充为黑色
self.getEvent()
self.blitWalls()
MainGame.window.blit(self.getTextSurface('还需要击败敌人%d'%MainGame.defeatNum),(5,5))#将绘制文字的小画布粘贴到窗口中
MainGame.window.blit(self.getTextSurface('剩余命数%d'%MainGame.remainLive),(5,23))#将绘制文字的小画布粘贴到窗口中
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
MainGame.TANK_P1.displayTank()#将我方坦克加入窗口
else:
del MainGame.TANK_P1#我方坦克被击中则删去该对象
MainGame.TANK_P1 = None
if MainGame.FLAG and MainGame.FLAG.live:
MainGame.FLAG.dispalyFlag()#将旗帜加入窗口
self.blitEnemyTank()#将敌方坦克加入窗口
if MainGame.TANK_P1 and not MainGame.TANK_P1.stop:#坦克存在并且打开开关时允许移动
MainGame.TANK_P1.move()
MainGame.TANK_P1.hitWalls()
MainGame.TANK_P1.hitEnemyTank()
if len(self.EnemyTank_list) < 5:
self.creatEnemyTank(MainGame.EnemyTank_count-len(self.EnemyTank_list))
self.blitBullet()#调用渲染子弹列表的方法
self.blitEnemyBullet()#敌方坦克的子弹
self.displayExplodes()
time.sleep(0.02)
pygame.display.update()
if MainGame.defeatNum == 0 or MainGame.remainLive == 0 or not MainGame.FLAG.live:
break
while True:
self.blitEndWord()
pygame.display.update()
def creatMyTank(self):
MainGame.TANK_P1 = MyTank(400,600)#创建我方坦克
music = Music('img/add.wav')
music.play()
def creatEnemyTank(self,count):#创建敌方坦克
top = 100
for i in range(count):
rand = random.randint(1,3)
if rand == 1:
left = 100
elif rand == 2:
left = 650
elif rand == 3:
left = 1200
speed = random.randint(3,4)
eTtank = EnemyTank(left,top,speed)
MainGame.EnemyTank_list.append(eTtank)
def blitEnemyTank(self):#将坦克加入到窗口中
for eTank in MainGame.EnemyTank_list:
if eTank.live:
eTank.displayTank()
eTank.randMove()#坦克移动
eTank.hitWalls()#调用敌方坦克与墙壁的碰撞方法
eBullet = eTank.shot()#敌方坦克射击,因为继承关系,所以有shot函数
eTank.hitMyTank()
if eBullet:
MainGame.Enemy_bullet_list.append(eBullet)#将敌方子弹坦克加入列表
else:
MainGame.EnemyTank_list.remove(eTank)
def blitBullet(self):#将我方子弹加入窗口
for bullet in MainGame.Bullet_list:
if bullet.live:#如果子弹还存在,则展示,否则,移除子弹
bullet.displayBullet()
bullet.bulletMove()
bullet.hitEnemyTank()#调用我方子弹和敌方坦克的碰撞方法
bullet.hitWalls()#判断子弹撞墙
bullet.hitFlag()
else:
MainGame.Bullet_list.remove(bullet)
def displayExplodes(self):#新增方法,展示爆炸效果列表
for explode in MainGame.Explode_list:
if explode.live:
explode.displayExplode()
else:
MainGame.Explode_list.remove(explode)
def blitEnemyBullet(self):#将敌方子弹加入窗口
for ebullet in MainGame.Enemy_bullet_list:
if ebullet.live:#如果子弹还存在,则展示,否则,移除子弹
ebullet.displayBullet()
ebullet.bulletMove()#让子弹移动
ebullet.hitWalls()
ebullet.hitFlag()
if MainGame.TANK_P1 and MainGame.TANK_P1.live:#坦克存在并活着才能调用
ebullet.hitMyTank()
else:
MainGame.Enemy_bullet_list.remove(ebullet)
def getTextSurface(self,text):
pygame.font.init()#初始化字体模块
font = pygame.font.SysFont('kaiti',18)#选中一个字体
textSurface = font.render(text,True,COLOR_RED)#进行绘制
return textSurface
def getEvent(self):#获取事件,比如鼠标事件、键盘事件
eventList = pygame.event.get()#获取
for event in eventList:
if event.type == pygame.QUIT:#判断是否点击退出
self.endGame()
if event.type == pygame.KEYDOWN:#判断按键按下,并判断是哪个键
if event.key == pygame.K_ESCAPE and not MainGame.TANK_P1:#ESC按键重生
self.creatMyTank()
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN:
MainGame.Num_Key += 1
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
if event.key == pygame.K_LEFT:#左方向键
print('左')
MainGame.TANK_P1.direction = 'L'#改变坦克的方向
MainGame.TANK_P1.stop = False
elif event.key == pygame.K_RIGHT:
print('右')
MainGame.TANK_P1.direction = 'R'#改变坦克的方向
MainGame.TANK_P1.stop = False
elif event.key == pygame.K_UP:
print('上')
MainGame.TANK_P1.direction = 'U'#改变坦克的方向
MainGame.TANK_P1.stop = False
elif event.key == pygame.K_DOWN:
print('下')
MainGame.TANK_P1.direction = 'D'#改变坦克的方向
MainGame.TANK_P1.stop = False
elif event.key == pygame.K_SPACE:
if len(MainGame.Bullet_list) < 5:
m = Bullet(MainGame.TANK_P1)#产生一个子弹
MainGame.Bullet_list.append(m)
print('射击')
music = Music('img/fire.wav')
music.play()
if event.type == pygame.KEYUP:#将坦克的移动状态修改
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN:
MainGame.Num_Key -= 1
if ((event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN) and MainGame.Num_Key == 0):
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
MainGame.TANK_P1.stop = True
def creatWalls(self): #创建墙壁的方法
wall = Wall(0,0)
L_left = MainGame.SCREEN_WIDTH/2 - MainGame.FLAG.rect.width - wall.rect.width
L_top = MainGame.SCREEN_HEIGHT - wall.rect.height
wall = Wall(L_left,L_top)
MainGame.Wall_list.append(wall)
for i in range(0,4):
wall = Wall(L_left + wall.rect.width * i,L_top - wall.rect.height)
MainGame.Wall_list.append(wall)
wall = Wall(L_left + wall.rect.width*3,L_top)
MainGame.Wall_list.append(wall)
for i in range (0,8):
wall = Wall((wall.rect.width)*3*i,400)
MainGame.Wall_list.append(wall)
for i in range(1,6):
wall = Wall((wall.rect.width)*6,400 - wall.rect.height * i)
MainGame.Wall_list.append(wall)
for i in range(1,6):
wall = Wall((wall.rect.width)*15,400 - wall.rect.height * i)
MainGame.Wall_list.append(wall)
wall = Wall((wall.rect.width)*10+(wall.rect.width)*1,400)
MainGame.Wall_list.append(wall)
wall = Wall((wall.rect.width)*9+(wall.rect.width)*1,400)
MainGame.Wall_list.append(wall)
for i in range(1,9):
wall = Wall((wall.rect.width)*15 - wall.rect.width*i,400 - wall.rect.height*2)
MainGame.Wall_list.append(wall)
for i in range(1,6):
wall = Wall((wall.rect.width)*18,400 + wall.rect.height * i)
MainGame.Wall_list.append(wall)
for i in range(1,6):
wall = Wall((wall.rect.width)*3,400 + wall.rect.height * i)
MainGame.Wall_list.append(wall)
for i in range(1,4):
wall = Wall((wall.rect.width)*i,400 - wall.rect.height * 4)
MainGame.Wall_list.append(wall)
for i in range(1,4):
wall = Wall(MainGame.SCREEN_WIDTH - (wall.rect.width)*i,400 - wall.rect.height * 4)
MainGame.Wall_list.append(wall)
def blitWalls(self):
for wall in MainGame.Wall_list:
if wall.live:
wall.dispalyWall()
else:
MainGame.Wall_list.remove(wall)
def endGame(self):#结束游戏
print('古德拜拜')
exit(0)#结束Python解释器
def blitEndWord(self):
image = EndWord()
image.dispalyEndWord()
def creatFlag(self):
MainGame.FLAG = Flag()
def restart(self):
self.startGame()
class BaseItem(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
class Tank(BaseItem):
def __init__(self,left,top):
self.images = {'U':pygame.image.load('img/p1tankU.gif'),
'D':pygame.image.load('img/p1tankD.gif'),
'L':pygame.image.load('img/p1tankL.gif'),
'R':pygame.image.load('img/p1tankR.gif')
}
self.direction = 'U'
self.image = self.images[self.direction]#根据坦克的方向选择照片
self.rect = self.image.get_rect()#坦克所在的区域
self.rect.left = left
self.rect.top = top#指定坦克的初始化位置,分别距离x,y轴的距离
self.speed = 5
self.stop = True
self.live = True
self.oldLeft = self.rect.left#新增属性,记录坦克之前的坐标
self.oldTop = self.rect.top
def move(self):#坦克移动
self.oldLeft = self.rect.left#新增属性,记录坦克之前的坐标
self.oldTop = self.rect.top#记录之前的坐标
if self.direction == 'L':
if self.rect.left > 0:
self.rect.left -= self.speed
elif self.direction == 'R':
if self.rect.left + self.rect.height <MainGame.SCREEN_WIDTH:
self.rect.left += self.speed
elif self.direction == 'U':
if self.rect.top > 0:
self.rect.top -= self.speed
elif self.direction == 'D':
if self.rect.top + self.rect.height <MainGame.SCREEN_HEIGHT:
self.rect.top += self.speed
def stay(self):
self.rect.left = self.oldLeft
self.rect.top = self.oldTop
def hitWalls(self):#新增坦克和墙壁碰撞方法
for wall in MainGame.Wall_list:
if pygame.sprite.collide_rect(wall,self):
self.stay()
def shot(self):#坦克射击
return Bullet(self)
def displayTank(self):#坦克进行显示(将坦克这个Surface绘制到窗口中,用blit())
self.image = self.images[self.direction]#重置坦克的图像
MainGame.window.blit(self.image,self.rect)#将坦克加入到窗口中
class MyTank(Tank):
def __init__(self,left,top):
super(MyTank,self).__init__(left,top)
def hitEnemyTank(self):
for eTank in MainGame.EnemyTank_list:
if pygame.sprite.collide_rect(eTank,self):
self.stay()
class EnemyTank(Tank):
def __init__(self,left,top,speed):
super(EnemyTank,self).__init__(left,top)
self.images = {'U':pygame.image.load('img/enemy1U.gif'),
'D':pygame.image.load('img/enemy1D.gif'),
'L':pygame.image.load('img/enemy1L.gif'),
'R':pygame.image.load('img/enemy1R.gif')
}
self.direction = self.randDirection()
self.image = self.images[self.direction]#根据坦克的方向选择照片
self.rect = self.image.get_rect()#坦克所在的区域
self.rect.left = left
self.rect.top = top#指定坦克的初始化位置,分别距离x,y轴的距离
self.speed = speed
self.stop = True
self.step = random.randint(38,53)#新增步数属性,用来控制敌方坦克随机移动
def randDirection(self):#用来随机生成坦克的方向
num = random.randint(1,4)
if num == 1:
return 'U'
elif num == 2:
return 'D'
elif num == 3:
return 'L'
elif num == 4:
return 'R'
def randMove(self):
if self.step <= 0:
self.direction = self.randDirection()#一定距离后改变方向
self.step = random.randint(23,43)#重置步数
else:
self.move()
self.step -= 1
def shot(self):
num = random.randint(1,70)
if num == 1:
return Bullet(self)
def hitMyTank(self):
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
if pygame.sprite.collide_rect(self,MainGame.TANK_P1):
self.stay()
class Bullet(BaseItem):
def __init__(self,tank):
self.image = pygame.image.load('img/enemymissile.gif')#图像
self.direction = tank.direction#方向
self.rect = self.image.get_rect()
self.speed = 7#速度
self.live = True#判断子弹是否应存在
if self.direction == 'U':#起始位置
self.rect.left = tank.rect.left + tank.rect.width/2 - self.rect.width/2
self.rect.top = tank.rect.top - self.rect.height
elif self.direction == 'D':
self.rect.left = tank.rect.left + tank.rect.width/2 - self.rect.width/2
self.rect.top = tank.rect.top + tank.rect.height
elif self.direction == 'L':
self.rect.left = tank.rect.left - self.rect.width/2 - self.rect.width/2
self.rect.top = tank.rect.top + tank.rect.width/2 - self.rect.width/2
elif self.direction == 'R':
self.rect.left = tank.rect.left + tank.rect.width
self.rect.top = tank.rect.top - self.rect.width/2 + tank.rect.width/2
def bulletMove(self):#子弹移动
if self.direction == 'U':
if self.rect.top > 0:
self.rect.top -= self.speed
else:
self.live = False
elif self.direction == 'D':
if self.rect.top < MainGame.SCREEN_HEIGHT - self.rect.height:
self.rect.top += self.speed
else:
self.live = False
elif self.direction == 'L':
if self.rect.left > 0:
self.rect.left -= self.speed
else:
self.live = False
elif self.direction == 'R':
if self.rect.left < MainGame.SCREEN_WIDTH - self.rect.width:
self.rect.left += self.speed
else:
self.live = False
def displayBullet(self):#子弹显示
MainGame.window.blit(self.image,self.rect)
def hitEnemyTank(self):#新增我方子弹碰撞敌方坦克的方法
for eTank in MainGame.EnemyTank_list:
if pygame.sprite.collide_rect(eTank,self):
self.live = False #子弹不再存回
eTank.live = False #坦克不再存活
explode = Explode(eTank)#产生一个爆炸效果
MainGame.Explode_list.append(explode)#将爆炸效果加入到爆炸效果列表
music = Music('img/hit.wav')
music.play()
MainGame.defeatNum -= 1
def hitMyTank(self):#新增敌方子弹和我方坦克
#因为前面已经遍历了敌方子弹,所以这里只需要判断一个子弹
if pygame.sprite.collide_rect(self,MainGame.TANK_P1):
self.live = False#修改子弹状态
MainGame.TANK_P1.live = False#修改我方坦克状态
explode = Explode(MainGame.TANK_P1)#产生爆炸效
MainGame.Explode_list.append(explode)
music = Music('img/hit.wav')
music.play()
MainGame.remainLive -= 1
def hitWalls(self):#子弹与墙壁碰撞
for wall in MainGame.Wall_list:
if pygame.sprite.collide_rect(wall,self):
self.live = False#修改属性
wall.hp -= 1
if wall.hp <= 0:
wall.live = False
def hitFlag(self):#子弹与旗帜碰撞
if pygame.sprite.collide_rect(MainGame.FLAG,self):
self.live = False#修改属性
MainGame.FLAG.live = False
class Explode():
def __init__(self,tank):
self.rect = tank.rect
self.step = 0
self.images = [
pygame.image.load('img/blast0.gif'),
pygame.image.load('img/blast1.gif'),
pygame.image.load('img/blast2.gif'),
pygame.image.load('img/blast3.gif'),
pygame.image.load('img/blast4.gif')]
self.image = self.images[self.step]
self.live = True#一开始要进行显示
def displayExplode(self):#展示子弹
if self.step < len(self.images):
MainGame.window.blit(self.image,self.rect)
self.image = self.images[self.step]
self.step += 1
else:
self.step = 0
self.live = False#不再进行显示
class Wall():
def __init__(self,left,top):
self.image = pygame.image.load('img/walls.gif')
self.rect = self.image.get_rect()
self.rect.left = left
self.rect.top = top
self.live = True#因为子弹打墙壁要是否消失,所以定义该属性
self.hp = 1 #墙壁生命值
def dispalyWall(self):#展示墙壁
MainGame.window.blit(self.image,self.rect)
class EndWord():
def __init__(self):
self.images = [pygame.image.load('img/over.gif'),
pygame.image.load('img/win.gif')]
if MainGame.remainLive == 0:
self.image = self.images[0]
if MainGame.defeatNum == 0:
self.image = self.images[1]
if not MainGame.FLAG.live:
self.image = self.images[0]
self.rect = self.image.get_rect()
self.rect.left = 650
self.rect.top = 350
def dispalyEndWord(self):#展示
MainGame.window.blit(self.image,self.rect)
class Flag():
def __init__(self):
self.image = pygame.image.load('img/flag.gif')
self.rect = self.image.get_rect()
self.rect.left = MainGame.SCREEN_WIDTH/2 - self.rect.width/2
self.rect.top = MainGame.SCREEN_HEIGHT - self.rect.height
self.live = True
def dispalyFlag(self):#展示
MainGame.window.blit(self.image,self.rect)
class Music():
def __init__(self,fileName):
self.fileName = fileName
pygame.mixer.init()
pygame.mixer.music.load(self.fileName)
def play(self):#开始播放音乐
pygame.mixer.music.play()
MainGame().startGame()
。。。真不好意思,竟然这么长,嘿嘿,效果展示如下:
?
?
?我方击败10个敌人,则胜利,我方死亡3次,或旗帜被攻打,会失败
2、缺点
胜利或者失败之后不能通过按键重新开始,障碍物也只有墙壁,敌方坦克只有一种,不过捏,学习为主,加上这些东西,要好多时间,会操作就好了~
|