本篇承接以兒子為素材的Web記憶遊戲,說明遊戲實作細節,包含以下部分
- 整體架構
- 螢幕適應
- 進度條
程式碼的部分,為了說明方便,會與實際程式中出現順序不同,但作用是一樣的,可能宣告在最外層,在某方法中賦值,之後在另一個方法中放至畫面上,說明時擺在一起看較容易理解。原始碼Open Source。
整體架構
主要重心都在javascript中,因此HTML部分只有一個div,每一個頁面,和遊戲中的元素,都是使用DOM或者動態附加HTML上去的,載入頁面後,才開始運行javascript進行初始化
<body onload="onload()">
<div id="game_area"></div>
</body>
首先定義有哪幾頁要呈現,像是讀取中、主選單、遊戲頁等
//關卡階段
var currentStage = 0;
const STAGE_LOADING = 0;
const STAGE_MENU = 1;
const STAGE_GALLERY = 2;
const STAGE_PREPARE = 3;
const STAGE_PLAYING = 4;
const STAGE_GAME_COMPLETE = 5;
const STAGE_GAME_OVER = 6;
接著宣告介面上會用到的物件,像是主選單標題、按鈕等,在需要時,動態添加到畫面上,不顯示時,再清除,但其實物件資訊都還保留在記憶體中,下一次再添加到畫面時,不需要重新宣告。
var title;
//建立一個div的物件
title = document.createElement('div');
title.id = 'title';
//設定該div的class
title.classList.add('center');
//建立一個img物件
var title_img = document.createElement('img');
title_img.src = 'title.png';
//將img放入div中
title.appendChild(title_img);
var gameArea = document.getElementById('game_area');
//將div放至畫面上呈現,此時該div才可見
gameArea.appendChild(title);
//將畫面上的元素都清除,即不可見
function clearScreen(){
removeAllElements(gameArea);
}
//實際走訪每個子節點,將其從父節點中清除
function removeAllElements(node){
while (node.firstChild) {
node.removeChild(node.firstChild);
}
}
//以遊戲完成頁面為例,先清除所有畫面上的物件,再添加此頁面所需要的物件
function gameComlete(){
currentStage = STAGE_GAME_COMPLETE;
clearScreen();
gameArea.appendChild(title);
tip.innerHTML = '恭喜你在時間內完成挑戰~';
gameArea.appendChild(empty);
gameArea.appendChild(tip);
btnIndex.classList.add('btn-index');
gameArea.appendChild(btnIndex);
gameArea.appendChild(logo);
}
螢幕適應
面對各種尺寸的手機,一開始我使用網頁的RWD作法,設定mediaquery,分橫豎兩個方向作呈現,不過後來發現太多預料外的狀況了,導致後來改用固定比例的呈現方式,固定以3比4呈現,空白的部分補黑邊,這樣相對單純許多,目前有個小問題,就是之前的mediaquery沒有拿掉就調整,所以有點混用到了,現在如果拿掉反而不會正常顯示,後續再來改正。
#game_area {
position: absolute;
left: 50%;
top: 50%;
background-image: url('bg.png');
}
//定義resize事件
window.addEventListener("resize", function(){
resizeGame();
});
//首次執行時,也需要手動觸發一次調整畫面
window.dispatchEvent(new Event('resize'));
//動態調整遊戲區域的寬高
function resizeGame() {
var widthToHeight = 3 / 4;
var newWidth = window.innerWidth;
var newHeight = window.innerHeight;
var newWidthToHeight = newWidth / newHeight;
if (newWidthToHeight > widthToHeight) {
newWidth = newHeight * widthToHeight;
gameArea.style.height = newHeight + 'px';
gameArea.style.width = newWidth + 'px';
} else {
newHeight = newWidth / widthToHeight;
gameArea.style.width = newWidth + 'px';
gameArea.style.height = newHeight + 'px';
}
//除寬高外,也要動態調整位置保持置中
gameArea.style.marginTop = (-newHeight / 2) + 'px';
gameArea.style.marginLeft = (-newWidth / 2) + 'px';
}
進度條
進度條設計主要是避免遊戲途中,圖片來不及讀取顯示空白或者音效首次播放延遲,因為以前沒有做過類似功能,有點不清楚,後來才知道原來不難,可以使用DOM的方式建立,再透過complete屬性判斷是否已讀取完成,或者建立onload事件,則會在讀取完成時觸發,指定src後,瀏覽器便會自動開始讀取,只要在讀取完成時,計算已完成的百分比,再更新進度條的長度,就完成了
是不是很簡單呢? 下方程式碼可以看到我同時使用complete和onload是因為,當瀏覽器已經有該資源的快取時,不會觸發onload所以只好先手動判斷一次complete屬性,確保有執行到更新進度條的方法。
var loadCount = 0;
var loadPercent = 0;
var img = new Image();
img.src = 'cover.png';
if(img.complete){
updateProgress();
}else{
img.onload = function(){
updateProgress();
};
}
//設定所有的照片讀取事件
for(var i=0;i<CARD_IMG_NUMBER;i++){
img = new Image();
img.src = 'card_'+cardImgList[i]+'.png';
if(img.complete){
updateProgress();
}else{
img.onload = function(){
updateProgress();
};
}
}
//設定音訊讀取事件
sound_start = document.createElement('audio');
sound_start.src = 'start.mp3';
sound_start.preload = 'auto';
sound_start.oncanplaythrough = updateProgress();
function updateProgress(){
loadCount++;
loadPercent = Math.floor(loadCount*100/(CARD_IMG_NUMBER + SOUND_NUMBER + 1));
//更新進度條長度
bar.style.width = loadPercent + '%';
//更新顯示百分比
percent.innerHTML = loadPercent + '%';
if(loadPercent == 100){
showMenu();
}
}

其餘部分的說明,請待下回分曉,如果有不同的想法歡迎一同討論~