基于canvas的js贪吃蛇小案例 --- 纯手工打造(^_^)

前言:写在前面的话

这个小案例是基于canvas实现的,当然也可以用一些其他的html标签来实现,但我觉得,用这个这个实现起来还是比较酷炫的。因为,这个最后做出来整个界面就是一张图片,所有的操作都是在操作这张图片中的像素,听起来,感觉是不是很牛叉。主要用到了这个canvas的两个方法:一个是clearRect(x, y, w, h);清除指定位置的矩形区域部分的像素,另一个是fillRect(x, y, w, h);和第一方法相反这个是填充指定位置的矩形部分的像素。

下面放一张效果图:这是做了核心的一小部分,其它像什么关卡啊什么的都没做。
贪吃蛇效果图

第一部分:贪吃蛇的核心类

话不多说上代码

该类的主要属性:

  1. this.box = box; // 存放canvas的容器
  2. this.w = w; // 宽度
  3. this.h = h;// 高度
  4. this.headColor = headColor; // 小蛇头的颜色
  5. this.bodyColor = bodyColor; // 小蛇身体的颜色
  6. this.preyColor = preyColor; // 猎物的颜色
  7. this.cellNum = cellNum; // 小室 数量
  8. this.cell = this.w / this.cellNum; // 小室
  9. this.ctx = canvas.getContext("2d"); // canvas ctx
  10. this.snakeInitLen = 7; // 初始小蛇长度
  11. this.snake = []; // 初试小蛇数组
  12. this.timeId = null; // 定时器id
  13. this.speed = speed; // 速度
  14. this.moveDirection = null; // 初始移动方向
  15. this.preyPoint = null; // 猎物坐标
  16. this.isEnd = false; // 游戏是否结束

该类的主要方法:
17. createBgPic: 创建一个背景图
18. createOneRandomPoint: 获得一个随机的坐标,这个坐标不会和贪吃蛇身上的坐标重复
19. createDirectionPoint: 根据上一个点和给定的方向,生成一个点的坐标
20. createOneContinuousPoint: 获取一个随机的连续的方向点
21. paintSnake: 根据坐标画出小蛇
22. createOneRandomSnake: 创建一条随机的小蛇,主要分为两步:第一步:以获得给定长度this.snakeInitLen的随机连续坐标,第二步:调用createOneRandomSnake方法画出小蛇
23. createOneRandomPrey: 创建随机的猎物,也是一个随机的点坐标,调用的是createOneRandomPoint
24. move: 实现小蛇的移动,分为两步:第一步:初始化随机的移动方向initMoveDirection 。第二步:设置定时器,根据给定的速度this.speed设置定时器setInterval(this.moveOneStep, speed)
25. initMoveDirection: 初始化移动方向
26. moveOneStep : 实现移动一步的方法,这里面主要实现了:
1. 根据上一个点和方向产生点
2. 碰撞检测
3. 将产生的点插入头部
4. 删除尾部的点,很像一个队列
5. 捕获猎物,就是一个入队操作,但也有其他一些细节需要注意
> 所谓移动,应该就是删除尾部元素,然后在头部插入带有方向的新元素

  1. controlDirection: 实现方向控制,也就是监听鼠标事件,上下左右键的按下,这里采用jsaddEventListener方法,绑定事件给window对象
  2. collisionDection: 碰撞检测,主要分为两个部分:① 边界检测;②触碰身体检测
  3. clearSnakeAndPrey: 清除小蛇和猎物的像素块,并且清空对象里面sanke小蛇数组,和置空preyPoint猎物位置
  4. init: 初始化:①绘制地图createBgPic②绘制小蛇createOneRandomSnake③绘制猎物createOneRandomPrey④注册键盘事件,实现控制controlDirection⑤移动move
  5. run: 运行,这个方法可以重复的调用,即:游戏结束重新开始游戏(清空小蛇和猎物的像素块),然后开始移动move.

第二部分:原码

utils.js

/** 贪吃蛇 **/
function Snake(box, w, h, cellNum, headColor, bodyColor, preyColor, speed) {
    if (!box) {
        box = document.createElement("div");
        document.body.appendChild(box);
    }
    if (!w) w = 500;
    if (!h) h = 500;
    if (!cellNum) cellNum = 20;
    if (!headColor) headColor = "red";
    if (!bodyColor) bodyColor = "green";
    if (!preyColor) preyColor = "gold";
    if (!speed) speed = 500;
    var canvas = document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    box.appendChild(canvas);
    // 容器
    this.box = box;
    // 宽度
    this.w = w;
    // 高度
    this.h = h;
    // 小蛇头的颜色
    this.headColor = headColor;
    // 小蛇身体的颜色
    this.bodyColor = bodyColor;
    // 猎物的颜色
    this.preyColor = preyColor;
    // 小室 数量
    this.cellNum = cellNum;
    // 小室
    this.cell = this.w / this.cellNum;
    // canvas ctx
    this.ctx = canvas.getContext("2d");
    // 初始小蛇长度
    this.snakeInitLen = 7;
    // 初试小蛇数组
    this.snake = [];
    // 定时器id
    this.timeId = null;
    // 速度
    this.speed = speed;
    // 初始移动方向
    this.moveDirection = null;
    // 猎物坐标
    this.preyPoint = null;
    // 游戏是否结束
    this.isEnd = false;
    // 创建 背景图
    this.createBgPic = function () {
        for (var i = 0; i <= this.cellNum; i++) {
            // 画横线
            this.ctx.beginPath();
            this.ctx.moveTo(0, i * this.cell);
            this.ctx.lineTo(this.w, i * this.cell);
            this.ctx.stroke();
            // 画竖线
            this.ctx.beginPath();
            this.ctx.moveTo(i * this.cell, 0);
            this.ctx.lineTo(i * this.cell, this.h);
            this.ctx.stroke();
        }
    };
    // 获取一个随机的坐标
    this.createOneRandomPoint = function () {
        /**
         * 这个随机的坐标,不会和小蛇的坐标重复
         */
        var point, len, flag = true;
        while (1) {
            point = [parseInt(Math.random() * this.cellNum), parseInt(Math.random() * this.cellNum)];
            len = this.snake.length;
            for (var i = 0; i < len; i++) {
                if (point[0] == this.snake[i][0] && point[1] == this.snake[i][1]) {
                    flag = false; // 标志随机坐标产生失败,需要重新获取
                    break;
                }
            }
            if (flag) break;
        }
        return point;
    };
    // 根据上一个点和给定的方向,生成一个点的坐标
    this.createDirectionPoint = function (lastPoint, direction) {
        var point;
        switch (direction) {
            case 0:
                point = [lastPoint[0] + 1, lastPoint[1]];
                break;
            case 1:
                point = [lastPoint[0], lastPoint[1] - 1];
                break;
            case 2:
                point = [lastPoint[0] - 1, lastPoint[1]];
                break;
            case 3:
                point = [lastPoint[0], lastPoint[1] + 1];
                break;
        }
        return point;
    }
    // 获取一个随机的连续的方向点
    this.createOneContinuousPoint = function (lastDirection) {
        /**
         * 目前,初始化小蛇长度如果高于4个会有问题。有待解决。。。
         */
        // 0, 1, 2, 3 // 0 - 2, 1 - 3 返回不会回头的方向
        var point = null, direction;
        var lastPoint = this.snake[this.snake.length - 1];
        while (1) {
            // 产生不后退的方向
            while (1) {
                direction = parseInt(Math.random() * 4);
                if (Math.abs(lastDirection - direction) != 2) {
                    // console.log(lastDirection, direction);
                    break;
                }
            }
            point = this.createDirectionPoint(lastPoint, direction);
            // 判断方向是否越界
            if (point[0] > 0 && point[0] < this.cellNum && point[1] > 0 && point[1] < this.cellNum)
                return [direction, point];
        }
    };
    // 根据坐标画出小蛇
    this.paintSnake = function () {
        // 画头
        this.ctx.fillStyle = this.headColor;
        this.ctx.clearRect(this.snake[0][0] * this.cell + 1, this.snake[0][1] * this.cell + 1, this.cell - 2, this.cell - 2);
        this.ctx.fillRect(this.snake[0][0] * this.cell + 1, this.snake[0][1] * this.cell + 1, this.cell - 2, this.cell - 2);
        // 画身体
        this.ctx.fillStyle = this.bodyColor;
        for (var i = 1; i < this.snakeInitLen; i++) {
            this.ctx.clearRect(this.snake[i][0] * this.cell + 1, this.snake[i][1] * this.cell + 1, this.cell - 2, this.cell - 2);
            this.ctx.fillRect(this.snake[i][0] * this.cell + 1, this.snake[i][1] * this.cell + 1, this.cell - 2, this.cell - 2);
        }
    };
    // 创建随机的小蛇
    this.createOneRandomSnake = function () {
        // ------------------ 创建随机相连坐标 ------------------
        // 1. 获得初试坐标点
        var initPoint = this.createOneRandomPoint();
        this.snake.push(initPoint);
        // 2. 根据方向产生后继的点
        var directionAndPoint = this.createOneContinuousPoint(-1);
        this.snake.push(directionAndPoint[1]);
        for (var i = 0; i < this.snakeInitLen - 2; i++) {
            // 将上一次的点传入 产生不后退的方向
            directionAndPoint = this.createOneContinuousPoint(directionAndPoint[0]);
            this.snake.push(directionAndPoint[1]);
        }
        // ------------------ 根据坐标画出小蛇 ------------------
        this.paintSnake();
    };
    // 产生随机的猎物
    this.createOneRandomPrey = function () {
        var point = this.createOneRandomPoint();
        this.ctx.fillStyle = this.preyColor;
        this.ctx.clearRect(point[0] * this.cell + 1, point[1] * this.cell + 1, this.cell - 2, this.cell - 2);
        this.ctx.fillRect(point[0] * this.cell + 1, point[1] * this.cell + 1, this.cell - 2, this.cell - 2);
        this.preyPoint = point;
    };
    // 实现小蛇的移动
    this.move = function () {
        if (this.timeId) { console.log("请等待当前定时完成清除,在点击"); return; }
        // 初始化移动方向
        this.moveDirection = this.initMoveDirection();
        // 设置定时器
        this.timeId = setInterval(this.moveOneStep, speed);
        this.isEnd = false;
    };
    // 初始化移动方向
    this.initMoveDirection = function(){
        // 如果按照这个方向移动,下一步会不会发生碰撞
        var direction, point;
        while(1){
            direction = parseInt(Math.random() * 4);
            point = this.createDirectionPoint(this.snake[0], direction);
            if(!this.collisionDection(point))
                return direction;
        }  
    }
    // 实现小蛇的一步移动
    this.moveOneStep = () => {
        /** 所谓移动,应该就是删除尾部元素,然后在头部插入带有方向的新元素 */
        // 1. 根据点和方向产生点
        var point = this.createDirectionPoint(this.snake[0], this.moveDirection);
        // 碰撞检测
        if (this.collisionDection(point)) {
            clearInterval(this.timeId);
            this.timeId = null;
            this.isEnd = true;
            console.log("游戏结束");
            alert("游戏结束:得分("+ (this.snake.length-this.snakeInitLen) +")");
        }
        // 2. 将新的点,插入的头部
        this.ctx.fillStyle = bodyColor; // 清除旧的头部色块
        this.ctx.fillRect(this.snake[0][0] * this.cell + 1, this.snake[0][1] * this.cell + 1, this.cell - 2, this.cell - 2);
        this.snake.unshift(point);
        this.ctx.fillStyle = headColor;
        this.ctx.fillRect(point[0] * this.cell + 1, point[1] * this.cell + 1, this.cell - 2, this.cell - 2);
        // 3. 删除尾部元素
        var delPoint = this.snake.pop();
        this.ctx.clearRect(delPoint[0] * this.cell + 1, delPoint[1] * this.cell + 1, this.cell - 2, this.cell - 2);
        // 4. 捕获猎物
        if (point[0] == this.preyPoint[0] && point[1] == this.preyPoint[1]) {
            this.ctx.fillStyle = bodyColor; // 清除旧的头部色块
            this.ctx.fillRect(this.snake[0][0] * this.cell + 1, this.snake[0][1] * this.cell + 1, this.cell - 2, this.cell - 2);
            this.snake.unshift(point);
            this.ctx.fillStyle = headColor;
            this.ctx.fillRect(point[0] * this.cell + 1, point[1] * this.cell + 1, this.cell - 2, this.cell - 2);
            console.log("吃了");
            // 创建新的随机猎物
            this.createOneRandomPrey();
        }
        console.log(this.snake)
    };
    // 控制方向
    this.controlDirection = function () {
        window.addEventListener("keydown", (ev) => {
            switch (ev.key) {
                case "ArrowRight":
                    this.moveDirection = 0;
                    break;
                case "ArrowUp":
                    this.moveDirection = 1;
                    break;
                case "ArrowLeft":
                    this.moveDirection = 2;
                    break;
                case "ArrowDown":
                    this.moveDirection = 3;
                    break;
            }
        });
    };
    // 碰撞检测
    this.collisionDection = function (point) {
        // 边界检测
        if (!(point[0] > 0 && point[0] < this.cellNum && point[1] > 0 && point[1] < this.cellNum)) return true;
        // 身体检测
        var len = this.snake.length;
        for (var i = 0; i < len; i++) {
            if (point[0] == this.snake[i][0] && point[1] == this.snake[i][1]) {
                return true;
            }
        }
        return false;
    };
    // 清除小蛇和猎物
    this.clearSnakeAndPrey = function () {
        var point;
        while (this.snake.length) {
            point = this.snake.pop();
            this.ctx.clearRect(point[0] * this.cell + 1, point[1] * this.cell + 1, this.cell - 2, this.cell - 2);
        }
        this.ctx.clearRect(this.preyPoint[0] * this.cell + 1, this.preyPoint[1] * this.cell + 1, this.cell - 2, this.cell - 2);
        this.preyPoint = null;
    };
    // 初始化
    this.init = function () {
        // 创建地图
        this.createBgPic();
        // 创建随机小蛇
        this.createOneRandomSnake();
        // 产生随机猎物
        this.createOneRandomPrey();
        // 移动
        this.move();
        // 控制方向
        this.controlDirection();
    }
    // 运行
    this.run = function () {
        if (this.isEnd) {
            console.log("重新开始");
            if (this.timeId) clearInterval(this.timeId);
            this.timeId = null;
            // 清除上次的小蛇和猎物
            this.clearSnakeAndPrey();
            // 创建随机小蛇
            this.createOneRandomSnake();
            // 产生随机猎物
            this.createOneRandomPrey();
        }

        // 移动
        snake.move();
    }
}

贪吃蛇.html

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>03-贪吃蛇</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #btn {
            width: 100px;
            height: 50px;
            line-height: 50px;
            text-align: center;
            font-size: 24px;
            margin: 0 auto;
            display: block;
        }

        .container {
            width: 100%;
            margin: 0 auto;
        }

        .box {
            margin: 0 auto;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="box"></div>
        <input id="btn" type="button" value="开始" onclick="btnClick()">
    </div>

    <!-- 引入外部文件 -->
    <script src="../utils.js"></script>
    <!--  -->
    <script>
        var box = document.getElementsByClassName("box")[0];
        var snake = new Snake(box);
        snake.init();
        function btnClick() {
            snake.run();
        }
    </script>

</body>

</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值