PYTHON+QT5用标签类搭建游戏开发框架

最近在学习使用PYTHONG和QT5的过程中,突发想法,就是各种游戏上不也就是各种图片进行拼图形成各种动作和特效,如果不用pygame等库,直接用QT5的QLabel控件应也能实现基本相同的效果,于是就编写了这个完全由QLabel标签控件搭建的游戏开发框架,框架可以定义多种队伍角色,可以实现鼠标左键对角色的指挥移动(动画方式),集结,鼠标中键实现射击(动画,到达位置也有指定效果),鼠标右键可以查看选定角色的当前属性界面等基本框架功能,没有编写具体的角色各种生命、攻击力、被武器击中失血等逻辑代码,感兴趣的朋友可以继续深化编制对应的逻辑代码即可。

本代码共三个模块文件:

1、MainWindow.py  主窗口模块,处理地图,角色创建等基本功能

2、label_Obj.py   角色类及武器类模块,基于继承QT5的QLabel,实现各种角色对象及武器对象的图片加载,移动,动画形成等功能

3、ImgObjData.py 为角色类及武器类提供所有的图片,角色属性值等资源及数据集合

本代码包中涉及到了一些我自已测试用的角色及武器图片序列,选的图片不太好,随意选的植物大战僵尸素材包中的一些图片,有兴趣的朋友可在将图片全部重新自定义一套即形成不同的地图,角色、武器完全不同的界面。

本博客只贴了三个模块的全部代码,如需对应的图片等资源包,需要在作者上传的代码资源中下载全套源码及资源,下载地址:PYQT5标签图片类游戏开发基础框架资源-CSDN文库

代码运行界面如下:

下面贴出三个模块文件的全部代码:

1、模块:MainWindow.py

#模块名:MainWindow.py
#包含类名: AppMainFrm:程序主窗口类
#          label_Bk:加载背景地图到主窗体的一标签类控件
#本示例代码实现了以下功能:
#1、没有使用任何如pygame等游戏库支持,完全采用QT5的标签控件,通过标签控件中的计时器来控制对应角色的图片列表的逐个加载形成动画等效果
#2、可以通过批量选择角色,对选择的角色进行拖动移位、左键点击集合,中键点击使用各自武器攻击,右键显示角色属性的基本功能
#3、主框架应已基本搭好,没有设计有攻击对方后生命值减少等逻辑代码,没有作角色随机创建的位置,示例全部创建在一个位置,最大可同时创建的角色数量可以通过修改MAX_OBJNUM来看效果
#4、本代码可以作为对战类小游戏的基本框架,也可作为益智类游戏的基本框架,核心就是标签加载不同的图片来实现移动等
#5、因代码中使用了一些示例图片,本博客只能提供代码,参考者可以按ImgObjData.py中的文件名自行组织一些渐变的图片对应即可。也可在作者的资源下载中下载完整的代码包
import sys
import PyQt5
from PyQt5 import *
from PyQt5 import QtCore   #不加此行要报错,上行不是全导入了嘛,没懂
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

import threading
import time
from timeit import default_timer as timer
import math
import copy 
import random 

######################################################
from label_Obj import  *  #标签小图形对象的类模块文件
from ImgObjData import *              #图形文件数据模块
DEBUG_OPENTRD=True
MAX_OBJNUM=32  #初始化时最大可创建的角色对象数量,数量不限,如调成1024,有点卡了
THREAD_NUM=1  #同时创建的多线程数量(用多线程处理武器子弹对象)
Lock = threading.Lock()   #多线程锁
dic_AObj={}    #建立obj小标签对象的字典(A队),此变量主窗体和对象类实例化共用,非图形标标签本身
dic_BObj={}    #建立obj小标签对象的字典(B队) ,此变量主窗体和对象类实例化共用
dic_ZObj={}    #建立obj小标签对象的字典(武器) ,此变量主窗体和对象类实例化共用
 #定义一个对象的列表,增加对象时,通过深COPY到对象字典中
            #0  1     2    3      4       5            6       7   8  9 10 11 12 13  14     15         16    17 
            #ID,别名,分组,类型,当前等级,当前显示状态,当前运动状态,x0,y0,w0,h0,x1,y1,w1,h1,选中边框颜色,透明度,对象是否支持拖动,扩展,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,  
lst_oneObj=[0, 'A队0', 0, 0,    0,           0,         0,       0, 0,50,50, 0, 0,50,50,QColor(0,0,255), 0.5,True,           '扩展...'] #单个对象

#主窗体类
class AppMainFrm(QMainWindow):
    global dic_AObj #建立obj小标签对象的字典保存属性值(A队),非图形标标签本身
    global dic_BObj
    global dic_ZObj
    global MAX_OBJNUM
    signal_clicked = QtCore.pyqtSignal(str)           #自定信号,即主窗体被单击时发出(传回参数为xxx)
    
    def __init__(self):
        super().__init__()
        self.setWindowIcon(QIcon("./res/main.png"))
        self.resize(1366, 768)
        self.setMaximumSize(1366,768)
        self.setWindowTitle('PYQT5标签图片类游戏框架')
        #self.setWindowOpacity(0.5)   # 设置整个窗口透明度
        self.initUi()

    #初始化主窗体上的控件
    def initUi(self):
        # 创建状态栏
        self.statusbar = self.statusBar()
        self.statusbar.showMessage('准备')
        #定义要创建的对象计数
        self.A_objcount=int(MAX_OBJNUM/2) #A组对象创建数量数,此数=dic_ownObj={}中对象的ID+1,即对象索引从0起
        self.B_objcount=int(MAX_OBJNUM/2)  #B组对象创建数量数量
        self.Z_objcount=self.A_objcount+self.B_objcount                  #Z组对象分创建数量(武器)
        self.curGroup='A'                  #当前本窗体可控制的主队为A队0序号角色
        
        self.lab_Bk =  label_Bk(self)   #定义背景用标签对象
        # 设置底图label的尺寸
        self.lab_Bk.setMaximumSize(1356,740)
        self.lab_Bk.setGeometry(5, 5, 1356,740)
        self.lab_Bk.setScaledContents(True)
        self.lab_Bk.lower()    #控件在最下层
        self.BkClickNum=0         #背景标签控件被单击的次数
        self.Lx0=0   #鼠标左键按下时坐标:#鼠标在底图上按下拖动一矩形框,以成批选择底图上的角色时:从上向下拖,同时包含到角色左上角和右下角坐标时视为选择,从下往上拖,只要包含角色右下角坐即认为选定
        self.Ly0=0
        self.Lx1=0    #鼠标左键释放时坐标
        self.Ly1=0

        self.id=0
        variety=0   #要创建的对象类型,0=角色0  1=另一种不同的角色.....
        level=0     #要创建的对象初始等级,如是复活,此数不为0
        self.Aobjs=[]  #定义创建的A组对象ID号的列表,列表成员为类对象
        self.Bobjs=[]  #定义创建的B组对象ID号的列表,列表成员为类对象:未细化。。。。。
        self.Zobjs=[]  #定义创建的B组对象ID号的列表,列表成员为类对象:未细化。。。。。
        self.bBulletEnd=True  #多线程是否已将本批次武器攻击数据全部处理完毕,处理完前,不允许清空lst_Zobjs中的数据
        
        self.lab_Bk.signal_LeftButtonclicked.connect(self.labBkLeftClick)      #在主窗口背景标签控件上左键单击时的槽函数(信号signal_LeftButtonclicked由背景标签类发出)
        self.lab_Bk.signal_MidButtonclicked.connect(self.labBkMidClick)        #在主窗口背景标签控件上中键单击时的槽函数(信号signal_MidButtonclicked由背景标签类发出)
        self.signal_clicked.connect(self.windowClick)                          #主窗口上单击时的槽函数(信号signal_clicked本类发出) 

        #创建A队各类型角色
        n=0
        #创建A队0组角色
        for k in range(0,int(self.A_objcount/2)):
                lst_oneObj[2]=0   #本轮建立的为A队0类型角色
                lst_oneObj[0]=self.id
                lst_oneObj[1]='A队0类№'+str(k)
                dic_AObj[self.id]=copy.deepcopy(lst_oneObj) #用copy的深度COPY确保字典的每个成员都是独立的
                variety=0
                #                 id,name,variety,level,health,sport,x0,y0,obj,parent=None
                x,y,w,h=self.getCreateObjPos(QRect(600,20,600,600))  #得到要创建对象的位置,仅为示例指定了一位置,实际根据情况来计算出位置(w,h参数没有用,在角色类中定义)
                Aobj = A_Obj(self.id,lst_oneObj[1],variety,0,0,0,x,y,dic_AObj,self)   #参数传入只传递要建对象的id号,别名,类形,等级,全局字典数组,将创建A组0号对象,主窗口只负责两个参数ID和name,对象类负责完成字典中的其他值        
                Aobj.setToolTip(lst_oneObj[1])
                self.Aobjs.append(Aobj)
                self.Aobjs[n].setScaledContents(True)                       #在对象中已有此代码让图片拉伸填满控件,但无效果,此处再调用一次
                self.Aobjs[n].signal_clicked.connect(self.labObjClick)      #将自定义的对象标签类信号绑定主窗体槽函数
                n+=1
                self.id+=1
        #创建A队1组角色
        for i in range(0,self.A_objcount-int(self.A_objcount/2)):
                lst_oneObj[2]=1   #本轮建立的为A队1类型角色
                lst_oneObj[0]=self.id
                lst_oneObj[1]='A队1类№'+str(i)
                dic_AObj[self.id]=copy.deepcopy(lst_oneObj) #用copy的深度COPY确保字典的每个成员都是独立的
                variety=1
                x,y,w,h=self.getCreateObjPos(QRect(600,20,600,600))  #得到要创建对象的位置,仅为示例指定了一位置,实际根据情况来计算出位置(w,h参数没有用,在角色类中定义)
                Aobj = A_Obj(self.id,lst_oneObj[1],variety,0,0,0,x,y,dic_AObj,self)   #参数传入只传递要建对象的id号,别名,类形,等级,全局字典数组,将创建A组0号对象,主窗口只负责两个参数ID和name,对象类负责完成字典中的其他值
                Aobj.setToolTip(lst_oneObj[1])
                self.Aobjs.append(Aobj)
                self.Aobjs[n].setScaledContents(True)                       #在对象中已有此代码让图片拉伸填满控件,但无效果,此处再调用一次
                self.Aobjs[n].signal_clicked.connect(self.labObjClick)      #将自定义的对象标签类信号绑定主窗体槽函数
                n+=1
                self.id+=1
        #创建B队0组(没创建,只作预留.....)
        m=0
        for m in range(0,self.B_objcount):
            lst_oneObj[0]=m
            lst_oneObj[1]='A队1类№'+str(m)
            dic_BObj[m]=copy.deepcopy(lst_oneObj)
            m+=1
            self.id+=1
            #........
            
        self.button1 = QPushButton(self)
        self.button1.setGeometry(1200, 10, 80,50)
        self.button1.setText('测试代码')
        self.button1.setToolTip('供编写代码时,因很多代码在计时器中不好调试,用此按纽随时测试一此中间结果,不用时删除')
        self.button1.clicked.connect(self.test)

   
    #重载绘图函数:
    #def paintEvent(self, event):
        #为防止背景图控件不是最底层窗体,这里里再设置下堆叠顺序
    #    self.lab_Bk.lower()

    #槽:主窗口被单击时(暂没用到,需要有鼠标下压事件等位置发出信号)
    def windowClick(self):
        print('主窗口槽函数:窗口被单击(主窗口发出信号,此处接收响应)')

    #槽:背景图标签被单击非选择时,将选中的对象派往单击的地点   
    def labBkLeftClick(self,Pointxy):
        if self.curGroup=='A':   #对A类队伍的全部角色对象进行处理
            #背景图标签被单击(背景标签类label_Bk发出鼠标被按下的信号(用信号槽方式,中键按下用的另一种直接调用方式),此处接收响应:如果有选中的角色标签对象,将设置这些对象前往的目的地,同时取消这些选取对象被绘制的外边框
            print('主窗体接到背景标签发送来的左键单击信号')
            for Aobj in self.Aobjs:  #遍历A类对象,对被选择的对象发送命令到指定的位置集合,
                if(Aobj.bObjSeled):
                    print(f'对象:{Aobj.objname}被选中将被派往{self.lab_Bk.lastPoint}')
                    Aobj.bObjSeled=False
                    Aobj.update()  #重画角色对象
                    Aobj.bGo=True
                    Aobj.startGo(self.lab_Bk.lastPoint)
                    
        elif self.curGroup=='B':  #示例不作处理B队,如B队为电脑方或敌对方角色
            pass

    #槽:背景图标签被中键单击时,将选中的对象向单击点发射武器   
    def labBkMidClick(self,Pointxy):
        print('主窗体接到背景标签发送来的中键单击信号')
        if self.curGroup=='A':  #对A类角色如认为是人工控制一方,单击中键后,向指定位置发送武器,B组等是否会响应自行编制逻辑代码
           i=0
           for aObj in self.Aobjs:
               if(aObj.bObjSeled):  #示例只对当前被选择的对象可以向指定点发射武器
                    x,y,w,h=aObj.getObjRect()
                    lst_oneObj[2]=1   #本轮建立的为A队1类型角色
                    lst_oneObj[0]=self.id
                    lst_oneObj[1]='Z武器0类№'+str(i)
                    dic_ZObj[self.id]=copy.deepcopy(lst_oneObj) #用copy的深度COPY确保字典的每个成员都是独立的
                    Zobj = Z_Obj(self.id,lst_oneObj[1],aObj.bullet,0,0,0,x,y,dic_ZObj,self)   #武器类对象创建后向指定位置攻击完成后,在类中自行销毁,主窗口中不对其作处理
                    Zobj.bGo=True
                    #计算子弹图片的旋转角度
                    if(Zobj.brot):
                        pass    #在Allobj类看和计时器中实时计算此旋转角度
                    Zobj.startGo(Pointxy)
                    i+=1
                    self.id+=1

      #槽:透明小标签(角色对象,武器对象等)被单击时     
    def labObjClick(self):
        print('主窗口中的槽响应函数labObjClick:透明小标签被单击(小标签类laber_Obj发出信号,此处接收响应)')
        
    
    #鼠标按下事件重载   
    def mousePressEvent(self, event):   #如在其子类又重载了此事件,子类中要加上super(A_Obj, self).mousePressEvent(event) #调用其父类的同样事件                               
        print(f'主窗体鼠标压下:当前坐标:x={event.pos().x()},y={event.pos().y()}')
        if (event.button() == Qt.LeftButton):    #and self.top.underMouse():
            self.Lx0 = event.pos().x()
            self.Ly0 = event.pos().y()
    # 鼠标左键释放事件重载       
    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.Lx1 = event.pos().x()
            self.Ly1 = event.pos().y()
            for aobj in self.Aobjs:  #遍历A类对象
                size = aobj.geometry()
                objx0 = size.x()
                objy0 = size.y()  
                objx1 = size.x()+size.width()
                objy1 = size.y()+size.height()
                 #开始判断所拖画的矩形区域有多少对象可以被选择
                if(aobj.bObjSeled):  #如对象当前是被选择状态,仍为被选择状态
                        continue
                if(self.Lx1>self.Lx0 and self.Ly1>self.Ly0):  #从上向下拖
                    print('从上向下拖')
                    if(objx0>self.Lx0 and objy0>self.Ly0 and objx1<self.Lx1 and objy1<self.Ly1): #所拖动的矩形区必须将角色所在矩形四个角点全部包括才视为被选择,类似于CAD
                        aobj.bObjSeled=True
                elif(self.Lx1<self.Lx0 and self.Ly1<self.Ly0):  #从下向上拖
                    print('从下向上拖')
                    if(objx1>self.Lx1 and objy1>self.Ly1 and objx1<self.Lx0 and objy1<self.Ly0):   #鼠标从下向上拖:只要挂到对象右下角即视为被选定
                        aobj.bObjSeled=True
            #aobj.update()  #重画对象

    #取消对所有对象的选择            
    def cancelAllObjSel(self):
        for obj in self.Aobjs:
            obj.bObjSeled=False
            obj.update()
    
    #设置要创建obj标签图像的位置和尺寸,仅为示例,随机在指定的区域生成
    def getCreateObjPos(self,makerect): 
        w=150
        h=100       
        mx0=makerect.x()
        my0=makerect.y()
        mx1=mx0+makerect.width()-w/2
        my1=my0+makerect.height()-h/2
        x=random.randrange(mx0,mx1)
        y=random.randrange(my0,my1)
        
        return x,y,w,h
    #$$$$$$$$$$$$$$$$$$$$$$测试代码¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥
    def test(self):
        pass

#######################################################################################################################      
#重载标签类,标签为窗体的背景,随主窗体同时缩放
class label_Bk(QLabel):
    signal_LeftButtonclicked = QtCore.pyqtSignal(object)    #自定标签类的信号,即标签被左键鼠标单击时发出(传回参数为点击的点)
    signal_MidButtonclicked = QtCore.pyqtSignal(object)    #自定标签类的信号,即标签被中键鼠标单击时发出(传回参数为点击的点)
    sig_ToMian = QtCore.pyqtSignal(str)                   #窗体间数据通讯用自定义信号

    #初始化
    def __init__(self, text=''):
        super(label_Bk, self).__init__(text)        
        cursor = QCursor(Qt.CrossCursor)  #光标类型    
        self.setFont(QFont('宋体', 16))  # 设置字体和大小
        self.bLeftMouseKey=False   #定义只鼠标左键才能进行绘画
        self.setFrameShape(QtWidgets.QFrame.Box)
        self.importImg("./res/底图.png")
        #self.signal_clicked.connect(self.on_clicked)
        #鼠标起点,鼠标终点
        self.lastPoint=QPoint()
        self.endPoint =QPoint()
        self.point0= QPoint()  #因lastPoint和endPoint用于处理拖动对象,不适合作单击的判断,增加此变更只用于判断是单击还是拖动
        self.point1= QPoint()  
        bDrawOK=True           #处理因重载绘图事件过快,可能会多画一此不可预料的杂图,初始化应True,让画布画白底一次
    #加载图片
    def importImg(self,imgfile):
        pixmap = QPixmap(imgfile)
        self.setPixmap(pixmap)
    #底图标签控件被单击时的槽函数
    def on_clicked(self):
        pass
        
    #鼠标按下事件重载   
    def mousePressEvent(self, event):
        super().mousePressEvent(event) #调用其父窗体窗体按下事件
        Mainfrm = self.parent()      #得到主窗口:仅示例,可以替代信号槽方式,直接用主窗口对象来调用主窗口的成员函数
        bDrawOK=True
        self.point0=event.pos()
        print(f'背景标签控件laber_Bk:的鼠标被按下,发出信号signal_LeftButtonclicked或signal_MidButtonclicked,鼠标压下坐标:x={event.pos().x()},y={event.pos().y()}')
         # 鼠标左键按下时
        if event.button() == Qt.LeftButton:
            self.lastPoint = event.pos()
            self.endPoint = self.lastPoint
            self.Rpoint = event.pos()
            #self.signal_LeftButtonclicked.emit(event.pos())   #移入鼠标释放事件,判断是单击还是拖动选择,是单位才向发送个号以移动主窗口上的被选择中角色图片
            print(f'底图标签鼠标按下位置,位置={event.pos()}')
        elif (event.button() == Qt.MidButton):    
            self.shot_Flag = True
            self.ShootPoint = event.pos()
            print(f'底图标签中键按下位置:{self.ShootPoint}')
            self.signal_MidButtonclicked.emit(self.ShootPoint)   #发送信号给主窗体,开始射击
        else:  
            Mainfrm.cancelAllObjSel() #取消主窗口中选择的所用标签角色控件 
    # 鼠标左键释放        
    def mouseReleaseEvent(self, event):
        self.point1=event.pos()
        if event.button() == Qt.LeftButton:
            if(abs(self.point1.x()-self.point0.x())<2 and abs(self.point1.y()-self.point1.y())<2):  #是单击不是拖动
                #print('是单击,不是拖放')
                self.signal_LeftButtonclicked.emit(event.pos())   #移入鼠标释放事件,判断是单击还是拖动选择,是单位才向发送个号以移动主窗口上的被选择中角色图片 
            else:
                super().mousePressEvent(event) #调用其父窗体窗体释放事件
                #print('是拖放,不是单击')
        Mainfrm = self.parent()      #得到主窗口
        super().mouseReleaseEvent(event)

#############################################################################################        
if __name__=="__main__":  
    app = QApplication(sys.argv)  
    myWin = AppMainFrm()  
    myWin.show()  
    sys.exit(app.exec())  

2、模块label_Obj.py

#模块名:label_Obj.py
#包含类名: AllObj:所有在窗体上的角色,武器图片的标签控件基类
#          A_Obj:继承自AllObj的所有A类型角色对象(如玩家中的自已)
#          B_Obj:继承自AllObj的所有B类型角色对象(如电脑控制的角色或敌方角色)
#          C_Obj:..........可定义任意的其他类别角色
#          Z_Obj:上述角色使用的武器对象(也是继承自AllObj)
#          transSubWindow:用于显示角色当前的属性值的一透明窗体类示例
import sys
import PyQt5
from PyQt5 import *
from PyQt5 import QtCore   
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

import time
import math
import copy 
import random
from ImgObjData import ImgObjData,A_ImgObjData,B_ImgObjData,Z_ImgObjData

MOVE_STEP=20.0 #对象每计时器中移动的像素位置
#########################################################################################################################
#重载标签类,标签可透明显示图像,用于在窗体上加载小分部图像
class AllObj(QLabel):  
    objcount=0   #定义在INIT之外的变量为类全部实例化成员共用,不同于INIT中的变量self.每个实例化对象各自拥有一套变量
    signal_clicked = QtCore.pyqtSignal(str)    #自定标签类的信号,即标签被单击时发出(传回参数为标签文本)

    #定义对象时,传递要建对象的id号,别名,类形,等级,健康度,是否运动,初始位置,父窗体等变量
    def __init__(self,id,name,variety,level,health,sport,x0,y0,obj,parent):  
        
        super(QLabel, self).__init__(parent)
        self.path='./res/'   #全部图像资源所在目录为PY程序文件下的res子目录中  
        self.setScaledContents(True)   #图片要适应标签的尺寸
        self.A_imgdata=A_ImgObjData()  #定义一个A类角色对象数据类,存储角色的全部属性:放在父类的原因,是初始化对象时要用到数据类的全局变量,如没有创建一个实体对象,将无法使用这些全局变量
        self.B_imgdata=B_ImgObjData()  #定义一个B类角色对象数据类,存储角色的全部属性
        self.Z_imgdata=Z_ImgObjData()  #定义一个武器类数据,存储武器的全部属性,本行只是实例化一个对象,使数据的全局变量可用
        #扩展其他类型角色数据.........  
        self.lst_Obj=[]             #本类实例化的对象属性不需要深度COPY,要父类对象的属性同全局字典变量中的一列表变量共用,对类对象的操作就是地全局字典变量的操作  
        self.Objid=id          #放在INIT外的变量,无论类实例化多少个,此变量类共同拥有,不同于INIT内的self,每个实例各拥有一套变量
        self.objname=name
        self.variety=variety        #本角色使用的角色类型:0,1,2,3....,如lstA_Imgs=[lstA0_Imgs,lstA1_Imgs],则对A角色共加入了两组图片,0对应lstA0_Imgs, 依次类推
        self.bullet=0               #本解色使用的武器类型
        self.imgIndextype=0         #对应的图片列表类型(一个角色在ImgObjData中的图片行数) ,如lstA0_Imgs有四组图片,则0=第一行的5个图片   
        self.orgLevel=level
        self.orgHealth=health
        self.sporting=sport
        self.objgroup='A等,在对子类对象中再重新赋值'      #当前对象组名:重载时重新赋值

        #父类中定义的以下参数不必在子类中再定义,可以直接使用
        self.obj_Type=['普通不透明','无框透明不可拖动','无框透明可拖动','小对象']
        #self.signal_clicked = QtCore.pyqtSignal(str)   #定义在此处要报错         
        self.sig_ToMian = QtCore.pyqtSignal(object)            #自定标签类的信号:窗体间数据通讯用自定义信号
        self.ordPosX=0     #对象移动的目地坐标
        self.ordPosY=0     
        self.clickPos=QPoint(0,0)   #鼠标在标签控件上按下时的坐标,传回主窗口以便计算相对控件在主窗体中的坐标,好移动操作控件
        #bLeftMouseKey = False  #判断是否鼠标左键被按下,此变量暂不用了
        
        self.dragPos = None          #用于控制整体移动窗体的点位,暂
        self.shoot_Flag = False      #射击标识
        self.noPatter =  QBrush(QColor(0, 0, 255, 0))  # 最后一个参数是透明度(0-255) # 创建一个透明的画刷 

        self.image=QImage()
        self.curImgFile='./res/none.png' #已不用逐一加载外部文件方式了,只用作字典查询内存文件用了
        self.curindex=0
        self.lst_pixmap = [] #此列表保存在标签中要依次加载的图片数据
        self.defSpeed=100  # 当前速度,暂没有
        self.curImgData=None #对应self.curImgFile加载到内存字典中的二进制数据

        self.curTimeNum=100    #对象一系列图象的总切换时间  
        self.curDirect=90      #当前加载的图片对应使用方向:向上为90度(正北),0为向东(左)
        self.imgDirect=0       #当前图片方向
        self.curwidth=50       #当前图片加载的矩形宽度,在运行中更新各个角字自有数据已改了
        self.curheight=50      #当前图片加载的矩形高度
        self.curBulletIndex=0  #本对象使用的武器索引号
        self.move_Flag = True     #此变量为True时,创建的角色可以在屏幕上被拖动,为False时,无拖动功能  
        self.bGo=False              #对象是否开始运动
        self.bObjSeled=False        #判断对象是否处理被选择状态,选择状态时,对象所在矩形画出边框,非选择状状时不画
        self.bArrived=False         #对象移动到批定位置是否已到达
        self.rotObj =0.00           #当前加载的图片要旋转的角度(角度非弧度单位)
        self.brot=False             #当前对象是否在运动时旋转
        self.curImgCount=0          #本批次图片的数量
        self.bResetPixMap=False     #在计时器中的开关,是否已经重新设置了图片列表,避免每次time设置一次,只对Z武器类对象有用
        self.gox=0
        self.goy=0

        self.setScaledContents(True)  #标签加载的图片自动缩放填满标签控件的矩形区域     
        self.signal_clicked.connect(self.on_clicked)

        self.mouse_x = self.mouse_y = self.origin_x = self.origin_y = 0  
        #根据当前新建的对象,初始化对象的一些基本属性值,
        self.initobj()  
    #继续对新建的对象的属性进行深化初始化
    def initobj(self):
       # 创建计时器,设置时间间隔为1000毫秒(1秒)
        self.timer = QTimer()
        # 计时器信号连接到timeout_slot槽函数
        self.timer.timeout.connect(self.time_objmove_slot)
        self.timer.start(self.curTimeNum)  # 开始计时器:
        self.bGo=False
        AllObj.objcount+=1 #父类对所有对象的计数+1
    #得到角色所有矩形参数
    def getObjRect(self):
        size = self.geometry()
        self.centerX=size.x()+size.width()/2
        self.centerY=size.y()+size.height()/2
        return size.x(),size.y(),size.width(),size.height()
    
    #在标签控件中播放下一帧
    def next_frame(self,speed=200):
        count = len(self.lst_pixmap)
        if(self.curindex>=(count-1)):
            self.curindex=0
        self.curImgData= self.lst_pixmap[self.curindex]
        self.image= QImage.fromData(self.curImgData) #将当前图像数据赋值给要显示的对象self.image,此时还不进行旋转等操作
        count = len(self.lst_pixmap)
        self.curindex = (self.curindex + 1) % count 
        self.timer.setInterval(speed)
    #对象计时器来显示动画效果
    def time_objmove_slot(self):
        #print(f'A组对象的计时器事件触发({self.curTimeNum}毫秒一次)')
        self.next_frame(self.curTimeNum)   #此函数要对self.imgage绑定当前内存图象数据:如果一下帖图象尺寸变化了,应对当前对象的显示区域作调整
        #移动前对图象进行方向进行处理
        if self.image.isNull():   # 没图片,则不执行任何操作
            return
        size = self.geometry()
        fx = size.x()+size.width()/2 #对象矩形中心的坐标
        fy = size.y()+size.height()/2 
        ex = self.gox
        ey = self.goy
        self.curDirect=self.getAngle(fx,fy,ex,ey)  #得到图象的旋转角度
        
        transform = QTransform()  
        if(self.brot):
            self.rotObj=self.curDirect-self.imgDirect   #计算图片加载时的旋转方向 ,顺时针的角度
            transform.rotate(self.rotObj)                # 将图片进行旋转以同前进方向一致(因图片问题,图片旋转后可能不太自然,,故对每种角色加了self.brot开关来控制是否旋转,还是要加工几套不同方向的图片供加载要好的多)
            #print(f'图片加载时旋转了{self.rotObj}度')
        self.image=self.image.transformed(transform);             
        self.setPixmap(QPixmap.fromImage(self.image))  # 显示图片到Qlabel控件
        self.resize(self.image.width(),self.image.height()) #用下行后用设置参数中的矩形,用本行就是图片本身的尺寸
        #self.resize(self.curwidth,self.curheight)

        if(self.bGo==True):  #开始向goX,goY移动
            sumL=math.sqrt((ex-fx)**2+(ey-fy)**2)
            n=int(sumL/(MOVE_STEP*1))
            nextX=0
            nextY=0
            if(n==0): return
            mx = sumL/n     #对象每计时器中移动的像素长度  
            if(abs((fx-ex))>=(mx+1*mx)) or abs((fy-ey))>=(mx+1*mx): #未移到位时
                self.bArrived=False
                oldx=fx
                oldy=fy
                if(ex==fx and ey<fy): #向正北方向
                    nextX=fx
                    nextY=fy-mx
                elif(ex==fx and ey>fy): #向正南方向
                    nextX=fx
                    nextY=fy+mx
                elif(ey==fy and ex<fx): #向正西方向
                    nextY=fy
                    nextX=fx-mx
                elif(ey==fy and ex>fx): #向正东方向
                    nextY=fy
                    nextX=fx+mx
                elif(ey==fy and ex==fx): #原地
                    return  
                else:
                    a=abs(math.atan(abs(ey-fy)/abs(ex-fx)))   #值为同X轴相交的一正角度(弧度)
                    oldx=nextX
                    oldy=nextY
                    if(ex>fx and ey<fy):  #一象限时
                        nextX=fx+mx*math.cos(a)-size.width()/2
                        nextY=fy-mx*math.sin(a)-size.height()/2                        
                    elif(ex<fx and ey<fy):  #二象限时
                        nextX=fx-mx*math.cos(a)-size.width()/2
                        nextY=fy-mx*math.sin(a)-size.height()/2
                    elif(ex<fx and ey>fy):  #三象限时
                        nextX=fx-mx*math.cos(a)-size.width()/2
                        nextY=fy+mx*math.sin(a)-size.height()/2
                    elif(ex>fx and ey>fy):  #四象限时
                        nextX=fx+mx*math.cos(a)-size.width()/2
                        nextY=fy+mx*math.sin(a)-size.height()/2
                    else:
                        print('还有特例???????????????????????')
                self.move(QPoint(nextX,nextY))
                if(abs(nextX-oldx)<mx/2  and abs(nextY-oldy)<mx/2 ):
                    self.bArrived=True
                
            else:   #移动位时,设置到位标志为True
                self.bArrived=True
        #print(f'前进方向夹角={self.curDirect}')
        self.update()

    #根据窗体上的两坐标点,计算出从点0到点1直线方向相对向左为0时顺时针的角度(非弧度)
    #因对象存在一矩形区域,以起点对象的到终点的矩形区域进行计算角度(调入时fx,fy要转换到对象的矩形中心位置)
    def getAngle(self,fx,fy,ex,ey):
        rota=0.0 #角度
        quadrant=1  #终点相对起点所在象限
        if(ex==fx and ey<fy): #向正北方向
            rota=270
        elif(ex==fx and ey>fy): #向正南方向
            rota=90
        elif(ey==fy and ex<fx): #向正西方向
            rota=180
        elif(ey==fy and ex>fx): #向正东方向
            rota=0
        elif(ey==fy and ex==fx): #原地
            rota=0
        else:
            a=abs(math.atan(abs(ey-fy)/abs(ex-fx)))  #(其值为同x轴相交的一正值数:弧度)
            if(ex>fx and ey<fy):  #一象限时
                rota=360-a*180/3.1415926
                quadrant=1
            elif(ex<fx and ey<fy):  #二象限时
                rota=180+a*180/3.1415926 
                quadrant=2
            elif(ex<fx and ey>fy):  #三象限时
                rota=180-a*180/3.1415926
                quadrant=3
            elif(ex>fx and ey>fy):  #四象限时
                rota=0+a*180/3.1415926
                quadrant=4 
        return rota               #也可以将象限值一并返回return rota, quadrant  
       
   
    #角色控件被单击时
    def on_clicked(self):
        print('角色标签控件被单击')
        self.bObjSeled=True
    #鼠标按下事件重载   
    def mousePressEvent(self, event):   #如在其子类又重载了此事件,子类中要加上super(A_Obj, self).mousePressEvent(event) #调用其父类的同样事件                               
        print(f'ALL_Obj鼠标压下:当前坐标:x={event.pos().x()},y={event.pos().y()}')
        # 当点击时候,先重新初始化拖动标识位为否
        #self.move_Flag = False
        # 核心部分: 当鼠标点击是左键 并且 在top控件内点击时候触发 (这里的top控件即为 我自定义的顶部窗体)
        if (event.button() == Qt.LeftButton and self.move_Flag):    #and self.top.underMouse():
            self.setCursor(Qt.OpenHandCursor)    #移动时设置成手型光标
            # 但判断条件满足时候, 把拖动标识位设定为真
            #self.move_Flag = True
            self.mouse_x = event.globalX()
            self.mouse_y = event.globalY()
            # 获取窗体当前坐标
            self.origin_x = self.x()
            self.origin_y = self.y()
            self.bObjSeled=True  
            
            self.signal_clicked.emit(self.text())   #在标签上按下鼠标键后,发送此信号出去,调用主窗体中对应定义的槽函数onclick响应
        elif event.button() == Qt.RightButton:  #打开一半透明窗体,窗体上列出对象的属性值
            self.lastPoint = event.pos()
            self.endPoint = self.lastPoint
            size=self.geometry()
            objx0 = size.x()
            objy0 = size.y()
            mainx = event.globalX()-objx0
            mainy = event.globalY()-objy0                #下面的self是传入当前角色标签对象
            self.subWindow = transSubWindow(mainx, mainy,self,0.8)     #打开一浮上主窗口上显示此小标签的属性等,透明度0.8,定义的子窗体必须有self,否则会一闪而过,
            transSubWindow.COUNTID=0
            self.subWindow.sig_ToMian.connect(self.getObj)             #同时定义对应子窗体发送的自定义信号'sig_ToMian',以便接收子窗体传来的数据
            self.subWindow.exec()                                      #会模态化显示对话框,直至关闭它

    #鼠标移动事件重载          
    def mouseMoveEvent(self, event):  
        #print(f'ALL_Obj鼠标移动:当前坐标:x={event.pos().x()},y={event.pos().y()}')
        # 拖动标识位设定为真时, 进入移动事件
        if self.move_Flag:
            # 计算鼠标移动的x,y位移
            move_x = event.globalX() - self.mouse_x
            move_y = event.globalY() - self.mouse_y
            # 计算窗体更新后的坐标:更新后的坐标 = 原本的坐标 + 鼠标的位移
            dest_x = self.origin_x + move_x
            dest_y = self.origin_y + move_y
            # 移动窗体
            size = self.geometry()
            self.move(dest_x, dest_y)
            self.gox=dest_x+size.width()/2   #不加这两行,放开对象后,对象在计时器的执行下回到上一个位置 
            self.goy=dest_y+size.height()/2
            self.raise_() #拖动的标签控件角色在最顶端显示
    # 鼠标左键释放        
    def mouseReleaseEvent(self, event):
        #print(f'ALL_Obj鼠标释放:当前坐标:x={event.pos().x()},y={event.pos().y()}')
        # 设定鼠标为普通状态: 箭头
        self.setCursor(Qt.ArrowCursor)
        if event.button() == Qt.LeftButton:
            self.endPoint = event.pos()
            #print(f'当前坐标:x={event.pos().x()},y={event.pos().y()}')
    #重载绘图函数:
    def paintEvent(self, event):
        super().update()                #调用主窗口的重绘事件,不用好象没影响什么
        pen = QPen()                    # 创建画笔对象
        brush = QBrush()                # 创建画刷对象
        painter = QPainter(self)        #此QPainter只能在paintEvent中定义,不能定义成类的self成员对象,也不能在其地方(如其他窗口,线程中)定义,否则没有绘画功能显示
        #绘制
        pencol = QColor(self.lst_Obj[15])    #画笔颜色:从当前对象属性中选
        pen.setColor(pencol)                 
        pen.setStyle(Qt.SolidLine)               
        pen.setWidth(2)                          # 设置画笔宽度
        painter.setPen(pen)                        #设置画笔
        #print(f'paint事件中的文件名{self.curImgFile}') 
        #self.image= QImage.fromData(self.curImgData)    #此语句移到其他位置对此内部变量进行赋值
        pixmap = QPixmap.fromImage(self.image)
        painter.drawPixmap(0, 0, pixmap)
        if self.bObjSeled==True and self.move_Flag:    #只有对象被选择状态时才绘制对象被选择的矩形边框(对武器类不支持可被选择)
            painter.drawRect(0,0,self.width(),self.height()) 
        

    #设置整个窗体的透明度:0=完全透明,1=完合不透明
    def setTransWindow(self,opacity=1.0):
        self.winstrans=opacity
        #self.setWindowOpacity(opacity)   # 设置整个窗口透明度


    #导入图像   
    def importImg(self,imgfile,sleeptime=0.0):
        if(sleeptime>0):
            time.sleep(sleeptime)
        self.curImgFile=imgfile
        pixmap = QPixmap(imgfile)
        self.setPixmap(pixmap)
        self.update()
        
    #得到本类实例的ID号及别名(双返回值)   
    def getID(self):
        return self.objID,self.objName
    
    #接收信号sig_ToMian的槽函数
    def getObj(self):
        print('laber_obj类对象接到信号sig_ToMian')
    #得到当前对象的位置信息    
    def getObjPos(self):
        size = self.geometry()
        w=size.width()
        h=size.height()
        x0 = size.x()+int(w/2) #对象矩形中心的坐标
        y0 = size.y()+int(h)/2 
        return x0,y0,w,h
   
    #对象前往指定的点
    def  startGo(self,PointXY):
        self.gox=PointXY.x()
        self.goy=PointXY.y()
    #得到本对象使用的武器类型
    def getBulletType(self):
        return self.curBulletIndex
##########################################################################################################################
#对象为A类型的角色类
class A_Obj(AllObj):
    global dic_AObj #建立obj小标签对象的字典(A队字典)
    Aobjcount=0    #使用时:A_Obj.Aobjcount
    def __init__(self,id,name,variety,level,health,sport,x0,y0,obj,parent=None):    
        super().__init__(id,name,variety,level,health,sport,x0,y0,obj,parent)               #继承类一定要对其父类补始化,且参数中要有同父类中的参数一致,
        #super(AllObj, self).__init__(id,name,variety,level,health,sport,x0,y0,obj,parent)
        self.objgroup='A' 
        self.bullet=variety         #A角色使用的武器类型,可更改试效果
        self.brot=False       #A类对象在变换加图片时暂不支持旋转(改成True可支持,但图片质量太差,效果不好)
        self.curBulletIndex=0 #本对象使用的武器类型
        self.dic_AObj=obj     #同主窗体类模块中的全局变量字典对象绑定关联上
                      #0  1     2        3        4       5            6         7   8  9 10 11 12 13  14     15         16    17 
                      #ID,别名,分组,    类型,   当前等级,当前显示状态,当前运动状态,x0,y0, w0,h0,x1,y1,w1,h1,选中边框颜色,透明度,对象是否支持拖动,扩展,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,  
        self.lst_aObj=[0, 'A组0','A', variety,    0,       0,         0,         0, 0, 50,50, 0, 0,50,50,QColor(0,0,0),  0.5,  True,'扩展'] #单个对象 #单个对象   #对A组单个对象 #默认对象的属性
        self.lst_Obj = self.dic_AObj[self.Objid]       #根据对象ID属性从字典中得到此当前此对象的全部属性值,变量定义在父类,但为何要报错??????????????????????
        #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 
        
        #根据当前新建的对象,初始化对象的一些基本属性值,
        self.lst_Obj[0]=self.Objid              #对象ID:
        self.lst_Obj[1]=self.objname           #对象别名
        self.lst_Obj[2]='A'                    #对象所属队伍(如A  B  C等)
        self.lst_Obj[3]= variety               #对象类型:如区分普通僵尸,铁筒僵尸等
        self.lst_Obj[4]= level                  #对象新建时的等级,如是复活和连续,此值不一定为0
        self.lst_Obj[5]= health                #对象新建时的显示状态,如是满血,还是受伤
        self.lst_Obj[6]= sport                 #对象新建时的运动状态,如是静止状态,还是运动状态,还是攻击状态等
        self.lst_Obj[7]= x0
        self.lst_Obj[8]= y0
        self.lst_Obj[15]=QColor('blue')         #对A组成员选成后画边框为蓝色
        self.lst_Obj[16]=0.5                    #对象透明度
        self.lst_Obj[17]=True                   #此对象是否支持用鼠标在主窗体上拖动
        
        #根据传入的ID等信息进一步初始化新建对象的属性,并将属必保存在字典self.dic_AObj中(此字典实际也是主窗体中的字典dic_AObj,他们其实是一个变量)
        self.initObj()                          #继续对新建的A组对象深化初始化
    def initObj(self):

        #设置新建角色出现的位置,其中xy由主窗口程序决定,宽度和高度由imgObjData的定义的数据决定
        self.ResetPixMap(self.lst_Obj[3],self.lst_Obj[4])  #初化图片为0组0行列表
        self.setGeometry(self.lst_Obj[7],self.lst_Obj[8],self.curwidth,self.curheight)   #设置新建对象的出现位置和尺寸
        A_Obj.Aobjcount+=1 #本子类对所有实例化的对象计数+1

    #根据当前对象类型,设置本对象要采用的图象数据列表,传入参数为数据类的一内存数据列表
    def ResetPixMap(self,variety,type):  #示例中group只支持group='A',type=0和1
        self.variety=variety
        self.imgIndextype=type
        lst_img = A_ImgObjData.lstA_Imgs[variety][type]
        lst_opt = A_ImgObjData.lstA_Opts[variety][type]
        self.curImgCount=len(lst_img)          
        self.curwidth=lst_opt[1]
        self.curheight=lst_opt[2]
        self.curDirect=lst_opt[3]  #此变量在计时器中计算时会得到对象的运动方向
        self.imgDirect=lst_opt[3]
        self.curTimeNum=lst_opt[4]
        self.lst_pixmap.clear()
        for imgfile in lst_img:
                imgfile=self.path+imgfile   #########字典中的文件名带相对路径,否则无法加载图片###################
                data=A_ImgObjData.dicA_memImg.get(imgfile, A_ImgObjData.dicA_memImg['NONE']) #用get函数来得到对应字典key的值,如果没有,得到默认值,防报错
                self.lst_pixmap.append(data)
                #上行对等self.lst_pixmap = [QPixmap(data) for data in lst_img]  但因文件二进制数所保存在一字典中了,要转换一道
        self.image= QImage.fromData(self.lst_pixmap[0]) #将当前图像数据的第0个数据给self.image,此时还不进行旋转等操作
    

#对象为B队的对象类:如计算机控制的角色。。。
class B_Obj(AllObj):
    global dic_BObj #建立obj小标签对象的字典(B类对象) 
    Bobjcount=0       #使用时:B_Obj.Bobjcount
    def __init__(self,id,name,parent=None):    
        super(AllObj, self).__init__(parent)  #继承类一定要对其父类补始化,且参数中要有同父类中的参数一致,否则将无法使用父类中的attrib值
        self.curCount = len(dic_BObj)
    #本代码仅为示例,只示范了创建A类角色和Z类武器,其他角色可参考自已继承和编写
    #......

###################################################
# 武器类对象:仍继承AllObj类
class Z_Obj(AllObj):
    global dic_ZObj #建立obj小标签对象的字典(B类对象) 
    Zobjcount=0    #使用时:Z_Obj.Zobjcount
    def __init__(self,id,name,variety,level,health,sport,x0,y0,obj,parent=None):    
        super().__init__(id,name,variety,level,health,sport,x0,y0,obj,parent)               #继承类一定要对其父类补始化,且参数中要有同父类中的参数一致,
        #super(AllObj, self).__init__(id,name,variety,level,health,sport,x0,y0,obj,parent)
        self.objgroup='Z' 
        self.brot=True     #武器类对象在行进过程中支持旋转
        self.bObjSeled = False #武器类不支持被选中
        self.move_Flag = False #武器类不支持在窗体上拖动对象
        self.curBulletIndex=0 #本对象使用的武器类型
        self.dic_ZObj=obj     #同主窗体类模块中的全局变量字典对象绑定关联上
                      #0  1     2        3        4       5            6         7   8  9 10 11 12 13  14     15         16    17 
                      #ID,别名,分组,    类型,   当前等级,当前显示状态,当前运动状态,x0,y0, w0,h0,x1,y1,w1,h1,选中边框颜色,透明度,对象是否支持拖动,扩展,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,  
        self.lst_ZObj=[0, '武器0','Z', variety,    0,       0,         0,         0, 0, 50,50, 0, 0,50,50,QColor(0,0,0),  0.5,  True,'扩展'] #单个对象 #单个对象   #对A组单个对象 #默认对象的属性
        self.lst_Obj = self.dic_ZObj[self.Objid]       #根据对象ID属性从字典中得到此当前此对象的全部属性值,变量定义在父类,但为何要报错??????????????????????
        #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 
        
        #根据当前新建的对象,初始化对象的一些基本属性值,
        self.lst_Obj[0]=self.Objid              #对象ID:
        self.lst_Obj[1]=self.objname           #对象别名
        self.lst_Obj[2]='Z'                    #对象所属队伍(如A  B  C等)
        self.lst_Obj[3]= variety               #对象类型:如区分普通僵尸,铁筒僵尸等
        self.lst_Obj[4]= level                  #对象新建时的等级,如是复活和连续,此值不一定为0
        self.lst_Obj[5]= health                #对象新建时的显示状态,如是满血,还是受伤
        self.lst_Obj[6]= sport                 #对象新建时的运动状态,如是静止状态,还是运动状态,还是攻击状态等
        self.lst_Obj[7]= x0
        self.lst_Obj[8]= y0
        self.lst_Obj[15]=QColor('white')         #无用不支持
        self.lst_Obj[16]=0.5                    #对象透明度
        self.lst_Obj[17]=True                   #此对象是否支持用鼠标在主窗体上拖动
        
        self.LoopImgNum=0                       #本批次图片已循环播放次数,父类中无此参数
        #根据传入的ID等信息进一步初始化新建对象的属性,并将属必保存在字典self.dic_AObj中(此字典实际也是主窗体中的字典dic_AObj,他们其实是一个变量)
        self.initObj()                          #继续对新建的A组对象深化初始化
    def initObj(self):
        #设置新建角色出现的位置,其中xy由主窗口程序决定,宽度和高度由imgObjData的定义的数据决定
        self.ResetPixMap(self.lst_Obj[3],self.lst_Obj[4])  #初化图片为0组0行列表
        self.setGeometry(self.lst_Obj[7],self.lst_Obj[8],self.curwidth,self.curheight)   #设置新建对象的出现位置和尺寸
        Z_Obj.Zobjcount+=1 #本子类对所有实例化的对象计数+1
        self.show()
    #根据当前对象类型,设置本对象要采用的图象数据列表,传入参数为数据类的一内存数据列表
    def ResetPixMap(self,variety,type):  #type=0和1
        self.variety=variety
        self.imgIndextype=type
        lst_img = Z_ImgObjData.lstZ_Imgs[variety][type]
        lst_opt = Z_ImgObjData.lstZ_opts[variety][type]
        self.LoopImgNum=0         
        self.curImgCount=len(lst_img)  
        self.curwidth=lst_opt[1]
        self.curheight=lst_opt[2]
        self.curDirect=lst_opt[3]
        self.imgDirect=lst_opt[3]
        self.curTimeNum=lst_opt[4]
     
        self.lst_pixmap.clear()
        for imgfile in lst_img:
                imgfile=self.path+imgfile   #########字典中的文件名带相对路径,否则无法加载图片###################
                data=Z_ImgObjData.dicZ_memImg.get(imgfile,Z_ImgObjData.dicZ_memImg['NONE']) #用get函数来得到对应字典key的值,如果没有,得到默认值,防报错
                self.lst_pixmap.append(data)
                #上行对等self.lst_pixmap = [QPixmap(data) for data in lst_img]  但因文件二进制数所保存在一字典中了,要转换一道
        self.image= QImage.fromData(self.lst_pixmap[0]) #将当前图像数据的第0个数据给self.image,此时还不进行旋转等操作

    #武器类对象重载父类All_Obj的计时器移动对象函数:用于处理其他角色没有的,当对象移动到指定位置时的击中对方效果的展示动作
    def time_objmove_slot(self):
        #在调用父类的计时器函数量,先明确了本类角色图片要旋转的角度

        super().time_objmove_slot()  #必须先运行父类的同名函数
        self.LoopImgNum+=1
        if(self.bArrived):  #到达指定位置了
            print('武器类对象已到达指定位置了')
            if(self.bResetPixMap==False):
               self.ResetPixMap(self.variety,1)  #重新设置图片列表,演示
               self.bResetPixMap=True
            if(self.LoopImgNum>=self.curImgCount):    #已到达同时一到达的选用图片(击中对方的图片列表)也循环了一遍
                self.hide()
                self.timer.stop()  #停止计时器
                self.close()
                self.destroy()

   #在标签控件中播放下一帧
    def next_frame(self,speed=500):
        super().next_frame(speed)   #先调用父类的,本继承类主要是处理不同图片的列表
        lst_opt = Z_ImgObjData.lstZ_opts[self.variety][self.imgIndextype]
        self.imgDirect=lst_opt[3]
        print(f'当前图片方向={self.imgDirect},运动方向={self.curDirect}')
####################################################################################
#重载子窗口类,窗口为一浮在主窗体上的透明置顶窗口
class transSubWindow(QDialog):
    COUNTID=0
    win_Type=['普通不透明','无框透明不可拖动','无框透明可拖动','小对象']
    signal_clicked = QtCore.pyqtSignal(str)           #自定子窗口的信号,即标签被单击时发出(传回参数为xxx)
    sig_ToMian = QtCore.pyqtSignal(object)            #自定子窗口的信号:窗体间数据通讯用自定义信号
    def __init__(self,x,y,obj,trans=0.8,type=2, parent=None):    
        super(QDialog, self).__init__(parent)
        #self.setAttribute(Qt.WA_TranslucentBackground, True)
        self.setAttribute(Qt.WA_NoSystemBackground, True)  #无边框
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setTransWindow(trans) #设置窗体为透明
        self.setGeometry(x, y,1200, 700)
        self.type=type           
        self.dragPos = None    #用于控制不支持拖放
        self.obj=obj  #同传入的角色对象关联,好在本窗体也可以使用角色的属性值和函数
        #在底图需要显示对象的位置上创建一标签控件来播放角色图片的动画
        self.lab_obj = QLabel("",self)
        self.lab_obj.setGeometry(350,180,129,188)
        
        self.lab_Name = QLabel("",self)
        self.lab_Name.setGeometry(300,45,200,50)
        # 设置标签的字体
        font = QFont()
        font.setFamily('黑体')  # 设置字体名称
        font.setPointSize(25)    # 设置字体大小
        font.setBold(True)       # 设置字体加粗
        self.lab_Name.setFont(font)
        self.lab_Name.setText(self.obj.objname)  #此窗体可显示角色的许多当前属性值,本示例不演示了
        # 设置QLabel的字体颜色为红色
        self.lab_Name.setStyleSheet(f"QLabel {{ color: {QColor(255, 0, 0).name()}; }}")

        btn_close = QPushButton("关闭窗口", self)
        btn_close.setGeometry(1110, 65, 65, 40)
        btn_close.setStyleSheet("color : rgba(0, 0, 0, 1)") #按纽也为半透明
        btn_close.setToolTip('此窗口可以显示刚点击对象的属性值等,单击此按纽关闭这个半透明的窗口')
        btn_close.raise_()
        btn_close.clicked.connect(self.btnClose_click)
        self.timer = QTimer()
        self.timer.timeout.connect(self.time_objshow)
        self.timer.start(self.obj.curTimeNum)  # 开始计时器,实时得到主窗体中的角色图片状态,并同步播放
    #设置整个窗体的透明度:0=完全透明,1=完合不透明
    def setTransWindow(self,opacity=1.0):
        self.winstrans=opacity
        self.setWindowOpacity(opacity)   # 设置整个窗口透明度

    #鼠标按下事件: 
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.dragPos = event.globalPos() - self.pos()
            event.accept()
    #鼠标移动事件
    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            self.move(event.globalPos() - self.dragPos)
            event.accept()

    #不用标签控件,直接在窗体上画出底图
    def paintEvent(self, event):
        painter = QPainter(self)
        pixmap = QPixmap('./res/角色属性底图.png')
        painter.drawPixmap(self.rect(), pixmap)

     #在计时器中将角色的动画图片同主窗体同一角色图片同步
    def time_objshow(self):
        transSubWindow.COUNTID+=1
        #self.image= QImage.fromData(self.obj.curImgData)
        pixmap1 = QPixmap.fromImage(self.obj.image)
        self.lab_obj.setPixmap(pixmap1)  
        if(transSubWindow.COUNTID>30):
            self.lab_obj.setPixmap(QPixmap(""))   #为何清除不了那张静止的图片?
            self.lab_obj.update()
            
 
    #单击关闭按纽的槽函数
    def btnClose_click(self):
        self.close()

3、模块ImgObjData.py

#模块名:ImgObjData.py
#包含类名:ImgObjData,A_ImgObjData,B_ImgObjData,Z_ImgObjData等等........
#功能:提供全部label_Obj需要的角色,武器等图象资源\属性等数据
import os,sys 
import math
import copy 
import random 
#定义基于所有角色对象的基类数据
class ImgObjData(): 
    path='./res/'   #全部图像资源所在目录为PY程序文件下的res子目录中
    defNoneImg=path+'none.png'   #初始化图片出错时的空图片数据,为避免每实例化一个对象加载这几行代码一次,将此代码放在父类的INIT之外
    defNoneImgData=None
    with open(defNoneImg, 'rb') as f:
            img = f.read()
            defNoneImgData=img  
    #父类初始化:本初始化的成员变量值为为图片索引相关,不是角色值
    def __init__(self): 
        self.curtype='IMG'  #图片类型:当图片为非GIF或视频时为'img' 否则为'MOV'
        self.level=0         #对象等级
        self.health=0        #对象健康状态
        self.speed=0         #对象速度
        self.sport=0         #对象运动状态
        #根据等级,显示状态,文件序号得到文本名和文件序号列表(第一个参数必须是self占位,否则传递时level会得到self的值)
        #                       角色分类, 方向,等级,显示状态,运动状态 
    def getObjImgFileName(self,variety,direct,level,health,sport,count=1,ext='png'):
        lstFile=[]
        for n in range(count):
            s='A'+'-'+str(variety)+'-'+direct+'-'+str(level)+'-'+str(health)+'-'+str(sport)+'-'+str(n)+ext
            lstFile.append(s)
        #print(lstFile)
        return lstFile 
    
    #得到当前字典中的文件名属于一般图片还是GIF或视频
    def getImgType(self,file_name):
        file_extension = str(file_name.split(".")[-1]).upper()
        #print(file_extension)
        if(file_extension=='GIF' or file_extension=='AVI' or file_extension=='MPEG' or file_extension=='MP4' or file_extension=='MOV'):
             return 'MOV'
        else:
             return 'IMG'
#定义A队角色数据
class A_ImgObjData(ImgObjData): 
    group='A'       #数据对应队伍
    #A队0类角色图片对象对应的图片(每行文件数量可不相同,数量不限),分类组数可自行扩充
    lstA0_Imgs=[['A0-西-0-0.png','A0-西-0-1.png','A0-西-0-2.png'],     #0->对象0满血静止状态的四组图片(文件可以是透明色的PNG,也可以是透明GIF )
               ['A0-西-0-0.png','A0-西-0-1.png','A0-西-0-2.png'],      #1->对象0满血运动状态的四组图片
               ['A0-西-0-0.png','A0-西-0-1.png','A0-西-0-2.png'],      #2->对象0受伤静止状态的四组图片
               ['A0-西-0-0.png','A0-西-0-1.png','A0-西-0-2.png']       #3->对象0受伤运动状态的四组图片
              ]
    #对应lstA_Imgs0的各组图片加载时要用的信息
                 #x,y, 方向(向左为0,顺时针的角度值),时间间隔
    lstA0_opts=[[0,85,60,180,100],               
                [1,85,60,180,100],
               [2,85,60,180,100],
               [3,85,60,180,100]
              ]
    #A队1类角色图片对象对应的图片(每行文件数量可不相同,数量不限),分类组数可自行扩充
    lstA1_Imgs=[['A1-西-0-0.png','A1-西-0-1.png','A1-西-0-2.png','A1-西-0-3.png','A1-西-0-4.png'],     #0->对象0满血静止状态的四组图片(文件可以是透明色的PNG,也可以是透明GIF )
               ['A1-西-0-0.png','A1-西-0-1.png','A1-西-0-2.png','A1-西-0-3.png','A1-西-0-4.png'],      #1->对象0满血运动状态的四组图片
               ['A1-西-0-0.png','A1-西-0-1.png','A1-西-0-2.png','A1-西-0-3.png','A1-西-0-4.png'],      #2->对象0受伤静止状态的四组图片
               ['A1-西-0-0.png','A1-西-0-1.png','A1-西-0-2.png','A1-西-0-3.png','A1-西-0-4.png']       #3->对象0受伤运动状态的四组图片
              ]
    #对应lstA_Imgs0的各组图片加载时要用的信息
                 #x,y, 方向,时间间隔
    lstA1_opts=[[0,129,188,0,100],               
                [1,129,188,0,100],
               [2,129,188,0,100],
               [3,129,188,0,100]
              ]
    lstA_Imgs=[lstA0_Imgs,lstA1_Imgs]    #仅示例,包含两种对象类型,每个角色类型可以对应很多实例化对象
    lstA_Opts=[lstA0_opts,lstA1_opts]    #仅示例,包含两种对象对应的图片主要参数

    dicA_memImg={}  #定义对应上述文件的内存文件,避免频繁加载文件,且在内存中处理图片更高效
    bLoadok=False  #数据是否已被第一个实例化对象初始化了,如第一个对象已经实例化内存文件,后面相关类对象不再重写内存文件

    def __init__(self,parent=None):
        super().__init__()  #必须初始化父类
        if(not A_ImgObjData.bLoadok):
            self.makeMemFile()          #初始化内存文件  #要优化,每实例化一个对象重载下内存文件没必要,只一次即可
            print('A组全部数据文件加载到内存中成功')
            A_ImgObjData.bLoadok=True

    #创建图片的内存文件
    def makeMemFile(self):
        #将上面定义的全部文件数据存入一对应的内存文件中(字典),文件名作为key,重名的只会存一个内存数据
        ng=0
        A_ImgObjData.dicA_memImg['NONE'] = self.defNoneImgData #为字典增加第0号元素:一个空PNG图,防无图上用
        for lstA_group in A_ImgObjData.lstA_Imgs:
            #print(f'第{ng}角色文件列表:')
            ng+=1
            for lstImgs in lstA_group: #再遍历每个角色的不同形态列表
                for imgFilename in lstImgs:
                    imgPathFile=self.path+imgFilename
                    # 打开图片
                    if(os.path.exists(imgPathFile)):
                        with open(imgPathFile, 'rb') as f:
                            img = f.read()
                            A_ImgObjData.dicA_memImg[imgPathFile]=img
    #得到对象的文件名:分类序号,方向,等级,序号
    def getObjImg(self,variety,level=0,sport=True,health=True,direct=''):   #仅示例,对方向参数direct没起作用
        lst_img=[[]]
        lst_opt=[[]]
        def_Image=copy.deepcopy(A_ImgObjData.lstA_Imgs[variety])
        if(level==0 and sport==False and health==True):       #对象类型=0,等级=0, 健康静止不动
            lst_img = self.lstA_Imgs[variety][0]
            lst_opt = self.lstA_Opts[variety][0]
        elif(level==0 and sport==True and health==True):      #对象类型=0,等级=0,健康运动状态
            lst_img = self.lstA_Imgs[variety][1]
            lst_opt = self.lstA_Opts[variety][1]
        elif(level==0 and sport==False and health==False):    #对象类型=0,等级=0,受伤静止不动
            lst_img = self.lstA_Imgs[variety][2]
            lst_opt = self.lstA_Opts[variety][2]
        elif(level==0 and sport==True and health==False):     #对象类型=0,等级=0,受伤运动状态
            lst_img = self.lstA_Imgs[variety][3]
            lst_opt = self.lstA_Opts[variety][3]
        else:
            pass
        return lst_img,lst_opt

########################################################################################        
#同A类似,对B队角色进行类功能化,略......
class B_ImgObjData(ImgObjData): 
    group='B'       #数据对应队伍
    pass
#########################################################################################
#定义武器(子弹)对象数据
class Z_ImgObjData(ImgObjData): 
    group='Z'       #数据对应子弹编组
    bLoadok = False 
    #武器类类角色图片对象对应的图片,文件名命名规Z-类型-序号.png
    lstZ0_Imgs=[['Z0-0-0.png','Z0-0-1.png','Z0-0-2.png','Z0-0-3.png'],      #子弹在运协时的四组图片(文件是透明色的PNG,每参数1指定毫秒数切换成动画,击中对方后切换成下一组图片)
               ['Z0-1-0.png','Z0-1-1.png','Z0-1-2.png','Z0-1-3.png']       #子弹击中对象时的四组图片(文件是透明色的PNG,每参数1指定毫秒数切换成动画 )
             ]
                #序号,w,h, 方向,时间间隔
    lstZ0_opts=[[0,25,25,0,100],               
                [1,100,100,0,100]
               ]
    lstZ1_Imgs=[['Z1-0-0.png','Z1-0-1.png','Z1-0-2.png','Z1-0-3.png','Z1-0-4.png'],       #同0一样的,仅为示例
               ['Z1-1-0.png','Z1-1-1.png','Z1-1-2.png','Z1-1-3.png']       #
             ]
                #序号,w,h, 方向,时间间隔
    lstZ1_opts=[[0,160,160,0,100],               
                [1,160,160,0,100]
               ]

    lstZ_Imgs=[lstZ0_Imgs,lstZ1_Imgs]    #仅示例,包含2种武器子弹对象图片
    lstZ_opts=[lstZ0_opts,lstZ1_opts]    #仅示例,包含2种武器子弹对象参数
    dicZ_memImg={}                       #定义对应上述文件的内存文件,避免频繁加载文件,且在内存中处理图片更高效
    def __init__(self,parent=None):
        super().__init__()  #必须初始化父类
        if(not Z_ImgObjData.bLoadok):
            self.makeMemFile()          #初始化内存文件  #要优化,每实例化一个对象重载下内存文件没必要,只一次即可
            print('武器组全部数据文件加载到内存中成功')
            Z_ImgObjData.bLoadok=True

    #创建图片的内存文件
    def makeMemFile(self):
        #将上面定义的全部文件数据存入一对应的内存文件中(字典),文件名作为key,重名的只会存一个内存数据
        Z_ImgObjData.dicZ_memImg['NONE'] = ImgObjData.defNoneImgData #为字典增加第0号元素:一个空PNG图,防无图上用
        for lst_group in Z_ImgObjData.lstZ_Imgs:
            for lstImgs in lst_group: #再遍历每个角色的不同形态列表
                for imgFilename in lstImgs:
                    imgPathFile=self.path+imgFilename
                    # 打开图片
                    if(os.path.exists(imgPathFile)):
                        with open(imgPathFile, 'rb') as f:
                            img = f.read()
                            Z_ImgObjData.dicZ_memImg[imgPathFile]=img
      
    
    #得到对象某状态下的一组图片列表
    #参数                武器类型  武器状态:0=运动  1=到达
    def getObjImg(self,variety,type=0):  
        lst_img=[[]]
        def_Image=copy.deepcopy(ImgObjData.defNoneImgData)
        if(type==1):         #得到武器已到达目的地的图片列表(即击中敌人时的图片列表)
            lst_img = self.lstZ_Imgs[variety][1]
        else: 
            lst_img = self.lstZ_Imgs[variety][0]
            pass
        return lst_img
                    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mr_LuoWei2009

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值