Python实现坦克大战(附源码)

Posted 嘟粥yyds

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python实现坦克大战(附源码)相关的知识,希望对你有一定的参考价值。

基于pygame的坦克大战小游戏

目录

基于pygame的坦克大战小游戏

一:开发环境

二、项目介绍

        2.1完成功能:

        2.2项目截图 :

        2.3项目文件

一:开发环境

我们先来看看我的开发环境,用的东西还是比较简单的:

作者:嘟粥yyds

时间:2022年12月3日

集成开发工具:PyCharm 2021.1.3

集成开发环境:python  3.10.6

第三方模块:pygame、pygame_menu、Image、ImageSequence

二、项目介绍

        2.1完成功能:

  1. 菜单选择:用户可以根据菜单进行选择,提高了程序的交互性。
  2. 多种模式:提供了关卡模式(设有35个关卡)、无尽模式、单挑模式、建造模式。
  3. 地图编辑:用户可以用键盘自由编辑地图,增加玩家趣味性。
  4. 简单敌人AI:敌人有简单的AI行为,如:发射子弹、漫步等。
  5. 动画效果:坦克在受到攻击时的爆破效果;敌方坦克复活的简单动画。
  6. 游戏循环:同一般软件不同,游戏需要采用主循环来更新场景状态并重绘屏幕。本程序以60FPS的帧率刷新

        2.2项目截图 :

主程序入口在main.py文件,在安装好pygame、pygame_menu等模块后就能直接运行。下面是运行截图:

下面是游戏开始正式运行的效果截图:

        2.3项目文件

(1)image、init、music

image是该项目所需的图片资源,init是项目加载时的效果图片资源,music是该项目所需的音乐资源。而批量修改图片尺寸和解析GIF均为临时使用的两个工具,不做解释。

(2)main.py

而main.py则是项目的主入口。主要进行初始界面菜单的加载。创建game_loader对象,然后根据在初始界面选择的模式运行相对应的方法。以下为main.py的大体框架:

(3)game_loder.py

game_loder.py中写了我们坦克大战游戏主体的模块,里面的TankWar类定义了游戏主体的一切行为。包括初始化屏幕、初始化pygame模块、创建敌方坦克、绘制地图、检测碰撞、监听事件等。

以下为game_loder.py的大体框架:

(4)其他模块均为对象描述

bulletClass.py:该模块为子弹类的描述

enemyTank.py:该模块为敌方坦克类的描述


 food.py:该模块为道具(敌方坦克所携带的奖励)类的描述

map_loader.py和maps.py均为地图加载模块

 special_effects.py:为特殊效果(爆炸、保护罩等)模块

 wall.py:该模块描述的是地图元素(砖块、石头等)

 因为代码比较多,这里就不介绍代码了。游戏还有许多不足之处,后续会继续更新。项目即将上传GitHub,欢迎各位来fork。今天就介绍到这里了~

 项目地址:0911duzhou/python- · GitHub

 pygame文档地址:https://www.pygame.org/docs/

python 全解坦克大战 辅助类 附完整代码雏形

要素材私聊我

突然来的兴趣

这个坦克大战是基于 pygame 的,由于没有完整的学过 pygame,之前一直以为 pygame 对于长按键不支持监听,就在几天前我竟然发现了可以,然后就打开了我的世界大门。

由于这个辅助类我随便写了几个小时,还有很多问题,咱们先慢慢来,先做个坦克大战好了。

这是演示效果:

目前这辅助类的功能有

使用这个辅助类只需要配置信息,自己创建对应对象,主角只需要创建后就可以自动可以移动,敌人也可以自己随机“AI”进行移动,并且子弹自动触碰敌人和墙壁会互相“销毁”完成射击效果。
我们先来看如何使用这辅助类。

使用示例 坦克大战

首先创建一个地图:

map_srpirte=[
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,w,w,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,w,w,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],

]

用个list就是可以了,w就是障碍物,简单吧?
接下来设置地图配置:

mapConf=
    space:-,
    w:"./tank/scene/brick.png",
    height:12,
    weight:20

space表示空间,w表示障碍物的精灵图,宽高为精灵图大小。
接下来写下宽高内容:

screenW=mapConf[weight]*34
screenH=mapConf[height]*48

接下来创建组,设置屏幕:

pygame.init()
screen = pygame.display.set_mode((screenW,screenH))
group_wallt = pygame.sprite.Group()
pygame.key.set_repeat(10)
framerate = pygame.time.Clock()
group_hero = pygame.sprite.Group()
enemy_hero = pygame.sprite.Group()

接下来使用我们的自己写的辅助类 ESprite:

sprite_hero = ESprite(screen,group_hero)

接着设置图片与设置组:

sprite_hero.load("./tank/playerTank/tank_T1_2.png",48, 48, 4, 2)
up="./tank/bullet/bullet_up.png"
down="./tank/bullet/bullet_down.png"
left="./tank/bullet/bullet_left.png"
right="./tank/bullet/bullet_right.png"
group_hero.add(sprite_hero)

使用我们自己写的敌人类循环创建敌人:

enemy_list=[]
for v in range(0,20):
    enemy = Enemy(screen,enemy_hero)
    enemy.load("./tank/enemyTank/enemy_1_0.png",48, 48, 4, 2)
    enemy_hero.add(enemy)
    enemy_list.append(enemy)

接下来使用自己写的精灵类创建不可触碰体,并且把这个对象添加到主角、敌人不可触碰体设置之中:

posx,posy=0,0
wallet=[]
for rows in map_srpirte:
    for v in rows:
        posx+=24
        if v!=mapConf[space]:
            sprite_wallt = ESprite(screen,group_wallt)
            sprite_wallt.load(mapConf[w],24, 24, 1, 1,posx,posy)
            group_wallt.add(sprite_wallt)
            sprite_hero.setCollision(sprite_wallt)#添加不可触碰
            enemy.setCollision(sprite_wallt)#添加不可触碰
            print(str(posx)+,+str(posy))
    posy+=24
    posx=0

最后开启主循环进行监听、刷新即可:

#主循环
while True:
    print((screenW,screenH))
    framerate.tick(30)
    ticks = pygame.time.get_ticks()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
        elif event.type==pygame.KEYDOWN:
            print(key down ......)
            sprite_hero.control.moveControl(event)
    pos=
        up_x:18,up_y:-10,down_x:18,down_y:50,
        left_x:-6,left_y:16,right_x:50,right_y:18,
    
    sprite_hero.shoot(up,down,left,right,12,12,1,1,pos,ticks,enemy_list)

    screen.fill((0,0,100))

    group_hero.update(ticks)
    group_hero.draw(screen)
    enemy_hero.update(ticks)
    enemy_hero.draw(screen)

    group_wallt.update(ticks)
    group_wallt.draw(screen)
    for v in enemy_list:
        v.autoMove((screenW,screenH))
    pygame.display.update()

一、写个精灵类

1.1 初始化

首先创建一个python 文件名为 Etank.py,并在如下引入依赖:

import pygame,random
from pygame.locals import *

pygame 、 random 是所需库,pygame.locals import * 主要是用来找到KEY。

接下来创建一个类名为 ESprite 继承于pygame 的Sprite 基类:

class ESprite(pygame.sprite.Sprite):

在 ESprite 中给到一个 init 方法:

def __init__(self,screen,group=None):

其中 screen 是需要进行屏幕刷新的屏幕对象,group 是当前类实例化后所对应的组。

接下来在 init 中调用父类初始化:

pygame.sprite.Sprite.__init__(self)

接下来在 init 方法中初始化一些内容:

def __init__(self,screen,group=None):#target是屏幕
        pygame.sprite.Sprite.__init__(self)
        #self.target_surface = target#精灵渲染目标为屏幕
        self.screen=screen
        self.image = None#初始化图片None
        self.main_image = None#主图片
        self.rect = None#需要画图的区域
        self.rframe = 0 #图片序列号 行
        self.cframe = 0 #图片序列号 列
        self.old_frame = -1#老图片序列号
        self.frame_width = 1#图片宽
        self.frame_height = 1#图片高
        self.cols = 1 #列
        self.rows = 1 #行
        self.last_time = 0 #上次更换时的总帧数,用于判断更换帧
        self.X=0
        self.Y=0
        self.speedX=1
        self.speedY=1
        self.control=SpriteController(self)#控制初始化
        self.group=group
        self.shootobj=[]
        self.upImg,self.downImg,self.leftImg,self.rightImg=None,None,None,None
        self.collisions=[]
        self.enemy_list=[]

这些初始化后的该类属性之后将会在方法中用到,咱们用到时再做说明。

1.2 添加不可碰撞体

在游戏中有很多的不可碰撞体,例如墙壁、障碍物、这些内容对于可活动的游戏觉得是有障碍的,在这里设置一个方法为当前的精灵设置一个不可碰撞体:

#添加不可触碰体
def setCollision(self,collision):
    self.collisions.append(collision)

1.3加载主图方法

在2d游戏中,每一个98%的角色都是需要图片给予对象视觉呈现,此时写一个方法 load 用于加载当前主图内容:

#加载用
def load(self, filename, width, height,rows,cols,posx=100,posy=100):
    self.main_image = pygame.image.load(filename).convert_alpha()#加载主图
    self.frame_width = width#宽高记录
    self.frame_height = height
    self.rect = [posx,posy,width,height]#绘制
    self.cols = cols
    self.rows = rows

该方法的参数说明如下:

  • filename 图片路径
  • width 每个图片宽
  • height 每个图片高
  • rows 行
  • cols 列
  • posx 主图起始绘制位置x
  • posy 主图起始绘制位置y

在以上方法中,通过 filename 加载主图,通过宽高选择主图所绘制的区域,图片示例如下:

宽高指的是图片大小的宽高,posx 和 posy 指图片左上角形成的坐标轴的位置,例如图片大小是4848,总长度是宽 482 长是 48*8,那就是8行2列内容,那么 8 就是 rows 2就是参数 cols,posx 和 posy 就是左上角0和0。

1.5刷新方法

加载图片后还需要刷新内容,创建一个方法 update,接收两个参数,一个是 current_time 是当前帧数,rate 为刷新时的帧值。

#每次图片动态更新绘制区域 动画播放
def update(self, current_time, rate=60):
    #当前帧总数如果已经超过了最初的 last_time + 60,那么表示
    #已经超过了60帧,那么 frame 图片序列号+1,开始下一张图片

    if current_time > self.last_time + rate:
        self.rframe += 1 #图片序列号+1  
        if self.rframe>self.cols-1:#大于图片最大列就说明进行了一个循环,因为 self.frame 初始值是 0
            self.rframe=0
        self.last_time = current_time#每次更改图片时就记录更换后的帧

    if self.rframe != self.old_frame:#新老次序不一 表示更换frame
        #frame_x绘制矩形的位置x就等于图片数*每个宽度得到x坐标值
        frame_x=self.rframe * self.frame_width
        frame_y=self.cframe * self.frame_height
        #y值跟随上下左右按键切换图,图片规定同一行一个动作
        # 不同按键对应上下左右
        # 绘制的区域使用 frame_width frame_height 代替
        rect = ( frame_x, frame_y, self.frame_width, self.frame_height )
        self.image = self.main_image.subsurface(rect)#选择区域进行图片提取
        self.old_frame = self.rframe

以上代码中 if current_time > self.last_time + rate: 表示当前帧是否大于最后一次更换帧数+60,大于则需要刷新,那么则 self.rframe += 1 图片序列号+1 ,表示更换图片 ,但是不能大于本身图片序列的行和列 if self.rframe>self.cols-1,大于则将 self.rframe=0 。

接着就替换一下 self.last_time = current_time 为最后一次的更换帧数,接下来则替换显示图片的坐标值,也就是 rect 值:

if self.rframe != self.old_frame:#新老次序不一 表示更换frame
        #frame_x绘制矩形的位置x就等于图片数*每个宽度得到x坐标值
        frame_x=self.rframe * self.frame_width
        frame_y=self.cframe * self.frame_height
        #y值跟随上下左右按键切换图,图片规定同一行一个动作
        # 不同按键对应上下左右
        # 绘制的区域使用 frame_width frame_height 代替
        rect = ( frame_x, frame_y, self.frame_width, self.frame_height )
        self.image = self.main_image.subsurface(rect)#选择区域进行图片提取
        self.old_frame = self.rframe

1.6 #创建发射对象

子弹上下左右的主图不一样,朝向不一,如图所示:

此时编写一个方法 shoot:

def shoot(self,upImg,downImg,leftImg,rightImg,width,height,rows,cols,pos,ticks=None,enemy_list=[]):
        self.enemy_list=enemy_list
        if self.control.isShoot==True:
            #创建发射物
            shootobj=ESprite(self.screen)
            self.group.add(shootobj)
            posx,posy=0,0
            sprite_img=
            if self.control.shoot_direction==self.control.direction_UP:
                posx=self.rect[0]+pos[up_x]
                posy=self.rect[1]+pos[up_y]

                sprite_img=upImg
            elif self.control.shoot_direction==self.control.direction_DOWN:
                posx=self.rect[0]+pos[down_x]
                posy=self.rect[1]+pos[down_y]

                sprite_img=downImg
            elif self.control.shoot_direction==self.control.direction_LEFT:
                posx=self.rect[0]+pos[left_x]
                posy=self.rect[1]+pos[left_y]

                sprite_img=leftImg
            elif self.control.shoot_direction==self.control.direction_RIGHT:
                posx=self.rect[0]+pos[right_x]
                posy=self.rect[1]+pos[right_y]
                sprite_img=rightImg

            shootobj.load(sprite_img, width, height, rows, cols,posx=posx,posy=posy)
            self.shootobj.append(obj:shootobj,shoot_direction:self.control.shoot_direction)          
            print(SHOOT!!!!!!!!!!...)
            #print(self.shootobj.rect)
        print(len(self.shootobj))
        #加组后一定要刷新,不然会读不到 surface.image 也就是没有在页面之上
        self.group.update(ticks)
        self.group.draw(self.screen)

        #数组中已经创建的继续移动
        for s in self.shootobj:
            if s[obj]!=None:
                SPEEDX=self.control.shoot_SPEEDX
                SPEEDY=self.control.shoot_SPEEDY
                if s[shoot_direction]==self.control.direction_UP:
                    print(direction_UP...)
                    SPEEDX=0
                    SPEEDY=-SPEEDY
                elif s[shoot_direction]==self.control.direction_DOWN:
                    print(direction_DOWN...)
                    SPEEDX=0
                    SPEEDY=+SPEEDY
                elif s[shoot_direction]==self.control.direction_LEFT:
                    print(direction_LEFT...)
                    SPEEDX=-SPEEDX
                    SPEEDY=0
                elif s[shoot_direction]==self.control.direction_RIGHT:
                    print(direction_RIGHT...)
                    SPEEDX=SPEEDX
                    SPEEDY=0
                s[obj].rect=s[obj].rect[0]+SPEEDX,s[obj].rect[1]+SPEEDY,s[obj].frame_width,s[obj].frame_height

                if self.collisions!=[]:
                    for index, v in enumerate(self.collisions):
                        if s[obj]!=None:
                            if pygame.sprite.collide_mask(s[obj],v):
                                s[obj].rect=-1000,-1000,s[obj].frame_width,s[obj].frame_height
                                s[obj].kill()
                                self.collisions[index].rect=-1000,-1000,self.collisions[index].rect[2],self.collisions[index].rect[3]
                                self.collisions[index].kill()
                                #s[obj]=False
                                #s[obj]=None
                if self.enemy_list!=[]:
                    for index, v in enumerate(self.enemy_list):
                        if s[obj]!=None:
                            if pygame.sprite.collide_mask(s[obj],v):
                                s[obj].rect=-1000,-1000,s[obj].frame_width,s[obj].frame_height
                                s[obj].kill()
                                self.enemy_list[index].rect=-1000,-1000,self.enemy_list[index].rect[2],self.enemy_list[index].rect[3]
                                self.enemy_list[index].kill()

以上方法中参数 upImg,downImg,leftImg,rightImg 为上下左右子弹的图片,width,height,rows,cols,pos,ticks=None,enemy_list=[] 一次是宽高,行列和ticks 刷新帧,enemy_list 敌人。

因为之后还需要检测敌人碰撞后销毁。

在该函数中第一行打码是 self.enemy_list=enemy_list 表示设置当前的敌人列表。
接着 if self.control.isShoot==True: 这个判断表示是否按发射键(之后会在控制中进行讲解);
按下发射键后就开始创建发射对象,同样这个对象是ESprite 对象:

#创建发射物
shootobj=ESprite(self.screen)
self.group.add(shootobj)
posx,posy=0,0
sprite_img=

接下来判断此时朝向,(根据之后会有一个控制方法,检测朝向)不同朝向的方位创建不同朝向的子弹:

if self.control.shoot_direction==self.control.direction_UP:
    posx=self.rect[0]+pos[up_x]
    posy=self.rect[1]+pos[up_y]

    sprite_img=upImg
elif self.control.shoot_direction==self.control.direction_DOWN:
    posx=self.rect[0]+pos[down_x]
    posy=self.rect[1]+pos[down_y]

    sprite_img=downImg
elif self.control.shoot_direction==self.control.direction_LEFT:
    posx=self.rect[0]+pos[left_x]
    posy=self.rect[1]+pos[left_y]

    sprite_img=leftImg
elif self.control.shoot_direction==self.control.direction_RIGHT:
    posx=self.rect[0]+pos[right_x]
    posy=self.rect[1]+pos[right_y]
    sprite_img=rightImg

接着加载图片,并且记录方向,如果不记录方向,之后使用同一个方法时将会与自身按键操作重合,会意外的控制子弹前进路线:

shootobj.load(sprite_img, width, height, rows, cols,posx=posx,posy=posy)
self.shootobj.append(obj:shootobj,shoot_direction:self.control.shoot_direction)  

接下来开始检测碰撞,检测碰撞前需要在屏幕刷新当前的sprite,因为检测膨胀是需要判断该精灵是否在屏幕之上:

self.group.update(ticks)
self.group.draw(self.screen)

加下来创建一个循环:

#数组中已经创建的继续移动
for s in self.shootobj:
    if s[obj]!=None:

这个循环遍历子弹是否与某些物体发生碰撞,并且进行位置移动。首先编写位置移动的内容:

SPEEDX=self.control.shoot_SPEEDX
SPEEDY=self.control.shoot_SPEEDY
if s[shoot_direction]==self.control.direction_UP:
    print(direction_UP...)
    SPEEDX=0
    SPEEDY=-SPEEDY
elif s[shoot_direction]==self.control.direction_DOWN:
    print(direction_DOWN...)
    SPEEDX=0
    SPEEDY=+SPEEDY
elif s[shoot_direction]==self.control.direction_LEFT:
    print(direction_LEFT...)
    SPEEDX=-SPEEDX
    SPEEDY=0
elif s[shoot_direction]==self.control.direction_RIGHT:
    print(direction_RIGHT...)
    SPEEDX=SPEEDX
    SPEEDY=0
s[obj].rect=s[obj].rect[0]+SPEEDX,s[obj].rect[1]+SPEEDY,s[obj].frame_width,s[obj].frame_height

判断刚刚所记录的方向,给予不同位置移动的坐标。
接着判断是否发生碰撞:

if self.collisions!=[]:
  for index, v in enumerate(self.collisions):
      if s[obj]!=None:
          if pygame.sprite.collide_mask(s[obj],v):
              s[obj].rect=-1000,-1000,s[obj].frame_width,s[obj].frame_height
              s[obj].kill()
              self.collisions[index].rect=-1000,-1000,self.collisions[index].rect[2],self.collisions[index].rect[3]
              self.collisions[index].kill()

只要设置了 collisions 阻碍物,那么就遍历阻碍物是否与子弹发生碰撞,如果发生膨胀首先将该物体移动到屏幕之外调用 kill() 方法对其进行销毁。这样就实现了子弹设计到物体,物体和子弹都同时消失。

最后判断子弹和敌人是否发生碰撞,遍历敌人:

if self.enemy_list!=[]:
for index, v in enumerate(self.enemy_list):
    if s[obj]!=None:
        if pygame.sprite.collide_mask(s[obj],v):
            s[obj].rect=-1000,-1000,s[obj].frame_width,s[obj].frame_height
            s[obj].kill()
            self.enemy_list[index].rect=-1000,-1000,self.enemy_list[index].rect[2],self.enemy_list[index].rect[3]
            self.enemy_list[index].kill()

实现方法与墙体类似。
最后在遍历之外加一个设计关闭即可,因为按一次就发射一个子弹:

self.control.isShoot=False#创建完一个后又关闭

二、写个精灵控制类

首先创建一个精灵控制类和初始化方法:

#精灵控制类            
class SpriteController():
    def __init__(self,sprite): 
        self.sprite=sprite
        self.direction=None
        self.shoot_direction=1
        self.direction_UP=1
        self.direction_DOWN=2
        self.direction_LEFT=3
        self.direction_RIGHT=4
        self.shoot_SPEEDX=10
        self.shoot_SPEEDY=10
        self.isShoot=False

接着编写精灵控制响应的方法:

#移动控制
def moveControl(self,event):
    stepX=self.sprite.speedX
    stepY=self.sprite.speedY
    #if self.isShoot!=True:
    if event.key == pygame.K_RIGHT:
        stepY=0
        stepX=stepX
        self.sprite.cframe=3
        self.shoot_direction=4
    elif event.key == pygame.K_LEFT:
        stepX=-stepX
        stepY=0
        self.sprite.cframe=2
        self.shoot_direction=3
    elif event.key == pygame.K_UP:
        stepY=-stepY
        stepX=0
        self.sprite.cframe=0
        self.shoot_direction=1
    elif event.key == pygame.K_DOWN:
        stepY=stepY
        stepX=0
        self.sprite.cframe=1
        self.shoot_direction=2
    elif event.key == pygame.K_SPACE:
        self.isShoot=True 
        stepX,stepY=0,0 
    self.sprite.rect=self.sprite.rect[0]+stepX,self.sprite.rect[1]+stepY,self.sprite.frame_width,self.sprite.frame_height
    if self.sprite.collisions!=[]:
        for v in self.sprite.collisions:
            result = pygame.sprite.collide_mask(self.sprite,v)
            while result:
                #bug 墙壁反弹错位
                unstepX,unstepY=stepX,stepY
                if unstepX!=0:
                    if unstepX>0:
                        unstepX=+10
                    else:
                        unstepX=-10
                if unstepY!=0:
                    if unstepY>0:
                        unstepY=+10
                    else:
                        unstepY=-10

                result = pygame.sprite.collide_mask(self.sprite,v)
                #for vi in range(0,20):
                self.sprite.rect=self.sprite.rect[0]-stepX,self.sprite.rect[1]-stepY,self.sprite.frame_width,self.sprite.frame_height
                print ("Collision occurred")

moveControl 方法接收1个参数event,判断按下键的内容,根据不同按键响应不同的移动参数,其他内容都是基础内容,主要是对于阻碍物的阻碍效果:

if self.sprite.collisions!=[]:
   for v in self.sprite.collisions:
       result = pygame.sprite.collide_mask(self.sprite,v)
       while result:
           #bug 墙壁反弹错位
           unstepX,unstepY=stepX,stepY
           if unstepX!=0:
               if unstepX>0:
                   unstepX=+10
               else:
                   unstepX=-10
           if unstepY!=0:
               if unstepY>0:
                   unstepY=+10
               else:
                   unstepY=-10

           result = pygame.sprite.collide_mask(self.sprite,v)
           #for vi in range(0,20):
           self.sprite.rect=self.sprite.rect[0]-stepX,self.sprite.rect[1]-stepY,self.sprite.frame_width,self.sprite.frame_height
           print ("Collision occurred")

遍历后如果发生碰撞直接给予回退:

self.sprite.rect=self.sprite.rect[0]-stepX,self.sprite.rect[1]-stepY,self.sprite.frame_width,self.sprite.frame_height

三、敌人AI类

敌人类属于 ESprite 类,首先创建一个类及初始化方法:

#敌人
class Enemy(ESprite):
    def __init__(self, screen, group=None):
        super().__init__(screen, group=group)
        self.screen=screen
        self.group=group
        #self.AIControl=SpriteController()
        self.movieStep=0
        self.stepY,self.stepX=0,10

movieStep 为默认自动运行次数,self.stepY,self.stepX=0,10 为默认行走值。

接下来编写自动运行方法:

def autoMove(self,Size):
   self.group.update(ticks)
   self.group.draw(self.screen)
   if self.movieStep==0:
       random.seed(random.randint(0,100))
       d=random.randint(1,16)
       if d>0 and d<5:#上
           self.stepY=-10
           self.stepX=0
       elif d>4 and d<9:#下
           self.stepY=10
           self.stepX=0
       elif d>8 and d<13:#左
           self.stepX=-10
           self.stepY=0
       elif d>12 and d<17:#右
           self.stepX=10
           self.stepY=0
       self.movieStep=20
   if self.movieStep!=0:
       if (self.rect[0],self.rect[1])>(60,60) and (self.rect[0],self.rect[1]) < (Size[0]-60,Size[1]-60):
           self.rect=self.rect[0]+self.stepX,self.rect[1]+self.stepY,self.frame_width,self.frame_height
       self.movieStep-=1

   if self.collisions!=[]:
       for v in self.collisions:
           #self.group.update(ticks)
           #self.group.draw(self.screen)
           result = pygame.sprite.collide_mask(self,v)
           while result:
               #bug 墙壁反弹错位
               unstepX,unstepY=self.stepX,self.stepY
               if unstepX!=0:
                   if unstepX>0:
                       unstepX=+10
                   else:
                       unstepX=-10
               if unstepY!=0:
                   if unstepY>0:
                       unstepY=+10
                   else:
                       unstepY=-10

               result = pygame.sprite.collide_mask(self,v)
               #for vi in range(0,20):
               self.rect=self.rect[0]-self.stepX,self.rect[1]-self.stepY,self.frame_width,self.frame_height
               print ("Collision occurred")

以上代码中以下代码表示该AI随机上下左右:

if self.movieStep==0:
    random.seed(random.randint(0,100))
    d=random.randint(1,16)
    if d>0 and d<5:#上
        self.stepY=-10
        self.stepX=0
    elif d>4 and d<9:#下
        self.stepY=10
        self.stepX=0
    elif d>8 and d<13:#左
        self.stepX=-10
        self.stepY=0
    elif d>12 and d<17:#右
        self.stepX=10
        self.stepY=0
    self.movieStep=20
if self.movieStep!=0:
    if (self.rect[0],self.rect[1])>(60,60) and (self.rect[0],self.rect[1]) < (Size[0]-60,Size[1]-60):
        self.rect=self.rect[0]+self.stepX,self.rect[1]+self.stepY,self.frame_width,self.frame_height
    self.movieStep-=1

不同方向他有不同值对应,self.movieStep运行次数为0则重新置于20,若不等于0则自动运行,只需要设置对应的 rect 即可。

接着就是判断是否碰到障碍物,实现与控制方法一致:

if self.collisions!=[]:
   for v in self.collisions:
       #self.group.update(ticks)
       #self.group.draw(self.screen)
       result = pygame.sprite.collide_mask(self,v)
       while result:
           #bug 墙壁反弹错位
           unstepX,unstepY=self.stepX,self.stepY
           if unstepX!=0:
               if unstepX>0:
                   unstepX=+10
               else:
                   unstepX=-10
           if unstepY!=0:
               if unstepY>0:
                   unstepY=+10
               else:
                   unstepY=-10

           result = pygame.sprite.collide_mask(self,v)
           #for vi in range(0,20):
           self.rect=self.rect[0]-self.stepX,self.rect[1]-self.stepY,self.frame_width,self.frame_height
           print ("Collision occurred")

完整代码

import pygame,random
from pygame.locals import *

class MySprite(pygame.sprite.Sprite):
    def __init__(self,screen,group=None):#target是屏幕
        pygame.sprite.Sprite.__init__(self)
        #self.target_surface = target#精灵渲染目标为屏幕
        self.screen=screen
        self.image = None#初始化图片None
        self.main_image = None#主图片
        self.rect = None#需要画图的区域
        self.rframe = 0 #图片序列号 行
        self.cframe = 0 #图片序列号 列
        self.old_frame = -1#老图片序列号
        self.frame_width = 1#图片宽
        self.frame_height = 1#图片高
        self.cols = 1 #列
        self.rows = 1 #行
        self.last_time = 0 #上次更换时的总帧数,用于判断更换帧
        self.X=0
        self.Y=0
        self.speedX=1
        self.speedY=1
        self.control=SpriteController(self)#控制初始化
        self.group=group
        self.shootobj=[]
        self.upImg,self.downImg,self.leftImg,self.rightImg=None,None,None,None
        self.collisions=[]
        self.enemy_list=[]

    #添加不可触碰体
    def setCollision(self,collision):
        self.collisions.append(collision)
    #加载用
    def load(self, filename, width, height,rows,cols,posx=100,posy=100):
        self.main_image = pygame.image.load(filename).convert_alpha()#加载主图
        self.frame_width = width#宽高记录
        self.frame_height = height
        self.rect = [posx,posy,width,height]#绘制
        self.cols = cols
        self.rows = rows

    #每次图片动态更新绘制区域 动画播放
    def update(self, current_time, rate=60):
        #当前帧总数如果已经超过了最初的 last_time + 60,那么表示
        #已经超过了60帧,那么 frame 图片序列号+1,开始下一张图片

        if current_time > self.last_time + rate:
            self.rframe += 1 #图片序列号+1  
            if self.rframe>self.cols-1:#大于图片最大列就说明进行了一个循环,因为 self.frame 初始值是 0
                self.rframe=0
            self.last_time = current_time#每次更改图片时就记录更换后的帧

        if self.rframe != self.old_frame:#新老次序不一 表示更换frame
            #frame_x绘制矩形的位置x就等于图片数*每个宽度得到x坐标值
            frame_x=self.rframe * self.frame_width
            frame_y=self.cframe * self.frame_height
            #y值跟随上下左右按键切换图,图片规定同一行一个动作
            # 不同按键对应上下左右
            # 绘制的区域使用 frame_width frame_height 代替
            rect = ( frame_x, frame_y, self.frame_width, self.frame_height )
            self.image = self.main_image.subsurface(rect)#选择区域进行图片提取
            self.old_frame = self.rframe

    #设置上下左右发射对象
    def setShootSprite(self,up,down,left,right,width,height,rows,cols):
        self.upImg=up
        self.downImg=down
        self.leftImg=left
        self.rightImg=right
        self.swidth=width
        self.sheight=height
        self.srows=rows
        self.scols=cols

    #创建发射对象
    def shoot(self,upImg,downImg,leftImg,rightImg,width,height,rows,cols,pos,ticks=None,enemy_list=[]):
        self.enemy_list=enemy_list
        if self.control.isShoot==True:
            #创建发射物
            shootobj=MySprite(self.screen)
            self.group.add(shootobj)
            posx,posy=0,0
            sprite_img=
            if self.control.shoot_direction==self.control.direction_UP:
                posx=self.rect[0]+pos[up_x]
                posy=self.rect[1]+pos[up_y]

                sprite_img=upImg
            elif self.control.shoot_direction==self.control.direction_DOWN:
                posx=self.rect[0]+pos[down_x]
                posy=self.rect[1]+pos[down_y]

                sprite_img=downImg
            elif self.control.shoot_direction==self.control.direction_LEFT:
                posx=self.rect[0]+pos[left_x]
                posy=self.rect[1]+pos[left_y]

                sprite_img=leftImg
            elif self.control.shoot_direction==self.control.direction_RIGHT:
                posx=self.rect[0]+pos[right_x]
                posy=self.rect[1]+pos[right_y]
                sprite_img=rightImg

            shootobj.load(sprite_img, width, height, rows, cols,posx=posx,posy=posy)
            self.shootobj.append(obj:shootobj,shoot_direction:self.control.shoot_direction)          
            print(SHOOT!!!!!!!!!!...)
            #print(self.shootobj.rect)
        print(len(self.shootobj))
        #加组后一定要刷新,不然会读不到 surface.image 也就是没有在页面之上
        self.group.update(ticks)
        self.group.draw(self.screen)

        #数组中已经创建的继续移动
        for s in self.shootobj:
            if s[obj]!=None:
                SPEEDX=self.control.shoot_SPEEDX
                SPEEDY=self.control.shoot_SPEEDY
                if s[shoot_direction]==self.control.direction_UP:
                    print(direction_UP...)
                    SPEEDX=0
                    SPEEDY=-SPEEDY
                elif s[shoot_direction]==self.control.direction_DOWN:
                    print(direction_DOWN...)
                    SPEEDX=0
                    SPEEDY=+SPEEDY
                elif s[shoot_direction]==self.control.direction_LEFT:
                    print(direction_LEFT...)
                    SPEEDX=-SPEEDX
                    SPEEDY=0
                elif s[shoot_direction]==self.control.direction_RIGHT:
                    print(direction_RIGHT...)
                    SPEEDX=SPEEDX
                    SPEEDY=0
                s[obj].rect=s[obj].rect[0]+SPEEDX,s[obj].rect[1]+SPEEDY,s[obj].frame_width,s[obj].frame_height

                if self.collisions!=[]:
                    for index, v in enumerate(self.collisions):
                        if s[obj]!=None:
                            if pygame.sprite.collide_mask(s[obj],v):
                                s[obj].rect=-1000,-1000,s[obj].frame_width,s[obj].frame_height
                                s[obj].kill()
                                self.collisions[index].rect=-1000,-1000,self.collisions[index].rect[2],self.collisions[index].rect[3]
                                self.collisions[index].kill()
                                #s[obj]=False
                                #s[obj]=None
                if self.enemy_list!=[]:
                    for index, v in enumerate(self.enemy_list):
                        if s[obj]!=None:
                            if pygame.sprite.collide_mask(s[obj],v):
                                s[obj].rect=-1000,-1000,s[obj].frame_width,s[obj].frame_height
                                s[obj].kill()
                                self.enemy_list[index].rect=-1000,-1000,self.enemy_list[index].rect[2],self.enemy_list[index].rect[3]
                                self.enemy_list[index].kill()

        self.control.isShoot=False#创建完一个后又关闭

#精灵控制类            
class SpriteController():
    def __init__(self,sprite): 
        self.sprite=sprite
        self.direction=None
        self.shoot_direction=1
        self.direction_UP=1
        self.direction_DOWN=2
        self.direction_LEFT=3
        self.direction_RIGHT=4
        self.shoot_SPEEDX=10
        self.shoot_SPEEDY=10
        self.isShoot=False

    #移动控制
    def moveControl(self,event):
        stepX=self.sprite.speedX
        stepY=self.sprite.speedY
        #if self.isShoot!=True:
        if event.key == pygame.K_RIGHT:
            stepY=0
            stepX=stepX
            self.sprite.cframe=3
            self.shoot_direction=4
        elif event.key == pygame.K_LEFT:
            stepX=-stepX
            stepY=0
            self.sprite.cframe=2
            self.shoot_direction=3
        elif event.key == pygame.K_UP:
            stepY=-stepY
            stepX=0
            self.sprite.cframe=0
            self.shoot_direction=1
        elif event.key == pygame.K_DOWN:
            stepY=stepY
            stepX=0
            self.sprite.cframe=1
            self.shoot_direction=2
        elif event.key == pygame.K_SPACE:
            self.isShoot=True 
            stepX,stepY=0,0 
        self.sprite.rect=self.sprite.rect[0]+stepX,self.sprite.rect[1]+stepY,self.sprite.frame_width,self.sprite.frame_height
        if self.sprite.collisions!=[]:
            for v in self.sprite.collisions:
                result = pygame.sprite.collide_mask(self.sprite,v)
                while result:
                    #bug 墙壁反弹错位
                    unstepX,unstepY=stepX,stepY
                    if unstepX!=0:
                        if unstepX>0:
                            unstepX=+10
                        else:
                            unstepX=-10
                    if unstepY!=0:
                        if unstepY>0:
                            unstepY=+10
                        else:
                            unstepY=-10

                    result = pygame.sprite.collide_mask(self.sprite,v)
                    #for vi in range(0,20):
                    self.sprite.rect=self.sprite.rect[0]-stepX,self.sprite.rect[1]-stepY,self.sprite.frame_width,self.sprite.frame_height
                    print ("Collision occurred")

#敌人
class Enemy(MySprite):
    def __init__(self, screen, group=None):
        super().__init__(screen, group=group)
        self.screen=screen
        self.group=group
        #self.AIControl=SpriteController()
        self.movieStep=0
        self.stepY,self.stepX=0,10
    def autoMove(self,Size):
        self.group.update(ticks)
        self.group.draw(self.screen)
        if self.movieStep==0:
            random.seed(random.randint(0,100))
            d=random.randint(1,16)
            if d>0 and d<5:#上
                self.stepY=-10
                self.stepX=0
            elif d>4 and d<9:#下
                self.stepY=10
                self.stepX=0
            elif d>8 and d<13:#左
                self.stepX=-10
                self.stepY=0
            elif d>12 and d<17:#右
                self.stepX=10
                self.stepY=0
            self.movieStep=20
        if self.movieStep!=0:
            if (self.rect[0],self.rect[1])>(60,60) and (self.rect[0],self.rect[1]) < (Size[0]-60,Size[1]-60):
                self.rect=self.rect[0]+self.stepX,self.rect[1]+self.stepY,self.frame_width,self.frame_height
            self.movieStep-=1

        if self.collisions!=[]:
            for v in self.collisions:
                #self.group.update(ticks)
                #self.group.draw(self.screen)
                result = pygame.sprite.collide_mask(self,v)
                while result:
                    #bug 墙壁反弹错位
                    unstepX,unstepY=self.stepX,self.stepY
                    if unstepX!=0:
                        if unstepX>0:
                            unstepX=+10
                        else:
                            unstepX=-10
                    if unstepY!=0:
                        if unstepY>0:
                            unstepY=+10
                        else:
                            unstepY=-10

                    result = pygame.sprite.collide_mask(self,v)
                    #for vi in range(0,20):
                    self.rect=self.rect[0]-self.stepX,self.rect[1]-self.stepY,self.frame_width,self.frame_height
                    print ("Collision occurred")

map_srpirte=[
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,w,w,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,w,w,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,w,w,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],
    [-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-],

]
mapConf=
    space:-,
    w:"./tank/scene/brick.png",
    height:12,
    weight:20

#map_srpirteConf=[w,24,24,1,1]
screenW=mapConf[weight]*34
screenH=mapConf[height]*48

pygame.init()
screen = pygame.display.set_mode((screenW,screenH))
group_wallt = pygame.sprite.Group()
pygame.key.set_repeat(10)
framerate = pygame.time.Clock()

group_hero = pygame.sprite.Group()
enemy_hero = pygame.sprite.Group()
sprite_hero = MySprite(screen,group_hero)

sprite_hero.load("./tank/playerTank/tank_T1_2.png",48, 48, 4, 2)
up="./tank/bullet/bullet_up.png"
down="./tank/bullet/bullet_down.png"
left="./tank/bullet/bullet_left.png"
right="./tank/bullet/bullet_right.png"
#sprite_hero.setShootSprite()
group_hero.add(sprite_hero)

enemy_list=[]
for v in range(0,20):
    enemy = Enemy(screen,enemy_hero)
    enemy.load("./tank/enemyTank/enemy_1_0.png",48, 48, 4, 2)
    enemy_hero.add(enemy)
    enemy_list.append(enemy)

posx,posy=0,0
wallet=[]
for rows in map_srpirte:
    for v in rows:
        posx+=24
        if v!=mapConf[space]:
            sprite_wallt = MySprite(screen,group_wallt)
            sprite_wallt.load(mapConf[w],24, 24, 1, 1,posx,posy)
            group_wallt.add(sprite_wallt)
            sprite_hero.setCollision(sprite_wallt)
            enemy.setCollision(sprite_wallt)
            print(str(posx)+,+str(posy))
    posy+=24
    posx=0

#sprite_wallt = MySprite(screen,group_wallt)
#sprite_wallt.load(mapConf[w],24, 24, 1, 1,posx,posy)
#group_wallt.add(sprite_wallt)
#主循环
while True:
    print((screenW,screenH))
    framerate.tick(30)
    ticks = pygame.time.get_ticks()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
        elif event.type==pygame.KEYDOWN:
            print(key down ......)
            sprite_hero.control.moveControl(event)
    pos=
        up_x:18,up_y:-10,down_x:18,down_y:50,
        left_x:-6,left_y:16,right_x:50,right_y:18,
    
    sprite_hero.shoot(up,down,left,right,12,12,1,1,pos,ticks,enemy_list)

    screen.fill((0,0,100))

    group_hero.update(ticks)
    group_hero.draw(screen)
    enemy_hero.update(ticks)
    enemy_hero.draw(screen)

    group_wallt.update(ticks)
    group_wallt.draw(screen)
    for v in enemy_list:
        v.autoMove((screenW,screenH))
    pygame.display.update()

以上是关于Python实现坦克大战(附源码)的主要内容,如果未能解决你的问题,请参考以下文章

Pygame小游戏神还原欢乐无穷的双人坦克大战小程序游戏,上手开玩~(附完整源码)

python 全解坦克大战 辅助类 附完整代码雏形

python 全解坦克大战 辅助类 附完整代码雏形

学习 Python 之 Pygame 开发坦克大战

学习 Python 之 Pygame 开发坦克大战

Python游戏开发,pygame模块,Python实现经典90坦克大战游戏