[python3]坦克大战

坦克大战完整版[Python3]

英文注释版


由https://www.cnblogs.com/lwj-jz/articles/TankWarGame.html转载

【写在前面】

还记得小时候玩过的坦克大战嘛~想当年几个小伙伴挤在一起,恨不得把键盘分成几瓣……

好了,废话不多说,直入主题。今天咱们要基本实现坦克大战的小游戏,主要利用第三方库pygame,不了解的友友们可以参考官方文档。由于最近http://www.pygame.org成了这鬼样子

害,就烦人,不过还好之前下载了文档,链接在这里pygame官方文档

如果你不想跟着学,想要完整代码点赞后私聊我;如果你想跟着学,我相信,你不用私聊我也可以做出来,并且会随手给我一个小小的赞~

【运行结果】

先上结果!!!

【准备工作】

结果满意不~

满意的话就去点个小小的赞,不要钱的哈。

不满意也不要紧,嘿嘿,看清楚了,这只是Tank War 1.0哦,这意味着什么?对,升级更新。不过,我相信,这些够你用的了,想升级的话,私聊我吧。

  1. 编辑环境:Pycharm
  2. 解释器:Python3
  3. 需要按步骤导入相关模块,一定核对好。
  4. 相关素材资源:链接我就直接给了哈,别回头说我不厚道……坦克大战素材

【项目需求】

首先我们得分析下,做这个《坦克大战》小游戏需要什么东西。

"""
Here are the project requirements for the Tank Wars game.

1. What classes are in the project?
    a) MainGame
    b) Tank(OurTank, EnemyTank)
    c) Bullet
    d) Wall
    e) Explosion
    f) Music
    g) BaseItem --> Sprite class

2. What are the methods in each class?
    a) methods in MainGame class
        1. startGame
        2. gameOver
        3. getEvent
        4. setTextSurface
    b) methods in Tank class
        1. shooting
        2. moveTank
        3. displayTank
        4. isCollide
    c) methods in Bullet class
        1. moveBullet
        2. displayBllet
        3. isCollide
    d) methods in Wall class
        1. displayWall
    e) methods in Explosion class
        1. displayExplosion
    f) methods in Music class
        1. playMusic
    g) methods in BaseItem class
"""

啊?英文的看不懂?不能吧,我已经很中文了。如果实在不想看、看不懂,那就翻译一下嘛。麻利给你一个翻译网址吧,免得你嘀咕我……谷歌翻译

【搭建框架】

有了项目需求,那么接下来就是搭建框架了。按照项目需求,咱们把框架立起来。

class BaseItem(Sprite):
    pass


class MainGame:
    pass


class Tank(BaseItem):
    pass


class OurTank(Tank):
    pass


class EnemyTank(Tank):
    pass


class Bullet(BaseItem):
    pass


class Wall(BaseItem):
    pass


class Explosion:
    pass


class Music:
    pass

【完善MainGame类】

这个类主要功能是:

  • 启动游戏窗口
  • 显示部分文本信息
  • 实时检测按键并做出响应
  • 结束游戏

先导入这些模块的方法:

from time import sleep
from pygame import display
from pygame import Color

首先设置窗口大小及背景颜色:

SCREEN_WIDTH = 700
SCREEN_HEIGHT = 500
BG_COLOR = Color(0, 0, 0)

完善MainGame类:

class MainGame:
    window = None
    
    def __init__(self):
        pass
    
    def startGame(self):
        display.init()
        MainGame.window = display.set_mode(size=[SCREEN_WIDTH, SCREEN_HEIGHT])
        display.set_caption("Tank War 1.0")
        while True:
            sleep(0.002)
            MainGame.window.fill(color=BG_COLOR)
            display.update()

在所有代码最下方添加:

if __name__ == "__main__":
    my_game = MainGame()
    my_game.startGame()

运行截图:

啊!点击关闭不管用,还卡死……别慌,没完呢。

继续导入以下模块的方法:

from sys import exit
from pygame import event
from pygame import QUIT
from pygame import quit

MainGame类里添加gameOver方法:

@staticmethod
def gameOver():
    quit()
    exit()

MainGame类里添加getEvent方法:

def getEvent(self):
    for e in event.get():
        if e.type == QUIT:
            self.gameOver()

MainGame类的startGame方法中的while循环中添加一句:

self.getEvent()

所以MainGame类变成了:

class MainGame:
    window = None
    
    def __init__(self):
        pass
    
    def startGame(self):
        display.init()
        MainGame.window = display.set_mode(size=[SCREEN_WIDTH, SCREEN_HEIGHT])
        display.set_caption("Tank War 1.0")
        while True:
            sleep(0.002)
            MainGame.window.fill(color=BG_COLOR)
            self.getEvent()
            display.update()
    
    @staticmethod
    def gameOver():
        quit()
        exit()
    
    def getEvent(self):
        for e in event.get():
            if e.type == QUIT:
                self.gameOver()

运行截图:

这下可以关闭啦~嘿嘿,就是这么皮……

不过,黑乎乎的,啥也不是……接着往下搞。

【完善BaseItem类】

问题来了,这个类是干嘛用的呢?哎,我告诉你,这个类是用来检测是否发生碰撞(坦克与坦克、坦克与子弹、坦克与墙壁)。原理是什么呢,请移步pygame官方文档查看pygame.sprite的相关内容,这里就不展开说了哈。

继续导入以下模块的方法:

from pygame.sprite import Sprite
from pygame.sprite import AbstractGroup

补充BaseItem类:

class BaseItem(Sprite):
    def __init__(self, *groups: AbstractGroup):
        super().__init__(*groups)

【完善Tank类】

坦克有四个方向:上下左右

UP = 1
DOWN = 2
LEFT = 3
RIGHT = 4

还有速度:

SPEED = 1

由于Tank类是OurTank类和EnemyTank类的父类,所以具体构造方法稍后讲解。

Tank类里添加displayTank方法:

def displayTank(self):
    self.my_image = self.images.get(self.direction, None)
    MainGame.window.blit(source=self.my_image, dest=self.rect)

此时Tank类为:

class Tank(BaseItem):
        UP = 1
        DOWN = 2
        LEFT = 3
        RIGHT = 4
        SPEED = 1
        
        def __init__(self, *groups: AbstractGroup):
            super().__init__(*groups)
            self.images = {}
            self.direction = Tank.UP
            self.my_image = None
            self.rect = None
            self.move_switch = None
            self.bullets = []
        
        def displayTank(self):
            self.my_image = self.images.get(self.direction, None)
            MainGame.window.blit(source=self.my_image, dest=self.rect)

先别运行哈,还没完善OurTank类呢。

【完善OurTank类】

导入以下模块的方法:

from pygame import image

OurTank类里添加属性,我方坦克阵营及其数量:

tanks = []
count = 0

先简单点,设定我方坦克阵营只有一个坦克。考虑到我方坦克的无限复活,应该设置单例模式。单例模式的介绍以及python3的实现这里就不展开了,提供学习单例模式的参考文章:Python单例模式(Singleton)的N种实现

OurTank类里添加属性:

__obj = None
__init_flag = True

OurTank类里添加__new__方法:

def __new__(cls, *args, **kwargs):
    if cls.__obj is None:
        cls.__obj = object.__new__(cls)
    return cls.__obj

OurTank类里添加__init__方法:

def __init__(self, left, top):
    super().__init__()
    if OurTank.__init_flag:
        OurTank.__init_flag = False

加载我方阵营的坦克的图片,由于方向有四个,对应就有四个图片。在OurTank类里的__init__方法中添加:

注意

  1. img文件夹是从坦克大战素材解压出来的,注意你的路径问题,我用的是相对路径,建议你使用绝对路径。

  2. 想要使用你自己的素材也可以,但图片后缀必须是.gif,否则会出问题。

self.images = {
    Tank.UP: image.load(r"img/p1tankU.gif"),
    Tank.DOWN: image.load(r"img/p1tankD.gif"),
    Tank.LEFT: image.load(r"img/p1tankL.gif"),
    Tank.RIGHT: image.load(r"img/p1tankR.gif"),
}

OurTank类里的__init__`方法中继续添加:

self.my_image = self.images[self.direction]
self.rect = self.my_image.get_rect()
self.rect.left = left
self.rect.top = top
OurTank.count += 1
OurTank.tanks.append(self)

自上往下分别表示:

  • 加载默认方向对应的图片
  • 获取图片的rect属性
  • 设置图片相对于窗口左端的位置
  • 设置图片相对于窗口顶端的位置
  • 我方坦克阵营的数量加一
  • 我方坦克阵营的对象列表增一

此时,OurTank类的__init__方法为:

def __init__(self, left, top):
    super().__init__()
    if OurTank.__init_flag:
        OurTank.__init_flag = False
    self.images = {
        Tank.UP: image.load(r"img/p1tankU.gif"),
        Tank.DOWN: image.load(r"img/p1tankD.gif"),
        Tank.LEFT: image.load(r"img/p1tankL.gif"),
        Tank.RIGHT: image.load(r"img/p1tankR.gif"),
    }
    self.my_image = self.images[self.direction]
    self.rect = self.my_image.get_rect()
    self.rect.left = left
    self.rect.top = top
    OurTank.count += 1
    OurTank.tanks.append(self)

【完善Tank类】

接下来,要考虑在游戏窗口生成我方阵营的坦克,我们使用随机位置生成坦克。由于位置是随机的,无法保证多个坦克图片之间不重合,因此,需要检测坦克是否发生碰撞。(我们这里认为,重合即碰撞)

导入以下模块的方法:

from inspect import getmro
from pygame.sprite import collide_rect

Tank类里添加isCollide方法:

def isCollide(self, collided_type):
    if "Tank" in [i.__name__ for i in getmro(cls=collided_type)]:
        for tank in collided_type.tanks:
            if tank != self and collide_rect(tank, self):
                return True
        return False
    else:
        return False

【完善MainGame类】

导入以下模块的方法:

from random import randint

MainGame类里的startGame方法中,在display.set_caption("Tank War 1.0")下一行添加:

while OurTank.count < 1:
    OurTank(randint(60, 640), randint(60, 440))
    if OurTank.tanks[-1].isCollide(OurTank):
        OurTank.tanks.pop()
        OurTank.count -= 1

MainGame类里的startGame方法中,在MainGame.window.fill(color=BG_COLOR)下一行添加:

for tank in OurTank.tanks:
    tank.displayTank()

此时,MainGame类里的startGame方法为:

def startGame(self):
    display.init()
    MainGame.window = display.set_mode(size=[SCREEN_WIDTH, SCREEN_HEIGHT])
    display.set_caption("Tank War 1.0")
    while OurTank.count < 1:
        OurTank(randint(60, 640), randint(60, 440))
        if OurTank.tanks[-1].isCollide(OurTank):
            OurTank.tanks.pop()
            OurTank.count -= 1
    while True:
        sleep(0.002)
        MainGame.window.fill(color=BG_COLOR)
        for tank in OurTank.tanks:
            tank.displayTank()
        self.getEvent()
        display.update()

运行截图:

可能你的坦克位置不一样,不要紧,因为是随机位置嘛~而且每次运行后,位置会发生随机变化哦……

【完善MainGame类】

话说,有图也没啥,得能动啊……

我们玩这个游戏,肯定要用到键盘,习惯呢,是“上下左右”键,也就是UpDownLeftRight键,于是,我们需要回到MainGame类。为啥?我们按下键,希望得到对应的响应,谁来检测呢?还是我们的getEvent方法,回到MainGame类。

导入以下模块的方法:

from pygame import KEYDOWN
from pygame import K_LEFT
from pygame import K_RIGHT
from pygame import K_UP
from pygame import K_DOWN
from pygame import KEYUP

MainGame类里的getEvent方法中,在if e.type == QUIT: self.gameOver()下一行(与if e.type == QUIT并列)添加:

if e.type == KEYDOWN:
    if (e.key == K_UP or
            e.key == K_DOWN or
            e.key == K_LEFT or
            e.key == K_RIGHT):
        for tank in OurTank.tanks:
            tank.move_switch = e.key
if e.type == KEYUP:
    if (e.key == K_UP or
            e.key == K_DOWN or
            e.key == K_LEFT or
            e.key == K_RIGHT):
        for tank in OurTank.tanks:
            tank.move_switch = None

到此,我们的按键就设置好了,接着安排按键对应的响应。

【完善Tank类】

Tank类里添加moveTank方法,考虑四个方向哦:

def moveTank(self, my_key):
    if my_key == K_UP:
        self.direction = self.UP
        if self.rect.top > 0:
            self.rect.top -= 1
            if not self.isCollide(OurTank):
                self.rect.top += 1
                self.rect.top -= self.SPEED
            else:
                self.rect.top += 1
    elif my_key == K_DOWN:
        self.direction = self.DOWN
        if self.rect.top < SCREEN_HEIGHT - self.rect.height:
            self.rect.top += 1
            if not self.isCollide(OurTank):
                self.rect.top -= 1
                self.rect.top += self.SPEED
            else:
                self.rect.top -= 1
    elif my_key == K_LEFT:
        self.direction = self.LEFT
        if self.rect.left > 0:
            self.rect.left -= 1
            if not self.isCollide(OurTank):
                self.rect.left += 1
                self.rect.left -= self.SPEED
            else:
                self.rect.left += 1
    elif my_key == K_RIGHT:
        self.direction = self.RIGHT
        if self.rect.left < SCREEN_WIDTH - self.rect.width:
            self.rect.left += 1
            if not self.isCollide(OurTank):
                self.rect.left -= 1
                self.rect.left += self.SPEED
            else:
                self.rect.left -= 1

那么多的if是干嘛用的?其实是要保证坦克不会走出窗口、不会与我方阵营其他坦克相撞(如果有的话)。

【完善MainGame类】

按键的响应也做好了,那么就可以往主窗口添加啦。

MainGame类里的startGame方法中,在while True循环中的self.getEvent()下一行添加:

for tank in OurTank.tanks:
    tank.moveTank(tank.move_switch)

运行截图:

我就不上动图了哈,不过,嘿嘿,你的坦克一定是在你的控制下乱跑,高兴不?点个赞呗先~

【完善EnemyTank类】

一个坦克总是孤零零的……那就多加几个呗~

咱们给它添加个敌方阵营的坦克,6个吧,多点热闹。

来到我们的EnemyTank类,添加几个属性:

tanks = []
count = 0

眼熟不?是的,前面写OurTank类的时候,也添加了这两个属性,意思差不多,阵营不一样而已。

EnemyTank类里添加__init__方法:

def __init__(self, left, top):
    super().__init__()
    self.images = {
        Tank.UP: image.load(r"img/enemy1U.gif"),
        Tank.DOWN: image.load(r"img/enemy1D.gif"),
        Tank.LEFT: image.load(r"img/enemy1L.gif"),
        Tank.RIGHT: image.load(r"img/enemy1R.gif"),
    }
    self.direction = randint(1, 4)
    self.my_image = self.images[self.direction]
    self.rect = self.my_image.get_rect()
    self.rect.left = left
    self.rect.top = top
    EnemyTank.count += 1
    EnemyTank.tanks.append(self)

OurTank类的__init__方法差不多,只有self.direction = randint(1, 4)这里不一样,随机生成敌方坦克的初始方向。

然后我们就可以在游戏窗口添加敌方阵营的坦克啦~

【完善MainGame类】

MainGame类里的startGame方法中,在while True的上一行添加:

while EnemyTank.count < 6:
    EnemyTank(randint(60, 640), randint(60, 220))
    if EnemyTank.tanks[-1].isCollide(EnemyTank):
        EnemyTank.tanks.pop()
        EnemyTank.count -= 1

啊哈,随机生成了6个敌方阵营的坦克,然后需要显示出来。在while True循环中,在self.getEvent()的上一行添加:

for tank in EnemyTank.tanks:
    tank.displayTank()

运行截图:

哎,这就出来了。敌方阵营的坦克都不动?别急,接着往下看……

【完善EnemyTank类】

要想让敌方阵营的坦克自己动起来,还是随机性地移动,那么就需要对moveTank方法进行重载。

EnemyTank类里添加属性:

turn_flag = 0

EnemyTank类里添加moveTank方法:

def moveTank(self, **kwargs):
    self.turn_flag += 1
    if self.turn_flag == 150:
        self.direction = randint(1, 4)
        self.turn_flag = 0

这个if干嘛用的?这个是控制敌方阵营的坦克转向的频率(游戏窗口更新频率较快,就是display.update()频率较快),不要太快也不要太慢,然后就是给它随机一个方向。

接下来给它加上“上下左右”四个方向,继续在EnemyTank类里的moveTank方法里添加:

if self.direction == self.UP:
    if self.rect.top > 0:
        self.rect.top -= 1
        if not (
                self.isCollide(OurTank) or
                self.isCollide(EnemyTank)
        ):
            self.rect.top += 1
            self.rect.top -= self.SPEED
        else:
            self.rect.top += 1
elif self.direction == self.DOWN:
    if self.rect.top < SCREEN_HEIGHT - self.rect.height:
        self.rect.top += 1
        if not (
                self.isCollide(OurTank) or
                self.isCollide(EnemyTank)
        ):
            self.rect.top -= 1
            self.rect.top += self.SPEED
        else:
            self.rect.top -= 1
elif self.direction == self.LEFT:
    if self.rect.left > 0:
        self.rect.left -= 1
        if not (
                self.isCollide(OurTank) or
                self.isCollide(EnemyTank)
        ):
            self.rect.left += 1
            self.rect.left -= self.SPEED
        else:
            self.rect.left += 1
elif self.direction == self.RIGHT:
    if self.rect.left < SCREEN_WIDTH - self.rect.width:
        self.rect.left += 1
        if not (
                self.isCollide(OurTank) or
                self.isCollide(EnemyTank)
        ):
            self.rect.left -= 1
            self.rect.left += self.SPEED
        else:
            self.rect.left -= 1

这里头的if语句还是要保证坦克不与其他坦克碰撞、不跑出屏幕范围。

【完善MainGame类】

敌方坦克阵营的移动设置好了,现在让它们在游戏窗口里动起来。在MainGame类里的startGame中,在while True循环里的display.update()上一行添加:

for tank in EnemyTank.tanks:
    tank.moveTank()

运行截图:

很好,所有的坦克都动起来了!不过还有个bug——上面那张图中,我方阵营的坦克可以主动与敌方坦克重合,我们希望所有的坦克不要重合,那需要修改下Tank类了。

【完善Tank类】

找到Tank类里的moveTank方法,将所有的检测碰撞的语句if not self.isCollide(OurTank):修改为:

if not (
        self.isCollide(OurTank) or
        self.isCollide(EnemyTank)
):

还没完呢,先别运行。

【完善MainGame类】

MainGame类里的startGame方法中,修改

while OurTank.count < 1:
    OurTank(randint(60, 640), randint(60, 440))
    if OurTank.tanks[-1].isCollide(OurTank) :
        OurTank.tanks.pop()
        OurTank.count -= 1

为:

while OurTank.count < 1:
    OurTank(randint(60, 640), randint(60, 440))
    if (
        OurTank.tanks[-1].isCollide(OurTank) or
        OurTank.tanks[-1].isCollide(EnemyTank)
    ):
        OurTank.tanks.pop()
        OurTank.count -= 1

修改

while EnemyTank.count < 6:
    EnemyTank(randint(60, 640), randint(60, 220))
    if EnemyTank.tanks[-1].isCollide(EnemyTank):
        EnemyTank.tanks.pop()
        EnemyTank.count -= 1

为:

while EnemyTank.count < 6:
        EnemyTank(randint(60, 640), randint(60, 220))
        if (
            EnemyTank.tanks[-1].isCollide(OurTank) or
            EnemyTank.tanks[-1].isCollide(EnemyTank)
        ):
            EnemyTank.tanks.pop()
            EnemyTank.count -= 1

然后运行:

好嘞,这下没毛病了,谁也不会重合了……

【完善Wall类】

光有坦克有点单调了,那就加些墙壁呗。

Wall类添加属性:

walls = []
count = 0

Wall类里添加__init__方法:

def __init__(self, left, top, *groups: AbstractGroup):
    super().__init__(*groups)
    self.image = image.load(r"img/walls.gif")
    self.rect = self.image.get_rect()
    self.rect.left = left
    self.rect.top = top
    self.hp = 4
    Wall.walls.append(self)
    Wall.count += 1

细心的你肯定发现,哎,好像多了点什么。是的,咱给墙壁加了个生命值self.hp,也就是说,子弹打到墙壁上,墙壁生命值就减少,最后会被打没喽~

Wall类添加displayWall方法:

def displayWall(self):
    MainGame.window.blit(source=self.image, dest=self.rect)

然后,咱就在游戏窗口里随机生成6个墙壁吧。

【完善MainGame类】

MainGame类里的startGame方法中,在while True循环中,在MainGame.window.fill(color=BG_COLOR)下一行添加:

while Wall.count < 6:
    Wall(randint(60, 640), randint(60, 440))
    for tank in EnemyTank.tanks:
        if Wall.walls and collide_rect(tank, Wall.walls[-1]):
            Wall.walls.pop()
            Wall.count -= 1
    for tank in OurTank.tanks:
        if Wall.walls and collide_rect(tank, Wall.walls[-1]):
            Wall.walls.pop()
            Wall.count -= 1
    for wall in Wall.walls[:-1]:
        if Wall.walls and collide_rect(wall, Wall.walls[-1]):
            Wall.walls.pop()
            Wall.count -= 1

这个while循环呢,就保证了每消失一个墙壁,就随机位置生成一个墙壁。当然,里头的三个for循环是为了保证生成的墙壁不与其他墙壁、坦克重合。

接着在游戏窗口显示生成的墙壁,在下一行(与while Wall.count < 6:并列)添加:

for wall in Wall.walls:
    wall.displayWall()

运行截图:

哎,墙壁有了。可问题又来了,坦克可以走过去跟墙壁重合……不慌,修改修改就好。

【完善Tank类】

找到Tank类的isCollide方法,在else: return False上一行(与if "Tank" in [i.__name__ for i in getmro(cls=collided_type)]并列)添加:

elif "Wall" in [i.__name__ for i in getmro(cls=collided_type)]:
    for wall in collided_type.walls:
        if collide_rect(wall, self):
            return True
    return False

Tank类里的moveTank方法中,修改所有的

if not (
        self.isCollide(OurTank) or
        self.isCollide(EnemyTank)
):

为:

if not (
        self.isCollide(Wall) or
        self.isCollide(OurTank) or
        self.isCollide(EnemyTank)
):

同理,还要修改敌方阵营坦克的移动。

【完善EnemyTank类】

EnemyTank类里的moveTank方法中,修改所有的

if not (
        self.isCollide(OurTank) or
        self.isCollide(EnemyTank)
):

为:

if not (
        self.isCollide(Wall) or
        self.isCollide(OurTank) or
        self.isCollide(EnemyTank)
):

运行截图:

终于好了,这下坦克没法穿墙了……

上子弹!!!咱要杀他个片甲不留!!!

【完善Bullet类】

Bullet类里添加__init__方法:

def __init__(self, tank, *groups: AbstractGroup):
    super().__init__(*groups)
    self.my_image = image.load(r"img/enemymissile.gif")
    self.direction = tank.direction
    self.rect = self.my_image.get_rect()

子弹的初始位置值得思考,我们必须在坦克正前方发射子弹。又考虑到坦克有四个方向,因此,子弹的位置分为四个情况,继续在Bullet类里的__init__方法里添加:

if self.direction == Tank.UP:
    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 == Tank.DOWN:
    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 == Tank.LEFT:
    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 == Tank.RIGHT:
    self.rect.left = tank.rect.left + tank.rect.width
    self.rect.top = tank.rect.top + (tank.rect.height / 2) - (self.rect.height / 2)

接着在Bullet类里添加displayBullet方法:

def displayBullet(self):
    MainGame.window.blit(source=self.my_image, dest=self.rect)

我们希望在游戏中,当按下空格的时候,我方阵营的坦克就会发射子弹。

【完善Tank类】

Tank类里添加shooting方法:

def shooting(self):
    self.bullets.append(Bullet(self))

一旦调用shooting,就会在属于自己的子弹列表里头增加一颗子弹。

然后,我们就去做个按键处理。

【完善MainGame类】

导入以下模块的方法:

from pygame import K_SPACE

MainGame类里的getEvent方法中,在

if (e.key == K_UP or
        e.key == K_DOWN or
        e.key == K_LEFT or
        e.key == K_RIGHT):
    for tank in OurTank.tanks:
        tank.move_switch = e.key

下一行(与if并列)添加:

if e.key == K_SPACE:
    for tank in OurTank.tanks:
        tank.shooting()

MainGame类里的startGame方法中,在while True:循环中,在display.update()上一行添加:

for tank in OurTank.tanks:
    for bullet in tank.bullets:
        bullet.displayBullet()

运行截图:

哎,每按一次空格,我方阵营的坦克就会发射一颗子弹。

但是,子弹咋都不动哎???

哦,对,子弹应该沿着初始方向,直线运动哈……

【完善Bullet类】

Bullet类添加moveBullet方法:

if (0 <= self.rect.left <= (SCREEN_WIDTH - self.rect.width) and
        0 <= self.rect.top <= (SCREEN_HEIGHT - self.rect.height)):
    if self.direction == Tank.UP:
        self.rect.top -= Tank.SPEED * 2
    elif self.direction == Tank.DOWN:
        self.rect.top += Tank.SPEED * 2
    elif self.direction == Tank.LEFT:
        self.rect.left -= Tank.SPEED * 2
    elif self.direction == Tank.RIGHT:
        self.rect.left += Tank.SPEED * 2

意思是,只要子弹被发射出去,就会一直以2倍坦克的移动速度直线移动。

然后在Bullet类的displayBullet方法中,在MainGame.window.blit(source=self.my_image, dest=self.rect)上一行添加:

self.moveBullet()

运行截图:

害,子弹没毛病,发射出去了……

还有三个小问题:

  • 在游戏边界不消失
  • 打不了敌方阵营坦克
  • 穿墙

咱逐一解决哈……

【完善MainGame类】

子弹在边界处消失,就需要遍历下所有我方阵营坦克的子弹,只要到达边界处,就删掉该子弹。

MainGame中的startGame方法中,在while True:循环中,在display.update()上一行添加:

for tank in OurTank.tanks:
    if tank.bullets:
        if not (0 <= tank.bullets[0].rect.left <=
                (SCREEN_WIDTH - tank.bullets[0].rect.width) and
                0 <= tank.bullets[0].rect.top <=
                (SCREEN_HEIGHT - tank.bullets[0].rect.height)):
            tank.bullets = tank.bullets[1:]

运行截图:

这下就看到,子弹到了边界就消失啦……

再解决子弹消灭敌人和打碎墙壁这两个问题。

【完善Bullet类】

Bullet类里添加isCollide方法,思路与Tank类里的isCollide方法类似:

def isCollide(self, collided_type):
    if "Tank" in [i.__name__ for i in getmro(cls=collided_type)]:
        for tank in collided_type.tanks:
            if collide_rect(tank, self):
                collided_type.tanks.remove(tank)
                del tank
                collided_type.count -= 1
                return True
        return False
    elif "Wall" in [i.__name__ for i in getmro(cls=collided_type)]:
        for wall in collided_type.walls:
            if collide_rect(wall, self):
                if wall.hp > 1:
                    wall.hp -= 1
                    return True
                else:
                    collided_type.walls.remove(wall)
                    del wall
                    collided_type.count -= 1
                    return True
        return False
    else:
        return False

设置好后,需要在游戏窗口实时检测是否发生碰撞。

【完善MainGame类】

MainGame类里的startGame方法中,在while True:循环中,在

for tank in OurTank.tanks:
    for bullet in tank.bullets:
        bullet.displayBullet()

下一行(与bullet.displayBullet()并列)添加:

if bullet.isCollide(EnemyTank) or bullet.isCollide(Wall):
    tank.bullets.remove(bullet)

运行截图:

我就不上动图啦,相信你可以发现,子弹打中坦克,坦克就消失了;子弹打四次墙壁,墙壁也消失了。高兴不,到这里,你已经里成功不远啦!!!

接着给敌方阵营的坦克上子弹,让它们随机发射子弹。

MainGame类里的startGame方法中,在while True:循环中,在

for tank in EnemyTank.tanks:
    tank.moveTank()

下一行(与for循环并列)添加:

for enemy_tank in EnemyTank.tanks:
    if randint(1, 2000) <= 10:
        enemy_tank.shooting()

用来随机发射敌方子弹。接着在MainGame类里的startGame方法中,在while True:循环中,在

for tank in OurTank.tanks:
    for bullet in tank.bullets:
        bullet.displayBullet()
        if bullet.isCollide(EnemyTank) or bullet.isCollide(Wall):
            tank.bullets.remove(bullet)

下一行(与for tank in并列)添加:

for tank in EnemyTank.tanks:
    for bullet in tank.bullets:
        bullet.displayBullet()
        if bullet.isCollide(OurTank) or bullet.isCollide(Wall):
            tank.bullets.remove(bullet)

到此,敌方坦克的子弹就显示出来了。

运行截图:

没错,哈哈哈哈,它把我方阵营给打没了……

不过,子弹到了边界没消失掉,没关系,跟处理我方阵营的子弹差不多,在MainGame类里的startGame方法中,在while True:循环中,在display.update()上一行添加:

for tank in EnemyTank.tanks:
    if tank.bullets:
        if not (0 <= tank.bullets[0].rect.left <=
                (SCREEN_WIDTH - tank.bullets[0].rect.width) and
                0 <= tank.bullets[0].rect.top <=
                (SCREEN_HEIGHT - tank.bullets[0].rect.height)):
            tank.bullets = tank.bullets[1:]

运行截图:

这下子弹都没问题啦!

不过,呜呜呜呜,我方阵营死得太惨了……刚出来就被打死……我要无限复活!!!无限复活!!!

安排,一般按下r键就可以复活,那就需要再添加点东西。

导入以下模块的方法:

from pygame import K_r

MainGame类里的getEvent方法中,在

if e.key == K_SPACE:
    for tank in OurTank.tanks:
        tank.shooting()

下一行(与if并列)添加:

if OurTank.count == 0 and e.key == K_r:
    OurTank(randint(60, 640), randint(60, 220))
    while (
            OurTank.tanks[-1].isCollide(Wall) or
            OurTank.tanks[-1].isCollide(EnemyTank)
    ):
        OurTank.tanks.pop()
        OurTank.count -= 1
        OurTank(randint(60, 640), randint(60, 220))

运行截图:

嘿嘿,无限复活,这下不怕死了!!!

不过,我现在想在屏幕上显示当前敌方阵营坦克的数量,怎么办?

来,接着往下看……

class BaseItem(Sprite):上一行添加:

FONT_COLOR = Color(255, 255, 0)

导入以下模块的方法:

from pygame import font

MainGame类中添加setTextSurface方法:

@staticmethod
def setTextSurface(text):
    font.init()
    my_font = font.SysFont(name="Times New Roman", size=18)
    text_surface = my_font.render(text, True, FONT_COLOR)
    return text_surface

MainGame类里的startGame方法中,在while True:循环中,在MainGame.window.fill(color=BG_COLOR)下一行添加:

MainGame.window.blit(
    source=self.setTextSurface("Enemy tanks: {0}.".format(
        EnemyTank.count)),
    dest=(10, 10)
)

运行截图:

哈哈,完美!!!

完了嘛?没有,这应该有爆炸效果吧……来,继续添加爆炸效果。

【完善Explosion类】

Explosion类里添加属性:

explosion = []

Explosion类里添加__init__方法:

def __init__(self, bullet):
    self.rect = bullet.rect
    self.rect.left -= 40
    self.rect.top -= 40
    del bullet
    self.images = [
        image.load(r"img/blast1.gif"),
        image.load(r"img/blast2.gif"),
        image.load(r"img/blast3.gif"),
        image.load(r"img/blast4.gif"),
        image.load(r"img/blast5.gif"),
        image.load(r"img/blast6.gif"),
        image.load(r"img/blast7.gif"),
        image.load(r"img/blast8.gif"),
    ]
    self.step = 0
    Explosion.explosion.append(self)

爆炸呢,是从小图片到大图片依次切换出来的,所以有多张图片。在Explosion类里添加displayExplosion方法:

def displayExplosion(self):
    if self.step < 8:
        MainGame.window.blit(source=self.images[self.step], dest=self.rect)
        self.step += 1
        return True
    else:
        return False

然后需要在子弹与墙壁和坦克碰撞时发生爆炸,于是……

【完善MainGame类】

MainGame类里的startGame方法中,在while True:循环中,在

for tank in OurTank.tanks:
    for bullet in tank.bullets:
        bullet.displayBullet()
        if bullet.isCollide(EnemyTank) or bullet.isCollide(Wall):
            tank.bullets.remove(bullet)

下一行(与tank.bullets.remove(bullet)并列)添加:

Explosion(bullet=bullet)

同理,在MainGame类里的startGame方法中,在while True:循环中,在

for tank in EnemyTank.tanks:
    for bullet in tank.bullets:
        bullet.displayBullet()
        if bullet.isCollide(OurTank) or bullet.isCollide(Wall):
            tank.bullets.remove(bullet)

下一行(与tank.bullets.remove(bullet)并列)添加:

Explosion(bullet=bullet)

在下一行(与for tank in EnemyTank.tanks:并列)添加:

for explosion in Explosion.explosion:
    if not explosion.displayExplosion():
        Explosion.explosion.remove(explosion)
        del explosion

运行截图:

终于截到爆炸时候的图了……我太难了!!!

好了,到这里就基本完成了,不过还少个音乐效果是吧,这简单,来。

【完善Music类】

导入以下模块的方法:

from pygame.mixer import init
from pygame.mixer import music

Music类里添加__init__方法:

def __init__(self, filename):
    self.filename = filename
    init()
    music.load(self.filename)

Music类里添加playMusic方法:

@staticmethod
def playMusic():
    music.play()

想了下,有这么几个声音需要添加:

  • 游戏开始声音
  • 子弹发射声音
  • 子弹爆炸声音
  • 我方阵营坦克复活声音

好,挨个挨个添加……

【完善Explosion类】

Explosion类里的displayExplosion方法中,在else里第一行添加:

Music(r"img/blast.wav").playMusic()

【完善MainGame类】

MainGame类里的getEvent方法中,在

if OurTank.count == 0 and e.key == K_r:
    OurTank(randint(60, 640), randint(60, 220))
    while (
            OurTank.tanks[-1].isCollide(Wall) or
            OurTank.tanks[-1].isCollide(EnemyTank)
    ):
        OurTank.tanks.pop()
        OurTank.count -= 1
        OurTank(randint(60, 640), randint(60, 220))

的下一行(与while并列)添加:

Music(r"img/add.wav").playMusic()

MainGame类里的getEvent方法中,在

if e.key == K_SPACE:
    for tank in OurTank.tanks:
        tank.shooting()

的下一行(与tank.shooting()并列)添加:

Music(r"img/fire.wav").playMusic()

MainGame类里的startGame方法中,在display.set_caption("Tank War 1.0")下一行添加:

Music(r"img/start.wav").playMusic()

运行,大功告成!!!!!!!

好激动!!!!!!恭喜你,学会了如何复现坦克大战!!!!

写博客太不容易了,希望有心的你,点个赞~~~

评论可不要捧我哟,毕竟是小白,咱确实没那资格。

有什么问题,记得私聊我哈,我会尽力帮助你的。

usic.load(self.filename)

Music类里添加playMusic方法:

@staticmethod
def playMusic():
    music.play()

想了下,有这么几个声音需要添加:

  • 游戏开始声音
  • 子弹发射声音
  • 子弹爆炸声音
  • 我方阵营坦克复活声音

好,挨个挨个添加……

【完善Explosion类】

Explosion类里的displayExplosion方法中,在else里第一行添加:

Music(r"img/blast.wav").playMusic()

【完善MainGame类】

MainGame类里的getEvent方法中,在

if OurTank.count == 0 and e.key == K_r:
    OurTank(randint(60, 640), randint(60, 220))
    while (
            OurTank.tanks[-1].isCollide(Wall) or
            OurTank.tanks[-1].isCollide(EnemyTank)
    ):
        OurTank.tanks.pop()
        OurTank.count -= 1
        OurTank(randint(60, 640), randint(60, 220))

的下一行(与while并列)添加:

Music(r"img/add.wav").playMusic()

MainGame类里的getEvent方法中,在

if e.key == K_SPACE:
    for tank in OurTank.tanks:
        tank.shooting()

的下一行(与tank.shooting()并列)添加:

Music(r"img/fire.wav").playMusic()

MainGame类里的startGame方法中,在display.set_caption("Tank War 1.0")下一行添加:

Music(r"img/start.wav").playMusic()

运行,大功告成!!!!!!!

[外链图片转存中…(img-hqFAgASO-1647272391768)]

好激动!!!!!!恭喜你,学会了如何复现坦克大战!!!!

写博客太不容易了,希望有心的你,点个赞~~~

评论可不要捧我哟,毕竟是小白,咱确实没那资格。

有什么问题,记得私聊我哈,我会尽力帮助你的。

注:完整代码可私聊我哈。

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值