iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 10
1
自我挑戰組

30天學python系列 第 10

[Day10] 圖形使用者介面 (GUI) 和遊戲開發

基於 tkinter 模組的 GUI

GUI 是圖形使用者界面的縮寫,GUI 開發模組是 tkinter,它提供了跨平台的 GUI 控制項。事實上,開發 GUI 應用並不是 Python 最擅長的工作,如果需要使用 Python 開發 GUI 應用,wxPython、PyQt、PyGTK 等模組都是不錯的選擇。

基本上使用 tkinter 來開發 GUI 應用需要以下 5 個步驟:

  1. 導入 tkinter 模組中我們需要的東西。
  2. 創建一個視窗物件並用它來承載整個 GUI 應用。
  3. 在視窗物件上添加 GUI 元件。
  4. 透過程式碼將這些 GUI 元件的功能組織起來。
  5. 進入主程式循環 (main loop)。
import tkinter
import tkinter.messagebox

def main():
    flag = True

    def change_label_text():        # 修改標籤上的文字
        nonlocal flag               # nonlocal 區域變數
        flag = not flag
        color, msg = ('red', 'Hello, world!')\
            if flag else ('blue', 'Goodbye, world!')
        label.config(text = msg, fg = color)

    def confirm_to_quit():          # 確認退出
        if tkinter.messagebox.askokcancel('溫馨提示', '确定要退出嗎'):
            top.quit()

    top = tkinter.Tk()              # 創建視窗
    top.geometry('240x160')         # 視窗大小
    top.title('小遊戲')              # 視窗標題
    # 創建標籤並添加到視窗
    label = tkinter.Label(top, text = 'Hello, world!', font = 'Arial -32', fg = 'red')
    # pack() 函數用來對主控制元件裡面的小控制元件來進行佈局分佈
    label.pack(expand = 1)         
    # Frame 是螢幕上的一快矩形區域,多是用來作為容器(container)布局。
    panel = tkinter.Frame(top)      
    # 創建按鈕物件,指定添加到哪個容器中,透過 command 參數绑定調用函數
    button1 = tkinter.Button(panel, text = '修改', command = change_label_text)
    button1.pack(side = 'left')     
    button2 = tkinter.Button(panel, text = '退出', command = confirm_to_quit)
    button2.pack(side = 'right')    
    panel.pack(side = 'bottom')     
    # 主程式循環
    tkinter.mainloop()

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190925/201211166dBwnoGbWI.png https://ithelp.ithome.com.tw/upload/images/20190925/20121116aYCCsRhlXI.png
https://ithelp.ithome.com.tw/upload/images/20190925/20121116LrbzS9rwRb.png
GUI 應用通常是事件驅動式的,之所以要進入主程式循環就是要偵測鼠標、鍵盤等各種事件的發生並執行對應的程式碼對事件進行處理。
Tk 為元件的擺放提供了三種佈局管理器,透過佈局管理器可以對元件進行定位。分別是:Placer(開發者提供元件的大小和擺放位置)、Packer(自動將元件填充到合適的位置)和 Grid(基於網格坐標來擺放元件)。

使用Pygame進行遊戲開發

Pygame 專門用於多媒體應用的開發,其中包含對圖像、聲音、視頻、事件、碰撞等。Pygame 建立在 SDL 的基礎上,SDL 是一套跨平台的多媒體開發庫,用 C 語言實現,被廣泛的應用於遊戲、模擬器、播放器等的開發。
下載 python,有些模組是沒有的,可以透過在 cmd 中輸入 pip install pygame,將 pygame 安裝進去。

介紹大球吃小球小遊戲

創建遊戲視窗並在視窗繪圖
可以透過 pygame 中 draw 模組的函數在視窗中繪圖,包括:線條、矩形、多邊形、圓、橢圓、圓弧等。
螢幕左上角設置為坐標原點 (0, 0),向右是 x 軸的正向,向下是 y 軸的正向,在表示位置或設置大小的時候,默認的單位都是像素。pygame 中表示顏色用的是色光三原色表示法,即通過一個元組或列表來指定顏色的 RGB 值,每個值都在 0 ~ 25 5之間,每種原色都用一個 8 位元 (bit) 的值來表示,三種顏色一共由 24 位元構成。

import pygame

def main():
    pygame.init()   # 初始化導入 pygame 的模組
    screen = pygame.display.set_mode((800, 600)) # 顯示的視窗並設置視窗大小
    pygame.display.set_caption('大球吃小球')   # 設置視窗標題
    screen.fill((242, 242, 242))    # 設置視窗背景顏色
    # 繪製圓(參數為:框架, 顏色, 圓心位置, 半徑, 0 表示填滿)
    pygame.draw.circle(screen, (255, 0, 0,), (100, 100), 30, 0)
    # 刷新視窗
    pygame.display.flip()
    running = True
    while running:  # 開啟一個事件循環處理發生的事件
        for event in pygame.event.get():    # 獲取事件並進行處理
            if event.type == pygame.QUIT:
                running = False

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190925/20121116zry2zzuW3E.png
上傳圖檔
可以使用 pygame 中 image 模組的函數來上傳圖檔。圖檔和 python 執行檔存放在同一樣的資料夾,只需打檔名就可以直接使用(如下面的程式碼),若不同,則需要列出圖檔存放位置。

import pygame

def main():
    pygame.init()   # 初始化導入 pygame 的模組
    screen = pygame.display.set_mode((800, 600)) # 顯示的視窗並設置視窗大小
    pygame.display.set_caption('大球吃小球')   # 設置視窗標題
    screen.fill((255, 255, 255))    # 設置視窗背景顏色
    ball_image = pygame.image.load('ball.png')    # 透過指定的文件名上傳圖檔
    screen.blit(ball_image, (50, 50))   # 在視窗上展示圖像
    # 刷新視窗
    pygame.display.flip()
    running = True
    while running:  # 開啟一個事件循環處理發生的事件
        for event in pygame.event.get():    # 獲取事件並進行處理
            if event.type == pygame.QUIT:
                running = False

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190925/20121116XdCeL62LZu.png
實現動畫效果
將不連續的圖片連續的播放,只要每秒鐘達到了一定的幀數,那麼就可以做出比較流暢的動畫效果。
如果要讓小球動起來,可以將小球的位置用變量來表示,並在循環中修改小球的位置再刷新整個視窗即可。

import pygame

def main():
    pygame.init()   # 初始化導入 pygame 的模組
    screen = pygame.display.set_mode((800, 600)) # 顯示的視窗並設置視窗大小
    pygame.display.set_caption('大球吃小球')   # 設置視窗標題
    x, y = 50, 50   # 定義變數表示小球在視窗上的位置
    running = True
    while running:  # 開啟一個事件循環處理發生的事件
        for event in pygame.event.get():    # 獲取事件並進行處理
            if event.type == pygame.QUIT:
                running = False
        screen.fill((255, 255, 255))    # 設置視窗背景顏色
        # 繪製圓(參數為:框架, 顏色, 圓心位置, 半徑, 0 表示填滿)
        pygame.draw.circle(screen, (255, 0, 0,), (x, y), 30, 0)
        # 每隔50毫秒就改變小球的位置再刷新窗口
        pygame.display.flip()
        pygame.time.delay(50)
        x, y = x + 5, y + 5

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190925/20121116t3pYz8X291.png

大球吃小球

碰撞檢測和事件處理
碰撞檢測在絕大多數的遊戲中都是一個必須得處理的問題,pygame 的 sprite(動畫精靈)模組就提供了對碰撞檢測的支持。
但要檢測兩個小球碰撞其實非常簡單,只需檢查球心的距離有沒有小於兩個球的半徑之和。為了製造出更多的小球,透過對鼠標事件的處理,在點擊鼠標的位置創建顏色、大小和移動速度都隨機的小球。
可以在事件循環中對鼠標事件進行處理,通過事件對象的 type 屬性可以判定事件類型,再通過 pos 屬性就可以獲得鼠標點擊的位置。如果要處理鍵盤事件做法與處理鼠標事件類似。

from enum import Enum, unique   # 該模組可以用於定義名稱和值
from math import sqrt
from random import randint

import pygame
 # 名稱不可重複,值可以重複
 # 使用 @unique 限制值不能重複
@unique    
class Color(Enum):          # 顏色
    
    RED = (255, 0, 0)
    GREEN = (0, 255, 0)
    BLUE = (0, 0, 255)
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)
    GRAY = (242, 242, 242)

    @staticmethod
    def random_color():     # 獲取隨機顏色
        r = randint(0, 255)
        g = randint(0, 255)
        b = randint(0, 255)
        return (r, g, b)

class Ball(object):        # 球

    def __init__(self, x, y, radius, sx, sy, color = Color.RED): # 初始化
        self.x = x
        self.y = y
        self.radius = radius
        self.sx = sx
        self.sy = sy
        self.color = color
        self.alive = True

    def move(self, screen): # 移動
        self.x += self.sx
        self.y += self.sy
        if self.x - self.radius <= 0 or \
                self.x + self.radius >= screen.get_width():
            self.sx = -self.sx
        if self.y - self.radius <= 0 or \
                self.y + self.radius >= screen.get_height():
            self.sy = -self.sy

    def eat(self, other):   # 吃其他球
        if self.alive and other.alive and self != other:
            dx, dy = self.x - other.x, self.y - other.y
            distance = sqrt(dx ** 2 + dy ** 2)
            if distance < self.radius + other.radius \
                    and self.radius > other.radius:
                other.alive = False
                self.radius = self.radius + int(other.radius * 0.146)

    def draw(self, screen): # 繪製球
        pygame.draw.circle(screen, self.color,
                           (self.x, self.y), self.radius, 0)
def main():
    balls = []  # 定義裝所有球的容器
    pygame.init()   # 初始化導入 pygame 的模組
    screen = pygame.display.set_mode((800, 600))  # 顯示的視窗並設置視窗大小
    pygame.display.set_caption('大球吃小球')    # 設置視窗標題
    running = True
    while running:  # 開啟一個事件循環處理發生的事件
        for event in pygame.event.get():    # 獲取事件並進行處理
            if event.type == pygame.QUIT:
                running = False
            # 處理鼠標事件
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                x, y = event.pos    # 獲得點擊鼠標的位置
                radius = randint(10, 100)
                sx, sy = randint(-15, 15), randint(-15, 15)
                color = Color.random_color()
                # 在點擊鼠標的位置創建球(大小、速度和顏色隨機)
                ball = Ball(x, y, radius, sx, sy, color)
                balls.append(ball)  # 將球添加到列表容器中
        screen.fill((255, 255, 255))    # 設置視窗背景顏色
        # 取出容器中的球,如果没被吃掉就會繪出,被吃掉就會移除
        for ball in balls:
            if ball.alive:
                ball.draw(screen)
            else:
                balls.remove(ball)
        # 每隔50毫秒就改變球的位置再刷新窗口
        pygame.display.flip()
        pygame.time.delay(50)
        for ball in balls:
            ball.move(screen)
            # 檢查球有没有吃到其他的球
            for other in balls:
                ball.eat(other)


if __name__ == '__main__':
    main()

想要知道執行結果,可以複製程式碼自己試驗,也能體會其中的小樂趣。


上一篇
[Day09] 物件導向進階
下一篇
[Day11] 文件操作和異常處理
系列文
30天學python30

尚未有邦友留言

立即登入留言