ps前端开发那些事程序员

canvas教程之《贪吃蛇》

2016-08-31  本文已影响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拜。

上一篇下一篇

猜你喜欢

热点阅读