记录16 贪吃蛇第二部分,实现,结束

构建Block构造方法

我们将定义一个Block 构造方法,它会创建对象来表示不可见的游戏网格中的单个的块。每个块都有 col(column 的缩写)和 row 属性, 它们将存储特定的块在网格上的位置。下图展示了这个带有数目固定的一 些列和行的网格。尽管这个网格并不会真的出现在屏幕上,游戏设计成让苹果和贪吃蛇段总是能够和网格中的块对齐。


Paste_Image.png

我们可以通过该构造方法创建一个块实例,比如下面创建了一个第5列第5行中的块

var sampleBlock = new Block(5,5);

上面只是创建了实例,但是没有将实例绘制出来,要在游戏面板上绘制出该实例还必须使用drawSquare或drawCircle方法。

添加equal方法

在游戏中,需要知道两个块是否位于同一位置。例如,如果苹果和贪吃蛇的头部位于同一位置,这意味着,贪吃蛇会吃掉苹果。另一方面,如果贪吃蛇的头部和尾部位于同一位置,那么,贪吃蛇碰到了自己。
为了使得比较块的位置更为容易,我们给Block 构造方法原型添加了 equal 方法。当在一个块对象上调用equal并传递另一个对象作为一个参数, 如果两个对象位于相同的位置,它将返回true(否则的话,返回false)。代码如下:

Block.prototype.equal = function (otherBlock){
            return this.col === otherBlock.col && this.row === otherBlock.row;
}

我们将使用 equal 方法来检查贪吃蛇是否吃到苹果或者碰到自己。

创建贪吃蛇

我们把贪吃蛇的位置存储为一个名为segments 的数组,其中包含了一系列的块对象。为了移动贪吃蛇,我们在segments 数组的 开头添加一个新的块,并且从数组的尾部删除该块。Segments 数组的第一个元素将表示贪吃蛇的头部。

  • Snake构造方法,贪吃蛇一开始长度为3个网格
    direction 属性存储了贪吃蛇的当前位置
    nextDirection 属性,它存储了贪吃蛇在下一个动画步骤将要移动的方向,除非按下方向键修改该属性,不然贪吃蛇的前进方向不会改变。构造方法将这两个属性都设置为 "right",因此游戏一开始的时候,贪吃蛇向右移动。
var Snake = function (){
    this.segments = [
        new Block(7,5),
        new Block(6,5),
        new Block(5,5)
    ];
        this.direction = "right";
    this.nextDirection = "right";
};
组成贪吃蛇最初的块

绘制贪吃蛇

为了绘制贪吃蛇,我们直接遍历其segments 数组中的每一个块,在每个 块上调用在前面所创建的drawSquare 方法。这将会为贪吃蛇的每一段都绘制 一个方块。

Snake.prototype.draw = function(){
    for(var i=0;i < this.segments.length; i++){
        this.segments[i].drawSquare("Blue"); //等价于Block实例.drawSquare("Blue");
    }
};

移动贪吃蛇

贪吃蛇移动看起来好像很复杂,身体的每个块都得移动,有的块可能方向可能会变。其实很简单,我们不用去移动贪吃蛇每个网格组成部分,只要往头部添加一个网格,把最尾部网格删除,其他网格不动就可以了。
我们将创建一个 move 方法,沿着贪吃蛇的当前方向将其移动一个块。为 了移动贪吃蛇,我们添加了一个新的头部段(在segments 数组的开头添加了一个新的block 对象),然后,从segments 数组 删除尾部段。

move方法还将调用一个checkCollision方法, 来查看新的头部是否与贪吃蛇其他的部分或者墙 发生碰撞,以及新的头部是否吃到了苹果。如果新的头部与身体或墙发生碰撞,调用gameOver 函数来结束游戏。如果贪吃蛇吃到了苹果,我们增加分数,并且将苹果移动到 新的位置。

将 this.direction 设置为和this. nextDirection 相等,这会将贪吃蛇的移动方向更 新为与近按下的箭头键一致(当我们介绍keydown 事件处理程序的时候, 将会更详细地看到这是如何工作的)。

对于动画中的每一步,贪吃蛇的 direction 属性都会更新一次,因为每个动画步骤都会调用一次move 方法。另一方面,当玩家在任何时候按下 一个箭头键,nextDirection属性都会更新(因此,如果玩家按键真的很快, 这个属性理论上可能会在每个动画步骤中更新多次)。通过保持这两个 属性各自分离,我们确保了如果玩家在动画的两个步骤之间非常快速地 按下两个箭头键,贪吃蛇不会回过头来碰到自己。

当 this.nextDirection 为 "down" 的时候创建 newHead

检查碰撞并添加头部

只有在checkCollision 返回true 的时候,才会遇到return关键字,因此,如果贪吃蛇没有和任何物体发生碰撞,将会执行剩余的代码。只要贪吃蛇没有和某个物体碰撞,就会在贪吃蛇的前面添加新的头部, 通过使用unshift 把 newHead 添加到segments 数组的开始,从而做到这一点。
贪吃蛇移动只要往头部添加一个网格,把最尾部网格删除,其他网格不动就可以了。由于贪吃蛇每个网格是保存在segments属性数组里面的,于是我们只要每次移动往该数组第一个网格添加一个头部元素(unshift添加一个元素),如果是吃到苹果那么就不用改变尾部,如果不是就通过pop把最后一个网格元素去掉。下次根据新的数组绘制出新的贪吃蛇。

数组unshift的用法:用unshift 把元素"Monkey" 和 "Polar Bear" 添加到数组的起始位 置,每一次原有的值都会向后顺延 一个索引位置。每次调用unshift,也会返回数 组新的长度,就像 push 一样。


使用equal 方法来比较newHead 和 apple.position。如果这两 个块位于相同的位置,equal 方法将会返回true,这意味着,贪吃蛇吃掉了苹果。

//创建一个新的网格加到当前贪吃蛇前进方向的头部,删除尾部网格
Snake.prototype.move = function(){
    var head = this.segments[0];
    var newHead;
    this.direction = this.nextDirection;
    if(this.direction === "right"){
        newHead = new Block(head.col+1,head.row);
    }else if(this.direction === "down"){
        newHead = new Block(head.col,head.row+1);
    }else if(this.direction === "left"){
        newHead = new Block(head.col-1,head.row);
    }else if(this.direction === "up"){
        newHead = new Block(head.col,head.row-1);
    }

    if (this.checkCollision(newHead)){
        gameOver();
        return;
    }

    this.segments.unshift(newHead);

    if(newHead.equal(apple.position)){
        score++;
        apple.move();
    }else{
        this.segments.pop();
    }
}

添加 checkCollision 方法

每次为贪吃蛇的头部设置一个新的位置的时候,都必须检查碰撞。碰撞检测在游戏机制中是一个很常见的步骤,往往也是游戏编程中较为复杂 的一个方面。好在,在贪吃蛇游戏中,碰撞检测相对简单。
注意:该游戏中(0,0)这样的位置不是活动位置边界,已经是由灰色边框网格填充了。游戏中四个边界都有10像素宽的边界,所以贪吃蛇头部网格的新位置head.col === 0就“Game Over”了。

添加 keydown 事件处理程序

var directions = {
    37: "left",
    38: "up",
    39: "right",
    40: "down"
};

$("body").keydown(function(event){
    var newDirection = directions[event.keyCode];
    if(newDirection !== undefined){
        snake.setDirection(newDirection);
    }
});

游戏入口程序

var intervalId = setInterval(function(){
    ctx.clearRect(0,0,width,height);
    drawScore();
    snake.move();
    snake.draw();
    apple.draw();
    drawBorder();
},100);

移动苹果

由于边框的存在,苹果出现的范文只能(1~38)*10,0和39都是边框。
调用 Math.fl oor(Math.random() * 38),它给出了从0到37的一个随机数,然后,给结果加1以得到1到38之间的一个数字。

添加 setDirection 方法

setDirection 还防止玩家调头以导致贪吃蛇立即碰到自己。例如,如果贪吃蛇向右移动,然后它突然向左转而不向上或向下移 以改变路径,那么它会和自己碰撞。这种现象叫作非法调头,因为我们不想让玩家这么做。也就是如果当前方向是向右,那么按键是向左直接return,不更新当前方向。


基于当前方向的有效的新方向

完整可运行代码

<!DOCTYPE html>
<html>
<head>
    <title>Learn</title>
</head>
<body>
    <canvas id="canvas" width="400" height="400"></canvas>
    <script type="text/javascript" src="jquery-3.1.1.js"></script>
    <script type="text/javascript">
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        var width = canvas.width;
        var height = canvas.height;

        //游戏面板看成是40个10*10的网格组成的
        var blockSize = 10;//每个网格的大小
        var widthInBlocks = width / blockSize;
        var heightInBlocks = height / blockSize;

        //每次贪吃蛇吃掉一个苹果的时候,分数加1
        var score = 0; 

        //创建一个drawBorder函数来绘制围绕画布的灰色边框,1个块(10 像素)那么宽。 
        var drawBorder = function(){
            ctx.fillStyle = "Gray";
            //绘制4条边框
            ctx.fillRect(0,0,width,blockSize);
            ctx.fillRect(0,height-blockSize,width,blockSize);
            ctx.fillRect(0,0,blockSize,height);
            ctx.fillRect(width-blockSize,0,blockSize,height);
        }

        //编写记分函数
        var drawScore = function(){
            ctx.font = "20px Courier";
            ctx.fillStyle = "Black";
            ctx.textAlign = "left";
            ctx.textBaseline = "top"; //文本左对齐
            ctx.fillText("Score:"+score,blockSize,blockSize);
        }

        //结束游戏
        var gameOver = function(){
            clearInterval(intervalId);
            ctx.font = "60px Courier";
            ctx.fillStyle = "Black";
            ctx.textAlign = "center";
            ctx.textBaseline = "middle";
            ctx.fillText("Game Over",width/2,height/2);
        }

        //绘制圆的方法
        var circle = function(x,y,radius,fillCircle){
            ctx.beginPath();
            ctx.arc(x,y,radius,0,Math.PI*2,false);
            if(fillCircle){
                ctx.fill();
            }else{
                ctx.stroke();
            }
        };

        //Block构造方法
        var Block = function(col,row){
            this.col = col;
            this.row = row;
        };

        //绘制方块
        Block.prototype.drawSquare = function(color){
            var x = this.col * blockSize;
            var y = this.row * blockSize;
            ctx.fillStyle = color;
            ctx.fillRect(x,y,blockSize,blockSize);
        }

        //绘制圆封装成Block的原型方法
        Block.prototype.drawCircle = function(color){
            var centerX = this.col * blockSize + blockSize/2;
            var centerY = this.row * blockSize + blockSize/2;
            ctx.fillStyle = color;
            circle(centerX,centerY,blockSize/2,true);
        }

        //检查Block是否再同一个位置已经是其他的Block了(如贪吃蛇前进,后面的块顶替前面块的位置,都是在同一个位置却是不同的块了)
        Block.prototype.equal = function (otherBlock){
            return this.col === otherBlock.col && this.row === otherBlock.row;
        }

        //贪吃蛇构造方法
        var Snake = function (){
            this.segments = [
                new Block(7,5),
                new Block(6,5),
                new Block(5,5)
            ];

            this.direction = "right";
            this.nextDirection = "right";
        };

        //绘制贪吃蛇身体的每一个网格部分
        Snake.prototype.draw = function(){
            for(var i=0;i < this.segments.length; i++){
                this.segments[i].drawSquare("Blue");
            }
        };

        //创建一个新的网格加到当前贪吃蛇前进方向的头部
        Snake.prototype.move = function(){
            var head = this.segments[0];
            var newHead;
            this.direction = this.nextDirection;
            if(this.direction === "right"){
                newHead = new Block(head.col+1,head.row);
            }else if(this.direction === "down"){
                newHead = new Block(head.col,head.row+1);
            }else if(this.direction === "left"){
                newHead = new Block(head.col-1,head.row);
            }else if(this.direction === "up"){
                newHead = new Block(head.col,head.row-1);
            }

            if (this.checkCollision(newHead)){
                gameOver();
                return;
            }

            this.segments.unshift(newHead);

            if(newHead.equal(apple.position)){
                score++;
                apple.move();
            }else{
                this.segments.pop();
            }
        }

        //检查蛇的头部是否碰到墙体或者吃到自己的尾巴
        Snake.prototype.checkCollision = function(head){
            var leftCollision = (head.col === 0);
            var topCollision = (head.row === 0);
            var rightCollision = (head.col === widthInBlocks-1);
            var bottomCollision = (head.row === heightInBlocks-1);
            
            var wallCollision = leftCollision || topCollision ||
                rightCollision || bottomCollision;

            var selfCollision = false;

            for(var i=0;i<this.segments.length;i++){
                if(head.equal(this.segments[i])){
                    selfCollision = true;
                }
            }
            return wallCollision || selfCollision;              
        }

        //根据按下的键盘决定贪吃蛇下次的移动方向
        Snake.prototype.setDirection = function(newDirection){
            if(this.direction === "up" && newDirection === "down" ){
                return;
            }else if(this.direction === "right" && newDirection === "left"){
                return;
            }else if(this.direction === "down" && newDirection === "up"){
                return;
            }else if(this.direction === "left" && newDirection === "right"){
                return;
            }
            this.nextDirection = newDirection;
        }

        //苹果构造器
        var Apple = function(){
            this.position = new Block(10,10);
        };

        //添加原型方法,在苹果的坐标属性的位置上绘制一个苹果
        Apple.prototype.draw = function(){
            this.position.drawCircle("LimeGreen");
        };

        //移动苹果到一个新的随机位置
        Apple.prototype.move = function(){
            var randomCol = Math.floor(Math.random()*(widthInBlocks-2))+1;
            var randomRow = Math.floor(Math.random()*(heightInBlocks-2))+1;
            this.position = new Block(randomCol,randomRow);
        };

        //游戏开始时,创建贪吃蛇和苹果
        var snake = new Snake();
        var apple = new Apple();

        //游戏动画,加载script便签就得到间歇调用的执行。其他代码比如函数定义也是执行了,只不过都是函数定义
        var intervalId = setInterval(function(){
            ctx.clearRect(0,0,width,height);
            drawScore();
            snake.move();
            snake.draw();
            apple.draw();
            drawBorder();
        },100);

        //keyCode用更形象的字符串来表示方向
        var directions = {
            37: "left",
            38: "up",
            39: "right",
            40: "down"
        };

        $("body").keydown(function(event){
            var newDirection = directions[event.keyCode];
            if(newDirection !== undefined){
                snake.setDirection(newDirection);
            }
        });
    </script>
</body>
</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 今天实现一款经典小游戏的实例,贪吃蛇想必大家都有接触过,当然今天实现的细节没有那么全面,只能算是简易版本的小游戏,...
    牛水聿阅读 1,156评论 0 3
  • 我们将自己构建经典的街机游戏贪吃蛇。 在贪吃蛇中,玩家控制一条蛇向上、向下、向左或向右移动。随着蛇在游戏区域内移动...
    suhuanzhen阅读 532评论 0 1
  • 关于我 我是IsCoding,11年开始做 Android 开发 已经做了7年在创业公司负责过技术,拿到过融资。想...
    IsCoding阅读 1,830评论 0 3
  • 翻译自“Collection View Programming Guide for iOS” 0 关于iOS集合视...
    lakerszhy阅读 3,810评论 1 22
  • 做个简单练习,题目关于点菜。要求用户注册登录之后,来到点菜页面,点菜页面数据从后台数据库获取后页面渲染出来。用户选...
    2010jing阅读 711评论 4 4