[javascript]自製記憶遊戲實作細節(2/2)

本篇承接以兒子為素材的Web記憶遊戲,說明遊戲實作細節,包含以下部分

  • 遊戲邏輯
  • 翻卡片動畫效果

程式碼的部分,為了說明方便,會與實際程式中出現順序不同,但作用是一樣的,可能宣告在最外層,在某方法中賦值,之後在另一個方法中放至畫面上,說明時擺在一起看較容易理解。原始碼Open Source。

還沒看過上一篇的同學,可以先去看看~ 自製記憶遊戲實作細節(1/2)

遊戲邏輯

雖然記憶遊戲看似簡單,但是把所有考慮到的部分列出來也是不少,下面先列出流程判斷,接著才在程式碼中解釋每個變數的作用,這樣對照參考比較清楚

卡片有3種狀態,背面、正面、已配對(不顯示),卡片點擊事件觸發時,要檢查以下條件

  1. 卡片當下的狀態,背面=>翻到正面
  2. 是否已有正面的卡片
    1. 無=>翻到正面
    2. 有一張=>翻到正面後比對是否相同
      1. 相同=>卡片狀態變更為已配對,並隱藏起來,最後判斷是否全部配對完成
      2. 不同=>則兩張卡片都翻回背面
  3. 因為卡片有翻轉動畫
    1. 需等待翻轉動畫結束再判斷,不然使用者看不到圖片翻為正面就消失了
    2. 翻轉時需鎖定不觸發卡片點擊,避免造成多張卡片被翻開
  4. 時間倒數計時
    1. 記答案的時間=>時間到全部卡片翻到背面
    2. 遊戲的時間到=>挑戰失敗
//遊戲時使用的卡片數量
const CARD_NUMBER = 8;
//所有卡片圖片數量
const CARD_IMG_NUMBER = 20;
//完成配對的排組數
var doneCount = 0;
//時間秒數計算
var timeCounter = 0;

//遊戲開始前初值化
doneCount = 0;
timeCounter = 0;

//暫停卡片點擊判斷
var gamePause = false;
//是否已有一張正面卡片
var hasFlipped = false;
//掀開的第一張牌和第二張牌element的id
var cardOneId = -1;
var cardTwoId = -1;

開始遊戲時,設置定時器,更新時間顯示並判斷時間是否用完

function startPlay(){
	currentStage = STAGE_PLAYING;
	pauseFlip = false;
	timeCounter = 0;
	minute = Math.floor((PLAY_TIME - timeCounter)/60);
	second = (PLAY_TIME - timeCounter) % 60;
	if(second < 10){second = '0' + second;}
	counterBoard.innerHTML = minute+':'+second;
	interval = setInterval(function(){
		if(!gamePause){
			timeCounter++;
		}
		minute = Math.floor((PLAY_TIME - timeCounter)/60);
		second = (PLAY_TIME - timeCounter) % 60;
		if(second < 10){second = '0' + second;}
		counterBoard.innerHTML = minute+':'+second;
		//檢查時間是否結束
		if(timeCounter >= PLAY_TIME){
			clearInterval(interval);
			sound_bg.pause();
			sound_over.play();
			gameOver();
		}
	}, 1000);
}

點擊事件是用全域監聽的方式,再判斷點擊的目標是否為我們所要的,並根據當前的階段(主選單、遊戲中…等)來判斷,以下只列出遊戲中的判斷

document.addEventListener('click',function(e){	
  switch(currentStage){
  	case STAGE_PLAYING:
  	  //點擊到卡片的正面或反面
  	  if(e.target && 
  	      (e.target.classList.contains('front') ||
  	      e.target.classList.contains('back'))){
  	  	if(!pauseFlip){
  	  	  var card = e.target.parentNode.parentNode;
  	  	  if(card.dataset['status'] == STATUS_BACK){
  	  	    flip(card);
  	  	    //若已經翻過一張,比對兩張是否相同
  	  		if(hasFlipped){
  	  		  pauseFlip = true;
  	  		  cardTwoId = card.id;								
  	  		  console.log('cardTwoId='+cardTwoId);
  	  		  //比對相同,播放音效,卡片消失
  	  		  if(document.querySelector('#'+cardOneId).dataset['id'] == document.querySelector('#'+cardTwoId).dataset['id']){
  	  		    doneCount++;
  	  		    sound_disappear.play();
  	  		    card.dataset['status'] = STATUS_DONE;
  	  		    document.querySelector('#'+cardOneId).dataset['status'] = STATUS_DONE;
  	  		    pauseFlip = true;
  	  		    //等待卡片動畫效果
  	  		    setTimeout(function(){
  	  		      card.classList.toggle('disappear');
  	  		      document.querySelector('#'+cardOneId).classList.toggle('disappear');
  	  		  	  pauseFlip = false;
  	  		  	  //判斷是否所有卡片皆已配對,若是則遊戲結束
  	  		  	  if(doneCount == CARD_NUMBER){
  	  		  	  	clearInterval(interval);
  	  		  	  	setTimeout(function(){
  	  		  	      sound_bg.pause();
  	  		  	  	  sound_complete.play();
  	  		  	  	  gameComlete();
  	  		  	  	}, 300);
  	  		  	  }
  	  		  	}, 300);
  	  		  }else{
  	  		    //比對不同張,翻回背面
  	  		  	setTimeout(function(){
  	  		  	  flip(card);
  	  		  	  flip(document.querySelector('#'+cardOneId));
  	  		  	},   300);
  	  		  }
  	  		  hasFlipped = false;
  	  		  setTimeout(function(){pauseFlip = false;}, 300);
  	  		}else{
  	  		  cardOneId = card.id;
  	  		  console.log('cardOneId='+cardOneId);
  	  		  hasFlipped = true;
  	  		}
		  }
		}
  	  }
  	  break;
  }
});

翻卡片動畫效果

翻頁的效果是由CSS處理,再用方法動態切換class,首先用javascript動態插入卡片的HTML

for(var i=0;i<CARD_NUMBER*2;i++){
    playground.innerHTML += 
      '<div class="flip-container" id="card_'+i+'" data-id="'+cardList[i]+'" data-status="'+STATUS_FRONT+'">' + 
      '<div class="flipper">' +
      '<div class="front" style="background-image: url(\'card_'+(cardImgList[cardList[i]])+'.png\');"></div>'+
      '<div class="back cover"></div>'+
    '</div>'+
  '</div>';
}

container包含整個卡片,其中又有前面和後面兩個div,利用transform: rotateY(0deg)進行水平翻轉,藉由backface-visibility: hidden;,可讓翻轉過去的元素變為不可見,所以原理就是,預設前面未翻轉,後面已翻轉,故不可見,將前面跟後面兩個div同時水平翻轉,前面消失,後面則顯現出來,以下為CSS翻轉動畫相關定義,若覺得原始碼中包含其他代碼不好理解,可以參考這篇Create a CSS Flipping Animation,單純講解翻轉的原理,筆者也是參考他修改而成的

/* entire container, keeps perspective */
.flip-container {
	display: inline-block;
	perspective: 1000px;
	
	margin: 1.4%;
}
	/* flip the pane when hovered */
	.flip-container.hover .flipper {
		transform: rotateY(180deg);
	}

.flip-container {
	width: 22%;
	height: 22%;
}

.flipper, .front, .back{
	width: 100%;
	height: 100%;
}

/* flip speed goes here */
.flipper {
	transition: 0.3s;
	transform-style: preserve-3d;
	-webkit-transform-style: preserve-3d;

	position: relative;
}

/* hide back of pane during swap */
.front, .back {
	backface-visibility: hidden;

	position: absolute;
	top: 0;
	left: 0;
					
	background-repeat: no-repeat;
	background-size: cover;
}

/* front pane, placed above back */
.front {
	z-index: 2;
	/* for firefox 31 */
	transform: rotateY(0deg);
	background-color: #ffffff;
}

/* back, initially hidden pane */
.back {
	transform: rotateY(180deg);
	background-color: #dddddd;
	
	background-image: url('cover.png');
}
再配合方法動態新增刪除hover屬性,配合transition: 0.3s;,自動補間產生0.3秒的動畫效果
function flip(e){
    e.classList.toggle('hover');
    if(e.classList.contains('hover')){
        e.dataset['status'] = STATUS_BACK;
    }else{
	e.dataset['status'] = STATUS_FRONT;
    }
}

實作細節到這裡較複雜的部份都做說明了,其他未說明的部分,都只是畫面切換,或者原理相同,就不一一贅述,若是有說明不清楚的地方,也歡迎提出問題,一起討論~

參考來源:

對「[javascript]自製記憶遊戲實作細節(2/2)」的一則回應

Add yours

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

在 WordPress.com 建立網站或網誌

向上 ↑

%d 位部落客按了讚: