方格遊戲的移動版適配開發,比我預計得快一點。
遊戲界面預覽
適配難點
1、touch 事件的四個方向的滑動監聽。
解決:找到了一個大神寫的工具類,完美解決問題。在此感謝。
2、方格寬高不固定,取值百分比,百分比瀏覽器返回值會捨棄小數點取整,導致對照字典偏移位置有偏差。
解決:設置計量單位,同步修改對照字典裏的偏移量。計算分數時,由原來的全等於判斷,變爲比較偏差值。
3、touch 事件多次觸發。
解決:設置布爾值 touchingScreen ,限制 touch 單次只能觸發一次。
4、頁面佈局調整。
屏幕窗口有限,分數榜單和棋盤不再同時出現。也是因爲寬度限制,棋盤方格使用百分比寬度,不再使用固定寬度。寬度百分比,設置高度等於寬度,需要 css 技巧。在方格上使用樣式 touch-action: none; 這樣任何觸摸事件都不會產生默認行爲,但是 touch 事件照樣觸發,從而解決無法被動偵聽事件 preventDefault 。
undo
1、分數值的顯示沒有在方格里居中。
2、沒有解決移動端瀏覽頁面滾動的問題。
3、遊戲開始時,小女孩正常移動後再回到起點會報一個錯誤。出錯位置: this.chess[i].usedScore 報錯信息: Cannot read property 'usedScore' of undefined.
主體代碼 index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<title>方格遊戲</title>
<link rel="shortcut icon" href="favicon.ico">
<meta name="keywords" content="方格遊戲">
<meta name="description" content="方格遊戲">
<script src="vue.js"></script>
<style>
body{
width: 100%;
height:100%;
margin:0;
background: #fff;
overflow: hidden;
box-sizing: border-box;
color:#1a2a65;
}
div.container{
width: 100%;
margin-top:80px;
text-align: center;
}
div.container div.chess{
width: 98%;
margin: 0 auto;
box-sizing: border-box;
border:1px solid #ccc;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
}
div.chess-grid{
width:14.2857%;
height:0;
box-sizing: border-box;
padding-bottom: 14.2857%; /* 讓div的高等於寬 */
line-height: 100%;
text-align: center;
color: #fff;
/*應用 CSS 屬性 touch-action: none; 這樣任何觸摸事件都不會產生默認行爲,但是 touch 事件照樣觸發,從而解決無法被動偵聽事件preventDefault*/
touch-action: none;
}
/*雪人*/
div.snowman{
width:100%;
padding-bottom: 100%; /* 讓div的高等於寬 */
background: url("head.jpg") no-repeat;
background-size: 100% 100%;
cursor: pointer;
}
/*雪人的家*/
div.home{
width:100%;
padding-bottom: 100%; /* 讓div的高等於寬 */
background: url("home.jpg") no-repeat;
background-size: 100% 100%;
}
div.container div.score{
width: 98%;
margin: 0 auto;
box-sizing: border-box;
border:1px solid red;
background: #f7f7f7;
text-align: center;
}
div.container div.score .title{
font-size:18px;
background:red;
color:#fff;
padding:10px;
}
/*遊戲計時器*/
div.head{
width:100%;
height:64px;
box-sizing: border-box;
text-align: center;
position: fixed;
top: 0;
background: #f7f7f7;
padding:12px;
}
div.head span,div.score span{
font-size: 24px;
font-weight: bold;
color:red;
padding:0 4px
}
div.game_start_box{
display: flex;
justify-content: center;
}
div.game_time_box{
margin-right:20px;
line-height: 38px;
}
div.game_time_box input{
height:30px;
width:40px;
box-sizing: border-box;
}
div.game_start_btn{
width:100px;
height:40px;
line-height: 40px;
box-sizing: border-box;
border:1px solid #ccc;
color: #fff;
cursor: pointer;
background: green;
}
/*控制器*/
div.control{
width:100%;
height:60px;
line-height:60px;
text-align: center;
display: flex;
justify-content: center;
position: fixed;
bottom: 0;
background: #f7f7f7;
}
div.btn{
width:200px;
height:40px;
line-height: 40px;
box-sizing: border-box;
border:1px solid #ccc;
margin:10px;
color: #fff;
cursor: pointer;
}
div.auto{
background: green;
}
div.refresh{
background: red;
}
</style>
</head>
<body>
<div id="root">
<div class="head" v-show="!showControl">
<div v-show="!showTime" class="game_start_box">
<div class="game_time_box">倒計時設置(10 ~ 60s):<input type="number" v-model="userSetGameTime" min="10" max="60"/></div>
<div class="game_start_btn" @click="game_start">開始遊戲</div>
</div>
<div v-show="showTime">倒計時<span>{{gameTime}}</span>秒 </div>
</div>
<div class="container">
<div class="chess" v-show="!showControl">
<div class="chess-grid"><div ref="snowman" class="snowman" :style="moveStyle" title="按鍵盤方向鍵移動我哦" @touchend="dealTouchEvent($event)"></div></div>
<div class="chess-grid" v-for="(item,index) in chess" :key="index" :style="{background:item.background}">{{item.usedScore == 0 ? item.usedScore : item.score}}</div>
<div class="chess-grid"><div class="home"></div></div>
</div>
<div class="score" v-show="showControl">
<div class="title">戰績</div>
<p v-for="(score,index) in gameScores" :key="index">第{{index+1}}戰得分<span>{{score}}</span>分</p>
</div>
</div>
<div class="control" v-show="showControl">
<div class="btn auto" @click="game_review">本局覆盤</div>
<div class="btn refresh" @click="game_update">再來一局</div>
</div>
</div>
<script src="touch.js"></script>
<script>
new Vue({
el: "#root",
data: {
touchingScreen:false,//是否正在觸屏 限制 touch 觸發頻率
showControl:false,//是否顯示本局覆盤、再來一局
showTime:false,//是否顯示倒計時 不顯示倒計時的時候會顯示開始遊戲按鈕
is_get_home:false,
gameTime:30,//系統設置的遊戲時間 30秒一局遊戲
userSetGameTime:30,//用戶設置的遊戲時間
gameScores:[],//單機多人遊戲 遊戲覆盤,存儲歷史得分
currentScore:0,//當前一局遊戲的得分
moveDown:0,
moveRitht:0,
chess:[],//存儲隨機的分數和背景色數組
moveStyle:{transform:"translate(0,0)"},//偏移樣式
unit:0,//記量單位 移動端 移動的距離不再是固定的
isDictionaryUpdate:false,//字典中一部分數據只用更新一次,因此設置flag,isDictionaryUpdate 是否已經同步更新數據,默認否。
dictionary:[//棋盤各塊偏移量對照字典 最後一格爲終點格
{"score":0," i":0, "r":1, "d":0},
{"score":0, "i":1, "r":2, "d":0},
{"score":0,"i":2, "r":3, "d":0},
{"score":0,"i":3, "r":4, "d":0},
{"score":0,"i":4, "r":5, "d":0},
{"score":0,"i":5, "r":6, "d":0},
{"score":0,"i":6, "r":0, "d":1},
{"score":0,"i":7, "r":1, "d":1},
{"score":0,"i":8, "r":2, "d":1},
{"score":0,"i":9, "r":3, "d":1},
{"score":0,"i":10, "r":4, "d":1},
{"score":0,"i":11, "r":5, "d":1},
{"score":0,"i":12, "r":6, "d":1},
{"score":0,"i":13, "r":0, "d":2},
{"score":0,"i":14, "r":1, "d":2},
{"score":0,"i":15, "r":2, "d":2},
{"score":0,"i":16, "r":3, "d":2},
{"score":0,"i":17, "r":4, "d":2},
{"score":0,"i":18, "r":5, "d":2},
{"score":0,"i":19, "r":6, "d":2},
{"score":0,"i":20, "r":0, "d":3},
{"score":0,"i":21, "r":1, "d":3},
{"score":0,"i":22, "r":2, "d":3},
{"score":0,"i":23, "r":3, "d":3},
{"score":0,"i":24, "r":4, "d":3},
{"score":0,"i":25, "r":5, "d":3},
{"score":0,"i":26, "r":6, "d":3},
{"score":0,"i":27, "r":0, "d":4},
{"score":0,"i":28, "r":1, "d":4},
{"score":0,"i":29, "r":2, "d":4},
{"score":0,"i":30, "r":3, "d":4},
{"score":0,"i":31, "r":4, "d":4},
{"score":0,"i":32, "r":5, "d":4},
{"score":0,"i":33, "r":6, "d":4},
{"score":0,"i":34, "r":0, "d":5},
{"score":0,"i":35, "r":1, "d":5},
{"score":0,"i":36, "r":2, "d":5},
{"score":0,"i":37, "r":3, "d":5},
{"score":0,"i":38, "r":4, "d":5},
{"score":0,"i":39, "r":5, "d":5},
{"score":0,"i":40, "r":6, "d":5},
{"score":0,"i":41, "r":0, "d":6},
{"score":0,"i":42, "r":1, "d":6},
{"score":0,"i":43, "r":2, "d":6},
{"score":0,"i":44, "r":3, "d":6},
{"score":0,"i":45, "r":4, "d":6},
{"score":0,"i":46, "r":5, "d":6},
{"score":0,"i":47, "r":6, "d":6}
]
},
methods: {
//生成隨機分數棋格
createChess:function(){
//7*7方格,掐頭去尾,需要生成47個隨機數。
var score;
var bgColor;
if(this.isDictionaryUpdate){
for(var i=0;i<47;i++){
// 按奇數偶數對應正負分值
if(i%2 ==0){
//正數 加分
score =Math.round(Math.random()*10)+2;
bgColor="#16a05d";
}else{
//負數 減分
score =-Math.round(Math.random()*6)-1;
bgColor="#e21918";
}
this.chess.push({
"score":score,
"usedScore":100,//隨便指定一個現今規則不可能有的一個分數即可
"background":bgColor
});
//同步更新對照字典,存儲分值。
this.dictionary[i].score = score;
}
}else{
for(var i=0;i<47;i++){
// 按奇數偶數對應正負分值
if(i%2 ==0){
//正數 加分
score =Math.round(Math.random()*10)+2;
bgColor="#16a05d";
}else{
//負數 減分
score =-Math.round(Math.random()*6)-1;
bgColor="#e21918";
}
this.chess.push({
"score":score,
"usedScore":100,//隨便指定一個現今規則不可能有的一個分數即可
"background":bgColor
});
//同步更新對照字典,存儲分值。
this.dictionary[i].score = score;
//同步更新對照字典,計算準確座標偏移量
this.dictionary[i].r = this.dictionary[i].r * this.unit;
this.dictionary[i].d = this.dictionary[i].d * this.unit;
}
this.isDictionaryUpdate=true;
}
},
//處理手機觸摸事件
dealTouchEvent:function(e){
e||event;
this.touchingScreen = true;
//使用的時候很簡單,只需要像下面這樣調用即可 up, right, down, left爲四個回調函數,分別處理上下左右的滑動事件
EventUtil.listenTouchDirection(e.target,true,this.upCallback, this.rightCallback, this.downCallback, this.leftCallback);
},
//touch的回調事件
upCallback:function(){
//當遊戲倒計時顯示時,即遊戲還未結束,才能觸發鍵盤事件,開始移動。
if(this.showTime && this.touchingScreen){
this.touchingScreen = false;//觸屏只觸發一次
//向上移動
this.moveDown -=this.unit;
this.moveDown < 0 ? this.moveDown = 0 : this.moveDown;
this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
//根據偏移的位置,統計得分。
this.countScore(this.moveRitht,this.moveDown);
}
},
rightCallback:function(){
if(this.showTime && this.touchingScreen){
this.touchingScreen = false;//觸屏只觸發一次
//向右移動
this.moveRitht +=this.unit;
//判斷界限值 不能超出棋盤活動
this.moveRitht > 6*this.unit ? this.moveRitht = 6*this.unit : this.moveRitht;
this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
//根據偏移的位置,統計得分。
this.countScore(this.moveRitht,this.moveDown);
}
},
downCallback:function(){
if(this.showTime && this.touchingScreen){
this.touchingScreen = false;//觸屏只觸發一次
//向下移動
this.moveDown +=this.unit;
this.moveDown > 6*this.unit ? this.moveDown = 6*this.unit : this.moveDown;
this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
//根據偏移的位置,統計得分。
this.countScore(this.moveRitht,this.moveDown);
}
},
leftCallback:function(){
if(this.showTime && this.touchingScreen){
this.touchingScreen = false;//觸屏只觸發一次
//向左移動
this.moveRitht -=this.unit;
//判斷界限值 不能超出棋盤活動
this.moveRitht < 0 ? this.moveRitht = 0 : this.moveRitht;
this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
//根據偏移的位置,統計得分。
this.countScore(this.moveRitht,this.moveDown);
}
},
//計算得分
countScore:function(r,d){
//遍歷偏移量字典,根據當前所在的位置,獲取對應的分值。
//偏移量字典(len=48)比棋格(len=47)多了一個終點的位置信息。
if(!(Math.abs(r - this.unit*6) < 7 && Math.abs(d - this.unit*6) < 7)){
//不在家,賦值false 防止回家後再離開的情形
this.is_get_home = false;
for(var i=0;i<48;i++){
//因爲瀏覽器將數值取整後返回,所以,與實際值差值小於1.累積偏移差值小於1*7. Math.abs 取絕對值.
if(Math.abs(r - this.dictionary[i].r) < 7 && Math.abs(d - this.dictionary[i].d) < 7){
//遊戲開始時,小女孩正常移動後再回到起點會報這個錯誤
//undo: this.chess[i].usedScore 有時會報錯 Cannot read property 'usedScore' of undefined
if(this.chess[i].usedScore == 100){
this.currentScore += this.dictionary[i].score;
//分數一次性有效 走過的分數變爲0.
//爲了覆盤,不直接改變分數,新分數存儲到 usedScore
this.chess[i].usedScore = 0;
}
}
}
}else{
//雪人到家
this.is_get_home = true;
}
},
//計時器,每次時間-1,時間單位秒。
timer:function(){
this.gameTime -= 1
},
//用戶點擊遊戲開始 創建定時器 顯示倒計時
game_start:function(){
if(this.gameTime != this.userSetGameTime){
//系統設置的遊戲時長和用戶設置的遊戲時長衝突,則使用用戶設置的時長
this.gameTime = this.userSetGameTime;
}
this.showTime=true;
timer1=setInterval(this.timer, 1000);
},
game_review:function(){
this.parameter_reset();
this.resetUsedScore();
},
//恢復棋盤 使用過的分數初始化
resetUsedScore:function(){
var len = 47;
while(len--){
this.chess[len].usedScore = 100;
}
},
//本局重玩,只需要重置參數。
parameter_reset:function(){
this.showControl=false;
this.is_get_home = false;
this.showTime=false;//先不顯示倒計時,顯示開始遊戲按鈕。
this.moveRitht=0;
this.moveDown=0;
this.moveStyle.transform = "translate(0,0)";
this.currentScore=0;
},
//頁面初始化 遊戲重新開始
game_update:function(){
this.parameter_reset();
this.gameScores=[];
this.chess=[];
this.createChess();
}
},
watch:{//監測遊戲時間
gameTime(){
if(this.gameTime == 0){
clearInterval(timer1);
this.showControl=true;
this.showTime=false;//倒計時結束,關閉倒計時結果顯示
if(this.is_get_home){
//遊戲結束:倒計時結束,雪人進入小屋。當前得分計入。
this.gameScores.push(this.currentScore);
}else{
//遊戲失敗: 倒計時結束,但雪人未進入小屋。本局得分爲0。
this.currentScore=0;
this.gameScores.push(0);
}
}
}
},
mounted(){
//計量單位等於小方格的寬或高
//獲取的高度值約等於實際值,存在差值。獲取的值取了實際值的近似整數。
this.unit = this.$refs.snowman.offsetHeight;
this.game_update();
}
})
</script>
</body>
</html>
引用的大神代碼工具類 touch.js
var EventUtil = {
addHandler: function (element, type, handler) {
if (element.addEventListener)
element.addEventListener(type, handler, false);
else if (element.attachEvent)
element.attachEvent("on" + type, handler);
else
element["on" + type] = handler;
},
removeHandler: function (element, type, handler) {
if(element.removeEventListener)
element.removeEventListener(type, handler, false);
else if(element.detachEvent)
element.detachEvent("on" + type, handler);
else
element["on" + type] = handler;
},
/**
* 監聽觸摸的方向
* @param target 要綁定監聽的目標元素
* @param isPreventDefault 是否屏蔽掉觸摸滑動的默認行爲(例如頁面的上下滾動,縮放等)
* @param upCallback 向上滑動的監聽回調(若不關心,可以不傳,或傳false)
* @param rightCallback 向右滑動的監聽回調(若不關心,可以不傳,或傳false)
* @param downCallback 向下滑動的監聽回調(若不關心,可以不傳,或傳false)
* @param leftCallback 向左滑動的監聽回調(若不關心,可以不傳,或傳false)
*/
listenTouchDirection: function (target, isPreventDefault, upCallback, rightCallback, downCallback, leftCallback) {
this.addHandler(target, "touchstart", handleTouchEvent);
this.addHandler(target, "touchend", handleTouchEvent);
this.addHandler(target, "touchmove", handleTouchEvent);
var startX;
var startY;
function handleTouchEvent(event) {
switch (event.type){
case "touchstart":
startX = event.touches[0].pageX;
startY = event.touches[0].pageY;
break;
case "touchend":
var spanX = event.changedTouches[0].pageX - startX;
var spanY = event.changedTouches[0].pageY - startY;
if(Math.abs(spanX) > Math.abs(spanY)){ //認定爲水平方向滑動
if(spanX > 30){ //向右
if(rightCallback)
rightCallback();
} else if(spanX < -30){ //向左
if(leftCallback)
leftCallback();
}
} else { //認定爲垂直方向滑動
if(spanY > 30){ //向下
if(downCallback)
downCallback();
} else if (spanY < -30) {//向上
if(upCallback)
upCallback();
}
}
break;
case "touchmove":
//阻止默認行爲
if(isPreventDefault)
event.preventDefault();
break;
}
}
}
};