canvas教程之《贪吃蛇》

作者: 不是加多宝的宝 | 来源:发表于2016-08-31 13:51 被阅读1074次

    本次我会带领大家用html5的新标签 canvas 来制作一个简单的贪吃蛇的游戏。本章学习需要了解canvas的基础知识和知晓如何使用canvas来画一个空心的方块。传送门:《canvas教程之初步使用》

    先睹为快

    先让大家看到效果,才能有动力更好的学习下去,老师已经将贪吃蛇的游戏完成,并放在了vps上,下面放上地址:贪吃蛇

    游戏的原理

    要想制作 贪吃蛇 的游戏,我们首先要对这个游戏的原理有所了解。

    1. 游戏的目标:操作我们的蛇来吃掉更多的食物,是蛇变长,从而得到更高的分数。
    2. 游戏的对象:蛇,食物。
    3. 游戏的结束:蛇碰到可见区域的边缘,或则蛇头碰到自己(吃到自己)。

    原理:其实就是我们变换两个游戏对象的状态。然后将他们的状态定时(帧率)显示在我们的canvas上。实际上这就是最简单的引擎。我们在这里使用js的setInterval函数来实现本游戏的主循环。

    开始动手

    我们先创建一个有canvas , 成绩显示标签,开始按钮 的html文档出来。

    <!DOCTYPE HTML>
    <html>
        <head>
            <meta charset="utf-8"/>
            <style>
                body{background:#eeeeee;}
                canvas{background:#ffffff;}
            </style>
        </head>
        <body>
            <!-- 我们要操作的canvas -->
            <canvas id="gbcanvas" width="300" height="300"></canvas>
            <br>
            <!-- 显示分数的span #score -->
            <span>your score : </span><span id="score">0</span>
            <br>
            <!-- 游戏操作提示 -->
            <span>操作方法:W A D S</span>
            <br>
            <!-- 游戏开始的按钮,点击调用startGame()函数,该函数后面完善 -->
            <button onclick="startGame();">Start Game</button>
            <script type="text/javascript">
                //我们的代码将在这里书写
            </script>
        </body>
    </html>
    

    第二步,我们把需要用到的东西都初始化以下:

    var canvas = document.getElementById('gbcanvas'); // 这里通过gbCanvas获取canvas对象
    var c = canvas.getContext('2d'); //这里通过canvas获取处理API的上下文context
    var s = document.getElementById('score'); //把游戏的分数显示在这里
    var mWidth = 300; //当前可视区域的宽,即canvas的宽
    var mHeight = 300;  //当前可视区域的高,即canvas的高
    var unit = 5; //设定每个格子的边长
    var mwid = mWidth / unit; //计算当前横向格子数量
    var mhei = mHeight / unit; //计算当前竖向格子数量
    var point = point = {x : 0 , y : 0}; //记录食物的坐标的变量初始化
    var score = 0; //记录成绩的变量初始化
    

    第三步,我们要创建最重要的“蛇”对象:
      计划一下我们的“蛇”对象应该有以下几个方法 和 属性:
      1.currOri : 蛇当前的前进方向,比如:right。
      2.ori : 蛇的方向组,左右为一组,上下为一组,因为蛇不能直接改变方向为同组方向。
      3.oriss : 所有允许的方向数组,值为left , down , right , up
      4.mes : 蛇的身体数组,记录蛇身体每个点的坐标
      5.add方法:用于吃一个食物后在身体最后追加一节。
      6.move[ori]方法:向某个方向移动一格。例如moveUp,moveLeft。
      7.move方法:由6调用,6是让头移动某个方向一格,然后调用此方法,让所有后面的身体跟着动。
      8.changeOri方法:改变蛇当前的移动方向,即currOri的值,通过ori和oriss属性判断合法性。
      9.canChangeOri方法,判断改变方向的合法性。由8调用。
      10.isCrashSelf方法:判断头是否吃到身体。
      11.isCrashWell方法:判断是否撞墙。
      12handleAdd方法:自动处理吃东西,作用,判断当前头是否吃到食物,如果迟到则调用add方法。
    代码与详尽解释:

    //注意本对象,并不改变其在画布上的样子,只是负责改变状态,改变样子的另有方法
    //蛇对象
    var shake = {
        startX : 3, //开始头x坐标
        startY : 0, //开始头y坐标
        currOri : 'right', //初始化方向
        ori : [['left' , 'right'] , ['up' , 'down']], //相逆方向数组
        oriss : ['left' , 'right' , 'up' , 'down'], //所有允许的方向,用来判断方向合法性,在canChangeOri方法
        mes : [{x : 3 , y : 0} , {x : 2 , y : 0} , {x : 1 , y : 0}], //初始化蛇的身体坐标,初始长度3
        //坐标为格子坐标非像素坐标
        //添加一个身体的方法
        add : function(){
            //判断当前尾部方向
            var last = this.mes[this.mes.length - 1]; //获取最后一个身体
            var plast = this.mes[this.mes.length - 2]; //获取倒数第二个身体
            var px = last.x - plast.x;
            var py = last.y - plast.y; //根据计算最后两个身体的坐标差,来计算添加身体应在的方向
            //计算新加元素位置
            var newEle = {x : last.x + px , y : last.y + py}; //创建一个新身体
            this.mes.push(newEle); //将新身体假如身体数组
        },
        //移动方向方法,下面几个方法类似,只是方向不同
        moveup : function(){
            var pre = this.mes[0]; //记录第一个身体,即头部的坐标
            this.mes[0] = {x : pre.x , y : pre.y - 1}; //让头部的坐标像上移动一格
            this.move(pre); //调用移动身体的方法
        },
        movedown : function(){
            var pre = this.mes[0];
            this.mes[0] = {x : pre.x , y : pre.y + 1};
            this.move(pre);
        },
        moveleft : function(){
            var pre = this.mes[0];
            this.mes[0] = {x : pre.x - 1 , y : pre.y};
            this.move(pre);
        },
        moveright : function(){
            var pre = this.mes[0];
            this.mes[0] = {x : pre.x + 1 , y : pre.y};
            this.move(pre);
        },
        //移动身体
        move : function(pre){//参数为之前第一个身体,即头部的位置对象
            var tmp;
            for(var i = 1 ; i < this.mes.length ; i++){ //遍历每一个身体节点
                tmp = this.mes[i];
                this.mes[i] = pre;
                pre = tmp;
            } //并且把每个节点的左边变化成前一个节点的坐标,达到依次向前的目的
        },
        //改变方向
        changeOri : function(ori){
            if(this.oriss.indexOf(ori) == -1){ //判断方向是否在允许方向内
                return;
            }
            if(!this.canChangeOri(ori)){ //判断改变方向是否合法
                return;
            }
            this.currOri = ori; //如果上面两个都通过,改变方向
        },
        //判断改变的方向是否合法
        canChangeOri : function(ori){ //参数为方向字符串 例如:left
            if(ori == this.currOri){ //判断方向是否为当前方向,如果是则无需操作
                return false;
            }
            var oris = null;
            for(var i in this.ori){ //判断是否改变方向为当前方向的逆方向
                if(this.ori[i].indexOf(this.currOri) != -1){
                    oris = this.ori[i];
                    break;
                }
            }
            if(oris.indexOf(ori) != -1){
                return false;
            }
            return true;
        },
        //判断是否碰撞到了自己
        isCrashSelf : function(){
            var head = this.mes[0]; //获取头节点
            for(var i = 1 ; i < this.mes.length ; i++){ //遍历身体节点
                if(this.mes[i].x == head.x && this.mes[i].y == head.y){ //判断头结点是否碰撞身体
                    return true;
                }
            }
            return false;
        },
        //判断是否撞墙
        isCrashWell : function(width , height){ //参数为横竖的格子数量
            var head = this.mes[0]; //获取头节点
            if(head.x < 0 || head.y < 0){ //判断是否撞左上墙
                return true;
            }
            if(head.x > (width - 1) || head.y > (height - 1)){ //判断是否撞右下墙
                return true;
            }
            return false;
        },
        //处理吃东西
        handleAdd : function(){
            var head = this.mes[0]; //获取头节点
            if(head.x == point.x && head.y == point.y){ //判断头节点是否碰撞食物节点,食物在外定义
                this.add(); //调用添加身体
                getPoint(); //生成一个节点
                setPoint(); //画一个节点
                score++; //加分
                s.innerHTML = score; //显示分数
            }
        }
    }
    

    第四步,关于食物的代码,这里没有使用面向对象。

    //生成点
    function getPoint(){
        point.x = Math.floor(Math.random(0 , mwid)*60);
        point.y = Math.floor(Math.random(0 , mhei)*60);
    }
    
    //画点
    function setPoint(){
        c.rect(point.x * unit , point.y * unit , unit , unit);
    }
    
    

    第五步,画蛇,清屏的代码,画蛇的代码可以自己封装到蛇对象中去,很容易哦。

    //画蛇
    function setShake(){
        for(var i = 0 ; i < shake.mes.length ; i++){
            c.fullStyle = '#ffffff';
            c.lineStyle = '#000000';
            c.rect(shake.mes[i].x * unit , shake.mes[i].y * unit , unit ,unit);
        }
        c.stroke();
    }
    
    //清屏
    function clear(){
        c.clearRect(0 , 0 , mWidth , mHeight);
    }
    

    最后一步,开始游戏和监听键盘的代码哦。

    //开始游戏
    function startGame(){
        clearInterval(window.looper); //终止游戏主循环
    
        //初始化状态
        shake.mes = [{x : 3 , y : 0} , {x : 2 , y : 0} , {x : 1 , y : 0}];
        shake.currOri = 'right';
    
        c.beginPath();  //开始画笔
        
        getPoint(); //设置点
        
        setPoint();
        
        setShake(); //话蛇
        
        //画
        c.stroke();
        
        //游戏主循环
        window.looper = setInterval(function(){
            var method = 'move' + shake.currOri + '()'; //调用方向函数
            eval('shake.' + method); //执行方向方法
            clear(); //清理屏幕
            c.beginPath(); //开始绘制
            shake.handleAdd(); //处理吃东西
            setPoint(point); //设置点
            setShake(); //画蛇
            if(shake.isCrashWell(mwid , mhei)){ //是否撞墙,未使用是否吃自己。想用调用shake.isCrashSelf方法。
                clearInterval(window.looper);
                console.log('you die');
                alert('you die , and your score is ' + score);
            }
        } , 200);
    }
    
    //键盘监听
    window.onkeyup = function(key){
        var ori = '';
        switch(key.keyCode){
            case 65:
                ori = 'left';
                break;
            case 68:
                ori = 'right';
                break;
            case 87:
                ori = 'up';
                break;
            case 83:
                ori = 'down';
                break;
        }
        if(ori == ''){
            return;
        }
        //改变蛇走向
        shake.changeOri(ori);
    }
    

    贪吃蛇的代码实际上非常简单,没有用到复杂的矩阵变幻,每个动作都是在游戏住循环中绘制,把绘制图形,和操作对象状态分开来,实际上也算是最简单的一个游戏引擎。希望对大家有所启发,虽然代码详尽但是并没有带着大家一点一点的做,所以还是要认真的看代码才行,然后必须要实际写一些才可以。
    留个小作业,完善这个贪吃蛇。
    1.把食物封装成对象。
    2.将绘制的方法也封装进对象中。这样只需要在游戏主循环中调用绘制方法即可。

    希望本文对大家有所启发,good拜。

    相关文章

      网友评论

      本文标题:canvas教程之《贪吃蛇》

      本文链接:https://www.haomeiwen.com/subject/ptbqettx.html