《從零開始PYTHON3》第十四講
通常來說,Python解釋執行,運行速度慢,並不適合完整的開發游戲。隨着電腦速度的快速提高,這種情況有所好轉,但開發游戲仍然不是Python的重點工作。
大多應用是利用Python開發效率高的特點,進行游戲原型驗證,或者在大的游戲系統中,使用Python進行地圖、場景等定制。還有就是使用游戲開發的技術和理念,將Python用於商業視覺展示、工程效果展示。
原型驗證:指的是有了一個好的游戲想法,完整的開發出來肯定需要大量的人員、費用、時間,利用Python編程簡單高效的特點,先模擬完成一部分游戲的功能,從而能夠展示給投資人、客戶,獲取大家的認可,進而得到經費投入。
地圖、場景定制:游戲的開發肯定需要很多專業技術方面的高精尖人才,但游戲的運營、地圖的設計、故事情節等。這都是商業或者藝術方面的專業強項,而這些人員不大可能使用c/c++等常用的游戲開發工具來做這些工作。因此,游戲開發過程中,通常完成Python語言的接口,讓這些商業、藝術工作人員也能使用比較方便的手段進行游戲功能的調整。
此外,現代的游戲開發已經是一個大團隊合作的產物,已經非常難以單打獨斗完成一款游戲。所以學習游戲編程的目標並不是希望自己獨立完成一個游戲,而是用這種思路來解決具體問題。
通常游戲開發的工作分工是這樣的:
其中音效、畫面都會由更專業的團隊完成。最后由程序人員集成在游戲中。在游戲中,音樂音效、操作控制、游戲邏輯、畫面幾個部分,都是並行在同時進行的。它們必須共同生效,游戲才會好玩。
Pygame編程和音樂播放
Pygame是一個強大的游戲擴展包,首先也是安裝:
#使用管理員模式啟動cmd命令行,然后執行:
pip install pygame #某些系統是pip3 install pygame
這個安裝擴展包的過程,我們重復了很多遍,這個算是最后一遍了。因為Pygame是我們課程講解的最后一個擴展包。比起來其它的軟件,Python的擴展包,只要你知道了名字,安裝幾乎都是相同的。即便不同的操作系統,差別也不大。
在這一講,我們會采用跟以前不同的方法來講述Pygame擴展包的使用。原因是Python有非常多的擴展包。即便官方內置的擴展包,也量非常大。如果完全等待別人教你使用這種方式是不可能的,此外即便是別人教過了,Python和擴展包的升級也非常的快。原有的使用方法,很可能現在已經不適用了。這些都要求你有自己探索的能力,在Python基本技能的學習掌握之后,根據自己的編程需求,選擇相應的擴展包,查找資料、文檔。在網上資料的幫助下,掌握擴展包的使用方法。
從目前行業內的使用情況看,最大的障礙在於目前主要的文檔來源都是英文的,這要求我們具備一定的英文閱讀能力。此外,雖然版本的更新對擴展包的使用有一些差別,但這種差別畢竟不算大。所以在國內一些相對較早的文檔幫助下,再對應國外新版本的文檔,也能降低你的學習門檻。
只是播放mp3,Python有很多擴展包可以選,很多操作起來也更簡便。不過pygame是為了游戲設計,除了背景音樂,音效、與畫面的協作也考慮的更多。所以雖然用起來復雜一些,我們依然還是選擇學習用Pygame播放mp3音樂。目的,更多是期望學習者除了學習python相關的知識,也更多理解現代計算機並發多任務和多種約束條件下的編程思維。
拿到一個新的擴展包,通常你有這樣幾種途徑了解它的使用:
-
到官網查看官方文檔(通常是英文)
-
在搜索引擎網站比如百度搜索中文的資料,這種情況比較多見,因為大多情況下,你之所以知道這個擴展包,也是在網上搜索相關資料的時候,別人介紹的。而通常這種情況下,都已經有包簡單實用的介紹。
-
使用Python內置的dir()/help()函數,當前還是英文資料,適合已經了解擴展包的基本架構,只是在函數選擇、調用的時候查找資料
所以,實際上,通過搜索引擎查找相關資料,應當是你上手的最優選擇。以pygame為例,通過查找中文的資料,總結之后,應當能寫出這樣的程序:
#MP3播放器
#引入擴展庫
import pygame
#歌曲文件
file='rongHua.mp3'
#初始化聲音庫
pygame.mixer.init(frequency=44100)
print("播放音樂-絨花")
#載入音樂文件
pygame.mixer.music.load(file)
#播放聲音
pygame.mixer.music.play()
程序每一條語句都有注釋,大概的框架上看,應當也是順序執行的。有一些參數可能你還不能明白,比如frequency=44100
,不過應當不影響你抄過來用。這個是指定音頻庫使用的采樣頻率,44100一般已經是高保真音樂的采樣頻率了。通常mp3文件都是這種格式。另外忘了交代,rongHua.mp3是我們要播放的聲音文件名稱,記得要提前准備好,放到程序同一個目錄。
執行程序之后發現,詭異的事情發生了,程序只顯示了一行文字:“播放音樂-絨花”,然后就退出了,並沒有事情發生,也沒有音樂播放出來。
一開始就說過了,本講重點不完全是播放一首音樂,而是希望能引導大家使用探索的方式,來了解一個新的擴展包如何學習和使用。所以不要等待着我說出答案,而是積極的思考,判斷出現了什么問題,並且嘗試去解決。
首先要說明的是,程序本身引入pygame庫、庫的初始化還有播放語句語句本身都並沒有什么錯誤。通常在網上查找資料的時候,只要認真閱讀,比較容易保證這一點。難以馬上學會並應用到編程中的,是關於某個庫“架構”方面的內容,也就是影響程序結構方面的內容。如果覺得這句話比較抽象的話,你可以回憶一下上一講我們嘗試過的flask網絡編程框架。框架、架構,這兩個詞在這里基本可以划等號了。
我們的程序沒有能播放出來音樂,也是這方面的原因。
通常游戲程序要包含至少4部分的內容,我們用本講開始的那張圖來說明,音樂、畫面、操控、邏輯這四部分內容是並行運行,相互配合,才能展現給用戶一個圖文並茂、流暢、吸引人的游戲。
因此作為游戲的一部分,音樂的播放也不可能像我們前面學過的繪圖、計算等操作一樣,在音樂沒有播放完成前,程序停止在那里一直等待。事實上通常游戲的做法都是,發出播放音樂的命令之后,命令本身馬上返回,讓程序有能力並行去處理按鍵輸入、繪圖等動作。
而在我們上面的程序中,播放這個命令肯定是發出去了,但沒有等音樂聲響起,程序就已經結束退出了。程序的結束退出將自動的釋放程序打開的各項資源,清理運行的痕跡,從而音樂也就不可能再放出來了。
這僅僅是我們推測分析的結果,我們來證明一下,方法就是在程序最后增加一行語句:
#程序等待5秒鍾
pygame.time.delay(1000*5)
使用這樣語句的目的是,如果我們上面的推測成立,那肯定要對程序做結構上的調整。這個工作量會比較大,所以我們先使用簡單的語句來驗證一下我們的思考。
再次運行程序,你會聽到音樂響了5秒鍾,然后程序退出,音樂也停止了。
這基本可以證明,我們的思考正確。此外似乎還有些別的問題,比如音樂一開始有一個“破音”,這讓人感覺不好。而且程序似乎有的時候能正常播放,有的時候還是不穩定,無法播放成功。
下面要如何改進程序呢?
通常我們會繼續在網上搜索pygame模塊使用的案例,閱讀別人的程序,有的時候運氣好,你碰到的程序代碼,跟你想寫的代碼是完全相同的功能,這時候你可以拷貝過來直接使用。但大多時候,你只能找到功能相近的代碼,所以仍然需要你閱讀別人的程序,並從其中學習對你有用的部分。
比如,你可能搜索到我們第一講演示的游戲,其中當然也有聲音處理的部分,你會重點閱讀這部分的代碼,來找出同自己程序的區別,以求解決問題。
在這個過程中,我們又做出了一些判斷,當然這些判斷依然需要大量程序的經驗,所以並不能要求初學者也能輕易做到。但復雜的做不到,你可以從簡單的入手,逐漸積累。這里只是想告訴你正確的學習思路:
- Pygame作為一個游戲開發庫,聲音的播放需要依賴一個窗口,也就是游戲的畫面。沒有窗口的情況下,播放進程無法穩定的工作。這一項原因推測來自於,很多網上找到的代碼,在聲音處理上並沒有太多不同,但能正常工作,所以會有這樣的猜測。
- Python的各個功能,初始化一般意味着建立各項必須的資源,完成工作后,退出之前,應當釋放掉這些資源,特別是系統公用的聲音、顯示等,如果程序只是退出,沒有釋放,就可能導致再次運行的時候,聲音無法正確完成初始化,畢竟一個系統的設備,是被所有程序所公用的。
- 系統本身原因,不能快速的連續的初始化及釋放,兩次運行之間應當等待片刻。這個判斷,在多次運行程序,查找規律的過程中,能很快的發現,當然需要你足夠的細心觀察。
- “破音”是因為在聲音設備初始化后,尚未穩定之前就開始發送音頻數據,此時的數據無法被正常解析,造成破音。這僅為猜測,需要實驗的證實。
驗證思考最好的辦法就是修改程序,然后再次運行實驗,因此我們再完成一版程序:
#引入擴展庫
import pygame
#歌曲文件
file='rongHua.mp3'
#初始化pygame顯示庫
pygame.display.init()
#打開一個窗口
screen = pygame.display.set_mode([200,100])
#初始化pygame聲音庫
pygame.mixer.init(frequency=44100)
print("播放音樂-絨花")
#載入音樂文件
pygame.mixer.music.load(file)
#保存當前音量
v = pygame.mixer.music.get_volume()
#設置為靜音,防止開始的爆破音
pygame.mixer.music.set_volume(0)
#播放聲音
pygame.mixer.music.play()
#延時0.2秒打開聲音,避過爆破音
pygame.time.delay(200)
pygame.mixer.music.set_volume(v)
#播放5秒鍾
pygame.time.delay(1000*5)
#停止播放
pygame.mixer.music.stop()
#退出聲音庫和顯示庫
pygame.mixer.quit()
pygame.display.quit()
每一行代碼都有注釋,我只講解跟上一版不同的代碼:
- 初始化的時候打開一個窗口,雖然什么也沒有顯示,但讓播放器有了載體。
- 一開始關閉聲音,延時再打開音量,避開一開始的爆破音。
- 程序退出前關閉播放,釋放各項資源。
此外這些工作中,用到了很多新的函數,這些函數一開始你並不可能知道。這些函數的學習一般是兩個方向,一是概要的瀏覽pygame的手冊或者幫助,在心中有一個粗的概念,這樣用到什么功能的時候,你會想起來可能有某個函數能完成這個功能,然后再精細查看。第二是希望用到某個功能,在網上查找使用Python或者pygame如何做到這個功能。當然還有另外一種渠道,有可能你直接搜索到了功能相近的代碼,從中間直接抄過來使用。
試運行之后我們開心的發現,穩定性問題和爆破音都解決了,剩下最關鍵的,如何完整的播放音樂文件?
這涉及到了我們前面講過的程序結構問題,也是一個框架型的程序庫對程序結構的要求。這一部分一般沒有好辦法,只能通過閱讀官方的文檔或者閱讀其它程序的成熟代碼來獲取,這個過程一般會較長。好在我們大多情況下不會上來就碰到這么復雜的問題,都是循序漸進。並且大多的擴展包只是增加功能性的函數,並不要求程序的結構有多少改變。
我們通過一張對比圖來說明pygame對程序結構的要求:
傳統程序雖然我們不怎么熟悉聲音處理,但結構我們都比較熟悉。程序中可能有循環,但總體是串行執行的,完成一件事情,才去做另外一件。
從外觀上看,右側的游戲程序結構,跟左側不過多了一個循環。但你要記得,這里面每一項都是並行執行的,每一個步驟並不會等待這一項工作做完,就會返回接受新的命令,所以程序的聲音、圖像、程序邏輯、鍵盤控制,才可能一起發生作用。
這種並行處理的程序,同傳統的程序比,有很多不可協調的理念區別,pygame為了做到並行,采用了“事件驅動”的理念來完成這種控制。
事件驅動實際是存在很久的編程方式了,一般傳統的Windows程序,都使用微軟公司提供的消息循環,來處理所有的窗口事件。Python pygame的事件處理,也是采用類似的機制。
總結一下使用事件驅動的方式來編寫pygame程序的要點:
- 聲音、圖像、鍵盤鼠標輸入、游戲邏輯必須並行進行,任何一個局部不能長時間無限制的執行(網絡編程實際也是並行的,但在小型網站項目中,沒有體現那么清晰和嚴格)
- 各個環節之間的同步、配合,都是通過互相發送消息的方式來完成的。從獨立一個功能(模塊)角度來看,往往是得到某個消息之后,開始進行某項任務,這種方式叫做事件驅動
- 各種消息都是通過核心的消息傳遞模塊完成的,程序的主循環一般就是不停的讀取消息,根據消息的定義分發給不同模塊,並執行不同功能,也稱為消息循環
我們根據剛才這些理念,重新改寫程序,這個程序最終形成code4.py,這里只介紹重點的消息循環部分:
#... 初始化及基本播放代碼忽略...
#自定義一條消息(一個事件)用於表示播放結束
#pygame.USEREVENT是pygame中預定義的用戶消息起始值
MUSIC_END = pygame.USEREVENT + 1
#設置當前音樂播放完成后,發送自定義的消息
pygame.mixer.music.set_endevent(MUSIC_END)
#延時0.2秒打開聲音,避過爆破音
pygame.time.delay(200)
pygame.mixer.music.set_volume(v)
#定義一個退出程序標志
requireQuit = False
#程序主循環
while not requireQuit:
#循環接受各種事件
for event in pygame.event.get():
#如果是自定義的播放完成消息
if event.type == MUSIC_END:
requireQuit=True #退出
break
#界面窗口菜單關閉申請
elif event.type == pygame.QUIT:
requireQuit=True
break
#有鍵盤抬起
elif event.type == pygame.KEYUP:
#q鍵
if event.key == pygame.K_q:
requireQuit=True
break
#... 退出操作 ...
程序中,我們自己定義了一條消息。所謂消息,並不是平常人類喜聞樂見的一條短信或者語音,其實就是一個整數數字。為了容易記憶,我們當然自己定義了一個變量名來代表它,但實際它就是一個數字。
原因是對計算機來講,其實一切都是數字,我們用一個字符串反而讓計算機執行的更慢。
隨后,因為我們的消息循環中肯定還可能嵌套循環,一個break語句只能打破內部的循環,並不能讓外部循環也退出,所以我們定義了一個bool的變量,來表示程序是否需要退出循環。
這里的消息循環從技術上並沒有啥難度,主要是你需要適應這么多新的函數和預定義的變量(這里當然當做常量來用,比如表示pygame需要退出)。
在內部循環中,我們判斷了三種可能需要退出的消息。一是自己定義的,如果音樂播放結束,應當退出;二是用戶用鼠標關閉窗口,程序應當退出;三是按q鍵表示用戶希望退出播放。
按下按鍵游戲采取相應動作是很常見的游戲處理工作,我們在這里等待用戶按下按鍵然后再松開的這一刻退出,這樣防止用戶按下q鍵一直沒有松手所導致的程序退出后,屏幕上還會出現很多q字符的情況。
現在的程序已經能正常的播放音樂了,實際上我們的程序還能進一步優化。比如1.添加播放的時間顯示;2.向前向后跳轉播放。
這兩個功能都可以在消息循環中處理,這樣程序才是並行的。現在你可能感覺到了,實際上消息循環中,才是程序的主要邏輯。的確如此,其實所有的游戲基本都是在消息循環中做所有的主要工作,當然具體工作細節,都是由已經定義好的函數或叫子程序來具體執行完成的,在主循環中,只是對這些函數的組織、管理和調用。
顯示播放位置:
#程序主循環
while not requireQuit:
#獲取當前播放位置
pos=pygame.mixer.music.get_pos()
#顯示
print("Playing:", pos,end='\r')
消息循環中,在按鍵部分添加代碼:
#如果是向右鍵,則前跳10秒
elif event.key == pygame.K_LEFT:
pygame.mixer.music.set_pos(pos/1000-10)
#如果是向左鍵,則后跳10秒
elif event.key == pygame.K_RIGHT:
pygame.mixer.music.set_pos(pos/1000+10)
這樣的功能增加,依賴於你對pygame擴展庫越來越熟悉,通過閱讀文檔,發現pygame擴展庫能提供什么樣的功能。而這個功能你又需要,就可以加入到程序中。
練習時間
其實本講可以說從開始到現在都是挑戰,因此沒有再設置單獨的挑戰環節。
我們直接進入練習的環節:
- 以本講前面最終版代碼code5.py為藍本,修改程序,實現由命令行參數接受mp3文件名,並播放
- 除了q鍵之外,請設定ESC鍵也作為退出按鍵。提示,ESC鍵的代碼為:pygame.K_ESCAPE
本講小結
- python並不是很適合進行游戲編程,但游戲編程的學習能讓你的程序更友好,並具有豐富的表現力
- 並行、事件驅動的編程思想,是現代程序開發的前沿思想,對於提高程序的效率和穩定性有重要的幫助
- 在一個新模塊的學習中,循序漸進,逐步完善代碼是常用的一種手段。在本講,我們更側重講述,你接觸到一個新的擴展包,如何查找資料、分析問題,最終掌握它的使用
練習答案
請參考mp3Player.py程序。
(所有本系列中出現、使用過的源碼將會在連載完成后統一整理提供下載。)