canvas学习案例之贪吃蛇

最近在看canvas画板,就自己尝试写一个简单的小例子 贪吃蛇  
涉及到的点有:碰撞检测,数组操作,canvas图形绘制,定时器,es6的类的使用
废话不多说直接上代码吧:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .score span {
            margin-left: 10px;
            vertical-align: middle;
            font-size: 28px;
            font-weight: bold;
            color: red;
        }
    </style>
</head>
<body>

<canvas width="800" height="500" id="snake"></canvas>
<div class="score">分数:
    <span id="score"></span>
</div>
</body>
<script>
    const WIDTH = 20;
    let cxt;//canvas上下文对象
    let snakes = [];//蛇数组
    let interval = null;//定时器
    let speedX = 0;//x轴速度
    let speedY = 0;//y轴速度
    let directionType = 0;//当前移动的方向 1.上 2.右 3.下 4.左
    let food;//食物对象
    let score = 0;//分数

    function init() {
        initCanvas();
        updateScore();
    }

    function initCanvas() {
        cxt = document.getElementById('snake').getContext('2d');
        cxt.fillStyle = '#eee';
        cxt.fillRect(0, 0, 800, 500);

        let subBox = new SubBox(cxt);
        subBox.drawBox();//画小格子
        snakes.push(new Snake(cxt, 100, 20));
        snakes.push(new Snake(cxt, 80, 20));
        snakes.push(new Snake(cxt, 60, 20));
        snakes.push(new Snake(cxt, 40, 20));
        let snake = new Snake(cxt, 20, 20);
        snakes.push(snake);
        food = new Food();
        food.getRandPlace();
        // 区分一下requestAnimationFrame(会自动计算动画间隔)和setInterval(自己设定) 两个动画的区别
        // window.requestAnimationFrame(function () {
        //     startAnimate(snake);
        // });
        console.log('food', food);
        startAnimate(3);

    }

    function stopAnimate() {
        if (interval) {
            clearInterval(interval);
        }
    }

    function restore() {
        cxt.fillStyle = '#eee';
        cxt.fillRect(0, 0, 800, 500);
        let subBox = new SubBox(cxt);
        subBox.drawBox();//画小格子
        food.draw();
    }
    function startAnimate(type) {
        // window.requestAnimationFrame(function () {
        //     startAnimate(snake);
        // });
        if ((directionType - type) % 2 != 0) {
            directionType = type;
            getSpeed(type);
        }
        stopAnimate();
        interval = setInterval(function () {
            //循环调用多次canvas Api 会比较耗性能  换个方式
            // for (let i = 0; i < snakes.length; i++) {
            //     let snake = snakes[i];
            //     snake.remove();
            // }
            restore();

            // debugger
            let len = snakes.length;//缓存数组的长度
            snakes[len - 1].x = snakes[0].x + speedX;
            snakes[len - 1].y = snakes[0].y + speedY;
            let a = snakes.pop();
            snakes.unshift(a);

            console.log('snakes hou', snakes);
            for (let i = 0; i < snakes.length; i++) {
                let snake = snakes[i];
                snake.draw();
            }
            if (hitSelf() || hitWall()) {
                gameOver(interval);
            }
            if (hitCheck(snakes[0], food)) {
                //    增加一个
                stopAnimate();
                addSnake();
                startAnimate(type);
                food.getRandPlace();
                food.draw();
                score++;
                updateScore();
            }
        }, 200);

    }

    function addSnake() {
        let len = snakes.length;
        let nX = snakes[len - 1].x;
        let nY = snakes[len - 1].y;
        let snake = new Snake(cxt, nX, nY);
        snakes.push(snake);
    }

    function updateScore() {
        let scoreDom = document.getElementById('score');
        scoreDom.innerText = score;
    }

    function gameOver(interval) {
        if (interval) {
            clearInterval(interval);
        }
        console.log('game over');
        alert('Game Over!');

    }

    //碰撞检测 食物与snake
    function hitCheck(snake, food) {
        let isHit = false;
        if (!(snake.x < food.x || snake.x > food.x + 18 || snake.y < food.y || snake.y > food.y + 18)) {
            isHit = true;
        }
        return isHit;
    }

    //碰撞检测  snake 和墙壁
    function hitWall() {
        let isHit = false;
        let headSnake = snakes[0];//第一个head
        if (headSnake.x >= 800 || headSnake.x < 0 || headSnake.y >= 500 || headSnake.y < 0) {
            isHit = true;
        }
        return isHit;
    }

    //碰撞检测 snake 和自己
    function hitSelf() {
        let isHit = false;
        let headSnake = snakes[0];
        for (let i = 1; i < snakes.length; i++) {
            let snake = snakes[i];
            if (!((headSnake.x > snake.x + 18) || (headSnake.y < snake.y) || (headSnake.y > snake.y + 18) || (headSnake.x < snake.x))) {
                isHit = true;
                break;
            }
        }
        return isHit;
    }

    class SubBox {
        constructor(context) {
            this.width = WIDTH;
            this.context = context;
            this.context.beginPath();

        }

        drawBox() {
            let allWidth = document.getElementById('snake').width;
            let allHeight = document.getElementById('snake').height;
            this.context.beginPath();
            this.context.strokeStyle = '#ddd';
            this.context.lineWidth = 1;
            //横向
            for (let i = 0; i < allHeight / this.width; i++) {
                this.context.moveTo(0, (i + 1) * this.width);
                this.context.lineTo(allWidth, (i + 1) * this.width)
            }
            //纵向
            for (let i = 0; i < allWidth / this.width; i++) {
                this.context.moveTo((i + 1) * this.width, 0);
                this.context.lineTo((i + 1) * this.width, allHeight);
            }
            this.context.stroke();
            
        }

    }

    class Snake {
        constructor(context, x, y) {
            this.x = x;
            this.y = y;
            this.width = 20;
            this.height = 20;
            this.color = '#ff0000';
            this.context = context;
            this.draw(this.x, this.y, this.width, this.height);
        }

        draw() {
            let width = this.width;
            let x = this.x;
            let y = this.y;
            this.context.fillStyle = this.color;
            this.context.fillRect(x, y, width, width);
            this.context.save();
        }

        //涉及到转弯  有个转弯点  所有的小方块过了转弯点type 才会改变
        move(type) {

            if (type) {
                this.type = type;
            }

            // console.log('t',this.type);
            //清楚之前的
            this.remove();

            switch (this.type) {
                case 4:
                    speedX = -20;
                    speedY = 0;
                    this.x = this.x - 20;
                    break;
                case 1:
                    speedX = 0;
                    speedY = -20;
                    this.y = this.y - 20;
                    break;
                case 2:
                    speedX = 20;
                    speedY = 0;
                    this.x = this.x + 20;
                    break;
                case 3:
                    speedX = 0;
                    speedY = 20;
                    this.y = this.y + 20;
                    break;
            }

            this.draw();
        }

        remove() {
            this.context.clearRect(this.x, this.y, this.width, this.height);
            cxt.fillStyle = '#eee';
            cxt.fillRect(this.x, this.y, this.width, this.height);
            cxt.strokeRect(this.x, this.y, this.width, this.height);
        }
    }

    function getSpeed(type) {
        switch (type) {
            case 4:
                speedX = -20;
                speedY = 0;
                break;
            case 1:
                speedX = 0;
                speedY = -20;
                break;
            case 2:
                speedX = 20;
                speedY = 0;
                break;
            case 3:
                speedX = 0;
                speedY = 20;
                break;
        }
    }

    class Food {
        constructor() {
            //食物随机生成
            this.x = 0;
            this.y = 100;
            this.color = '#000f43';
        }

        getInteger(number) {
            if (number % 20 == 0) {

                return number;
            } else {
                return this.getInteger(++number);
            }
        }

        isOverlap() {
            let isSnakeLoc = false;
            for (let i = 0; i < snakes.length; i++) {
                let snake = snakes[i];
                if ((snake.x == this.x) && (this.y == snake.y)) {
                    isSnakeLoc = true;
                    break;
                }
            }
            return isSnakeLoc;
        }
        getRandPlace(){
            this.x = this.getInteger(parseInt(Math.random() * 780));
            this.y = this.getInteger(parseInt(Math.random() * 480));
        }

        draw() {
            if (this.isOverlap()) {
                this.getRandPlace();
                this.draw();
            } else {
                cxt.fillStyle = this.color;
                cxt.fillRect(this.x, this.y, WIDTH, WIDTH);
                cxt.save();
            }


        }
    }

    //键盘事件
    document.onkeydown = function (e) {
        let event = e || window.event;
        let keyCode = event.keyCode;
        switch (keyCode) {
            case 37:
                //左
                startAnimate(4);
                break;
            case 38:
                //上
                startAnimate(1);
                break;
            case 39:
                //右
                startAnimate(2);
                break;
            case 40:
                //下
                startAnimate(3);
                break;
        }
    };

    init();
</script>
</html>
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值