方格遊戲移動版

方格遊戲的移動版適配開發,比我預計得快一點。

 

遊戲界面預覽

 

適配難點

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;
            }
        }
    }
};

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章