1. 程式人生 > 實用技巧 >Qt 實現遊走糖豆人

Qt 實現遊走糖豆人

類的實現

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()

效果截圖