JS 的互動範例,主要能學習到 settimeout, event, dom 的控制與視覺互動。包含了鍵盤事件的調整與控制。遊戲設計如下:
按下遊戲開始,會開始規劃 60 秒鐘期限,每次分數歸 0 開始計時計分。
共有 100 次機會事件,隨機的對九宮格進行 yellow->red 事件狀態。
每次 red 事件狀態會停留隨機 1~3 秒,若超過停留秒數會自動回到 yellow 狀態
當用戶進行點選 red 事件,則代表得分,會變化為 green 事件。
green 事件停留 1 秒後,回到 yellow 狀態。
遊戲結束後,可繼續遊戲,回到步驟 1 全新的計分計時。
注意:遊戲設計過程中需符合合理流程與排除操作問題性。
規劃版面與樣式表 先利用 div 規劃出 9 宮格的樣式,固定一行有三張圖片。並試著調整好畫面距離,同時利用 class=cell 作為所有圖片的 css 調整,之後能以 JS DOM 去找到這些圖片 (getelementsbyclassname)。然而 title 這裡我們作為圖片說明,以及之後的 DOM 解讀狀態使用。
同時準備三張圖片相同寬長為 100px。有三個事件為等待 (Yellow),出現 (red),得分 (green)。
HTML+CSS <div class ="content" > <div class ="row" > <img src ="yellow.png" class ="cell" title ="yellow" /> </div > <div class ="row" > <img src ="yellow.png" class ="cell" title ="yellow" /> </div > <div class ="row" > <img src ="yellow.png" class ="cell" title ="yellow" /> </div > <div class ="control" > <div class ="msg" > <span id ="time" > 剩餘時間:0s</span > <button > 遊戲開始</button > <span id ="combo" > 成績分數:0</span > </div > <hr /> <b > 遊戲說明</b > <p > 打地鼠遊戲,請點擊紅色圖片獲得分數(滿分 100)</p > <hr /> </div > </div >
body { margin :0 ; display : flex; justify-content : center; height :100vh ; text-align : center; } .content { display :flex; flex-flow : column nowrap; align-items : center; justify-content : center; } .cell { background : yellow; border :5px solid #6bd1eb ; cursor : pointer; width :100px ; height :100px ; } .control { max-width :350px ; } .msg { margin :5px auto; display : flex; justify-content : space-around; line-height : 2rem ; } button ,span { width :33% ; }
到目前為止你應該能得到以下的版面。
程式規劃之 JS 部分 這裡相對於比較複雜,根據遊戲規則需要拆成多筆處理來判斷。利用 function 分工合作處理。
初始宣告 有一些變數我們我們先宣告出來準備好可跨全域,利於所有 function 都能接受使用。包含以下:
按鈕 btn: (偵測/禁止/開放)遊戲開始之點擊
倒數 time: 畫面上的剩餘時間文字
分數 combo: 畫面上的目前分數
旗幟 flag: 解讀目前是遊戲中/未開始
秒數 sec: 遊戲剩餘時間
得分 count: 遊戲累積分數
等待之事件 wait: 陣列型態,收錄 100 筆事件的等待物件
9 格之物件 thedog: 陣列型態,收錄 9 格 DOM 的 HTML 物件
此外除了宣告變數,畫面載入時先給予允許 btn 使用 click。
var btn=document .getElementsByTagName ("button" )[0 ]; var time=document .getElementById ("time" ); var combo=document .getElementById ("combo" ); var animal=document .getElementsByClassName ("cell" );var flag=0 ; var sec=0 ,count=0 ;var beYellow=new Array (); btn.addEventListener ("click" ,gamestart);
gamestart 遊戲開始 每次遊戲開始要做以下動作:
先把 flag 打開、分數歸 0、倒數為 60s,並將 DOM 對象的畫面文字修改好。
把 btn 的 click 關閉,避免遊戲重複執行。
規劃倒數器,每 1 秒更新並減少畫面上的 sec。
如果倒數為 0: 此時關閉倒數器,並開放 click 點選,flag 關閉(非遊戲中狀態)
創造出 100 次得分機會。
做好時間軸上的分配,透過隨機方式分散到 0~60 秒(透過延遲觸發)
隨機的指定要到那些位置去,共有 9 格。使用 0~8 數字代表。
每次的得分機會曝光多久,隨機 1~3 秒
觸發時機到達時,去執行另一個函式 showit() 做紅燈事件。
function gamestart ( ){ sec=60 ,count=0 ,flag=1 ; time.textContent =`剩餘時間:${sec} ` ; combo.textContent =`成績分數:${count} ` ; btn.removeEventListener ("click" ,gamestart); let start=setInterval (()=> { if (sec==0 ){ clearInterval (start); flag=0 ; btn.addEventListener ("click" ,gamestart); } else { sec--; time.textContent =`剩餘時間:${sec} ` ; } },1000 ); for (let i=0 ;i<100 ;i++){ let ontime=Math .floor (Math .random ()*57000 ); let which=Math .floor (Math .random ()*9 ); let delay=Math .floor (Math .random ()*3 )+2 ; setTimeout (function ( ){ showit (which,delay,i); },ontime); } }
這裡設計時間軸範圍 0~57 秒,是為了消耗剩餘機會,避免時間到了還有得分機會發生。
觸發紅燈事件 要做紅燈 (red) 需要先檢查,是否指定的位置屬於黃燈狀態 (yellow),如果不屬於黃燈勢必代表有事件正在運作,我們要思考如何處理手上的孤兒,盡可能的找到合適的位置發生出來(否則無法 60 秒內消耗 100 次機會)。
showit() 需要三個來源變數,分別是:siWhich(哪個位置),siDelay(曝光時間),siId(事件編號)。做以下動作:
如果目標位置 who 已經有事件再發生,想辦法塞到別處並晚 0.5 秒處理(遲一下避免迴圈無限可能)
如果目標位置沒有事件,那就進行以下行為
更改 DOM 之事件狀態 (title), 圖片 (src), 機會編號 (alt), 色塊 (BGcolor)
將曝光延遲規畫下去,並利用 wait 陣列紀錄此物件。利於之後需要取消此延遲
曝光後當事件未被得分,調整所有狀態回到 state 模式。
function showit (siWhich,siDelay,siId ){ if (animal[siWhich].title !="yellow" ){ let next=Math .floor (Math .random ()*9 ); setTimeout (function ( ){ showit (next,siDelay,siId); },100 ); } else { animal[siWhich].src ="red.png" ; animal[siWhich].style .background ="red" ; animal[siWhich].title ="red" ; animal[siWhich].alt =siId; beYellow[siId]=setTimeout (() => { animal[siWhich].src ="yellow.png" ; animal[siWhich].style .background =null ; animal[siWhich].title ="yellow" ; animal[siWhich].alt =null ; }, siDelay*1000 ); } }
這裡需要透過 beYellow{}去管理延遲事件,是因為在一些場合下會破壞事件的發生和理性。譬如來說:當發生 red 時,延遲 3 秒後改為 yellow,若此時 3 秒內發生 green,yellow,red。此時 red 的曝光時間會不正常。所以我們需要管理某些情況下,要能取消此延遲之變化。
接下來當畫面已經有了yellow→red
& red→yellow
事件,我們要開始規劃red→green
& green→yellow
事件。不過在這之前,我們需要確認如何取得鍵盤事件
鍵盤事件 利用 dom.onkeydown 去跑一個 function 判斷此 event.keycode 為多少。並轉換為針對對象 0~8 之位置事件行為,同時每次要檢查是否屬於遊戲中,才允許去觸發鍵盤事件對應函式。
document .onkeydown =keyboard;function keyboard ( ){ if (flag){ switch (event.keyCode ) { case 103 : getcount (0 );break ; case 104 : getcount (1 );break ; case 105 : getcount (2 );break ; case 100 : getcount (3 );break ; case 101 : getcount (4 );break ; case 102 : getcount (5 );break ; case 97 : getcount (6 );break ; case 98 : getcount (7 );break ; case 99 : getcount (8 );break ; } } }
getcombo 位置事件 進入 getcombo 時需要以下處理:
檢查目標對象是否為 red 狀態(排除亂按導致畫面錯亂),若是 red 才能做更改為 green。
此外也要記得做加分動作。
必須去取消這個 red 狀態當時的延遲 yellow 事件,使得過程狀態完整。舉例來說: 正確過程 red→green→yellow(by getcombo)->finished 錯誤過程 red→green→yellow(by beYellow’s FN)→red(new event)→…
function getcount (who ) { if (animal[who].title =="red" ){ animal[who].src ="green.png" ; animal[who].style .background ="green" ; animal[who].title ="green" ; count++; combo.textContent =`成績分數:${count} ` ; id=animal[who].alt ; clearTimeout (beYellow[id]); animal[who].alt =null ; setTimeout (() => { animal[who].src ="yellow.png" ; animal[who].style .background =null ; animal[who].title ="yellow" ; }, 1000 ); } }
恭喜你已完成打地鼠遊戲,此時請思考如何添加滑鼠事件,使得手機螢幕點擊(滑鼠)也能適用。
手機(滑鼠)模式 透過網路資源取得以下 meta 宣告,這能確保你在手機上連擊兩下禁止畫面放大作業
<meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
再來調整HTML,直接加入onclick到每個圖片,此時我們很清楚getcode對象是誰
<div class ="row" > <img src ="yellow.png" class ="cell" title ="yellow" onclick ="getcount(0)" /> <img src ="yellow.png" class ="cell" title ="yellow" onclick ="getcount(1)" /> <img src ="yellow.png" class ="cell" title ="yellow" onclick ="getcount(2)" /> </div > <div class ="row" > <img src ="yellow.png" class ="cell" title ="yellow" onclick ="getcount(3)" /> <img src ="yellow.png" class ="cell" title ="yellow" onclick ="getcount(4)" /> <img src ="yellow.png" class ="cell" title ="yellow" onclick ="getcount(5)" /> </div > <div class ="row" > <img src ="yellow.png" class ="cell" title ="yellow" onclick ="getcount(6)" /> <img src ="yellow.png" class ="cell" title ="yellow" onclick ="getcount(7)" /> <img src ="yellow.png" class ="cell" title ="yellow" onclick ="getcount(8)" /> </div >
接著由於滑鼠事件直接到 getcombo 處理了,flag 這個部分我們要調整一下檢查關卡往後一步到 getcombo 去處理。
修改 keyboard(){},拿掉 flag 檢查,往後一部統一由 getcombo 做檢查,改為
function keyboard ( ) { switch (event.keyCode ) { case 97 : getcombo (6 );break ; case 98 : getcombo (7 );break ; case 99 : getcombo (8 );break ; case 100 : getcombo (3 );break ; case 101 : getcombo (4 );break ; case 102 : getcombo (5 );break ; case 103 : getcombo (0 );break ; case 104 : getcombo (1 );break ; case 105 : getcombo (2 );break ; } }
修改 getcombo 增加 flag 的判斷,可以跟原判斷合併執行
function getcount (who ) { if (animal[who].title =="red" &&flag==1 ){ animal[who].src ="green.png" ; animal[who].style .background ="green" ; animal[who].title ="green" ; count++; combo.textContent =`成績分數:${count} ` ; id=animal[who].alt ; clearTimeout (beYellow[id]); animal[who].alt =null ; setTimeout (() => { animal[who].src ="yellow.png" ; animal[who].style .background =null ; animal[who].title ="yellow" ; }, 1000 ); } }