坦克大战完整版[Python3]
英文注释版
由https://www.cnblogs.com/lwj-jz/articles/TankWarGame.html转载
【写在前面】
还记得小时候玩过的坦克大战嘛~想当年几个小伙伴挤在一起,恨不得把键盘分成几瓣……
好了,废话不多说,直入主题。今天咱们要基本实现坦克大战的小游戏,主要利用第三方库pygame
,不了解的友友们可以参考官方文档。由于最近http://www.pygame.org成了这鬼样子
害,就烦人,不过还好之前下载了文档,链接在这里pygame官方文档
如果你不想跟着学,想要完整代码请点赞后私聊我;如果你想跟着学,我相信,你不用私聊我也可以做出来,并且会随手给我一个小小的赞~
【运行结果】
先上结果!!!
【准备工作】
结果满意不~
满意的话就去点个小小的赞,不要钱的哈。
不满意也不要紧,嘿嘿,看清楚了,这只是Tank War 1.0
哦,这意味着什么?对,升级更新。不过,我相信,这些够你用的了,想升级的话,私聊我吧。
- 编辑环境:
Pycharm
。 - 解释器:
Python3
。 - 需要按步骤导入相关模块,一定核对好。
- 相关素材资源:链接我就直接给了哈,别回头说我不厚道……坦克大战素材
【项目需求】
首先我们得分析下,做这个《坦克大战》小游戏需要什么东西。
"""
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__
方法中添加:
注意:
img文件夹是从坦克大战素材解压出来的,注意你的路径问题,我用的是相对路径,建议你使用绝对路径。
想要使用你自己的素材也可以,但图片后缀必须是
.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类】
话说,有图也没啥,得能动啊……
我们玩这个游戏,肯定要用到键盘,习惯呢,是“上下左右”键,也就是Up
、Down
、Left
、Right
键,于是,我们需要回到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)]
好激动!!!!!!恭喜你,学会了如何复现坦克大战!!!!
写博客太不容易了,希望有心的你,点个赞~~~
评论可不要捧我哟,毕竟是小白,咱确实没那资格。
有什么问题,记得私聊我哈,我会尽力帮助你的。
注:完整代码可私聊我哈。