Python入門教程100天:Day10-圖形使用者介面和遊戲開發

Python_Jack發表於2019-04-07

基於tkinter模組的GUI

GUI是圖形使用者介面的縮寫,圖形化的使用者介面對使用過計算機的人來說應該都不陌生,在此也無需進行贅述。Python預設的GUI開發模組是tkinter(在Python 3以前的版本中名為Tkinter),從這個名字就可以看出它是基於Tk的,Tk是一個工具包,最初是為Tcl設計的,後來被移植到很多其他的指令碼語言中,它提供了跨平臺的GUI控制元件。當然Tk並不是最新和最好的選擇,也沒有功能特別強大的GUI控制元件,事實上,開發GUI應用並不是Python最擅長的工作,如果真的需要使用Python開發GUI應用,wxPython、PyQt、PyGTK等模組都是不錯的選擇。

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

  1. 匯入tkinter模組中我們需要的東西。
  2. 建立一個頂層視窗物件並用它來承載整個GUI應用。
  3. 在頂層視窗物件上新增GUI元件。
  4. 通過程式碼將這些GUI元件的功能組織起來。
  5. 進入主事件迴圈(main loop)。

下面的程式碼演示瞭如何使用tkinter做一個簡單的GUI應用。

import tkinter
import tkinter.messagebox


def main():
	flag = True

	# 修改標籤上的文字
	def change_label_text():
		nonlocal flag
		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')
	label.pack(expand=1)
	# 建立一個裝按鈕的容器
	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()複製程式碼

需要說明的是,GUI應用通常是事件驅動式的,之所以要進入主事件迴圈就是要監聽滑鼠、鍵盤等各種事件的發生並執行對應的程式碼對事件進行處理,因為事件會持續的發生,所以需要這樣的一個迴圈一直執行著等待下一個事件的發生。另一方面,Tk為控制元件的擺放提供了三種佈局管理器,通過佈局管理器可以對控制元件進行定位,這三種佈局管理器分別是:Placer(開發者提供控制元件的大小和擺放位置)、Packer(自動將控制元件填充到合適的位置)和Grid(基於網格座標來擺放控制元件),此處不進行贅述。

使用Pygame進行遊戲開發

Pygame是一個開源的Python模組,專門用於多媒體應用(如電子遊戲)的開發,其中包含對影象、聲音、視訊、事件、碰撞等的支援。Pygame建立在SDL的基礎上,SDL是一套跨平臺的多媒體開發庫,用C語言實現,被廣泛的應用於遊戲、模擬器、播放器等的開發。而Pygame讓遊戲開發者不再被底層語言束縛,可以更多的關注遊戲的功能和邏輯。

下面我們來完成一個簡單的小遊戲,遊戲的名字叫“大球吃小球”,當然完成這個遊戲並不是重點,學會使用Pygame也不是重點,最重要的我們要在這個過程中體會如何使用前面講解的物件導向程式設計,學會用這種程式設計思想去解決現實中的問題。

製作遊戲視窗

import pygame


def main():
	# 初始化匯入的pygame中的模組
    pygame.init()
    # 初始化用於顯示的視窗並設定視窗尺寸
    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 __name__ == '__main__':
    main()複製程式碼

在視窗中繪圖

可以通過pygame中draw模組的函式在視窗上繪圖,可以繪製的圖形包括:線條、矩形、多邊形、圓、橢圓、圓弧等。需要說明的是,螢幕座標系是將螢幕左上角設定為座標原點(0, 0),向右是x軸的正向,向下是y軸的正向,在表示位置或者設定尺寸的時候,我們預設的單位都是畫素。所謂畫素就是螢幕上的一個點,你可以用瀏覽圖片的軟體試著將一張圖片放大若干倍,就可以看到這些點。pygame中表示顏色用的是色光三原色表示法,即通過一個元組或列表來指定顏色的RGB值,每個值都在0~255之間,因為是每種原色都用一個8位(bit)的值來表示,三種顏色相當於一共由24位構成,這也就是常說的“24位顏色表示法”。

import pygame


def main():
	# 初始化匯入的pygame中的模組
    pygame.init()
    # 初始化用於顯示的視窗並設定視窗尺寸
    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()複製程式碼

####載入影象

如果需要直接載入影象到視窗上,可以使用pygame中image模組的函式來載入影象,再通過之前獲得的視窗物件的blit方法渲染影象,程式碼如下所示。

import pygame


def main():
    # 初始化匯入的pygame中的模組
    pygame.init()
    # 初始化用於顯示的視窗並設定視窗尺寸
    screen = pygame.display.set_mode((800, 600))
    # 設定當前視窗的標題
    pygame.display.set_caption('大球吃小球')
    # 設定視窗的背景色(顏色是由紅綠藍三原色構成的元組)
    screen.fill((255, 255, 255))
    # 通過指定的檔名載入影象
    ball_image = pygame.image.load('./res/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()複製程式碼

####實現動畫效果

說到動畫這個詞大家都不會陌生,事實上要實現動畫效果,本身的原理也非常簡單,就是將不連續的圖片連續的播放,只要每秒鐘達到了一定的幀數,那麼就可以做出比較流暢的動畫效果。如果要讓上面程式碼中的小球動起來,可以將小球的位置用變數來表示,並在迴圈中修改小球的位置再重新整理整個視窗即可。

import pygame


def main():
    # 初始化匯入的pygame中的模組
    pygame.init()
    # 初始化用於顯示的視窗並設定視窗尺寸
    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))
        pygame.draw.circle(screen, (255, 0, 0,), (x, y), 30, 0)
        pygame.display.flip()
        # 每隔50毫秒就改變小球的位置再重新整理視窗
        pygame.time.delay(50)
        x, y = x + 5, y + 5


if __name__ == '__main__':
    main()複製程式碼

碰撞檢測

通常一個遊戲中會有很多物件出現,而這些物件之間的“碰撞”在所難免,比如炮彈擊中了飛機、箱子撞到了地面等。碰撞檢測在絕大多數的遊戲中都是一個必須得處理的至關重要的問題,pygame的sprite(動畫精靈)模組就提供了對碰撞檢測的支援,這裡我們暫時不介紹sprite模組提供的功能,因為要檢測兩個小球有沒有碰撞其實非常簡單,只需要檢查球心的距離有沒有小於兩個球的半徑之和。為了製造出更多的小球,我們可以通過對滑鼠事件的處理,在點選滑鼠的位置建立顏色、大小和移動速度都隨機的小球,當然要做到這一點,我們可以把之前學習到的物件導向的知識應用起來。

from enum import Enum, unique
from math import sqrt
from random import randint

import pygame


@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
               a 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)複製程式碼

事件處理

可以在事件迴圈中對滑鼠事件進行處理,通過事件物件的type屬性可以判定事件型別,再通過pos屬性就可以獲得滑鼠點選的位置。如果要處理鍵盤事件也是在這個地方,做法與處理滑鼠事件類似。

def main():
    # 定義用來裝所有球的容器
    balls = []
    # 初始化匯入的pygame中的模組
    pygame.init()
    # 初始化用於顯示的視窗並設定視窗尺寸
    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(-10, 10), randint(-10, 10)
                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)
        pygame.display.flip()
        # 每隔50毫秒就改變球的位置再重新整理視窗
        pygame.time.delay(50)
        for ball in balls:
            ball.move(screen)
            # 檢查球有沒有吃到其他的球
            for other in balls:
                ball.eat(other)


if __name__ == '__main__':
    main()複製程式碼

上面的兩段程式碼合在一起,我們就完成了“大球吃小球”的遊戲(如下圖所示),準確的說它算不上一個遊戲,但是做一個小遊戲的基本知識我們已經通過這個例子告訴大家了,有了這些知識已經可以開始你的小遊戲開發之旅了。其實上面的程式碼中還有很多值得改進的地方,比如重新整理視窗以及讓球移動起來的程式碼並不應該放在事件迴圈中,等學習了多執行緒的知識後,用一個後臺執行緒來處理這些事可能是更好的選擇。如果希望獲得更好的使用者體驗,我們還可以在遊戲中加入背景音樂以及在球與球發生碰撞時播放音效,利用pygame的mixer和music模組,我們可以很容易的做到這一點,大家可以自行了解這方面的知識。事實上,想了解更多的關於pygame的知識,最好的教程是pygame的官方網站,如果英語沒毛病就可以趕緊去看看啦。 如果想開發3D遊戲,pygame就顯得力不從心了,對3D遊戲開發如果有興趣的讀者不妨看看Panda3D


相關文章