底下是開發這個遊戲的一些技術重點紀錄。
刮刮樂的部分是使用html5的canvas製作。原理是使用兩個canvas,一個作底圖圖層(c0),一個做為可被刮除的前景遮罩圖層(c1),c1預設不可見為一記憶體圖層。把c0的原始圖形內容事先存起來作為後續更新使用,並在c0上安裝onmousedown,onmouseup及onmousemove等訊息處理函式。滑鼠滑動時以在c1上畫粗直線的方式模擬出刮除效果,然後再以新的c1前景遮罩結合事先儲存的c0底圖重新合併更新畫面,這樣就能作出刮刮樂的效果。
c1上的刮除效果是利用將2d context的globalCompositeOperation設為'destination-out'模式,並且strokeStyle設為'rgba(0,0,0,1)',以這樣子的設定在c1上畫線時,得到的是一個擦除的效果,這樣就作到刮刮樂刮除的效果。
c0.onmousemove = function(e) { if (mouse_down) { ctx1.strokeStyle = 'rgba(0,0,0,1)'; ctx1.globalCompositeOperation = 'destination-out'; ctx1.lineWidth = 18; ctx1.lineCap = 'round'; ctx1.beginPath(); ctx1.moveTo(last_x, last_y); ctx1.lineTo(x, y); ctx1.stroke(); last_x = x; last_y = y; updateCanvas(); } }
更新畫面的方法是以getImageData從c1抓取整張前景遮罩,對每一個pixel檢查如果alpha值是0表示這個點己刮除,則這個點取c0的同一位置的顏色值,否則表示這個點還未刮除需要取c1的前景遮罩顏色。所有pixel都處理完成後,再把整個新的合併事先儲存的c0的背景底圖內容的imagedata透過putImageData畫到c0上更新。
function updateCanvas() { var imgData = ctx1.getImageData(0, 0, width, height); var imgDataNew = ctx1.getImageData(0, 0, width, height); for (var i = 0; i < width*height*4; i+=4) { var a = imgData.data[i+3]; if (0 == a) { imgDataNew.data[i] = imgDataSrc.data[i]; imgDataNew.data[i+1] = imgDataSrc.data[i+1]; imgDataNew.data[i+2] = imgDataSrc.data[i+2]; imgDataNew.data[i+3] = imgDataSrc.data[i+3]; } } ctx.putImageData(imgDataNew, 0, 0); }
如果是使用chrome browser來執行這個遊戲,則能夠利用chrome的文字轉語音(TTS)功能唸出英文單字。這裡是透過window.speechSynthesis及SpeechSynthesisUtterance來完成這個功能,如下程式片段所示。
function trySpeak(s) { if (window.speechSynthesis) { var msg = new SpeechSynthesisUtterance(s); if (msg) { msg.lang = "en-US"; window.speechSynthesis.speak(msg); } }
為了避免刮刮樂canvas太大在計算時計算量跟著太大,所以把大小限制為300x300px,但這樣一來畫面顯示的時候又會顯的太小。所以為了讓這個300x300px的canvas可以填滿畫面,需多作一點微調。
首先在html內的viewport屬性就把它設為300px,讓畫面寬度剛好符合300px。另外記得把body預設的margin和padding都設為0,才不會不小心多出其它px,讓canvas的300px被切掉。
<head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style type="text/css"> body {margin:0;padding:0;} ... </style> ... </head>
如果是在android裡用WebView來作顯示時,需要多作點參數設定,確保view可以填滿畫面。
WebView wv = findViewById(R.id.webView); wv.getSettings().setLoadWithOverviewMode(true); wv.getSettings().setUseWideViewPort(true);
最後如果是在一般browser裡顯示的話,為了讓畫面顯示比較符合原始設計,使用iframe把原始html包裹起來,大小設定可以包含刮刮樂canvas即可。
<iframe src="emojiscratch.html" title="EmojiScratch" width="310" height="500" frameborder="0">
遊戲內以表格事先編好有那些emoji的資料,但因為這個遊戲是以web的方式來呈現,而在不同裝置上的browser呈現的emoji可能都各不相同,甚至有些emoji是不支援無法顯示的。所以需要在遊戲前先把那些不支援無法顯示的emoji挑選出來,只是目前沒有什麼標準的方法或api可以作這樣的檢查,所以這裡用一個間接的方式來作檢查。使用的方式是試著把要檢查的emoji畫到一個作為檢查用的canvas,這個canvas的大小只要設為1個pixel大小。如果這個canvas上面有畫出東西(alpha值不為0),那麼就表示這個emoji是可顯示的,否則就當作它無法顯示。當然用這種方式不能保證100%正確有效,但至少是個可行的辨法,可以避免在emoji不支援時顯示出一個X。
function supportsEmoji(ch) { const ctx = document.createElement("canvas").getContext("2d"); ctx.canvas.width = ctx.canvas.height = 1; ctx.fillText(ch, -4, 4); return ctx.getImageData(0, 0, 1, 1).data[3] > 0; // Not a transparent pixel. }