完整项目下载

运行结果

实战JavaScript:实现贪吃蛇——面向对象练习_构造函数
实战JavaScript:实现贪吃蛇——面向对象练习_原型对象_02

知识点

  1. 利用面向对象的思想实现——一个食物对象、一个蛇对象、一个游戏总控对象。
  2. 在使用××.prototype= {}重写原型对象的时候,一定要加上一句constructor:该对象。不然会造成实例化出来的实例的constructor为object。
    Food.prototype = {
        constructor: Food,
    };
    
  3. 在underscore中,使用_.random(a,b)即可获得a-b中的一个随机数。
  4. 在求食物的随机位置的时候,用到了panel.clientHeight/this.height - 1) * this.height。
    原理是使用盒子的高度/小球的高度,可以算得最多放多少个小球。因为要控制小球不能超过边界,所以总数量要减去1,数量×高度即为随机位置的最大值。
  5. 在蛇对象中,用body数组存放蛇身体每一个部分对象。蛇的绘制过程就是遍历body,在面板上绘制。
  6. 蛇的移动分为两部分。
    ① 蛇节移动到前一个蛇节的位置。直到蛇头后一个蛇节移动到蛇头的位置。
    ② 根据direction判断蛇头如何移动。
    注意:在游戏绘制的过程中,界面的每一次绘制都要删除之前的绘制,不然会叠加到一起。
  7. 在蛇的闭包中建一个局部数组,存储蛇对象,可以更加方便的删除操作。
  8. 只有在原型对象中的方法和属性,外界是可以调用的。
  9. 蛇的移动(动画)必然需要定时器协助。定时器的时间,即象征着刷新速度,也就是难度。
  10. this所在的函数在哪一个对象中,this就指向谁。单独写一个函数的时候,如果调用之前对象的this,需要备份指针(将对象的this赋值给另一个变量)。
  11. JavaScript原生的键盘按下事件(keydown) 中,事件有一个keyCode属性,其值代表按下的键。其中:37—left、38—top、39—right、40—bottom。
  12. 边界控制。通过判断蛇头与最大X和Y的关系,判断是否碰到边界。
  13. confirm()方法用于显示一个带有指定消息和确认及取消按钮的对话框。
  14. window.location.reload(); 重新加载当前文档
  15. window.close() 方法用于关闭浏览器窗口。
  16. 与食物的碰撞检测:如果蛇头和食物坐标重叠,将蛇尾添加到body中。并重新绘制一个食物点,将之前的食物删掉。

代码

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <style>
        #panel {
            width: 800px;
            height: 600px;
            background: skyblue;
            position: relative;
            margin: 60px auto;
            border-radius: 10px;
            border: 5px solid orange;
            overflow: hidden;
        }
    </style>
</head>
<body>
   <div id="panel"></div>
<script src="js/UnderScore.js"></script>
<script src="js/Foods.js"></script>
<script src="js/Snake.js"></script>
<script src="js/Game.js"></script>
<script>
    var panel = document.getElementById('panel');
    var game = new Game(panel);
    game.render();
    game.run();
</script>
</body>
</html>

Food.js

(function (window) {
    // 全局指针
    var self; // 记录当前游戏对象
    /**
     * 构造函数-游戏
     * @param panel
     * @constructor
     */
    function Game(panel) {
        this.food = new Food({});
        this.snake = new Snake({});
        this.panel = panel;
        // 赋值
        self = this;
    }

    Game.prototype = {
        constructor: Game,
        /**
         * 初始化
         */
        render: function () {
            // 1.把蛇和食物对象,渲染到面板上
            this.food.render(this.panel);
            this.snake.render(this.panel);
        },

        run: function () {
            // 1.蛇跑起来
            snakeMove();
            // 2.方向控制
            dirControl();
        }
    };

    /**
     * 蛇开始移动
     */
    function snakeMove() {
        var timerId = setInterval(function () {
            // 1. 蛇开始移动和绘制
            self.snake.move(self.food, self.panel);
            self.snake.render(self.panel);

            // 2. 边界控制
            var maxX = self.panel.clientWidth / self.snake.width;
            var maxY = self.panel.clientHeight / self.snake.height;
            var headX = self.snake.body[0].x;
            var headY = self.snake.body[0].y;

            console.log(self.panel.clientWidth, self.snake.width, self.snake.body[0].x);

            // 2.1 水平方向控制
            if (headX < 0 || headX >= maxX) {
                clearInterval(timerId);
                if(confirm('很遗憾,您输了!再来一次?')){
                    window.location.reload();
                }else {
                    window.close();
                }
            }

            // 2.2 垂直方向控制
            if (headY < 0 || headY >= maxY) {
                clearInterval(timerId);
                if(confirm('很遗憾,您输了!再来一次?')){
                    window.location.reload();
                }else {
                    window.close();
                }
            }
        }, 100);
    }

    /**
     * 方向控制
     */
    function dirControl() {
        document.addEventListener('keydown', function (e) {
            console.log(e['keyCode']);
            /*
              37 - left
              38 - top
              39 - right
              40 - bottom
            */
            switch (e['keyCode']) {
                case 37:
                    self.snake.direction = 'left';
                    break;
                case 38:
                    self.snake.direction = 'top';
                    break;
                case 39:
                    self.snake.direction = 'right';
                    break;
                case 40:
                    self.snake.direction = 'bottom';
                    break;
            }
        }, false);
    }

    window.Game = Game;
})(window);

Snake.js

(function (window) {
    // 食物数组
    var snakeArr = [];
    var remove = function() {
        for (var i = snakeArr.length - 1; i >= 0; i--) {
            // 1.删除div
            snakeArr[i].remove();
            // 2.删除数组中的元素
            snakeArr.splice(i, 1);
        }
    };
    /**
     * 构造函数-蛇
     * @param {Object}options
     * @constructor
     */
    function Snake(options) {
        options = options || {};
        // 1.蛇节的大小
        this.width = options.width || 20;
        this.height = options.height || 20;
        // 2.蛇移动的方向
        this.direction = options.direction || 'right';
        // 3.蛇的身体和蛇头 (坐标基数、颜色)
        this.body = [
            {x: 2, y: 0, color: 'purple'},
            {x: 1, y: 0, color: 'red'},
            {x: 0, y: 0, color: 'red'}
        ];
    }

    Snake.prototype = {
        constructor: Snake,
        /**
         * 绘制小蛇
         */
        render: function (panel) {
            // 1. 先删除
            remove();

            // 2. 把每一个蛇节渲染到地图上
            for (var i = 0, len = this.body.length; i < len; i++) {
                // 蛇节
                var object = this.body[i];
                var div = document.createElement('div');
                // 设置样式
                div.style.position = 'absolute';
                div.style.width = this.width + 'px';
                div.style.height = this.height + 'px';
                div.style.left = object.x * this.width + 'px';
                div.style.top = object.y * this.height + 'px';
                div.style.borderRadius = this.width / 2 + 'px';
                div.style.backgroundColor = object.color;
                panel.appendChild(div);
                // 记录当前蛇
                snakeArr.push(div);
            }
        },
        /**
         * 移动小蛇
         */
         move: function (food, panel) {
            // 1. 控制蛇的身体移动(当前蛇节 到 上一个蛇节的位置)
            for (var i = this.body.length - 1; i > 0; i--) {
                this.body[i].x = this.body[i - 1].x;
                this.body[i].y = this.body[i - 1].y;
            }
            // 2. 控制蛇头的移动
            // 2.1 判断蛇移动的方向
            var head = this.body[0];
            switch(this.direction) {
                case 'right':
                    head.x += 1;
                    break;
                case 'left':
                    head.x -= 1;
                    break;
                case 'top':
                    head.y -= 1;
                    break;
                case 'bottom':
                    head.y += 1;
                    break;
            }

            // 3. 判断蛇头是否和食物的坐标重合
            var headX = head.x * this.width;
            var headY = head.y * this.height;
            if (headX === food.x && headY === food.y) {
                // 让蛇增加一节
                // 获取蛇的最后一节
                var last = this.body[this.body.length - 1];
                this.body.push({
                    x: last.x,
                    y: last.y,
                    color: last.color
                });

                // 4. 随机在面板上重新生成食物
                food.render(panel);
            }
        }
    };

    // 暴露构造函数给外部
    window.Snake = Snake;
})(window);

Game.js

(function (window) {
    // 全局指针
    var self; // 记录当前游戏对象
    /**
     * 构造函数-游戏
     * @param panel
     * @constructor
     */
    function Game(panel) {
        this.food = new Food({});
        this.snake = new Snake({});
        this.panel = panel;
        // 赋值
        self = this;
    }

    Game.prototype = {
        constructor: Game,
        /**
         * 初始化
         */
        render: function () {
            // 1.把蛇和食物对象,渲染到面板上
            this.food.render(this.panel);
            this.snake.render(this.panel);
        },

        run: function () {
            // 1.蛇跑起来
            snakeMove();
            // 2.方向控制
            dirControl();
        }
    };

    /**
     * 蛇开始移动
     */
    function snakeMove() {
        var timerId = setInterval(function () {
            // 1. 蛇开始移动和绘制
            self.snake.move(self.food, self.panel);
            self.snake.render(self.panel);

            // 2. 边界控制
            var maxX = self.panel.clientWidth / self.snake.width;
            var maxY = self.panel.clientHeight / self.snake.height;
            var headX = self.snake.body[0].x;
            var headY = self.snake.body[0].y;

            console.log(self.panel.clientWidth, self.snake.width, self.snake.body[0].x);

            // 2.1 水平方向控制
            if (headX < 0 || headX >= maxX) {
                clearInterval(timerId);
                if(confirm('很遗憾,您输了!再来一次?')){
                    window.location.reload();
                }else {
                    window.close();
                }
            }

            // 2.2 垂直方向控制
            if (headY < 0 || headY >= maxY) {
                clearInterval(timerId);
                if(confirm('很遗憾,您输了!再来一次?')){
                    window.location.reload();
                }else {
                    window.close();
                }
            }
        }, 100);
    }

    /**
     * 方向控制
     */
    function dirControl() {
        document.addEventListener('keydown', function (e) {
            console.log(e['keyCode']);
            /*
              37 - left
              38 - top
              39 - right
              40 - bottom
            */
            switch (e['keyCode']) {
                case 37:
                    self.snake.direction = 'left';
                    break;
                case 38:
                    self.snake.direction = 'top';
                    break;
                case 39:
                    self.snake.direction = 'right';
                    break;
                case 40:
                    self.snake.direction = 'bottom';
                    break;
            }
        }, false);
    }

    window.Game = Game;
})(window);