治療面對pygame的mouse.get_pressed()和MOUSEBUTTONDOWN時的選擇障礙

閱讀時間約 10 分鐘

「小孩子才做選擇!」是句大家耳熟能詳的戲謔話,絕大多數的人在說這句話時,真正的實情是:全部都想要,但是沒能力全拿,有得選就偷笑了!

就是因為全部都想要,但是沒能力全拿,所以產生了選擇障礙這種症狀。其實,有個很簡單的辦法可以治療選擇障礙這個症狀:就丟銅板咩!反正自己下不了決心,那就讓老天爺幫忙決定不就好了?

雖然說一個銅板就能去除選擇障礙帶來的麻煩,但是寫程式時可不能這麼做。寫程式遇到有不同的東西都可以用來處理同一件事時,得先搞清楚,這些不同的東西之間到底有什麼差異?是不是在使用上有不同的限制?如果沒搞清楚就亂用一通,或許一時之間程式跑起來沒什麼不對勁,但那說不定是走了狗屎運,沒採到雷,等到處理的問題改變了,或者操作方式不一樣了,說不定就會出現完全想像不到的結果。最近在使用pygame偵測滑鼠動作的功能時,就充分體現了這種狀況。

原本是要寫一個模擬砲彈被大砲射出之後是如何運動的程式,因為覺得一執行程式就看到砲彈在空中飛,實在是有點太單調,所以就想著加入發砲的功能,也就是當按一下滑鼠左鍵時,就發射一顆砲彈。

這功能寫起來其實挺簡單的,只要能偵測到滑鼠左鍵有沒有被按下就可以了。查了一下,在pygame中,可以用mouse.get_pressed()或MOUSEBUTTONDOWN來偵測滑鼠是不是被按下,不過MOUSEBUTTONDOWN還牽涉到什麼event不event的,感覺用起來挺麻煩的。既然兩個都可以用來偵測滑鼠是不是被按下,那當然選用比較不麻煩的mouse.get_pressed()囉!

mouse.get_pressed()的用法很簡單,它會傳回一個tuple,裡頭放著三個boolean值,分別代表滑鼠左鍵、通常是滾輪的中間鍵、右鍵的狀態。如果按鍵是按下的,boolean值就是True,不然就是False。所以要偵測滑鼠左鍵是不是被按下,只要檢查mouse.get_press()[0]是不是True就可以了。

程式寫好之後,就開始測試看看效果如何。一開始還挺順的,一面點著滑鼠,一面看著砲彈滿天飛,還真是有點療癒。可是……怎麼覺得哪邊怪怪的。

咦?!為什麼同樣是按一下滑鼠,有時候會發射一顆砲彈,有時候卻會發射三顆砲彈?這可真是奇怪!明明程式是這樣寫的

while True:
    :
    if pygame.mouse.get_pressed()[0]:
        # 發射砲彈

看起來一點問題都沒有啊!那怎會這樣?如果這樣寫不行,那要怎樣寫才能按一下發射一顆砲彈?

爬文加上寫些小程式驗證自己的想法,總算搞清楚是怎麼一回事了。

寫小程式驗證想法,這個做法很重要,畢竟不管是網路上討論的文章或官網的文件,都不見得能面面俱到所有的小細節。有時候人家認為理所當然可以一句話帶過的觀念,卻是自己百思不得其解的大疑問。這時候,就只能靠自己寫程式,實際去驗證想法對不對了。

那為什麼會出現按一下發射三顆砲彈的情形呢?原來,這一切都因為mouse.get_pressed()傳回的是滑鼠按鈕的「狀態」,而不是「動作」。所以,只要呼叫mouse.get_pressed()時,滑鼠某個按鈕處於按下的狀態,對應於那個按鈕的傳回值,就會是True。因為從滑鼠按鈕按下到放開之間的時間雖然很短,但如果在這當中呼叫了不只一次mouse.get_pressed(),那就會被誤判為按了不只一次的滑鼠,導致出現明明只按了一次滑鼠,但卻發射了三顆砲彈的情況。

既然mouse.get_pressed()會有實際只按一次,但卻誤判為按了好幾次的情形,那豈不是沒什麼用處?畢竟那簡直就像是滑鼠用太久,已經快掛掉的症狀。其實,在玩射擊遊戲時,mouse.get_pressed()這樣的函數倒是挺好用的。滑鼠按著不放,子彈就砰砰砰砰砰連續發射,這時候巴不得發射越多越好,先前以為的缺點,這時候可不再是缺點,而是大大的優點啊!

mouse.get_pressed()在使用上,還有一個地方要注意的,那就是有可能滑鼠明明就按下又放開了,但卻沒偵測到。這也不難理解,這種情形會發生在滑鼠按鍵按下去再放開的過程中,mouse.get_pressed()根本就沒有被執行到。例如,當設定的fps不高或程式跑很慢時,有可能在滑鼠按鍵已經放開了之後,才執行到mouse.get_pressed(),這時候傳回來的滑鼠狀態,當然就是按鍵放開的狀態囉!

總之,mouse.get_pressed()就是個往事不需回首、過去了的就過去了,只活在當下,還有可能會恍神漏接的傢伙。

看來想要按一下滑鼠就只發射一顆砲彈,是不能用mouse.get_pressed()來處理了。那怎麼辦呢?沒有選擇之下,只好摸摸鼻子,用那個牽涉到什麼event不event的MOUSEBUTTONDOWN了。

什麼是event呢?翻譯成中文就是「事件」,就是某件發生的事情,例如滑鼠按鍵被按下或放開、滑鼠被移動、鍵盤被按下或放開等。在pygame中,定義了許多事件,如MOUSEBUTTONDOWN、MOUSEBUTTONUP、MOUSEMOTION、KEYDOWN、KEYUP、QUIT等,而使用者也可以自己定義需要的事件。

pygame利用事件佇列(event queue)來管理事件訊息的傳遞。每當有事件發生,例如滑鼠被按下時,就把這事件放到事件佇列裡頭去排隊等著處理,先排先處理。事件佇列有一定的容量,當放滿時,後續想要來排隊的事件,就會被默默地丟棄,彷彿從來不曾存在一般。

既然有事件發生時,就會被送入事件佇列中去等著執行,所以MOUSEBUTTONDOWN就不會像mouse.get_pressed()一樣,有漏接的情況出現。從這裡就可以看出來,MOUSEBUTTONDOWN的個性和mouse.get_pressed()是完全相反的。mouse.get_pressed()只活在當下,完全不管過去發生過什麼事;MOUSEBUTTONDOWN則會記得所有還沒處理完的事,只要事件佇列還沒被塞滿的話。

總之,跟mouse.get_pressed()的做法不同,MOUSEBUTTONDOWN偵測的是「動作」,也就是狀態的變化,而不是「狀態」。本來嘛,「事件發生」裡頭的「發生」這個詞,就是指「從沒有變有」,或「從有變沒有」,這種狀態的改變,不是嗎?

就因為MOUSEBUTTONDOWN偵測的是「動作」,而同一個滑鼠按鍵被按下這個動作,是不可能連續發生的,中間一定會有滑鼠按鍵被放開這個動作。所以,想要按一下滑鼠就只會發射一顆砲彈,MOUSEBUTTONDOWN就會是這種情況下的必然選擇。

治好了面對mouse.get_pressed()和MOUSEBUTTONDOWN時的選擇障礙後,接下來就是要搞清楚,使用MOUSEBUTTONDOWN時,要怎麼分辨被按下的,是滑鼠的哪個按鍵。

由pygame所定義的事件,都會帶有根據事件類型所設定的屬性,透過這些屬性,就可以知道事件更進一步的詳細資料。以MOUSEBUTTONDOWN來說,透過button這個屬性,就可以知道被按下的是哪個按鍵。當button裡頭放的值是1時,代表滑鼠左鍵被按下;是2時,代表按下的是中間鍵;是3時,則按下的是右鍵。

到這裡,總算搞清楚MOUSEBUTTONDOWN是怎麼運作的了。不過,要怎麼知道在事件佇列裡頭,有MOUSEBUTTONDOWN這個事件在排隊等著被處理?這個倒也不是太難辦到,只要利用event.get()把佇列裡頭的所有東西都拿出來,然後一個一個檢查,就可以知道了。

整合一下所有的東西,要想做到按一下滑鼠左鍵發射一顆砲彈的功能,程式可以這樣寫:

for event in pygame.event.get():
    if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                # 發射一顆砲彈

最後,關於event.get(),有個細節應該要知道:event.get()是把在事件佇列裡頭的東西拿出來,不是複製出來,而且拿出來之後,不會再放回去。所以呼叫完event.get()之後,事件佇列是被清空的。先前提到過,事件佇列滿了之後,新的事件會被默默地丟棄。現在既然event.get()會把事件佇列清空,那就不需要去擔心這個問題了,因為通常event.get()會不斷地被呼叫,這樣才能知道是不是有新發生的事件需要處理,這也是為什麼使用pygame的程式,通常會看到下面的程式片段的原因:

while True:
    for event in pygame.event.get():
         if event.type == pygame.QUIT:
             :

發射砲彈的完整程式,可以在
《The Nature of Code閱讀心得筆記——使用Python實作》《The Nature of Code閱讀心得與Python實作》第3.2節Exercise 3.3中找到。

avatar-img
15會員
131內容數
寫點東西自娛娛人
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
ysf的沙龍 的其他內容
身為頂級藝術細胞缺乏者,總是非常羨慕那些能隨手畫出漂亮圖畫的人。看著自己寫的程式在螢幕上展現出事先無法預期的漂亮圖案,還真是很有成就感,也不免有些興奮地想著:「嘿!看來頂級藝術細胞缺乏者有藥醫了!」 這個圖
忽然覺得,似乎、彷彿、好像、應該有個for... else語法,可以讓程式漂亮一點。
就這樣,因為拖延症犯了想偷懶,結果是對dictionary的使用時機和使用方式,有了新的體會。原來,dictionary這樣用,也是可以的啦!
「蛤?!居然當機!」瞪著畫面凍結的螢幕,心裡一面嘀嘀咕咕,一面敲著鍵盤,企圖死馬當活馬醫,看看能不能免去重開機的麻煩。 一切的努力都是徒然,這是徹底的當機!滑鼠、鍵盤完全失去作用,只餘關電源強迫關機一條路可走。 在重開機的當兒,一面看著螢幕有沒有顯示異常的訊息,一面開始分析可能的當機原因。
看來這應該是pygame的bug,而不是自己寫的程式有問題。為了進一步證實這個猜測,重寫了一個單純只畫出圓球的程式,除了畫出不同位置的圓球之外,沒有任何其他作用
俗話說「萬事起頭難」還真是一點也沒錯,從開始動筆寫《The Nature of Code閱讀心得筆記——使用Python實作》,到寫完頭一章,再到把文章放上網站開始發表,總共隔了快三個月的時間。
身為頂級藝術細胞缺乏者,總是非常羨慕那些能隨手畫出漂亮圖畫的人。看著自己寫的程式在螢幕上展現出事先無法預期的漂亮圖案,還真是很有成就感,也不免有些興奮地想著:「嘿!看來頂級藝術細胞缺乏者有藥醫了!」 這個圖
忽然覺得,似乎、彷彿、好像、應該有個for... else語法,可以讓程式漂亮一點。
就這樣,因為拖延症犯了想偷懶,結果是對dictionary的使用時機和使用方式,有了新的體會。原來,dictionary這樣用,也是可以的啦!
「蛤?!居然當機!」瞪著畫面凍結的螢幕,心裡一面嘀嘀咕咕,一面敲著鍵盤,企圖死馬當活馬醫,看看能不能免去重開機的麻煩。 一切的努力都是徒然,這是徹底的當機!滑鼠、鍵盤完全失去作用,只餘關電源強迫關機一條路可走。 在重開機的當兒,一面看著螢幕有沒有顯示異常的訊息,一面開始分析可能的當機原因。
看來這應該是pygame的bug,而不是自己寫的程式有問題。為了進一步證實這個猜測,重寫了一個單純只畫出圓球的程式,除了畫出不同位置的圓球之外,沒有任何其他作用
俗話說「萬事起頭難」還真是一點也沒錯,從開始動筆寫《The Nature of Code閱讀心得筆記——使用Python實作》,到寫完頭一章,再到把文章放上網站開始發表,總共隔了快三個月的時間。
你可能也想看
Google News 追蹤
Thumbnail
今天要實作和體驗的是拼單字的小遊戲,類似小時候在報紙、英文童書、或著電子辭典的小遊戲,一開始都是空白,隨著使用者拼對而逐漸顯示原本的單字樣貌,直到整個單字拼出來為止。 場景: 電腦隨機從單字庫裡面撈一個單字出來。 讓使用者扮演玩家去玩拼單字的遊戲。
Thumbnail
這篇內容,將透過實戰教學,來講解「滑鼠點方塊」的程式碼。包括如何測試遊戲、座標系統、自訂參數和內建參數、if else、and、遊戲的邏輯設計、程式碼解析。
Thumbnail
這篇內容,將透過實戰教學,介紹GameMaker中的Sprite。包括建立新的Sprite、重新命名及刪除、建議的命名方式、編輯圖像、調整圖像大小、動畫的概述、原點設置、碰撞遮罩的概述。
這個系列的教學會列出基本上所有常見的造型和一些基礎 , 讓各位可以自行搭配造型~在這個第五篇呢 , 我們會來一起了解: 常見的按鈕樣式特性,怎麼使用他們,他們的作用是甚麼。讓我們一起了解如何自訂滑鼠以及使用常見的按鈕樣式特性,學會讓按鈕播放音效,切換滑鼠造型,以及避免圖片透明的地方被點擊到。
當發出「不要」的心情時,是為什麼而不要?不要體驗?不想要體驗?因為沒有好處? 像打拳一樣,一件事一件事地去處理與解決 為什麼不能像打拳那樣豪邁?怕痛?怕輸?怕日子過不下去?
Thumbnail
這篇教學專注在使用 Renpy 讓選項在選擇後消失和一個一個出現的功能上。透過建立變量和修改選項,讓玩家在選擇後影響後續劇情發展。教學將提供範例代碼和具體步驟,讓讀者能夠輕鬆上手。
Thumbnail
小問接過搖桿,嘗試操作上面的按鍵和方向鍵,一時間也不知道這是要做什麼,正想開口詢問時,劉富安已經打開遊戲,並熟練地使用搖桿操作畫面裡的光標,進行選項的選擇。 遊戲很快地就進入人物選擇畫面,劉富安便說:「你可以選擇喜歡的人物,要用……」 劉富安正想教小問如何使用搖桿選擇人物,卻沒想到他動作飛快地選
Thumbnail
在人生中總是會遇到許多選擇,做了不同的選擇產生的結果也會不一樣,大多時候都是在為了要吃什麼在抉擇,小孩才做選擇大人全部都要,全部都要何嘗也不是一個選擇。 在Python程式語言中也有選擇的語法,就是If Else,如果是就做什麼,不是就做什麼,有別於其他程式語言,他不一定要有else,可以只有If
Thumbnail
今天要實作和體驗的是拼單字的小遊戲,類似小時候在報紙、英文童書、或著電子辭典的小遊戲,一開始都是空白,隨著使用者拼對而逐漸顯示原本的單字樣貌,直到整個單字拼出來為止。 場景: 電腦隨機從單字庫裡面撈一個單字出來。 讓使用者扮演玩家去玩拼單字的遊戲。
Thumbnail
這篇內容,將透過實戰教學,來講解「滑鼠點方塊」的程式碼。包括如何測試遊戲、座標系統、自訂參數和內建參數、if else、and、遊戲的邏輯設計、程式碼解析。
Thumbnail
這篇內容,將透過實戰教學,介紹GameMaker中的Sprite。包括建立新的Sprite、重新命名及刪除、建議的命名方式、編輯圖像、調整圖像大小、動畫的概述、原點設置、碰撞遮罩的概述。
這個系列的教學會列出基本上所有常見的造型和一些基礎 , 讓各位可以自行搭配造型~在這個第五篇呢 , 我們會來一起了解: 常見的按鈕樣式特性,怎麼使用他們,他們的作用是甚麼。讓我們一起了解如何自訂滑鼠以及使用常見的按鈕樣式特性,學會讓按鈕播放音效,切換滑鼠造型,以及避免圖片透明的地方被點擊到。
當發出「不要」的心情時,是為什麼而不要?不要體驗?不想要體驗?因為沒有好處? 像打拳一樣,一件事一件事地去處理與解決 為什麼不能像打拳那樣豪邁?怕痛?怕輸?怕日子過不下去?
Thumbnail
這篇教學專注在使用 Renpy 讓選項在選擇後消失和一個一個出現的功能上。透過建立變量和修改選項,讓玩家在選擇後影響後續劇情發展。教學將提供範例代碼和具體步驟,讓讀者能夠輕鬆上手。
Thumbnail
小問接過搖桿,嘗試操作上面的按鍵和方向鍵,一時間也不知道這是要做什麼,正想開口詢問時,劉富安已經打開遊戲,並熟練地使用搖桿操作畫面裡的光標,進行選項的選擇。 遊戲很快地就進入人物選擇畫面,劉富安便說:「你可以選擇喜歡的人物,要用……」 劉富安正想教小問如何使用搖桿選擇人物,卻沒想到他動作飛快地選
Thumbnail
在人生中總是會遇到許多選擇,做了不同的選擇產生的結果也會不一樣,大多時候都是在為了要吃什麼在抉擇,小孩才做選擇大人全部都要,全部都要何嘗也不是一個選擇。 在Python程式語言中也有選擇的語法,就是If Else,如果是就做什麼,不是就做什麼,有別於其他程式語言,他不一定要有else,可以只有If