• 贪吃蛇


    贪吃蛇

    今天呢,主要和小伙伴们分享一下一个贪吃蛇游戏从构思到实现的过程~因为我不是很喜欢直接PO代码,所以只copy代码的童鞋们请出门左转不谢。

    按理说canvas与其应用是老生常谈了,可我在准备阶段却搜索不到有用的资料(不是代码!),所以说呢,只能自力更生 -_- 

    首先是大致要考虑的东西:

    1.要有蛇(没蛇怎么叫贪吃蛇)。

    2.然后要有地图(蛇是不能上天的)。

    3.不能水平垂直掉头(如果想掉头,需要至少变换方位并且至少移动一格才可)。

    4.食物(不然怎么贪吃)。

    5.吃了食物要变长(这才是精髓)。

    PS:~现在我回想起来,当时的确只想到这么多(⊙﹏⊙)

    构思完毕,开工!

    怎么做呢?从大到小,先画个矩形作地图,可我觉得太丑,于是花了一张图出来:

    1
    2
    3
    4
    5
        context.beginPath();
    var bgImg = new Image();
    bgImg.src = "img/background.png";
    context.drawImage(bgImg, 0, 0, 600, 600);
    context.closePath();

    现在我们有地图了

    地图上好像缺点什么……没错就是礼物,所以我们现在生成礼物,那么问题来了:礼物最多有几个、生成位置、何时生成。

    我这里暂时定义为:最多2个、随机位置生成、当礼物个数小于2时生成至2个。

    接下来就很简单了,上图中,允许蛇活动的范围是14颗树(周围两颗树是墙),然后16颗树=600px,很容易我们得到每格多宽~

    所以呢,我们只需要定义一个随机生成1-14整数的方法就可以很轻松找到应该生成的位置:

    1
    2
    3
    4
    //随机数
    function selectfrom() {
       return Math.floor(Math.random() * 14 + 1);
    }

    然后再用求出的数乘以每一格子的宽度,即可求出生成的具体X坐标,因为是正方形,所以Y也一样:

    1
    2
    var x = selectfrom() * (600/16);
    var y = selectfrom() * (600/16);

    并且每得到一组礼物坐标后,都需要存储在一个数组内(一会儿有大用处),至于画矩形太基础我就不说了。

    And Now,我们有了礼物,有了地图,就差蛇了,那么问题又来了:出生的蛇多长、出生地、死亡方式、移动方式、转弯方式、如何判断吃掉了礼物、吃掉了礼物变长到哪里。

    出生蛇长度:实际编写过程中,我发现默认长度1和2都不能够很好的体现“蛇的转弯”,所以定义为3,并且需将蛇身所有坐标记录在数组内。

    出生地:地图中央或者自己定一个位置(按照格子来分),XY坐标求取方式上面已经说过不再赘述。

    死亡方式:碰到障碍,或者(吃到自己)蛇头碰到蛇身。

    移动方式:通过定义一个全局变量记录当前方向(0、1、2、3,默认1),并且使用计时器驱动蛇运动。

    转弯方式:加入键盘按键检测事件,当方向键按下的时候修改-记录方向的全部变量即可。

    如何判断吃掉了礼物:每次蛇头移动时,都要遍历下礼物集合(上面有说过),如果蛇头将要移动到的下个坐标与之重合了,则视为吃掉了礼物。

    吃掉了礼物变长到哪里:直接加在头部可能会导致意外的死亡,所以我决定吃到礼物后的下一次移动不消除蛇尾(最后一个元素)。

    有了上面的构思,我们可以着手定义一些可能会用到的公共变量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var canvas = document.getElementById("mycanvas");//画布主体
    var context = canvas.getContext("2d");
    var timer;//计时器
    const WIDTH = canvas.width;//画布宽
    const HEIGHT = canvas.height;//画布高
    const XSUM = 16; //画布宽分为几格
    const YSUM = 15; //画布高分为几格
    const MAXFFOD = 2; //最大食物数量
    var score = 0;//定义记录游戏得分
    var xsplit = WIDTH / XSUM; //x每一格子的宽度
    var ysplit = HEIGHT / YSUM; //y每一格子的高度
    var foodcount = 0; //当前食物数量
    var sinak = []; //贪吃蛇坐标集
    var get = []; //礼物坐标集
    var MoveTo = 1; //移动方向 默认1(右)

    有了这些变量,是不是发现很多东西都通了呢?

    我们先来画蛇:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //画贪吃蛇
    function drawsinak(sl) { //sl默认长度
        context.beginPath();
        context.fillStyle = "#000";
        var ling = 0; //贪吃蛇被打印长度
        for (var r = 0; r < sinak.length; r++) {
            context.fillRect(sinak[r].split(',')[0], sinak[r].split(',')[1], xsplit, ysplit);
            ling++;
        }
        if (ling == 0) {
            for (var i = 0; i < sl; i++) {
                context.fillRect(xsplit * (7 - i), ysplit * 6, xsplit, ysplit); //默认出生点:7,6默认中心点
                sinak.push(xsplit * (7 - i) + ',' + ysplit * 6);
            }
        }
        context.fill();
        context.closePath();
    }

    可以看到我将生成的蛇的坐标都计入了数组内,生成的礼物自然也要计入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    context.beginPath();
      var x = selectfrom(XSUM - 2) * xsplit;
      var y = selectfrom(YSUM - 2) * ysplit;
      context.fillStyle = "red";
      for (var i = 0; i < get.length; i++) {
          context.fillRect(get[i].split(',')[0], get[i].split(',')[1], xsplit, ysplit);
          context.fill();
          foodcount++;
      }
      if (MAXFFOD > foodcount) {
          context.fillRect(x, y, xsplit, ysplit);
          context.fill();
          foodcount++;
          get.push(x + ',' + y);
      }
      context.closePath();

    接下来比较重要了,蛇的移动,以及吃到礼物和触发死亡判断:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    //移动方法
    //[c]移动方向 上右下左 0123
    function sinakMove(c) {
        context.beginPath();
     
        //默认右侧为头
        var tou = sinak[0]; //头
        var weiba = sinak[sinak.length - 1]; //尾巴
     
        var oldX = tou.split(',')[0]; //头部旧X坐标
        var oldY = tou.split(',')[1]; //头部旧Y坐标
     
        var newX = 0; //头部最新X坐标
        var newY = 0; //头部最新Y坐标
     
        //计算头部最新XY坐标
        switch (c) {
            case 0:
                newX = oldX;
                newY = oldY - ysplit;
                break;
            case 1:
                newX = (oldX - 0) + xsplit;
                newY = oldY;
                break;
            case 2:
                newX = oldX;
                newY = (oldY - 0) + ysplit;
                break;
            case 3:
                newX = oldX - xsplit;
                newY = oldY;
                break;
        }
     
        var flag = 0; //有沒有吃到礼物 0沒有1有
     
        //如果吃到了礼物,则不消减尾部最后元素
        for (var i = 0; i < get.length; i++) {
            if (newX == get[i].split(',')[0] && newY == get[i].split(',')[1]) {
                sinak.unshift(newX + ',' + newY);
                foodcount--; //礼物计数减少1个
                get.splice(i, 1); //清空礼物
                flag = 1;
            }
        }
        //如果沒有吃到礼物,则判断是否碰到障碍或吃到自己
        if (flag == 0) {
            for (var i = 0; i < sinak.length; i++) {
                if (newX == sinak[i].split(',')[0] && newY == sinak[i].split(',')[1]) {
                    if (confirm('吃掉了自己,游戏失败!是否重新开始?')) {
                        location.reload(true);
                    else {
                        context.clearRect(0, 0, WIDTH, HEIGHT);
                    }
                }
            }
            if (xsplit * (XSUM - 2) < newX || ysplit * (YSUM - 2) < newY || newX == 0 || newY == 0) {
                if (confirm('撞墙了,游戏失败!是否重新开始?')) {
                    location.reload(true);
                }
            }
        }
     
        //如果没有吃到礼物,那么进行普通移动
        if (flag == 0) {
            sinak.unshift(newX + ',' + newY);
            sinak.splice(sinak.length - 1, 1);
        }
     
        //画蛇
        for (var r = 0; r < sinak.length; r++) {
            context.fillRect(sinak[r].split(',')[0], sinak[r].split(',')[1], xsplit, ysplit);
        }
        context.closePath();
    }

    控制蛇的方向:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //键盘事件
    document.onkeydown = function (event) {
        var e = event || window.event || arguments.callee.caller.arguments[0];
        var move = 0; //移动方向
        if (e && e.keyCode == 37) { //左
            move = (MoveTo == 1 ? 1 : 3);
        else if (e && e.keyCode == 38) { //上
            move = (MoveTo == 2 ? 2 : 0);
        else if (e && e.keyCode == 39) { //右
            move = (MoveTo == 3 ? 3 : 1);
        else if (e && e.keyCode == 40) { //下
            move = (MoveTo == 0 ? 0 : 2);
        else if (e && e.keyCode == 32) {//暂停游戏
            clearInterval(timer);
        }
        MoveTo = move; //修改当前移动方向
    };

    这里做了防误操作,当蛇正在朝向某方向移动时,直接输入反方向是无效的。如:蛇正向右走,这时直接按←键是无效的,仍然往右走。

    一路跟着做到这里,相信大家的贪吃蛇已经可以正常游戏了,不过我这个做的很糙,大家可以加入一些自己的想法,比如:

    计分通关,通关之后通过加快蛇的移动速度来增加难度。

    随机生成多种果实,比如加速果实,双倍成长果实等。

    加入WebSocket,实现网络版贪吃蛇。

    我写过关于WebSocket的实现,有兴趣的也可以去看看,下面是链接:

    基于SuperSocket实现的WebSocket(后端)

    基于SuperSocket实现的WebSocket(前端)

  • 相关阅读:
    JavaScript之数学对象Math
    JavaScript之数据类型转换
    JavaScript之操作符
    JavaScript之基本语句
    JavaScript之基本概念(二)
    JavaScript之基本概念(一)
    使用velero进行kubernetes灾备
    minikube配置CRI-O作为runtime并指定flannel插件
    使用thanos管理Prometheus持久化数据
    linux开启tcp_timestamps和tcp_tw_recycle引发的问题研究
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/6058329.html
Copyright © 2020-2023  润新知