前言
《坦克大战》,1985年由日本开发商南梦宫(Namco)开发,是第一款可以双打的红白机游戏。当时使用的还是小霸王。如何用python实现这个游戏
资源获取
图片音频等素材资源包括源代码(ps:源代码可能后面会改可能跟下方不一样)我放在了GitHub上了,有想法的xd可以去下载下来完善或者玩一下也行,https://github.com/fbozhang/python/tree/master/pygame/tank
一、项目介绍
1.pygame是什么?
Pygame是跨平台Pyth,Pygame 作者是 Pete Shinners, 协议为 GNU Lesser General Public License。
pygame是跨平台Python模块,专为电子游戏设计。包含图像、声音。建立在SDL基础上,允许实时电子 游戏研发而无需被低级语言(如机器语言和汇编语言)束缚。基于这样一个设想,所有需要的游戏功能和理念都(主要是图像方面)都完全简化为游戏逻辑本身,所有的资源结构都可以由高级语言提供,如Python。我们这个游戏主要就是使用pygame的模块实现。
2.操作指南
玩家:上下左右 移动 空格 射击 F1: 复活玩家 F2: 复活我方老鹰 F3: 复活敌方坦克 F4: 外挂模式——子弹自动追踪敌方
3.项目演示
演示视频:
演示gif(利用下面网站转换):
二、项目实现
1.安装库
pip install pygame
2.引入库
代码如下(示例):
import math
import random
import sys
import time
import pygame
3.项目代码
PS: 以下代码以尽量都注释了,如还有哪里不明白可以call me添加注解
本质上这是一个贴图游戏
3.1 主逻辑类
开始游戏,结束游戏,获取事件(比如鼠标事件、键盘事件)并处理
class MainGame():
window = None
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 500
background = "images/background.gif"
Home = None
TANK_P1 = None
EnemyTank_List = []
EnemyTank_count = 3
Bullet_List = []
Enemy_bullet_List = []
Explode_List = []
Wall_List = []
cheat = False
def startGame(self):
pygame.display.init()
MainGame.window = pygame.display.set_mode([MainGame.SCREEN_WIDTH, MainGame.SCREEN_HEIGHT])
self.creatMyTank()
self.creatEnemyTank()
self.creatHome()
self.creatWalls()
pygame.display.set_caption("坦克大战" + version)
music = Music("audios/start.wav")
music.play()
while True:
MainGame.window.fill(COLOR_BLACK)
self.blitBackground()
self.getEvent()
MainGame.window.blit(self.getTextSurface("剩余敌方坦克%d辆" % len(MainGame.EnemyTank_List)), (5, 5))
self.blitWalls()
self.blitHome()
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
MainGame.TANK_P1.displayTank()
else:
del MainGame.TANK_P1
MainGame.TANK_P1 = None
self.blitEnemyTank()
if MainGame.TANK_P1 and not MainGame.TANK_P1.stop:
MainGame.TANK_P1.move()
MainGame.TANK_P1.hitWalls()
MainGame.TANK_P1.hitEnemyTank()
self.blitBullet()
self.blitEnemyBullet()
self.displayExplodes()
time.sleep(0.02)
if not MainGame.Home.live:
MainGame.window.blit(self.getTextSurface_Tips("Game Over!!"),
(200, MainGame.SCREEN_HEIGHT / 2 - 60))
if len(MainGame.EnemyTank_List) == 0:
MainGame.window.blit(self.getTextSurface_Tips("victory!!"),
(250, MainGame.SCREEN_HEIGHT / 2 - 60))
pygame.display.update()
def creatMyTank(self):
MainGame.TANK_P1 = MyTank(275, 440)
'''
在纵坐标100以上的位置随机创建敌方坦克
'''
def creatEnemyTank(self):
top = 100
for i in range(MainGame.EnemyTank_count):
speed = random.randint(3, 6)
left = random.randint(1, int(MainGame.SCREEN_WIDTH / 100 - 1))
eTank = EnemyTank(left * 100, top, speed)
MainGame.EnemyTank_List.append(eTank)
'''
因只是案例所以随便设置了一些墙,如果想完善ui可以在这设计墙壁
'''
def creatWalls(self):
for i in range(6):
for j in range(2):
for k in range(2):
wall = Wall(140 * i + 31 * j, MainGame.SCREEN_HEIGHT / 2 - 31 * k)
MainGame.Wall_List.append(wall)
for i in range(3):
for j in range(4):
if i != 0:
if j == 0 or j == 4 - 1:
wall = Wall(MainGame.Home.rect.left - 31 + 30 * j, MainGame.SCREEN_HEIGHT - 30 * i)
MainGame.Wall_List.append(wall)
else:
wall = Wall(MainGame.Home.rect.left - 31 + 30 * j, MainGame.SCREEN_HEIGHT - 30 * 3)
MainGame.Wall_List.append(wall)
def creatHome(self):
MainGame.Home = Home()
def blitWalls(self):
for wall in MainGame.Wall_List:
if wall.live:
wall.displayWall()
else:
MainGame.Wall_List.remove(wall)
def blitBackground(self):
Background(MainGame.background).displayBackground()
def blitHome(self):
MainGame.Home.displayHome()
def blitEnemyTank(self):
for eTank in MainGame.EnemyTank_List:
if eTank.live:
eTank.displayTank()
eTank.randMove()
eTank.hitWalls()
eTank.hitMyTank()
eBullet = eTank.shot()
if eBullet:
MainGame.Enemy_bullet_List.append(eBullet)
else:
MainGame.EnemyTank_List.remove(eTank)
def blitBullet(self):
for bullet in MainGame.Bullet_List:
if bullet.live:
bullet.displayBullet()
print(bullet.cheat)
bullet.cheat = MainGame.cheat
if bullet.cheat:
if len(MainGame.EnemyTank_List) > 0:
bullet.bulletFollowMove(MainGame.EnemyTank_List[0].rect.left,
MainGame.EnemyTank_List[0].rect.top)
else:
bullet.bulletMove()
bullet.hitEnemyTank()
bullet.hitWalls()
bullet.hitBullet()
bullet.hitHome()
else:
MainGame.Bullet_List.remove(bullet)
def blitEnemyBullet(self):
for eBullet in MainGame.Enemy_bullet_List:
if eBullet.live:
eBullet.displayBullet()
eBullet.bulletMove()
eBullet.hitWalls()
eBullet.hitHome()
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
eBullet.hitMyTank()
else:
MainGame.Enemy_bullet_List.remove(eBullet)
def displayExplodes(self):
for explode in MainGame.Explode_List:
if explode.live:
explode.displayExplode()
else:
MainGame.Explode_List.remove(explode)
def getEvent(self):
eventList = pygame.event.get()
for event in eventList:
if event.type == pygame.QUIT:
self.endGame()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_F1 and not MainGame.TANK_P1:
self.creatMyTank()
if event.key == pygame.K_F2 and not MainGame.Home.live:
MainGame.Home.live = True
if event.key == pygame.K_F3 and len(MainGame.EnemyTank_List) == 0:
self.creatEnemyTank()
if event.key == pygame.K_F4:
MainGame.cheat = True
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
if event.key == pygame.K_LEFT:
print("坦克向左调头, 移动")
MainGame.TANK_P1.direction = "L"
MainGame.TANK_P1.stop = False
elif event.key == pygame.K_RIGHT:
print("坦克向右调头, 移动")
MainGame.TANK_P1.direction = "R"
MainGame.TANK_P1.stop = False
elif event.key == pygame.K_UP:
print("坦克向上调头, 移动")
MainGame.TANK_P1.direction = "U"
MainGame.TANK_P1.stop = False
elif event.key == pygame.K_DOWN:
print("坦克向下调头, 移动")
MainGame.TANK_P1.direction = "D"
MainGame.TANK_P1.stop = False
elif event.key == pygame.K_SPACE:
print("发射子弹")
if len(MainGame.Bullet_List) < 3:
m = MainGame.TANK_P1.shot()
MainGame.Bullet_List.append(m)
music = Music("audios/fire.wav")
music.play()
else:
print("子弹数量不足")
print("当前屏幕中的子弹数量为:%d" % len(MainGame.Bullet_List))
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN:
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
MainGame.TANK_P1.stop = True
"""
# 获取键盘的第二种方法,考虑后面用该方法控制2P
key = pygame.key.get_pressed()
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
if key[pygame.K_a]:
print("坦克向左调头, 移动")
MainGame.TANK_P1.direction = "L"
MainGame.TANK_P1.move()
elif key[pygame.K_w]:
print("坦克向上调头, 移动")
MainGame.TANK_P1.direction = "U"
MainGame.TANK_P1.move()
elif key[pygame.K_s]:
print("坦克向下调头, 移动")
MainGame.TANK_P1.direction = "D"
MainGame.TANK_P1.move()
elif key[pygame.K_d]:
print("坦克向右调头, 移动")
MainGame.TANK_P1.direction = "R"
MainGame.TANK_P1.move()
# 这个方法暂未解决边移动边设计
elif key[pygame.K_KP0]:
print("射击")
# 产生一颗子弹
m = Bullet(MainGame.TANK_P1)
# 将子弹加入到子弹列表
MainGame.Bullet_List.append(m)
"""
def getTextSurface(self, text):
pygame.font.init()
""""如果需要改其他系统字体可以先查询,下面选择的楷体"""
font = pygame.font.SysFont("kaiti", 18)
textSurface = font.render(text, True, COLOR_RED)
return textSurface
def getTextSurface_Tips(self, text):
pygame.font.init()
font = pygame.font.SysFont("kaiti", 60, bold=True, italic=True)
textSurface = font.render(text, True, COLOR_RED)
return textSurface
def endGame(self):
print("谢谢使用")
sys.exit()
3.2 背景类
加载游戏背景(也可以写成主逻辑类的方法)
class Background():
def __init__(self, image):
self.image = pygame.image.load(image)
self.rect = self.image.get_rect()
self.rect.left = 0
self.rect.top = 0
def displayBackground(self):
MainGame.window.blit(self.image, self.rect)
3.3 基类
继承精灵类,用于作为需要做碰撞测试的父类,详细参考API文档
class BaseItem(pygame.sprite.Sprite):
def __int__(self):
pygame.sprite.Sprite.__init__(self)
3.4 坦克类
坦克的移动,射击以及坦克的展示。因为需要做碰撞测试所以继承基类
class Tank(BaseItem):
def __init__(self, left, top):
self.images = {
"U": pygame.image.load("images/p1tankU.gif"),
"D": pygame.image.load("images/p1tankD.gif"),
"L": pygame.image.load("images/p1tankL.gif"),
"R": pygame.image.load("images/p1tankR.gif"),
}
self.direction = "U"
self.image = self.images[self.direction]
self.rect = self.image.get_rect()
self.rect.left = left
self.rect.top = top
self.speed = 5
self.stop = True
self.live = True
self.oldLeft = self.rect.left
self.oldTop = self.rect.top
"""
因为是贴图游戏所以移动即是坐标的加减变化,利用视觉暂留原理实现移动
"""
def move(self):
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.width < 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 stay(self):
self.rect.left = self.oldLeft
self.rect.top = self.oldTop
def hitWalls(self):
for wall in MainGame.Wall_List:
if pygame.sprite.collide_rect(wall, self):
self.stay()
def shot(self):
return Bullet(self)
def displayTank(self):
self.image = self.images[self.direction]
MainGame.window.blit(self.image, self.rect)
3.5 MyTank类
我方坦克,撞到敌方坦克不能移动
class MyTank(Tank):
def __init__(self, left, top):
super(MyTank, self).__init__(left, top)
def hitEnemyTank(self):
for eTank in MainGame.EnemyTank_List:
if pygame.sprite.collide_rect(eTank, self):
self.stay()
3.6 EnemyTank类
重写了移动和射击方法,让敌方坦克随机移动射击
class EnemyTank(Tank):
def __init__(self, left, top, speed):
super(EnemyTank, self).__init__(left, top)
self.images = {
"U": pygame.image.load("images/enemy1U.gif"),
"D": pygame.image.load("images/enemy1D.gif"),
"L": pygame.image.load("images/enemy1L.gif"),
"R": pygame.image.load("images/enemy1R.gif"),
}
self.direction = self.randDirection()
self.image = self.images[self.direction]
self.rect = self.image.get_rect()
self.rect.left = left
self.rect.top = top
self.speed = speed
self.stop = True
self.step = 20
def randDirection(self):
num = random.randint(1, 4)
if num == 1:
return 'U'
elif num == 2:
return 'D'
elif num == 3:
return 'L'
elif num == 4:
return 'R'
def randMove(self):
if self.step <= 0:
self.direction = self.randDirection()
self.step = 20
else:
self.move()
self.step -= 1
def shot(self):
num = random.randint(1, 100)
if num <= 2:
return Bullet(self)
def hitMyTank(self):
if MainGame.TANK_P1 and MainGame.TANK_P1.live:
if pygame.sprite.collide_rect(self, MainGame.TANK_P1):
self.stay()
3.7 子弹类
子弹的移动,碰撞以及展示子弹
class Bullet(BaseItem):
def __init__(self, tank):
self.image = pygame.image.load("images/enemymissile.gif")
self.direction = tank.direction
self.rect = self.image.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.width / 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.width / 2 - self.rect.height / 2
self.speed = 7
self.live = True
self.cheat = False
def bulletMove(self):
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 < MainGame.SCREEN_HEIGHT - self.rect.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 < MainGame.SCREEN_WIDTH - self.rect.width:
self.rect.left += self.speed
else:
self.live = False
用微分的思想使用三角函数求得子弹的新坐标(字丑勿槽)
def bulletFollowMove(self, x, y):
velocity = 10000
time = 1 / 1000
space = velocity * time
clock = pygame.time.Clock()
clock.tick(120)
distance = math.sqrt(pow(x - self.rect.left, 2) + pow(y - self.rect.top, 2))
sina = (y - self.rect.top) / distance
cosa = (x - self.rect.left) / distance
self.rect.left = self.rect.left + space * cosa
self.rect.top = self.rect.top + space * sina
print(self.rect.left,self.rect.top, x, y)
def displayBullet(self):
MainGame.window.blit(self.image, self.rect)
def hitEnemyTank(self):
for eTank in MainGame.EnemyTank_List:
if pygame.sprite.collide_rect(eTank, self):
explode = Explode(eTank)
MainGame.Explode_List.append(explode)
self.live = False
eTank.live = False
def hitMyTank(self):
if pygame.sprite.collide_rect(self, MainGame.TANK_P1):
explode = Explode(MainGame.TANK_P1)
MainGame.Explode_List.append(explode)
self.live = False
MainGame.TANK_P1.live = False
def hitWalls(self):
for wall in MainGame.Wall_List:
if pygame.sprite.collide_rect(self, wall):
self.live = False
wall.hp -= 1
if wall.hp <= 0:
wall.live = False
def hitBullet(self):
for bullet in MainGame.Enemy_bullet_List:
if pygame.sprite.collide_rect(bullet, self):
self.live = False
bullet.live = False
def hitHome(self):
if pygame.sprite.collide_rect(self, MainGame.Home):
self.live = False
MainGame.Home.live = False
3.8 爆炸类
展示子弹命中后的爆炸效果
class Explode():
def __init__(self, tank):
self.rect = tank.rect
self.step = 0
self.images = [
pygame.image.load("images/blast0.gif"),
pygame.image.load("images/blast1.gif"),
pygame.image.load("images/blast2.gif"),
pygame.image.load("images/blast3.gif"),
pygame.image.load("images/blast4.gif")
]
self.image = self.images[self.step]
self.live = True
def displayExplode(self):
if self.step < len(self.images):
self.image = self.images[self.step]
MainGame.window.blit(self.image, self.rect)
time.sleep(0.03)
self.step += 1
else:
self.live = False
self.step = 0
3.9 墙壁类
展示墙壁(如想要做红白机中的砖墙和石墙的效果可以参考坦克类实现,素材中也有)
class Wall():
def __init__(self, left, top):
self.image = pygame.image.load("images/cement_wall.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)
3.10 水晶类
展示我方小鸟
class Home():
def __init__(self):
self.image_symbol = pygame.image.load("images/symbol.gif")
self.image_symbol_destoryed = pygame.image.load("images/symbol_destoryed.gif")
self.rect = self.image_symbol.get_rect()
self.rect.left = MainGame.SCREEN_WIDTH / 2 - self.rect.width / 2
self.rect.top = MainGame.SCREEN_HEIGHT - self.rect.height
self.live = True
def displayHome(self):
if self.live:
MainGame.window.blit(self.image_symbol, self.rect)
else:
MainGame.window.blit(self.image_symbol_destoryed, self.rect)
3.11 音乐类
播放音乐
class Music():
def __init__(self, fileName):
self.fileName = fileName
pygame.mixer.init()
pygame.mixer.music.load(self.fileName)
def play(self):
pygame.mixer.music.play(loops=0)
最后调用主类的开始方法即可
if __name__ == '__main__':
MainGame().startGame()
4. 项目打包
写完之后想给别人玩,但是除了计算机专业很少人会安装python的运行环境,于是将该py文件打包为exe程序 打包问题可以参考我上一个博客,python打包exe
参考文档
官方API: https://www.pygame.org/docs/ c语言中文网: http://c.biancheng.net/pygame/sprite.html
总结
此次项目主要是了解pygame的使用,了解了pygame小游戏的总体运行情况,熟悉了python的一些语法还有一些库并且明白面向对象是什么,提升了编程能力还提高了使用Photoshop的能力。 以下是挺有用的网站: 坦克大战介绍:https://www.retrowan.com/battle-city/ 画图的网站:https://mastergo.com/(那些提示图片的制作) 消除图片背景:https://www.remove.bg/zh/upload 视频转gif:https://www.apowersoft.cn/video-to-gif-online png,jpg,gif转ico:http://www.ico51.cn/ ,https://www.butterpig.top/icopro 当然还有很多。 因为时间有限,很多功能并未完善 游戏的不足: 1)未加菜单功能 2)玩家2P功能没有开通 3)游戏中还有一些小bug由于时间原因没有完善,例如敌方复活位置刚好与我方坦克重叠会卡住双方移动(在演示中有体现) 4)未加分数机制 5)游戏ui较丑 6)地图墙壁设计太差,没有参考坦克大战的地图设计 7)未加关卡功能 8)未加奖励机制,例如吃到道具可以强化强化墙壁或者使用其他类型子弹 9)游戏结束界面比较潦草,仅仅是显示文字而已
|