第二話 按鈕事件

第二話 按鈕事件

不論是製作遊戲或者是商用軟體,最基礎也是最重要的功能,應該就是讓你所製作的圖片可以被使用者點擊後產生互動。不管是再厲害的功能或畫面,再複雜的介面操作都是從這個基礎來進行開發的。

因此,這一話主要來聊聊怎麼讓元件能夠被偵聽事件,以及當事件(比如當某張圖片被使用者按下就是)發生時要讓某個函式被呼叫的開發技巧。

在介紹這一話的程式碼之前,還是先來聊聊全域與區域變數還有函式這兩個觀念,讓沒有程式基礎的讀者能夠理解。

如果說電腦科學最重要的基礎為何,我認為就是變數。變數就像是容器一般,可以儲存數值.字串.甚至是元件的參考(如同我們在第一話使用變數來儲存圖片的記憶體位置,用來控制元件),有了這樣的結構,之後才能開始結合函式.判斷.迴圈等進階的處理。

那麼全域與區域的意思,就如同字面代表了這個變數的存在範圍。全域變數最單純,它存活於你整個開發專案週期,能夠被任何程式碼存取,所有人存取到的數值也相同。而區域變數則只存在於某個指定區域,一旦離開這個區域就會死亡,因此也只有這個區域的程式碼能夠存取這個區域變數。

所謂的某指定區域要如何判斷呢?最簡單的訣竅是看該區域變數被宣告的時間,又可說成是變數出生的時間。下面這個例子雖然是一行,但其實包含了兩個行為:宣告與賦值。既然是兩個行為,彼此間就可以單獨存在,例如把=1去掉,就變成一個單純的宣告變數x。

所以說,只要觀察該區域變數被宣告時位在哪個區域,那麼該區域以及該區域的包含區域都可以存取這個區域變數。

底下再用一張圖表來說明得更清楚一些,這是一個包含兩個程式碼檔案的專案...

上圖用虛線來代表某個區域的範圍,注意到區域1的範圍包括了區域2,區域2又包括了區域3。這就是說區域2和3都是區域1的包含區域,在區域1內所宣告的變數都能在區域2和3裏頭進行存取。反過來說,區域7是屬於另一個程式檔案的函式,不被區域1包含,所以區域7就無法存取囉。很簡單不是嗎?

上圖帶出區域變數的另一個觀念,如果變數的名稱相同,區域內的優先於區域外的,區域又優先於全域。

舉個生活中的例子,假如有個班級裡頭的學生也叫林書豪,和NBA球星同名。當老師在教室裡頭喊林書豪的時候,大家都會認為是喊這名學生。可以當離開學校在外面聊天的時候,其他人就會認為林書豪代表的是NBA球星。因此,球星林書豪就像全域變數,而學生林書豪就像是區域變數,兩者的優先級因區域產生了差異。

接下來,聊聊函式,就像是下面這樣的結構。

上面的結構宣告了一個名為fun1的函式,只要在下面輸入fun1(),就可以使用這個函式了。

你可能會好奇說為什麼要宣告函式勒?在第一話裡頭不需要宣告函式就可以撰寫程式,不是嗎?
的確,並不是一定要有函式程式才能跑。函式存在最主要的目的就是將會重複使用的程式段給包裝起來,這樣一來可以節省撰寫的效率,二來當要修改的時候就不需要改很多處,總的來說,可以大幅提升你的開發效率。

跟變數一樣,函式也分為全域函式與區域函式,只有區域內的程式碼才能叫用區域函式,概念是完全相同的。來看看全域函式與區域函式宣告上的不同。

同學可能覺得很奇怪區域函式長得怪怪的,好像是宣告了一個區域變數然後進行賦值,可是這個值又長得很特別。你說的沒錯,下面的這個區域函式真的就是先宣告了一個名為zoneFun的區域變數,接下來又宣告了一個匿名函式(所謂匿名,就是沒有名字,有沒發現就像是把全域函式的函式名稱給省略掉?),接著把這個函式賦值給zoneFun,最後zoneFun就從一個區域變數升級成區域函式變數,變數名稱升級成函式名稱。

同學如果有學過較為嚴謹的語言恐怕不太能接受變數可以升級成函式的概念,但是在lua語言裡頭,變數和函式之間只有一線之隔而已。 (或許說,只有一個等號之隔)變數可以賦值任何的內容,也包括了函式。

看到了這裡,希望沒有讓同學們覺得太複雜。馬上就要回到遊戲製作囉,只要再說明一個觀念。

lua語言是從上往下讀取的,因此在開發時一定要注意到程式碼有可能會發生本來是能正確執行的,但是調整了一下順序之後,不但行為改變了,甚至可能無法執行。為什麼會這樣勒?

想一想,如果今天要做菜,我們來做咖喱飯吧。第一件事要先把豬肉給拌炒一下,除了爆香之外還能產生油來炒接下來的蔬菜粒。這時候你發現你還沒把紅蘿蔔切成塊,甚至你還沒洗勒。糟糕了這時候,當你準備好油要炒蔬菜粒卻找不到,因為流程出了問題因此咖喱飯變了樣。而就程式來說就是死給你看。

那是否意思是一定要在程式執行前就把所有的變數都給準備好才行,如果變數的值還不確定怎麼辦?

是也不是!lua語言也需要經過編譯器來進行編譯,在編譯的過程中電腦會為你確認程式碼是否有異狀。它的檢查邏輯很好懂,就是檢查這個變數或函式名稱是否存在。

是否想起稍早我提過的,變數在宣告後就開始存在。也就是為何只要變數經過宣告就可以通過編譯的檢查,即便還沒有進行賦值,所指向的內容為空,都沒有關係。但是當真正開始執行,需要使用該變數或函式之前就一定要完成賦值,否則就會跳出錯誤,這也就是要特別注意順序的原因。

程式觀念課到此告一段落,讓我們來寫點程式...

第二話的程式碼可以說是第一話的延伸,畢竟沒有介面要怎麼實作事件偵聽勒。在這個例子中,下面的四個按鈕已經可以和使用者互動,按下不同的按鈕之後,就會設定畫面中間的隱藏文字內容,顯示每個按鈕的id。

重複的部分直接跳過,直接就新的知識點予以說明...


    btn_bag = display.newImageRect( "btn_bag.png", 100, 100 )
	btn_bag.x = _SCREEN.CENTER.X - 230
	btn_bag.y = _SCREEN.HEIGHT - 130
	btn_bag.id = "bag" --新增id屬性,用來作為辨識之用
	btn_bag:addEventListener( "touch" , onClickBtn )--新增touch事件的偵聽器,呼叫onClickBtn函式

在第一話已經說明過如何顯示圖片,稱之為圖片元件。你需要瞭解的是每個元件會有諸如x.y等自訂屬性供你讀取或設定,而你自己也可以寫入新的屬性以供自己之後使用。比如說在第4行我們新增了一個名為id的屬性,用來之後按下之後可讀取這個屬性來顯示在畫面上。

上圖為addEventListener函式的使用結構,比如obj可能是剛才建立的圖片元件,我們希望這個圖片元件可以對使用者觸發的點擊動作發生反應,也就是呼叫onClickBtn這個函式,就可以這樣寫。


    onClickBtn = function (event)
       local id = event.target.id
       if (event.phase == "began") then
          print("began")
       elseif (event.phase == "moved") then
          print("moved")
       elseif (event.phase == "ended" or event.phase == "canceled") then
          print("ended or canceled")
          lb_console.text = id
       end 
    end

在這幾行裡頭宣告了一個名為onClickBtn的function。被偵聽器呼叫的函式一般都會傳入一個event物件,裏頭主要有target和phase兩個屬性。

target屬性就是觸發事件的元件,在這個例子就是被觸碰的圖片元件。
phase屬性就是事件的階段,比如touch事件就包含了 began.moved.canceled.ended這四種階段。

因為每個按鈕被觸發都會呼叫onClickBtn函式,因此策略上透過取出target.id來辨識事件元件為何,接下來x行就是當使用者手指離開的時候,將id設值給console文字元件的內容,就可以在螢幕上呈現出來。

PS1:會在第20行先宣告onClickBtn變數的原因是在上面的圖片進行事件偵聽宣告就會用到onClickBtn變數,如果不先宣告會出現錯誤。
PS2:print函式可用來在console(或稱為終端機,當開啟Corona模擬器時就會出現)印出文字,方便用來偵錯,如下圖。

以下是所有的原始碼:


_SCREEN = {
	HEIGHT = display.contentHeight,   --執行裝置螢幕的高度
	WIDTH = display.contentWidth      --執行裝置螢幕的寬度
}

_SCREEN.CENTER = {
	X = display.contentCenterX,       --執行裝置螢幕的中心點X軸
	Y = display.contentCenterY        --執行裝置螢幕的中心點Y軸
}

display.setStatusBar( display.HiddenStatusBar )

--宣告區域變數
local btn_bag
local btn_team
local btn_store
local btn_social
local lb_console
--宣告區域變數函式
local onClickBtn

local logo = display.newImageRect( "logo.png", _SCREEN.WIDTH/1.5, _SCREEN.HEIGHT/3 )
logo.x = _SCREEN.CENTER.X
logo.y = _SCREEN.CENTER.Y

timer.performWithDelay(3000, function ( )
	logo:removeSelf( )
	logo = nil

	local bg = display.newImageRect( "bg.png", _SCREEN.WIDTH, _SCREEN.HEIGHT )
	bg.x = _SCREEN.CENTER.X
	bg.y = _SCREEN.CENTER.Y

	btn_bag = display.newImageRect( "btn_bag.png", 100, 100 )
	btn_bag.x = _SCREEN.CENTER.X - 230
	btn_bag.y = _SCREEN.HEIGHT - 130
	btn_bag.id = "bag" --新增id屬性,用來作為辨識之用
	btn_bag:addEventListener( "touch" , onClickBtn )--新增touch事件的偵聽器,呼叫onClickBtn函式

	btn_team = display.newImageRect( "btn_team.png", 100, 100 )
	btn_team.x = _SCREEN.CENTER.X - 115
	btn_team.y = _SCREEN.HEIGHT - 130
	btn_team.id = "team"--新增id屬性,用來作為辨識之用
	btn_team:addEventListener( "touch" , onClickBtn )--新增touch事件的偵聽器,呼叫onClickBtn函式

	btn_store = display.newImageRect( "btn_store.png", 100, 100 )
	btn_store.x = _SCREEN.CENTER.X + 145
	btn_store.y = _SCREEN.HEIGHT - 130
	btn_store.id = "store"--新增id屬性,用來作為辨識之用
	btn_store:addEventListener( "touch" , onClickBtn )--新增touch事件的偵聽器,呼叫onClickBtn函式

	btn_social = display.newImageRect( "btn_social.png", 100, 100 )
	btn_social.x = _SCREEN.CENTER.X + 260
	btn_social.y = _SCREEN.HEIGHT - 130
	btn_social.id = "social"--新增id屬性,用來作為辨識之用
	btn_social:addEventListener( "touch" , onClickBtn )--新增touch事件的偵聽器,呼叫onClickBtn函式

	local lb_id = display.newText( "哥布林程式學苑", 200, 70 , system.nativeFont, 21 )
	lb_id.anchorX = 1
	lb_id:setFillColor( 0,0,255/255 )

	local lb_lv = display.newEmbossedText( "2", 35, 128, system.nativeBoldFont, 38 )
	lb_lv:setFillColor( 1,0,0 )

	local color = 
	{
	    highlight = { r=1, g=1, b=1 },
	    shadow = { r=0.3, g=0.3, b=0.3 }
	}
	lb_lv:setEmbossColor( color )

	local lb_exp = display.newText( "10%", 200, 128, system.nativeFont, 21 )
	lb_exp.anchorX = 1
	lb_exp:setFillColor( 0,0,255/255 )

	local lb_money = display.newText( "9900", 430, 70, system.nativeFont, 21 )
	lb_money.anchorX = 0
	lb_money:setFillColor( 0,0,255/255 )

	local lb_stone = display.newText( "1000", 430, 128, system.nativeFont, 21 )
	lb_stone.anchorX = 0
	lb_stone:setFillColor( 0,0,255/255 )

	local lb_stone = display.newText( "30/30", _SCREEN.CENTER.X + 15, 800, system.nativeFont, 40 )

	--這個文字用來呈現按鈕事件之後的反饋
	lb_console = display.newText("" , _SCREEN.CENTER.X , _SCREEN.CENTER.Y , system.nativeFont , 40)

end )

--宣告當touch事件發生後被呼叫的區域函式
onClickBtn = function (event)
	local id = event.target.id
	if (event.phase == "began") then
		print("began")
	elseif (event.phase == "moved") then
		print("moved")
	elseif (event.phase == "ended" or event.phase == "canceled") then
		print("ended or canceled")
		lb_console.text = id
	end
end
  • 13~20 宣告區域變數與區域變數函式
  • 20~89 生成圖形元件,設定屬性,加入偵聽器等
  • 91~101 實作區域函式

第二話就到這裡,下一話將為大家說明如何製作遊戲常用的移動動畫,請期待...

分享這篇文章:

發佈自 林品爵

林品爵
一個堅信程式教育能夠改變下一代未來的狂熱者,青少年揚帆計畫的推動者。十年以上全端程式開發,上百場程式教學經驗,累積學生數達百人以上。 成大學士畢業,使用過J2EE.PHP.MySQL.CoronaSDK.Android.Objective-C.Swift.Python等技術知識,歷任友訊科技總部Java工程師首席.黑快馬股份有限公司研發經理.諾亞數碼娛樂執行長等職位,現任哥布林程式教育學苑創辦人,所開發過專案涵括產品管理系統.POS系統.拍賣平台.擴增實境.問券.社區App等等。 程式啟蒙班講師 . Laravel網頁程式班講師 . App程式班講師

關聯文章:

留下留言