博主不久前做了一道题,要求用pygame实现坦克大战的简易游戏。由于是第一次上手,python也只会基本的操作,class什么的都还不会,所以写的非常复杂,挂在这里算是一个留念,也希望能帮到有需要的朋友,代码很长,放在最后。
库与环境
使用了pygame,math库,在Python 3.7 64-bit下编写
游戏要求
能实现独立控制两个坦克,坦克运动尽量与现实相符。相撞视为平局,不能超出边界。其中一个坦克可以作为NPC自动与玩家战斗,自动向玩家坦克移动并攻击。
思路介绍
以其中一个坦克(不妨叫做tank1)为例,因为要求坦克的行动尽量符合现实,描述坦克状态最少要三个量,分别是 x 坐标,y 坐标,坦克朝向的角度。我的思路是通过中间小量δs1 和δθ来更新角度,这样的话一直按住按键坦克也会有移动,而不是一按一动。按下按键和松开按键会更改△s1 和△θ的值,通过这两个量的累加可以实时计算出 x 坐标和 y 坐标,以及角度,当然在判定撞墙时 x、y 的累加不进行,只进行角度的累加。炮弹也有这三个量,由于做直线运动,x,y 坐标只要确定了初始的变化量就可以一直重复累加,而方向恒等于射出时坦克的方向。对于 NPC模式,用 if 语句屏蔽了通过键盘对于 tank2 的操控,计算 tank2 和 tank1 的距离和角度,让 tank2 一直向 tank1 的方向旋转,同时一直向前移动,并不断调用开炮代码。
代码
import pygame
from pygame.locals import *
import math
#判断是否撞墙,不撞返回True
def wall(a,add,total_a):
if ((a + add) < total_a-50 and (a + add) > 0):
return True
else:
return False
#判断是否碰撞
def beat(location1,location2,judge=30):
#judge是碰撞判定边界值,假如两个坐标的差小于这个值,返回True
if math.fabs(location1-location2)<judge:
return True
else:
return False
def reset(): #一个回合结束,位置重置
global x1, x2, y1, y2, theta1, theta2, ifshoot1, ifshoot2
theta1, theta2 = 0, 180
x1, y1 = 100, 100
x2, y2 = total_X - 100, total_Y - 100
ifshoot1, ifshoot2 = False, False
#设置资源文件路径
bg_image_filename="file\\background.png"
tank1_image_filename="file\\tank1.png"
tank2_image_filename="file\\tank2.png"
bullte_image_filename="file\\shot.gif"
bgm_filename="file\\bgm.wav"
sound_shoot_filename="file\\shoot.wav"
sound_boom_fliename="file\\boom.wav"
pygame.init() #初始化设置
pygame.mixer.init() #初始化混响器
my_font=pygame.font.Font(None,30) #初始化字体
ifai=False #是否开启NPC模式
total_X,total_Y = 800,600 #设置场地总大小
width_of_line=20 #设置显示的边界线的宽度
#初始化tank1
x1, y1 = 100, 100 #x1,y1是tank1的实时位置
deltas1 = 0 #deltas1是检测到的移动距离
deltatheta1 = 0 #deltatheta1是转动角度
theta1 = 0 #theta1是实时角度
x1_move, y1_move = 0, 0 #x1_move和y1_move是通过前两者算出的转化后的坐标变化量
mark1 = 0 #tank1的得分
#初始化tank2,属性同上
x2, y2 = total_X-100, total_Y-100
deltas2=0
deltatheta2=0
theta2=180
x2_move,y2_move=0,0
mark2 = 0
distance=0 #NPC模式时计算tank1和tank2的距离,用来做算角度的中间量
angle=0 #NPC模式时得到的坦克间角度(弧度制)
sina=0 #NPC模式时得到的sin值
cosa=0 #NPC模式时得到的cos值
#下面是对于炮弹的初始化
ifshoot1,ifshoot2=False,False #是否开炮
x_shoot1,y_shoot1,x_shoot2,y_shoot2=-100,-100,-100,-100 #炮弹位置
theta_of_shoot1,theta_of_shoot2=0,0 #炮弹朝向
xshoot1_move,yshoot1_move,xshoot2_move,yshoot2_move=0,0,0,0 #炮弹位置变化量
#导入文件并校准图片方向
background = pygame.image.load(bg_image_filename) #导入背景
tank1_image = pygame.image.load(tank1_image_filename) #导入tank1图片
tank2_image = pygame.image.load(tank2_image_filename) #导入tank2图片
bullet_origin_image = pygame.image.load(bullte_image_filename) #导入子弹图片,作为子弹1和子弹2的模板
bullet_origin_image = pygame.transform.rotate(bullet_origin_image,180) #校准子弹方向
tank1_image=pygame.transform.rotate(tank1_image,-90) #导入tank1图片
tank2_image=pygame.transform.rotate(tank2_image,-90) #导入tank2图片
tank1_image_rotation=pygame.transform.rotate(tank1_image,theta1) #校准tank1方向
tank2_image_rotation=pygame.transform.rotate(tank2_image,theta2) #校准tank2方向
pygame.mixer.music.load(bgm_filename) #导入BGM
sound_boom=pygame.mixer.Sound(sound_boom_fliename) #导入爆炸声
sound_shoot=pygame.mixer.Sound(sound_shoot_filename) #导入射击声
bullet1_image=bullet_origin_image #初始化子弹1图像
bullet2_image=bullet_origin_image #初始化子弹2图像
screen=pygame.display.set_mode((total_X,total_Y),pygame.RESIZABLE) #创建一个可变的窗口
pygame.display.set_caption("坦克大战") #创建标题
white=(255,255,255) #定义白色,画线用
pygame.mixer.music.play(-1) #放BGM
ifbgm=True #定义BGM状态
#游戏主体
while True:
text_image = my_font.render("ai_control:I bgm_control:O mark1=%d mark2=%d "%(mark1,mark2), True, white) #显示说明和分数
for event in pygame.event.get():
if event.type == QUIT: #控制程序退出
exit()
if event.type == KEYDOWN: #从键盘获得操作
if event.key == K_o: #关闭或开启背景音乐
ifbgm = not ifbgm
if ifbgm:
pygame.mixer.music.play(-1)
else:
pygame.mixer_music.stop()
if event.key == K_i: #关闭或开启NPC模式
deltas2 = 0
deltatheta2 = 0
ifai = not ifai
if ifai:
tank2_image = pygame.transform.rotate(tank2_image,-90) #图片方向校准
else:
tank2_image = pygame.transform.rotate(tank2_image,+90) #图片方向校准
if event.key == K_w:
deltas1 = 1
if event.key == K_s:
deltas1 = -1
if event.key == K_d:
deltatheta1 = -1
if event.key == K_a:
deltatheta1 = 1
if event.key == K_g:
if not ifshoot1: #限制一次只能打出一颗子弹
ifshoot1 = True
x_shoot1, y_shoot1 = x1+30, y1+30
bullet1_image = pygame.transform.rotate(bullet_origin_image, theta1)
xshoot1_move = math.sin(math.radians(theta1))
yshoot1_move = math.cos(math.radians(theta1))
sound_shoot.play()
if not ifai: #假如没有开启NPC模式,则接收来自键盘对于tank2的控制
if event.key == K_UP:
deltas2=1
if event.key == K_DOWN:
deltas2=-1
if event.key == K_RIGHT:
deltatheta2 = -1
if event.key == K_LEFT:
deltatheta2 = 1
if event.key == K_SLASH:
if not ifshoot2:
ifshoot2 =True
x_shoot2,y_shoot2=x2+30,y2+30
bullet2_image=pygame.transform.rotate(bullet_origin_image,theta2)
xshoot2_move=math.sin(math.radians(theta2))
yshoot2_move=math.cos(math.radians(theta2))
sound_shoot.play()
elif event.type == KEYUP:
if event.key == K_w:
deltas1=0
if event.key == K_s:
deltas1=0
if event.key == K_d:
deltatheta1 = 0
if event.key == K_a:
deltatheta1 = 0
if not ifai:
if event.key == K_UP:
deltas2=0
if event.key == K_DOWN:
deltas2=0
if event.key == K_RIGHT:
deltatheta2 = 0
if event.key == K_LEFT:
deltatheta2 = 0
if ifai:
distance = math.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2)) #两点距离公式
sina = (y1 - y2) / distance
cosa = (x2 - x1) / distance
angle = math.atan2(y2 - y1, x2 - x1) #两坦克之间线段的弧度值
theta2 = math.degrees(-angle) #弧度转角度
x2, y2 = (x2 - cosa, y2 + sina)
#开炮
if not ifshoot2:
ifshoot2=True
x_shoot2, y_shoot2 = x2 + 30, y2 + 30
bullet2_image = pygame.transform.rotate(bullet_origin_image, theta2-90)
xshoot2_move = -cosa #math.sin(math.radians(theta2))
yshoot2_move = sina #math.cos(math.radians(theta2))
sound_shoot.play()
#tank1的位置和方向的变化
theta1+=deltatheta1
x1_move=math.sin(math.radians(theta1))*deltas1
y1_move=math.cos(math.radians(theta1))*deltas1
#tank2的位置和方向的变化
theta2+=deltatheta2
x2_move=math.sin(math.radians(theta2))*deltas2
y2_move=math.cos(math.radians(theta2))*deltas2
#tank撞墙判定,为降低tank1难度,tank1速度更快
if wall(x1,x1_move,total_X):
x1 += x1_move*1.5
if wall(y1,y1_move,total_Y):
y1 += y1_move*1.5
if wall(x2,x2_move,total_X):
x2 += x2_move
if wall(y2,y2_move,total_Y):
y2 += y2_move
#tank1子弹维持飞行
if ifshoot1==True:
x_shoot1+=xshoot1_move*2
y_shoot1+=yshoot1_move*2
#发射的子弹撞墙判定
if(not wall(x_shoot1,0,total_X) or not wall(y_shoot1,0,total_Y)):
ifshoot1=False
else:
x_shoot1,y_shoot1=-100,-100
#tank2子弹维持飞行
if ifshoot2==True:
x_shoot2+=xshoot2_move*2
y_shoot2+=yshoot2_move*2
#发射的子弹撞墙判定
if(not wall(x_shoot2,0,total_X) or not wall(y_shoot2,0,total_Y)):
ifshoot2=False
else:
x_shoot2,y_shoot2=-100,-100
#tank1子弹命中tank2判定
if beat(x_shoot1,x2) and beat(y_shoot1,y2):
reset()
sound_boom.play()
mark1+=1
print("player2 die!!")
#tank2子弹命中tank1判定
if beat(x_shoot2,x1) and beat(y_shoot2,y1):
reset()
sound_boom.play()
mark2+=1
print("player1 die!!")
#相撞判定,平局不计分
if beat(x1,x2,50) and beat(y1,y2,50):
print("equal")
reset()
sound_boom.play()
#刷新
screen.blit(background,(0,0)) #显示背景
tank1_image_rotation=pygame.transform.rotate(tank1_image,theta1) #tank1旋转
tank2_image_rotation=pygame.transform.rotate(tank2_image,theta2) #tank2旋转
screen.blit(text_image,(100,0)) #打印字
screen.blit(tank1_image_rotation,(x1,y1)) #显示tank1
screen.blit(tank2_image_rotation,(x2,y2)) #显示tank2
screen.blit(bullet1_image, (x_shoot1, y_shoot1)) #显示子弹1
screen.blit(bullet2_image,(x_shoot2,y_shoot2)) #显示子弹2
pygame.display.update()
其中的图片分别是
至于音频,相信对程序的实现没有什么太大关系,假如没有的话直接把声音的那些代码删掉便是。假如出现了一些看起来没什么用的变量,那可能是我忘记删了,应该不影响阅读。