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()
上一篇下一篇

猜你喜欢

热点阅读