Bootstrap

坦克大战游戏开发

需求分析

  1. 首先要有一个主窗口,游戏中所有的图形都在这里面渲染
  2. 要有一个坦克类,有两个子类:我方坦克类、敌方坦克类
  3. 坦克发射子弹,需要有一个子弹类
  4. 游戏里有墙壁,要有墙壁类
  5. 坦克死亡时会爆炸,定义爆炸类
  6. 游戏有音乐和音效,需要定义一个音乐类

实现过程

导包

import pygame
from time import sleep
import random
from pygame.sprite import collide_rect

坦克类

  1. 坦克类的属性
  • 坦克的存活状态、坦克的移动状态、坦克的位置、坦克的图片资源、坦克的图形、坦克的移动速度,在具体实现过程中根据需要在具体添加。
  1. 坦克的行为/方法
  • 坦克的移动、射击、碰撞墙壁检测、碰撞敌方坦克检测。敌方坦克的行为和我方坦克的行为略有差距,对于有差别的部分分别创建不同的方法,对于相同的部分,把这些属性和方法写到基类里,子类进行继承即可。
  1. 代码
    基类
class Tank:
    '''
    坦克类
    '''
    def __init__(self) -> None:
        # 坦克存活的状态
        self.live = True
        # 记录坦克原来的位置
        self.old_left = 0
        self.old_top = 0


    def display_tank(self) -> None:
        '''
        显示坦克
        '''
        if self.live: #坦克存活时显示坦克
            # 获取坦克朝向
            self.image = self.images.get(self.direction)
            MainGame.window.blit(self.image,self.rect)
        else: #坦克不存活时,删除坦克
            MainGame.my_tank = None
    def move(self) -> None:
        '''
        移动坦克
        '''
        # 记录坦克位置,方便还原坦克碰撞墙壁之前的位置
        self.old_left = self.rect.left
        self.old_top = 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.width < WINDOW_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 < WINDOW_HEIGHT:
                # 向下移动坦克
                self.rect.top += self.speed
    def shot(self) -> None:
        '''
        坦克射击
        '''
        pass

    def tank_hit_wall(self) -> None:
        '''
        检测坦克和墙壁的碰撞
        '''
        for wall in MainGame.wall_list: # 遍历墙壁列表
            if collide_rect(self,wall): # 判断是否碰撞
                # 恢复坦克位置
                self.rect.left = self.old_left
                self.rect.top = self.old_top

    def tank_collide_tank(self,tank) -> None:
        '''
        检测坦克和坦克是否碰撞
        '''
        if self and self.live and tank and tank.live: # 坦克存活时检测
            if collide_rect(self,tank): # 判断是否碰撞
                # 恢复坦克位置
                self.rect.left = self.old_left
                self.rect.top = self.old_top 

我方坦克类

class MyTank(Tank):
    '''
    我方坦克类
    '''
    def __init__(self) -> None:
        super(MyTank,self).__init__() # 继承父类的构造方法
        # 加载坦克图片资源
        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.get(self.direction)
        # 获取图片矩形
        self.rect = self.image.get_rect()
        # 设置我方坦克位置
        self.rect.left = 350
        self.rect.top = 400
        # 坦克移动的速度
        self.speed = 15
        # 增加坦克移动的状态
        self.remove = False

敌方坦克类

class EnemyTank(Tank):
    '''
    敌方坦克类
    '''
    def __init__(self,left,top,speed) -> None:
        super(EnemyTank,self).__init__()
        # 加载敌方坦克图片资源
        self.images = {
            'U':pygame.image.load("./img/enemy1U.gif"),
            'D':pygame.image.load("./img/enemy1D.gif"),
            'R':pygame.image.load("./img/enemy1R.gif"),
            'L':pygame.image.load("./img/enemy1L.gif"),
        }
        # 设置坦克方向
        self.direction = self.rand_direction()
        # 获取图片信息
        self.image = self.images.get(self.direction)
        # 获取图片的矩形
        self.rect = self.image.get_rect()
        # 设置坦克速度
        self.speed = speed
        # 设置坦克位置
        self.rect.left = left
        self.rect.top = top
        # 设置敌方坦克移动的步长
        self.step = 6

    def shot(self): # 方法重写
        '''
        敌方坦克射击
        '''
        num = random.randint(1,100)
        if num < 5:
            return Bullet(self)
    def rand_direction(self) -> str:
        '''
        生成坦克方向
        '''
        num = random.randint(1,4)
        if num == 1:
            return "U"
        elif num == 2:
            return "D"
        elif num == 3:
            return "R"
        elif num == 4:
            return "L"

    def rand_move(self):
        '''
        随机移动敌方坦克
        '''
        if self.step < 0:
            self.direction = self.rand_direction()
            self.step = 6
        else:
            self.move()
            self.step -= 1

子弹类

  1. 子弹的属性
  • 子弹的图片资源、子弹的图形信息、子弹的位置、子弹的移动速度、子弹的存活状态
  1. 子弹的方法
  • 子弹的移动、子弹碰撞敌方坦克、子弹碰撞我方坦克、子弹碰撞墙壁、子弹的显示
  1. 代码
class Bullet:
    '''
    子弹类
    '''   
    def __init__(self,tank) -> None:
        # 加载图片
        self.images = pygame.image.load("./img/enemymissile.gif")
        # 获取子弹方向
        self.direction = tank.direction
        # 获得子弹图形
        self.rect = self.images.get_rect()
        # 设置子弹位置
        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
            self.rect.top = tank.rect.top + tank.rect.height / 2 - self.rect.height / 2
        elif self.direction == 'R':
            self.rect.left = tank.rect.left + tank.rect.width
            self.rect.top = tank.rect.top + tank.rect.height / 2 - self.rect.height / 2
        # 设置子弹的速度
        self.speed = 10
        # 设置子弹状态
        self.live = True

    def hit_enemy_tank(self):
        '''
        检测子弹是否碰撞敌方坦克
        '''
        for e_tank in MainGame.etank_list:
            if collide_rect(self,e_tank): # 检测子弹是否碰撞敌方坦克
                # 爆炸效果
                explode = Explode(e_tank)
                MainGame.explode_list.append(explode)
                e_tank.live = False # 修改坦克状态
                self.live = False # 修改子弹状态

    def hit_my_tank(self):
        '''
        检测子弹是否碰撞我方坦克
        '''
        if MainGame.my_tank and MainGame.my_tank.live and collide_rect(self,MainGame.my_tank): # 检测子弹是否碰撞我方坦克
            # 爆炸效果
            explode = Explode(MainGame.my_tank)
            MainGame.explode_list.append(explode)
            MainGame.my_tank.live = False # 修改坦克状态
            self.live = False # 修改子弹状态
            MainGame.my_tank = None
    
    def hit_wall(self):
        '''
        检测子弹是否碰撞墙壁,若碰撞墙壁,改变子弹存活状态
        '''
        for wall in MainGame.wall_list:
            if collide_rect(self,wall): # 检测子弹是否碰撞墙壁
                self.live = False # 修改子弹状态
                wall.hp -= 1 # 减少墙壁血量
                if wall.hp <= 0: # 墙壁血量小于等于0,移除墙壁
                    wall.live = False # 修改墙壁状态
                # 创建攻击墙壁的声音
                hit_wall_music = Music("./img/hit.wav")
                hit_wall_music.play_music()

    #显示子弹
    def display_bullet(self) -> None:
        MainGame.window.blit(self.images,self.rect)
    #移动子弹
    def move(self) -> None:
        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 + self.rect.height < WINDOW_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 + self.rect.width < WINDOW_WIDTH:
                self.rect.left += self.speed
            else:
                self.live = False # 子弹达到窗口边界,改变存活状态

墙壁类

  1. 墙壁的属性
  • 墙壁的图片资源、墙壁的图形信息、墙壁的位置、墙壁的生命值、墙壁的存活状态
  1. 墙壁的方法
  • 墙壁的显示
  1. 代码
class Wall:
    '''
    墙壁类
    ''' 
    def __init__(self,left,top) -> None:
        # 加载墙壁图片资源
        self.image = pygame.image.load("./img/steels.gif")
        # 获取墙壁图形
        self.rect = self.image.get_rect()
        # 设置墙壁位置
        self.rect.left = left
        self.rect.top = top
        # 设置墙壁生命值
        self.hp = 5
        # 设置墙壁状态
        self.live = True
    #显示墙壁
    def display_wall(self) -> None:
        MainGame.window.blit(self.image,self.rect)

爆炸类

  1. 爆炸的属性
  • 爆炸的图片资源、爆炸的图形信息、爆炸的位置、爆炸的状态
  1. 爆炸的方法
  • 爆炸的显示
  1. 代码
class Explode:
    '''
    爆炸效果类
    '''
    def __init__(self,tank:Tank) -> None:
        # 加载爆炸图片资源
        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.rect = tank.rect
        # 设置爆炸的索引
        self.step = 0
        # 获取爆炸图片
        self.image = self.images[self.step]
        # 设置爆炸状态
        self.live = True
    #显示爆炸
    def display_explode(self) -> None:
        if self.step < len(self.images):
            self.image = self.images[self.step] #获得爆炸动画图片
            MainGame.window.blit(self.image,self.rect) # 渲染爆炸效果
            self.step += 1 
        else:
            self.step = 0
            self.live = False

音效类

  1. 音效的属性
  • 音乐资源
  1. 音效的方法
  • 音效的播放:不同的音效在不同的敌方播放
  1. 代码
class Music:
    '''
    音乐音效类
    '''    
    def __init__(self,filename) -> None:
        # 初始化mixer
        pygame.mixer.init()
        # 加载音乐文件
        pygame.mixer.music.load(filename)
    #显示音乐
    def play_music(self) -> None:
        # 播放音乐
        pygame.mixer.music.play()

游戏主窗口类

  1. 游戏所有的对象都要在主窗口类中创建对象,然后调用方法运行。
  2. 主窗口类的方法:
  • 最重要的是开始游戏方法,所有的对象、方法都要在这个方法里创建、运行。
  • 另外在开发过程中,可以根据具体需求,在主窗口类中添加新的方法和属性,用来辅助一些功能的实现。
  • 对于一些参数,可以设置为全局性属性,需要修改时找到定义的变量即可。
  1. 代码
    全局性变量
# 通用属性设置
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 700
BG_COLOR = pygame.Color(0,0,0)
TEXT_COLOR = pygame.Color(255,0,0)

主窗口类

class MainGame:
    '''
    游戏主窗口类
    '''
    # 游戏主窗口对象
    window = None
    # 坦克对象
    my_tank = None
    # 敌方坦克数量
    etank_count = 6
    # 存储敌方坦克列表
    etank_list = []
    # 存储子弹列表
    my_bullet_list = []
    # 存储敌方子弹列表
    etank_bullet_list = []
    # 存储爆炸列表
    explode_list = []
    # 存储墙壁列表
    wall_list = []
    def __init__(self) -> None:
        pass
    def start_game(self) -> None:
        '''
        开始游戏
        '''
        # 初始化窗口模块
        pygame.display.init()
        # 创建窗口
        MainGame.window = pygame.display.set_mode((WINDOW_WIDTH,WINDOW_HEIGHT))
        #设置窗口标题
        pygame.display.set_caption("坦克大战1.0")
        # 创建一个我方坦克
        self.create_my_tank()
        # 创建敌方坦克
        self.create_enemy_tank()
        # 创建墙壁
        self.create_wall()
        # 刷新窗口
        while True:
            # 刷新的慢一点
            sleep(0.03)
            # 设置窗口背景颜色
            MainGame.window.fill(BG_COLOR)
            # 设置提示文字
            # 1.获得文字
            #num = 6
            text = self.get_surface_text(f"敌方坦克剩余{len(MainGame.etank_list)}")
            # 2.把文字放到窗口中
            MainGame.window.blit(text,(10,10))
            # 增加事件处理
            self.get_event()
            # 判断我方坦克是否存活
            if MainGame.my_tank and MainGame.my_tank.live:
                # 显示我方坦克
                MainGame.my_tank.display_tank()
                # 移动我方坦克
                if MainGame.my_tank.remove:
                    MainGame.my_tank.move()
                    # 检测我方坦克是否和墙壁发生碰撞
                    MainGame.my_tank.tank_hit_wall()
                    for e_tank in MainGame.etank_list:
                        # 检测我方坦克是否和敌方坦克发生碰撞
                        MainGame.my_tank.tank_collide_tank(e_tank)
            # 显示敌方坦克
            self.display_etank()
            # # 坦克移动状态为True时再移动
            # if MainGame.my_tank and MainGame.my_tank.live and MainGame.my_tank.remove:
            #     MainGame.my_tank.move()
            # 显示我方子弹
            self.display_my_bullet()
            # 显示敌方子弹
            self.display_etank_bullet()
            # 显示爆炸效果
            self.display_explode()
            # 显示墙壁
            self.display_wall()
            pygame.display.update()
    
    def create_wall(self) -> None:
        '''
        创建墙壁
        '''
        for i in range(7):
            wall = Wall(i*120,300)
            MainGame.wall_list.append(wall)

    def display_wall(self) -> None:
        '''
        显示墙壁
        '''
        for wall in MainGame.wall_list: # 遍历墙壁列表
            if wall.live: # 墙壁存活时在显示墙壁
                wall.display_wall()
            else:
                MainGame.wall_list.remove(wall)

    def create_my_tank(self) -> None:
        '''
        创建我方坦克
        '''
        MainGame.my_tank = MyTank()
        # 播放音乐
        start_music = Music("./img/start.wav") #创建音乐对象
        start_music.play_music() # 播放音乐

    def display_explode(self) -> None:
        '''
        显示爆炸效果
        '''
        for explode in MainGame.explode_list: # 遍历爆炸列表
            if explode.live: # 爆炸存活才爆炸
                explode.display_explode()
            else:
                MainGame.explode_list.remove(explode)
    # 显示我方子弹
    def display_my_bullet(self):
        for my_bullet in MainGame.my_bullet_list: # 遍历子弹列表
            if my_bullet.live: # 子弹存活才在窗口显示子弹
                my_bullet.display_bullet() # 在窗口显示子弹
                my_bullet.move() #子弹移动
                my_bullet.hit_enemy_tank() # 子弹与敌方坦克碰撞检测
                my_bullet.hit_wall() # 子弹与墙壁碰撞检测
            else:
                MainGame.my_bullet_list.remove(my_bullet) # 子弹不存活,不显示且把该子弹从列表中删除
    def create_enemy_tank(self):
        '''
        创建敌方坦克
        '''
        self.top = 100
        self.speed = 6
        for _ in range(MainGame.etank_count):
            left = random.randint(0,600)
            # 创建地方坦克对象
            eTank = EnemyTank(left,self.top,self.speed)
            # 把生成的敌方坦克对象放入列表
            self.etank_list.append(eTank)
            

    def display_etank(self) -> None:
        '''
        显示敌方坦克
        '''
        for e_tank in self.etank_list:
            if e_tank.live: # 敌方坦克存活
                e_tank.display_tank() # 显示坦克
                e_tank.rand_move() # 随机移动
                e_tank.tank_hit_wall() # 敌方坦克与墙壁碰撞检测
                e_tank.tank_collide_tank(MainGame.my_tank) # 敌方坦克与我方坦克碰撞检测
                e_bullet = e_tank.shot() # 敌方坦克射击
                if e_bullet:
                    MainGame.etank_bullet_list.append(e_bullet) 
            else:
                MainGame.etank_list.remove(e_tank) # 敌方坦克不存活,不显示且把该坦克从列表中删除

    def display_etank_bullet(self):
        '''
        显示敌方坦克子弹
        '''
        for e_bullet in MainGame.etank_bullet_list: # 遍历子弹列表
            if e_bullet.live: # 子弹存活才显示子弹
                e_bullet.display_bullet() # 显示子弹
                e_bullet.move() # 子弹移动
                e_bullet.hit_my_tank() # 子弹与我方坦克碰撞检测
                e_bullet.hit_wall() # 子弹与墙壁碰撞检测
            else:
                MainGame.etank_bullet_list.remove(e_bullet)
    def end_game(self) -> None:
        '''
        结束游戏
        '''
        print("谢谢使用,欢迎再次使用")
        exit()
        
    def get_event(self) -> None:
        '''
        获取事件
        '''
        # 从系统的输入队列获取所有事件
        event_list = pygame.event.get()
        # 遍历事件
        for event in event_list:
            # 判断事件
            # 关闭游戏
            if event.type == pygame.QUIT:
                # 关闭按钮
                self.end_game()
            # 键盘检测
            if event.type == pygame.KEYDOWN:
                # 我方坦克不存活时
                if not MainGame.my_tank and event.key == pygame.K_ESCAPE:
                    # 按下esc我方坦克重生
                    print("我方坦克重生")
                    self.create_my_tank()
                # 我方坦克存活存活时
                if MainGame.my_tank and MainGame.my_tank.live:
                    # 坦克移动
                    if event.key == pygame.K_LEFT:
                        print("坦克向左移动")
                        MainGame.my_tank.direction = 'L'
                        # 改变坦克移动状态
                        MainGame.my_tank.remove = True
                    elif event.key == pygame.K_RIGHT:
                        print("坦克向右移动")
                        MainGame.my_tank.direction = 'R'
                        # 改变坦克移动状态
                        MainGame.my_tank.remove = True
                    elif event.key == pygame.K_UP:
                        print("坦克向上移动")
                        MainGame.my_tank.direction = 'U'
                        # 改变坦克移动状态
                        MainGame.my_tank.remove = True
                    elif event.key == pygame.K_DOWN:
                        MainGame.my_tank.direction = 'D'
                        # 改变坦克移动状态
                        MainGame.my_tank.remove = True
                        print("坦克向下移动")
                    elif event.key == pygame.K_SPACE:
                        if len(MainGame.my_bullet_list) < 5: # 限制子弹数量
                            print("发射子弹")
                            # 创建子弹
                            my_bullet = Bullet(MainGame.my_tank)
                            # 将子弹添加到列表中
                            MainGame.my_bullet_list.append(my_bullet)
                            # 播放发射子弹的音效
                            shot_music = Music("./img/fire.wav")
                            shot_music.play_music()

            # 键盘检测:松开特定键盘时触发的事件
            if event.type == pygame.KEYUP and event.key in (pygame.K_LEFT,pygame.K_RIGHT,pygame.K_UP,pygame.K_DOWN) and MainGame.my_tank:
                MainGame.my_tank.remove = False

                
    def get_surface_text(self,text:str) -> None:
        '''
        获得文字的图片
        '''
        # 初始化字体模块
        pygame.font.init()
        # 创建字体
        font = pygame.font.SysFont("kaiti",18)
        # 绘制文字信息
        surface_text = font.render(text,True,TEXT_COLOR)
        # 将绘制的文字信息返回
        return surface_text

收获

  1. 熟悉了使用python的pygame库来开发游戏的一些常用模块和方法。
  2. 学会了使用面向对象思想和第三方库来设计并开发游戏的流程和方法。
  3. 学会了在开发过程中为了开发某个功能而去添加某个属性或方法,并且可以检索并解决因为属性和方法的变动产生的报错。
  4. 对于项目功能的整体设计可以去略微有效的去摸索合理优雅的写法。
;