tank大战
2022-01-01 本文已影响0人
Chaweys
# -*-coding:utf-8 -*-
# @Author : hudechao
# @Time : 2021/12/12 18:50
"""
坦克大战游戏的需求(基于面向对象的分析):
1、项目中有哪些类
2、每个类中有哪些方法
1、主类
开始游戏
结束游戏
2、坦克类(我方坦克,地方坦克)
移动
射击
显示坦克的方法
3、子弹类
移动
显示子弹的方法
4、爆炸效果类
展示爆炸效果
5、强壁类
属性:是否可以通过
6、音效类
播放音乐
v1.25
优化功能:
1、实现音效处理
我方坦克出生音效
发射子弹音效
子弹打中坦克音效
"""
import pygame
import time
import random
version = "v1.25"
_display = pygame.display
COLOR_BLACK = pygame.Color(0,0,0)
COLOR_RED = pygame.Color(255,0,0)
class MainGame:
#游戏主窗口
window = None
#宽
SCREEN_WIDTH = 800
#高
SCREEN_HEIGHT = 500
#创建我方坦克
TANK_P1 = None
#新增属性开关:控制坦克的移动停止,默认Ture坦克停止
Tank_Flag = True
#敌方坦克容器
EnamyTank_list = []
#敌方坦克数量
EanmyTank_count = 5
#我方坦克子弹容器
TankBullet_list = []
#敌方坦克子弹容器
EanmyBullet_list = []
#爆炸效果列表
Explode_list = []
#墙壁类列表
Walls_list = []
#启动游戏方法
def startGame(self):
_display.init()
#初始化显示模块,返回的是surface对象
MainGame.window = _display.set_mode([MainGame.SCREEN_WIDTH,MainGame.SCREEN_HEIGHT])
#给显示游戏窗口添加标题
_display.set_caption("坦克大战"+version)
#创建我方坦克:只需创建一次
self.createMyTank()
#创建敌方坦克:调用方法创建多个敌方坦克
self.createEnamyTank()
#创建墙壁类
self.createWall()
while True:
#因为要持续显示游戏窗口,所以需要加循环
#给游戏窗口添加背景填充色
MainGame.window.fill(COLOR_BLACK)
#循环获取事件
self.getEvent()
#循环设置字体画布在游戏的主画布上
#dest=(1,1)代表游戏画布的坐标x=1,y=1,即左上角
MainGame.window.blit(self.getTextSurface("剩余敌方坦克%d辆"%len(MainGame.EnamyTank_list)),(1,1))
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
#加载我方坦克到游戏主窗口
MainGame.TANK_P1.displayTank()
else:
#如果我方坦克失活,删除我方坦克内存
del MainGame.TANK_P1
MainGame.TANK_P1 = None
#加载敌方坦克到游戏窗口:移动方法记载到了这里,再循环之下,所以会一直移动,所以需要重写父类的移动方法父类的移动方法
self.biltEnamyTank()
#展示墙壁类
self.biltWall()
#判断坦克的开关=Ture时停止移动坦克,如果开关=False坦克继续移动:我方坦克能控制移动是因为设置了移动开关:Tank_Flag
if not MainGame.Tank_Flag and MainGame.TANK_P1:
MainGame.TANK_P1.move()
#调用我方坦克的碰撞墙壁的方法
MainGame.TANK_P1.hitWalls()
#调用我方坦克是否碰撞到敌方坦克
MainGame.TANK_P1.hitEnamyTank()
#循环展示我方子弹到游戏主窗口
self.biltBullet()
#循环展示敌方子弹到游戏主窗口
self.ebiltBullet()
#循环展示爆炸效果
self.disExplode()
# 循环显示游戏窗口
time.sleep(0.02)
_display.update()
#获取游戏运行期间所有的事件
def getEvent(self):
#获取所有事件
eventList = pygame.event.get()
for event in eventList:
#判断事件类型,如果是QUIT,则退出游戏
if event.type == pygame.QUIT:
self.stopGame()
#判断事件类型,如果是按键类型,则根据具体按键事件具体响应
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE and not MainGame.TANK_P1:
self.createMyTank()
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
if event.key == pygame.K_LEFT:
print("坦克向左掉头移动")
# 重新设置我方坦克的方向
MainGame.TANK_P1.direction = "L"
# 移动我方坦克-按键一次移动一次,需要优化
# MainGame.TANK_P1.move()
# 按下键后打开坦克的移动开关
MainGame.Tank_Flag = False
elif event.key == pygame.K_RIGHT:
print("坦克向右掉头移动")
# 重新设置我方坦克的方向
MainGame.TANK_P1.direction = "R"
# 移动我方坦克-优化移动
# MainGame.TANK_P1.move()
# 按下键后打开坦克的移动开关
MainGame.Tank_Flag = False
elif event.key == pygame.K_UP:
print("坦克向上掉头移动")
# 重新设置我方坦克的方向
MainGame.TANK_P1.direction = "U"
# 移动我方坦克-优化移动
# MainGame.TANK_P1.move()
# 按下键后打开坦克的移动开关
MainGame.Tank_Flag = False
elif event.key == pygame.K_DOWN:
print("坦克向下掉头移动")
# 重新设置我方坦克的方向
MainGame.TANK_P1.direction = "D"
# 移动我方坦克-优化移动
# MainGame.TANK_P1.move()
# 按下键后打开坦克的移动开关
MainGame.Tank_Flag = False
elif event.key == pygame.K_SPACE:
print("发射子弹")
if len(MainGame.TankBullet_list) < 3:
# 创建子弹
# m = Bullet(MainGame.TANK_P1),或者
m = MainGame.TANK_P1.shot()
# 将子弹加入到子弹列表里
MainGame.TankBullet_list.append(m)
music = Music('img/fire.wav')
music.playMusic()
else:
print("子弹数量不足")
print("当前子弹数量:{}".format(len(MainGame.TankBullet_list)))
#判断事件类型,松开按键就停止坦克的移动
if event.type == pygame.KEYUP:
if event.key == pygame.K_UP or event.key == pygame.K_DOWN or event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
#松开按键,关闭坦克的移动开关
MainGame.Tank_Flag = True
#游戏内文字绘制方法
def getTextSurface(self,text):
#初始化字体模块
pygame.font.init()
#获取所有支持的字体
# print(pygame.font.get_fonts())
#设置选用的字体
font = pygame.font.SysFont('kaiti',25)
#字体模块设置后返回依然是一个surface-小画布
textSurface = font.render(text,True,COLOR_RED)
return textSurface
#创建我方坦克
def createMyTank(self):
MainGame.TANK_P1 = MyTank(400, 400)
#创建我方坦克后播放开始音效
music = Music('img/start.wav')
music.playMusic()
#创建敌方坦克
def createEnamyTank(self):
#敌方坦克的位置规定,top固定,left变化
top = 100
for i in range(MainGame.EanmyTank_count):
#每个坦克的速度随机
speed = random.randint(1, 2)
#left决定敌方坦克的水平位置,每个坦克需要随机不同位置
left = random.randint(1,7)*100
MainGame.EnamyTank_list.append(EnamyTank(left,top,speed))
#展示敌方坦克
def biltEnamyTank(self):
#敌方坦克类调用父类的展示坦克方法
for eTank in MainGame.EnamyTank_list:
if eTank.live:
#敌方坦克加载到游戏主窗口
eTank.displayTank()
#敌方坦克移动:调用了父类的移动方法
#eTank.move()
eTank.randMove()
#调用敌方坦克的碰撞墙壁方法
eTank.hitWalls()
#调用敌方坦克碰撞到我方坦克方法
eTank.hitMyTank()
#敌方坦克射击子弹
ebullet=eTank.shot()
if ebullet is not None:
MainGame.EanmyBullet_list.append(ebullet)
else:
MainGame.EnamyTank_list.remove(eTank)
#我方子弹渲染
def biltBullet(self):
#遍历子弹列表展示
for bullet in MainGame.TankBullet_list:
if bullet.live:
#我方子弹展示
bullet.displayBullet()
#我方移动子弹
bullet.bulletMove()
#我方子弹是否击中敌方坦克
bullet.hitEanmyTank()
#我方子弹是否击中墙壁
bullet.hitWalls()
else:
#子弹消失
bullet.live = False
MainGame.TankBullet_list.remove(bullet)
#敌方子弹渲染
def ebiltBullet(self):
#遍历子弹列表展示
for ebullet in MainGame.EanmyBullet_list:
if ebullet.live:
#子弹展示
ebullet.displayBullet()
#移动子弹
ebullet.bulletMove()
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
#敌方子弹是否击中我方坦克
ebullet.hitMyTank()
#敌方子弹是否击中墙壁
ebullet.hitWalls()
else:
#子弹消失
ebullet.live = False
MainGame.EanmyBullet_list.remove(ebullet)
#展示爆炸效果
def disExplode(self):
for explode in MainGame.Explode_list:
if explode.live:
explode.displayExplode()
#展示爆炸效果
music = Music('img/hit.wav')
music.playMusic()
else:
#移除爆炸效果类,移除后即不会再展示了
MainGame.Explode_list.remove(explode)
#创建墙壁
def createWall(self):
for i in range(6):
wall = Wall(190*i,240)
MainGame.Walls_list.append(wall)
#展示墙壁
def biltWall(self):
for wall in MainGame.Walls_list:
if wall.live:
wall.displayWall()
else:
MainGame.Walls_list.remove(wall)
#结束游戏方法
def stopGame(self):
print("谢谢使用")
exit()
class BaseItem(pygame.sprite.Sprite):
"""
Tank坦克类和Bullet子弹类都继承该类,该类又继承精灵类,从而实现:
Tank坦克类和Bullet子弹类的碰撞消失
因为Tank类和Bullet类都需要共同的精灵类属性
"""
def __init__(self, color, width, height):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
#坦克父类
class Tank(BaseItem):
#初始化坦克属性
def __init__(self,left,top):
#每个方向都是一个坦克的图片,存在一个字典里,从该字典里获取各个不同方向的坦克图片:U D L R 代表上下左右
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也是一个个surface
self.image = self.images[self.direction]
#初始化坦克所在的区域,self.image.get_rect()返回Rect对象,该对象包含Rect(left, top, width, height) -> Rect
#坦克的宽和高就使用图片本身的宽和高,坦克的位置就使用传入的入参left和right
self.rect = self.image.get_rect()
self.rect.left = left
self.rect.top = top
#坦克速度属性
self.speed = 5
#坦克是否存活的属性:默认存活
self.live = True
#记录坦克的移动之前的旧坐标:
self.oldLeft = self.rect.left
self.oldTop = self.rect.top
#移动方法
def move(self):
"""
坦克的移动就是修改坐标值:
向左移动就是left值减少,向右移动就是left值增加,
向上移动就是top值减少,向下移动就是top值增加
:return:
"""
#移动之前先记录坦克的坐标:这里只要坦克的位置坐标发生改变,就会覆盖重写旧坐标
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 shot(self):
return Bullet(self)
#展示坦克方法
def displayTank(self):
"""
坦克对象也是一张一张的surface,展示坦克就是把每个坦克的surface加载到游戏主窗口中,使用到blit()
:return:
"""
#重新设置坦克的位置
self.image = self.images[self.direction]
#加载坦克到游戏主窗口中
MainGame.window.blit(self.image,self.rect)
def stay(self):
#坦克碰撞后恢复到之前的坐标
self.rect.left = self.oldLeft
self.rect.top = self.oldTop
#坦克与墙壁的碰撞方法
def hitWalls(self):
for wall in MainGame.Walls_list:
if pygame.sprite.collide_circle(self,wall):
#如果坦克与墙壁发生碰撞,则恢复坦克的坐标为移动之前的坐标
self.stay()
class MyTank(Tank):
#我方坦克类
def __init__(self,left,top):
super().__init__(left,top)
#判断我方坦克是否碰撞到敌方坦克
def hitEnamyTank(self):
for etank in MainGame.EnamyTank_list:
if pygame.sprite.collide_rect(self,etank):
#调用父类的还原坦克位置方法
self.stay()
class EnamyTank(Tank):
#敌方坦克类:继承Tank父类
def __init__(self,left,top,speed):
#需要使用父类的默认属性,则需要显示的调用父类的初始化方法
super().__init__(left,top)
#每个方向都是一个坦克的图片,存在一个字典里,从该字典里获取各个不同方向的坦克图片:U D L R 代表上下左右
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.showDriection()
#初始化时坦克的图片:self.image也是一个个surface
self.image = self.images[self.direction]
#初始化坦克所在的区域,self.image.get_rect()返回Rect对象,该对象包含Rect(left, top, width, height) -> Rect
#坦克的宽和高就使用图片本身的宽和高,坦克的位置就使用传入的入参left和right
self.rect = self.image.get_rect()
self.rect.left = left
self.rect.top = top
#坦克速度属性
self.speed = speed
#坦克步数属性
self.step = 10
#随机展示敌方坦克方向
def showDriection(self):
i = random.randint(1,4)
if i == 1:
return 'U'
elif i == 2:
return 'D'
elif i == 3:
return 'L'
elif i == 4:
return 'R'
#随机移动敌方坦克
def randMove(self):
if self.step <= 0:
self.direction = self.showDriection()
self.step = 10
else:
self.move()
self.step -= 1
#重写父类的射击方法
def shot(self):
num = random.randint(1,1000)
if num <= 20:
return Bullet(self)
else:
return None
#判断敌方坦克是否碰撞到我方坦克
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.rect = self.image.get_rect()
#设置子弹的方向:初始方向等同于坦克的方向
self.direction = tank.direction
#设置子弹的位置:判断四个方位的位置
if self.direction == "U":
#子弹的left = 坦克的left + 坦克宽度/2 - 子弹宽度/2
#子弹的top = 坦克的top - 子弹的高度
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":
#子弹的left = 坦克的left + 坦克宽度/2 - 子弹宽度/2
#子弹的top = 坦克的top + 坦克的高度
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":
#子弹的left = 坦克的left - 子弹的高度
#子弹的top = 坦克的top + 坦克的高度/2 - 子弹的宽度/2
self.rect.left = tank.rect.left - self.rect.height
self.rect.top = tank.rect.top + tank.rect.height/2 - self.rect.width/2
elif self.direction == "R":
#子弹的left = 坦克的left + 坦克的宽度 +子弹的宽度
#子弹的top = 坦克的top + 坦克的高度/2 - 子弹的宽度/2
self.rect.left = tank.rect.left + tank.rect.width
self.rect.top = tank.rect.top + tank.rect.height/2 - self.rect.width/2
#子弹速度
self.speed = 10
#子弹属性,显示子弹是否还活着
self.live = True
#移动子弹方法
def bulletMove(self):
if self.direction == "U":
if self.rect.top > 0:
#才允许子弹移动
self.rect.top = 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.rect.top + self.speed
else:
#子弹消失
self.live = False
elif self.direction == "L":
if self.rect.left > 0:
self.rect.left = 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.rect.left + self.speed
else:
#子弹消失
self.live = False
#展示子弹到游戏主窗口方法
def displayBullet(self):
MainGame.window.blit(self.image,self.rect)
#判断我方子弹是否集中敌方坦克方法
def hitEanmyTank(self):
#循环判断敌方坦克的列表是否被子弹击中
for eTank in MainGame.EnamyTank_list:
#pygame.sprite.collide_rect(var1,var2)需要两个参数
#这里self指当前子弹对象,eTank指敌方坦克对象
if pygame.sprite.collide_rect(self,eTank):
#子弹消失
self.live = False
#敌方坦克消失
eTank.live = False
#击中敌方坦克就将爆炸效果加入到爆炸效果列表中
explode=Explode(eTank)
MainGame.Explode_list.append(explode)
#判断敌方坦克子弹是否击中我方坦克
def hitMyTank(self):
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
#因为我方坦克只有一辆,不需要循环坦克列表
if pygame.sprite.collide_rect(self,MainGame.TANK_P1):
#产生爆炸效果类,并加入到爆炸效果列表
explode = Explode(MainGame.TANK_P1)
MainGame.Explode_list.append(explode)
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
#我方坦克失活
MainGame.TANK_P1.live = False
#敌方子弹失活
self.live = False
#判断子弹是否击中墙壁
def hitWalls(self):
for wall in MainGame.Walls_list:
#如果子弹和墙壁发生碰撞,子弹属性失活
if pygame.sprite.collide_rect(self,wall):
self.live = False
wall.hp -=1
if wall.hp < 0:
wall.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):
length = len(self.images)
if self.step < length:
self.image = self.images[self.step]
MainGame.window.blit(self.image, self.rect)
self.step += 1
else:
self.live = False
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 displayWall(self):
#墙壁加载到游戏窗口中
MainGame.window.blit(self.image,self.rect)
class Music:
#音乐类
def __init__(self,filename):
#filename指音效文件
self.filename = filename
#报错:pygame.error: mixer not initialized,在使用pygame.mixer.music模块必须先初始化pygame.mixer模块
pygame.mixer.init()
#加载音乐文件
pygame.mixer.music.load(self.filename)
#播放音乐方法
def playMusic(self):
#播放音效文件
pygame.mixer.music.play()
MainGame().startGame()