类的实现

1 糖豆人类--BFish

  1. 糖豆人对象的继承:

糖豆人类继承 QThread 类,每个糖豆人作为一个单独的线程进行形态的变化

class BFish(QThread):
  1. 糖豆人的基础属性和基础控制属性

各个基础属性和控制属性的功能如下注释

class BFish(QThread):
    signal_changed = pyqtSignal()

    def __init__(self):
        # 糖豆人的基础属性
        super(BFish, self).__init__()
        self.x = 100.0  # 糖豆人的坐标 x y
        self.y = 100.0
        self.size = 100.0  # 糖豆人大小
        self.dir = 0      # 糖豆人的运动方向
        self.m = 45  # 糖豆人的开口大小
        self.mouth_speed = 5  # 糖豆人的开闭口速度
        self.swim_speed = 8  # 糖豆人的运动速度
        self.color = QColor(0, 255, 0)  # 糖豆人的颜色(初始)
        self.pen = QPen(self.color, 4.0, Qt.SolidLine,
                        Qt.RoundCap, Qt.RoundJoin)  # 绘制糖豆人的“笔”,控制糖豆人的外表特性
        # 糖豆人的基础控制状态
        self.is_open = True  # 糖豆人的开闭口状态
        self.on_boarders = False  # 糖豆人是否在边界上的状态
  1. 糖豆人的运动方法:swim()

糖豆人根据运动方向和运动速度,使用三角函数改变糖豆人的坐标位置,一次调用仅运动一次。
每次运动完成后进行边界判定,若已处于边界则反向。并设置边界状态为在边界上,阻塞随机变相的进行,防止糖豆人卡死在边界。

def swim(self):  # 控制糖豆人根据速度和方向运动一次
        self.x += self.swim_speed * math.cos(self.dir * math.pi / 180)
        self.y -= self.swim_speed * math.sin(self.dir * math.pi / 180)
        if (self.x < 0 or self.y < 0 or self.x + self.size > 700 or self.y + self.size > 800) and not self.on_boarders:
            self.dir = (self.dir + 180) % 360
            self.on_boarders = True
  1. 糖豆人的方向改变方法:change_dir(d)
def change_dir(self, d):  # 设置糖豆人的运动方向
        if (not self.on_boarders):
            self.dir = d
  1. 糖豆人的开闭嘴方法:open_mouth(self)

根据当前的张开角度和开闭状态修改糖豆人嘴巴的张开角度。

def open_mouth(self):  # 控制糖豆人开闭口一次
        if self.is_open:
            self.m += self.mouth_speed
            if self.m >= 45:
                self.is_open = not self.is_open
                self.m = 45
        else:
            self.m -= self.mouth_speed
            if self.m <= 0:
                self.is_open = not self.is_open
                self.m = 0
  1. 糖豆人的显示方法:show_me(self, g)

糖豆人的其他方法都只是在根据方法修改对象的数据,并不会在可视的层面显示我们想要的变化,
因此需要一个函数将我们抽象的数据展示为一个具体的糖豆人来表现这些变化。
糖豆人类本身是不具备展示的界面,因而需要调用者传入一个 Painter 供糖豆人对象绘制。

def show_me(self, g):  # 在传入的 painter 上绘制糖豆人
        # 生成用于绘制糖豆人的随机的颜色
        self.color = QColor(random.randint(0, 256), random.randint(
            0, 256), random.randint(0, 256))
        self.pen.setColor(self.color)
        g.setPen(self.pen)
        # 绘制糖豆人的身体
        g.drawPie(self.x, self.y, self.size, self.size,
                  (self.m + self.dir) * 16, (360 - 2*self.m) * 16)
        g.setBrush(self.color)  # 用 brush 填充糖豆人的眼睛
        # 绘制糖豆人的眼睛
        point = QPoint(self.x + self.size / 2 + math.cos((self.dir + 125) * math.pi / 180) * self.size / 4,
                       self.y + self.size / 2 - math.sin((self.dir + 125) * math.pi / 180) * self.size / 4)
        g.drawEllipse(point, self.size / 8, self.size / 8)
  1. 糖豆人的线程方法:run()

启动糖豆人的线程后,糖豆人就会不断地移动并释放状态改变信号 signal_changed。并会每隔一段时间随机改变运动方向,若糖豆人已处于边界则停止随机改变运动方向,延反向后的方向运动一段距离后再回复随机变相功能。

def run(self):  # 线程循环
        # run 结束线程死亡
        dir_change_count = 0
        on_boarder_count = 0
        while True:
            # 张开嘴
            self.open_mouth()
            if self.on_boarders:
                if on_boarder_count == 3:
                    self.on_boarders = False
                    on_boarder_count = 0
                else:
                    on_boarder_count += 1
            else:
                if dir_change_count == 10:
                    self.dir = random.randint(0, 360)
                    dir_change_count = 0
                else:
                    dir_change_count += 1
            self.swim()
            # 释放信号
            self.signal_changed.emit()
            QThread.usleep(100000)

2 窗口类--KDialog

  1. 窗口类的继承

继承 QDialog 类

class KDialog(QDialog):
  1. 窗口的基础属性和初始化步骤

设置窗口基本属性,生成糖豆人对象并将糖豆人的改变信号连接到继承自父类的 repaint 槽函数,使每次糖豆人状态改变窗口界面都会重新绘制(清除原先绘制的糖豆人)。

def __init__(self):
        super(KDialog, self).__init__()
        self.setWindowTitle("大嘴鱼(×)糖豆人(√)")
        self.resize(700, 800)

        self.fish = BFish()  # 创建一个糖豆人实例
        # 将 fish 的静态信号与 QWidgets 类的重新绘制槽函数 repaint 连接
        # 当接收到糖豆人改变信号时触发 repaint 函数重新绘制窗口
        self.fish.signal_changed.connect(self.repaint)
        self.fish.start()  # 启动糖豆人线程
  1. 窗口的动画刷新方法

在 repaint 函数将画面清空后,再将 repaint 槽函数触发的 paintEvent 事件重写,在重写函数中绘制更新后的糖豆人,达到动画更新的效果。

def paintEvent(self, e):  # 重写 paint 事件触发的槽函数,创建本窗口的 painter 并交给糖豆人对象绘制
        print("窗体正在绘制")
        # 构建绘制器
        painter = QPainter(self)
        # 绘制
        self.fish.show_me(painter)
  1. 获取按键事件控制糖豆人运动

根据上下左右键的按下事件,将糖豆人的方向设置为上下左右

def keyPressEvent(self, e):  # 重写按键事件触发的槽函数
        # 键盘的事件处理
        print("pressed")
        key = e.key()
        if key == Qt.Key_Right:
            self.fish.change_dir(0)
            self.fish.swim()
        if key == Qt.Key_Up:
            self.fish.change_dir(90)
            self.fish.swim()
        if key == Qt.Key_Left:
            self.fish.change_dir(180)
            self.fish.swim()
        if key == Qt.Key_Down:
            self.fish.change_dir(270)
            self.fish.swim()
        pass

3 应用类--KApp

继承 QApplication 类的自定义应用类,其中包含了自定义的窗口 KDialog。

class KApp(QApplication):
    def __init__(self):
        super(KApp, self).__init__(sys.argv)
        self.dlg = KDialog()
        self.dlg.show()

效果截图

效果截图