canvas小游戏-贪吃蛇

使用canvas完成贪吃蛇小游戏

  • 可以设置速度
  • 可以设置有墙或无墙状态
  • 可以修改蛇身的皮肤


    image.png

html代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇</title>
    <link rel="stylesheet" href="./index.css">
    <script src="http://code.jquery.com/jquery-1.12.4.js"></script>
</head>

<body>
    <div class="top">
        <div class="left">Snake</div>
        <div class="right">Score:<span class="score">0</span></div>
    </div>
    <canvas id="canvas"></canvas>
    <!-- 开始游戏盒子 -->
    <div class="begin">
        <p>SNAKE</p>
        <div class="start">start</div>
        <br>
        <div class="set">setting</div>
    </div>
    <!-- 设置盒子 -->
    <div class="setBox">
        <p>Settings</p>
        <div class="start">start</div>
        <div class="speed">Speed: <div class="slow">Slow</div>
            <div class="normal">Normal</div>
            <div class="fast">Fast</div>
        </div>
        <div class="wall">Wall: <div class="on">On</div>
            <div class="off">Off</div>
        </div>
    </div>
    <!-- 结束盒子 -->
    <div class="end">
        <p>Game Over</p>
        <div class="endText">游戏结束</div>
        <div class="start">start</div>
        <br>
        <div class="set">setting</div>
    </div>
    <audio id="audio"
        src="https://dl.stream.qqmusic.qq.com/C400000uYx7S4VMZm0.m4a?guid=3943565997&vkey=D44390A154E3DC9A97128581F9652304787C70CB45A7C298ED80BB455C7C21128025EF7F3D3E2F134784118817FB0EE265DB174397269D32&uin=2497208832&fromtag=120002"></audio>
        
    <audio src="./audio/game_over.mp3" id="audio1"></audio>
</body>
<script src="./index.js"></script>

</html>

CSS代码

@font-face {
  font-family: 'VT323';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/s/vt323/v17/pxiKyp0ihIEF2isfFJA.ttf) format('truetype');
}
* {
  margin: 0px;
  padding: 0px;
}
a {
  text-decoration: none;
  color: #000;
}
ul,
ol {
  list-style-type: none;
}
table {
  border-collapse: collapse;
}
input {
  outline: none;
  border-width: 1px;
}
textarea {
  outline: none;
  resize: none;
  overflow: auto;
}
.clear {
  clear: both;
}
.clear::after {
  content: '';
  clear: both;
}
body {
  background-color: #000;
}
* {
  color: #fff;
  font-family: "VT323";
}
.top {
  margin: 0 auto;
  width: 340px;
  overflow: hidden;
  font-size: 26px;
  padding-top: 10px;
  margin-bottom: 20px;
}
.top .left {
  float: left;
}
.top .right {
  float: right;
}
body > div {
  width: 340px;
  margin: 0 auto;
}
#canvas {
  border: 10px solid #fff;
  display: block;
  margin: 0 auto;
  display: none;
}
.begin,
.end {
  text-align: center;
}
.end{
  display: none;
}
.begin > p,
.end > p {
  text-align: center;
  font-size: 50px;
  padding-top: 30px;
  animation: logo-ani 1000ms linear infinite;
  margin-bottom: 20px;
}
.begin .start,
.end .start,
.begin .set,
.end .set {
  font-size: 26px;
  display: inline-block;
  line-height: 40px;
  cursor: pointer;
}
.begin .start::before,
.end .start::before,
.begin .set::before,
.end .set::before {
  content: ">";
  padding-right: 10px;
  display: none;
}
.begin .start:hover::before,
.end .start:hover::before,
.begin .set:hover::before,
.end .set:hover::before {
  display: inline-block;
}
.begin .endText,
.end .endText {
  font-size: 30px;
}
@keyframes logo-ani {
  50% {
    transform: scale(1.3, 1.3);
  }
  100% {
    transform: scale(1, 1);
  }
}
.setBox {
  text-align: center;
  display: none;
}
.setBox > p {
  text-align: center;
  font-size: 50px;
  padding-top: 30px;
  margin-bottom: 20px;
}
.setBox .start {
  font-size: 26px;
  display: inline-block;
  line-height: 40px;
  cursor: pointer;
  padding: 0px !important;
}
.setBox .start::before {
  content: ">";
  padding-right: 10px;
  display: none;
}
.setBox .start:hover::before {
  display: inline-block;
}
.setBox > div {
  text-align: center;
  font-size: 26px;
  line-height: 30px;
  padding: 5px 0;
}
.setBox > div > div {
  display: inline-block;
  cursor: pointer;
  padding: 0 5px;
}
.setBox > div > .active {
  background-color: #fff;
  color: #000;
}

js代码

/*
    键盘控制
    上  下  左  右
*/

//设置画布的宽高
canvas.width = 640;
canvas.height = 640;
var ctx = canvas.getContext('2d');

//定义有没有墙
var hasWall = true;
//定义速度
var speed = 60;

//定义一个数组保存所有的格子
var arr = [];
//定义一个数组,保存蛇身所有的格子
var snake = [512, 513, 514, 515, 516, 517];

//声明一个变量 表示当前行进状态
var fx = 39;  // 37 : 左 , 38 : 上  , 39 : 右 , 40 : 下 

//定义一个变量,保存食物 的格子对象
var foods = null;

//定义一个变量,保存分数
var score = 0;

//定义一个蛇身渲染 和 键盘事件的开关
var flog = false;

//蛇头坐标
var gird = null;



//给设置墙的按钮绑定点击方法
$('.wall>div').click(function () {
    var text = $(this).text();
    // console.log(text);
    switch (text) {
        case 'On':
            hasWall = true;
            break;
        case 'Off':
            hasWall = false;
            break;
    }
    // console.log(hasWall);
    //点击添加calss名  并删除兄弟标签的class名
    $(this).addClass('active').siblings().removeClass('active');
})

//模拟让 on 按钮被点击
$('.wall>div').eq(0).click();



//给设置速度的按钮绑定点击方法
$('.speed>div').click(function () {
    var text = $(this).text();
    // console.log(text);
    switch (text) {
        case 'Slow':
            speed = 80;
            break;
        case 'Normal':
            speed = 60;
            break;
        case 'Fast':
            speed = 40;
            break;
    }
    // console.log(hasWall);
    //点击添加calss名  并删除兄弟标签的class名
    $(this).addClass('active').siblings().removeClass('active');
    console.log(speed);
})

//模拟让 on 按钮被点击
$('.speed>div').eq(1).click();




//给set按钮的点击方法  游戏设置按钮
$('.set').click(function () {
    //关掉所有的盒子
    $('.begin').hide();
    $('.end').hide();
    $('#canvas').hide();
    //显示setBox盒子
    $('.setBox').show();
})



//定义一个格子的类
function Rect(x, y, n,f) {
    this.x = x;
    this.y = y;
    this.index = n;
    // this.c = c || true;  //默认为黑色  true 代表黑色   false代表白色
    this.fx = f;
}

//添加一个绘制黑色格子的方法
Rect.prototype.drawB = function () {
    ctx.beginPath();
    ctx.fillStyle = 'block';
    ctx.fillRect(this.x, this.y, 20, 20);
    ctx.closePath();
    ctx.stroke();
}
//添加一个绘制白色格子的方法
Rect.prototype.drawW = function () {
    ctx.beginPath();
    ctx.fillStyle = '#fff';
    ctx.fillRect(this.x, this.y, 20, 20);
    ctx.closePath();
    ctx.stroke();
}

//添加一个绘制食物的方法
Rect.prototype.drawFood = function () {
    ctx.beginPath();
    ctx.arc(this.x + 10, this.y + 10, 10, 0, 360)
    ctx.fillStyle = 'gold';
    ctx.fill();
    ctx.closePath();
    ctx.stroke();
}


//添加绘制蛇身皮肤的方法
Rect.prototype.drawBody = function (img) {
    var that = this;
    img.onload = function () {
        var deg = Math.PI / 180;
        ctx.save();
        switch (that.fx) {
            case 37:
                ctx.translate(that.x, that.y + 20);
                ctx.rotate(270 * deg);
                break;
            case 38:
                ctx.translate(that.x, that.y);
                ctx.rotate(0);
                break;
            case 39:
                ctx.translate(that.x + 20, that.y);
                ctx.rotate(90 * deg);
                break;
            case 40:
                ctx.translate(that.x + 20, that.y + 20);
                ctx.rotate(180 * deg);
                break;
        }
        ctx.drawImage(this, 0, 0, 20, 20);
        ctx.restore();
    }
}


//生成棋盘的方法
function createBox() {
    var x = 0;
    var y = 0;
    arr = [];
    for (var i = 0; i < 32 * 32; i++) {
        var obj = new Rect(x, y, i, 39);
        obj.drawB();
        arr.push(obj);
        x += 20;
        if ((i + 1) % 32 == 0) {
            y += 20;
            x = 0;
        }
    }

    //设置一下墙
    if (hasWall) {
        canvas.style.borderColor = '#fff';
    } else {
        canvas.style.borderColor = '#333';
    }
}



//绘制蛇身的方法
function drawSnake() {
    for (var i = 0; i < snake.length; i++) {
        var a = snake[i];

        // if(a == gird){
        //     arr[a].fx = fx;
        // }

        //蛇身皮肤
        // if (i == snake.length - 1) {
        //     //执行画头部的方法
        //     var img = new Image();
        //     img.src = './img/1.png';
        //     arr[a].drawBody(img);
        // } else if (i == 0) {
        //     //执行画尾部的方法
        //     var img = new Image();
        //     img.src = './img/3.png';
        //     arr[a].drawBody(img);
        // } else {
        //     //执行画身体的的方法
        //     var img = new Image();
        //     img.src = './img/2.png';
        //     arr[a].drawBody(img);
        // }

        //正常小白快蛇身
        arr[a].drawW();
    }
    //
    flog = false;
}


//定义一个蛇身移动的方法
function move() {
    var timer = setInterval(function () {
        canvas.width = canvas.width;

        //调用生成格子的方法
        createBox();
        //调用绘制蛇身的方法
        drawSnake();

        //绘制食物
        arr[foods].drawFood();

        //判断蛇是否吃到食物
        // eat() 返回值  如果吃到食物返回true  如果没吃到返回false
        if (!eat()) {
            //删掉蛇身数组中的第一个元素
            snake.shift();
        }
        //根据行进状态,改变蛇身数组
        var num = null;
        switch (fx) {
            case 37://左
                num = snake[snake.length - 1] - 1;
                break;
            case 38://上
                num = snake[snake.length - 1] - 32;
                break;
            case 39://右
                num = snake[snake.length - 1] + 1;
                break;
            case 40://下
                num = snake[snake.length - 1] + 32;
                break;


                
        }

        //碰撞判断
        if (wall(num)) {
            //判断有没有墙壁
            if (hasWall) {
                //有墙,撞上就结束游戏
                clearInterval(timer)
                // alert('游戏结束');
                end('你个老六,你撞墙了!!!')
                audio.pause();
                audio1.play();
            } else {
                //无墙 撞上之后从反方向出现
                var head = snake[snake.length - 1];
                //右墙
                if ((head + 1) % 32 == 0 && fx == 39) {
                    num = head - 31;
                }
                //左墙
                if (head % 32 == 0 && fx == 37) {
                    num = head + 31;
                }
                //上墙
                if (head < 32 && fx == 38) {
                    num = head + 31 * 32;
                }
                //下墙
                if (head >= 32 * 31 && fx == 40) {
                    num = head - 31 * 32;
                }
            }
        }

        //判断是否咬到自己
        if (eatSelf()) {
            clearInterval(timer)
            audio.pause();
            audio1.play();
            // alert('游戏结束');
            end('你个老六,咬到自己了!!!')
        }

        snake.push(num);
        // console.log(snake);
    }, speed)
}


//定义 碰撞检测的方法
function wall(n) {
    //上下墙的碰撞检测
    if (n < 0 || n > (32 * 32 - 1)) {
        return true;  //撞到墙壁
    }
    //蛇头坐标
    var head = snake[snake.length - 1];
    //右墙的碰撞检测
    if ((head + 1) % 32 == 0 && n == head + 1) {
        return true;
    }
    //左边墙壁的碰撞检测
    if (head % 32 == 0 && n == head - 1) {
        return true;
    }
}



//定义一个生成食物的方法
function food() {
    var num = randNum(0, arr.length - 1);
    if (snake.indexOf(num) == -1) {
        // console.log(arr[num]);
        return num;
    } else {
        return food();
    }
}


//判断蛇吃食物
function eat() {
    //获取蛇头
    var head = snake[snake.length - 1];

    if (head == foods) {
        //蛇吃到食物
        //重新生成食物
        foods = food();
        arr[foods].drawFood();
        score++;
        $('.score').text(score);
        return true;
    } else {
        //没吃到
        return false;
    }
}


//判断蛇是否咬到自己
function eatSelf() {
    var head = snake[snake.length - 1];
    // console.log(snake);
    //先将蛇头从 蛇身数组中去掉
    snake.pop();
    // console.log(snake);

    if (snake.indexOf(head) == -1) {
        snake.push(head);
        return false;
    } else {
        //咬到自己
        snake.push(head);
        return true;
    }
}




//给start按钮的点击方法  开始游戏按钮
$('.start').click(function () {
    //关掉所有的盒子
    $('.begin').hide();
    $('.end').hide();
    $('.setBox').hide();
    //显示canvas盒子
    $('#canvas').css('display', 'block');

    audio.play();
    audio1.pause();
    //调用绘制蛇身的方法
    // drawSnake()

    //将蛇身初始化
    snake = [512, 513, 514, 515, 516];
    //清空画布
    canvas.width = canvas.width;
    //修改键值方向
    fx = 39;
    //清空分数
    score = 0;
    $('.score').text(score);

    //调用生成格子的方法
    createBox();

    //生成食物
    foods = food();
    //绘制食物
    arr[foods].drawFood();


    move();
})



//键盘事件
window.onkeydown = function (e) {
    var code = e.keyCode;
    // 向哪一个方向行进时,不能向反方向移动
    if (fx == 39 && code == 37) {
        return;
    }
    if (fx == 37 && code == 39) {
        return;
    }
    if (fx == 38 && code == 40) {
        return;
    }
    if (fx == 40 && code == 38) {
        return;
    }
    //蛇身渲染 和 键盘事件的开关
    if (flog) {
        return;
    }
    //如果不是这四个键,不执行
    if (code == 37 || code == 38 || code == 39 || code == 40) {
        gird = snake[snake.length - 1];
        fx = code;
        flog = true;
    }
}


//游戏结束的方法
function end(t) {
    //关掉所有的盒子
    $('.begin').hide();
    $('#canvas').hide();
    $('.setBox').hide();
    //显示end盒子
    $('.end').show();

    $('.endText').text(t)
}


//封装随机数
function randNum(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
}


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

推荐阅读更多精彩内容