最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 贪吃蛇游戏实现思路、优化方法与源码

    正文概述 掘金(Devil)   2021-02-23   668

    贪吃蛇游戏

    • 游戏的目的是用来体会 JavaScript 高级语法的使用,暂时不需要具备抽象对象的能力。使用面向对象的方式分析问题,需要一个漫长的积累过程。

    搭建页面

    • 一个容器盛放游戏场景 div#stage,设置样式.

    分析对象

    • 食物对象 Food
    • 蛇对象 Snake
    • 游戏对象 Game

    创建工具类

    • 实现随机数、随机颜色方法

    创建食物对象

    • 创建 Food 的构造函数,并设置属性
      • 位置 x、y(实际位置)
      • 宽度和高度 width、height
      • 颜色
      • 食物集合
      • 定位:绝对定位
    • 通过原型设置方法
      • render 随机渲染一个食物对象,并添加到stage上
    • 通过自调用函数,进行封装,并通过window暴露Food构造函数。

    创建蛇对象

    • 创建 Snake 的构造函数,并设置属性
      • 宽度和高度 width、height
      • body 数组,蛇的头部和身体,第一个位置是蛇头
      • direction 蛇运动的方向
      • 定位:绝对定位
    • 通过原型设置方法
      • render 随机创建一个蛇对象,并添加到stage上
    • 通过自调用函数,进行封装。并通过window暴露Snake构造函数。

    创建游戏对象

    • 创建 Game 的构造函数,并设置属性
      • food
      • snake
      • stage
    • 通过原型设置方法
      • start 开始游戏(绘制所有游戏对象,渲染食物对象和蛇对象)
    • 通过自调用函数,进行封装,通过 window 暴露 Game 构造函数

    游戏逻辑

    游戏逻辑1

    • 蛇的move方法
      • 在蛇对象 (snake.js) 中,在 Snake 的原型上新增 move 方法
      • 1.让蛇移动起来,把蛇身体的每一部分往前移动一下
      • 2.蛇头部分根据不同的方向决定 往哪里移动(+1)

    游戏逻辑2

    • 让蛇自己动起来
      • 在 snake 中添加删除蛇的私有方法,在 render 中调用。
      • 在 game.js 中添加 runSnake 的私有方法,开启定时器调用蛇的 move 和 render 方法,让蛇动起来。
      • 判断蛇是否撞墙。
      • 在 game.js 中添加 bindKey 的私有方法,通过键盘控制蛇的移动方向,在 start 方法中调用 bindKey。

    游戏逻辑3

    • 判断蛇是否吃到食物
      • 在 Snake 的 move 方法中判断在移动的过程中蛇是否吃到食物。
      • 如果蛇头和食物的位置重合代表吃到食物。
      • 食物的坐标是像素,蛇的坐标是几个宽度,需要进行转换
      • 吃到食物,往蛇节的最后加一节
      • 最后把现在的食物对象删除,并重新随机渲染一个食物对象
    • 升级为多个食物时,需要循环遍历查看是否重合,再随机渲染一个食物对象

    其他处理

    其他处理1

    • 将测试执行的代码,整合到main.js文件中,并设置其为自调用函数,使js脱离html。
    • 注意:引用js文件名的时候,要注意顺序,后引用的可以使用之前引用的文件,反之不行

    其他处理2

    • 运行游戏后,在控制台network下可以看到HTTP向服务器请求的文件数
      • 如下图(其中有5次js请求文件)

    贪吃蛇游戏实现思路、优化方法与源码

    • 浏览器的性能优化中,包含一个减少发送http请求的条数,每多发送一个http请求,就要多在服务器里面请求一条数据,然后再去返回。
    • 解决方法:将所有的js文件整合到一个js文件中,命名为:index.js
      • 再次运行查看,如图(只请求了1次js文件)
      贪吃蛇游戏实现思路、优化方法与源码
      • 注意:在将js文件整合时,需要注意:每个js文件的先后顺序

    其他处理3

    • js代码压缩优化,可以使传输时效率更快
      • 搜索:代码压缩
      • 压缩后文件命名:index.min.js
    • 在vscode中,查看选项卡 -> 自动切换换行

    其他处理4

    • 自调用函数必须加分号。
      • 问题
      // 两种情况都是:控制台输出1,之后会报错
      // 第一种情况
      // 因为第一个执行完后,有一个返回值:默认为undefined
      // undefined(function (){}) 进行函数调用,就报错
      (function (){
            console.log("1");
        })()
        (function (){
            console.log("2");
        })()
      
        // 第二种情况
        // 如果函数作为事件或者函数表达式方式,
        // 在后面写的自调用函数也要注意
        var fun = function (){
            console.log("1");
        }
        (function (){
            console.log("2");
        })()
        // 其会看成 var fun = function (){}(function (){})
        // 即:var fun = function (){}()  undefined() -> 报错
      
      • 1.养成习惯,在函数末尾添加;
      (function (){
      })();
      
      • 2.在函数前添加; 表示前一段语句已经结束
      ;(function (){
      })()
      

    其他处理5

    • 自调用函数的参数
      • 传入 window 对象:将来代码压缩的时候,可以把 function(window) 压缩成 function(w)。避免了跳出作用域去全局作用域查找window
      • 传入 undefined
      • 写的代码中会把 undefined 作为函数的参数(当前案例没有使用)。因为在有的老版本的浏览器中 undefined 可以被重新赋值,防止 undefined 被重新赋值。
        // IE8可以重新赋值
        undefined = "llll";
        console.log(undefined); // llll
      
        // 自调用函数参数
        (function (window,undefined){
        })(window,undefined);
      

    其他处理6

    • 在遍历数组时,如果正向遍历,循环删除数组中的值,会出错
    • 解决方法,如下
    // 从舞台中清除蛇
      for(var i = this.elements.length - 1; i >= 0; i--){
          stage.removeChild(this.elements[i]);
      }
      // 清空数组
      this.elements = [];
    

    源码

    index.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="css/index.css">
    </head>
    <body>
        <div class="stage" id = "stage"></div>
        <!-- js文件书写的位置:后面可以用前面引用的文件内容 -->
        <!-- <script src="js/tools.js"></script>
        <script src="js/food.js"></script>
        <script src="js/snake.js"></script>
        <script src="js/game.js"></script>
        <script src="js/main.js"></script> -->
        <!-- 简化http向服务器请求文件数,将其他的整合到一个文件中 -->
        <script src="js/index.min.js"></script>
    </body>
    </html>
    <!-- 疑问:为什么清除定时器之后,还会往前走一格 -->
    

    index.css文件

    /* 清除默认样式 */
    * {
        margin: 0;
        padding: 0;
    }
    /* 给舞台添加样式 */
    .stage {
        width: 800px;
        height: 600px;
        background-color: lightgray;
        position: relative;
    }
    

    index.js文件

    // 整合所有的js文件
    // =========================工具类=========================
    // 制作一个工具对象,内部添加多种工具的方法
    (function (window,undefined){
        var Tools = {
            getRandom: function (min,max){
                // 向上取整
                min = Math.ceil(min);
                // 向下取整
                max = Math.floor(max);
                return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
            },
            getColor : function (){
                // 颜色参数是rgb(r,g,b)
                var r = this.getRandom(0,255);
                var g = this.getRandom(0,255);
                var b = this.getRandom(0,255);
                // 返回的时候:进行拼接
                var rgb = "rgb(" + r + "," + g + "," + b  + ")";
                // 返回颜色值
                return rgb;
            }
        }
        window.Tools = Tools;
    })(window,undefined);
    
    // ===========================food食物=======================
    (function (window,undefined){
        // 分析食物:位置x,y 宽高 颜色
        function Food(option){
            // 判断用户输入的是否正确
            // option = option || {};
            option = option instanceof Object ? option : {};
            // 传入的数据可能是类似数组等对象,所以需要进一步判断
            this.x = option.x || 0;
            this.y = option.y || 0;
            this.width = option.width || 20;
            this.height = option.height || 20;
            this.color = option.color || "blue";   
            // 如果后期难度加大,相当于一次出现三个小方块,就需要保存下
            this.elements = [];
        }
        
        // 定义全局变量
        // 将函数内部赋值后,不会改变的值,放在外面声明下更好
        var bs = "absolute";
        // 共享的方法:随机变换位置、颜色
        // 渲染一个元素到页面上,需要添加到原型对象的方法中
        Food.prototype.render = function(stage){
            // 创建一个新的div元素
            var ele = document.createElement("div");
            // 每次设置样式之前,都随机获取一个x和y的值
            this.x = Tools.getRandom(0,stage.clientWidth / this.width - 1) * this.width;
            this.y = Tools.getRandom(0,stage.clientHeight / this.height - 1) * this.height;
            // 添加对应的样式
            ele.style.left = this.x + "px";
            ele.style.top = this.y + "px";
            ele.style.width = this.width + "px";
            ele.style.height = this.height + "px";
            ele.style.backgroundColor = Tools.getColor();
            ele.style.position = bs;
            // 将新元素添加到父元素
            stage.appendChild(ele);
            // 将新元素添加到数组中,方便后期进行调用删除
            this.elements.push(ele);
        }
        
        // 当贪吃蛇碰到一个食物的时候,就应该让其消失
        // 即:从数组中删除
        Food.prototype.remove = function (stage,i){
            //从stage舞台上将其移除
            stage.removeChild(this.elements[i]);
            //从数组中将其移除
            this.elements.splice(i,1);
        }
        // 利用window对象暴露Food函数可以给外部使用
        // 相当于在外部找到了一个指针,指向它
            window.Food = Food;
        })(window,undefined);
    // ===================蛇=====================
    // 自调用函数
    (function (window,undefined){
        var bs = "absolute";
        // 创建蛇对象
        function Snake(option){
            option = option instanceof Object ? option : {};
            // 每个蛇节的宽、高
            this.width = option.width || 20;
            this.height = option.height || 20;
            // 蛇身体
            this.body = [
                {x:3,y:2,color:"red"},
                {x:2,y:2,color:"blue"},
                {x:1,y:2,color:"blue"}
            ]; 
            this.position = bs;
            this.direction = "right";
            this.elements = [];
        }
        // 渲染出来蛇对象
        Snake.prototype.render = function (stage){
            // 优化写法:没必要定义变量,因为for循环,第一句原本就只执行一次
            // var len = this.body.length;
            for(var i = 0,len = this.body.length;i < len; i++){
                // 创建一个对象
                var ele = document.createElement("div");
                // 赋样式值
                ele.style.width = this.width + "px";
                ele.style.height = this.height + "px";
                ele.style.left = this.body[i].x * this.width + "px";
                ele.style.top = this.body[i].y * this.height + "px";
                ele.style.backgroundColor = this.body[i].color;
                ele.style.position = this.position;
                // 添加到舞台上
                stage.appendChild(ele);
                // 添加到数组的末尾
                this.elements.push(ele);
            }
        }
        // 蛇运动
        Snake.prototype.move = function (){
            // 蛇身循环
            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;
            }
            // 蛇头通过位置加减
            var head = this.body[0];
            // console.log(this.direction);
            switch(this.direction){
                case "right":
                    head.x++;
                    break;
                case "left":
                    head.x--;
                    break;
                case "top":
                    head.y--;
                    break;
                case "bottom":
                    head.y++;
                    break;
            }
        }
        // 移除之前渲染的蛇
        Snake.prototype.remove = function (stage){
            
            // 从舞台中清除蛇
            for(var i = this.elements.length - 1; i >= 0; i--){
                stage.removeChild(this.elements[i]);
            }
            // 清空数组
            this.elements = [];
        }
        // 通过window暴露属性
        window.Snake = Snake;
    })(window,undefined);
    // =====================游戏=======================
    // 自调用函数
    (function (window,undefined){
        // 全局变量:存储当前的game实例对象
        var that;
        function Game(stage){
            // 食物
            this.food = new Food();
            // 蛇
            this.snake = new Snake();
            // 舞台
            this.stage = stage;
            that = this;
        }
    
        // 游戏逻辑
            // 1.蛇往前走(定时器)
            // 2.走到边界,游戏结束
            // 3.吃到食物
            // 4.食物消失,蛇变长
        Game.prototype.start = function (){
            this.food.render(this.stage);
            this.food.render(this.stage);
            this.food.render(this.stage);
            this.snake.render(this.stage);
            // 1.蛇往前走(定时器)
            snakeRun();
            // 3.吃到食物
            bindKey();
    
        }
        // 通过按键控制蛇吃食物
        function bindKey() {
            // 给文档绑定键盘按下事件
            document.onkeydown = function (e){
                // console.log(e.keyCode);
                // 上:38 下:40
                // 右:39 左:37
                // var dir = that.snake.direction;
                switch(e.keyCode){
                    case 38:
                        that.snake.direction = "top";
                        break;
                    case 40:
                        that.snake.direction = "bottom";
                        break;
                    case 39:
                        that.snake.direction = "right";
                        break;
                    case 37:
                        that.snake.direction = "left";
                        break;
                }
            };
        }
        // 蛇运动
        function snakeRun(){
            var timer = setInterval(function (){
                // 1.蛇往前走(定时器)
                // this.snake.move();
                // 移除之前渲染的蛇
                // this.snake.remove(this.stage);
                // 渲染蛇
                // this.snake.render(this.stage);
                // 此时出现了问题:上一次渲染的蛇和这次渲染的重叠了
                // 正常来说,应该:这次渲染后,上次的就应该消失
                // 直接将代码粘贴过来执行,行不通,因为此时的this不再是game实例对象
                that.snake.move();
                that.snake.remove(that.stage);
                that.snake.render(that.stage);
                
                // 每次移动都需要判断下,是否到达边界
                var maxX = that.stage.offsetWidth / that.snake.width;
                var maxY = that.stage.offsetHeight / that.snake.height;
                // 待判断的数值
                var headX = that.snake.body[0].x;
                var headY = that.snake.body[0].y;
                // 每次移动一个新位置的时候,都需要判断下是否吃到了食物
                // 即:蛇头和食物的位置是否一致
                // var foodX = that.food.x;
                // var foodY = that.food.y;
                var hX = headX * that.snake.width;
                var hY = headY * that.snake.height;
                // 有三个食物,因此需要for循环遍历
                for(var i = 0; i < that.food.elements.length; i++){
                    if(that.food.elements[i].offsetLeft == hX && that.food.elements[i].offsetTop == hY){
                        // 清除食物
                        // 问题:当食物的数量增加时,就不能固定传下标0
                        that.food.remove(that.stage,i);
                        // 再随机生成一个食物
                        that.food.render(that.stage);
                        // 添加一个新的蛇节
                        var last = that.snake.body[that.snake.body.length - 1];
                        that.snake.body.push({
                            x:last.x,
                            y:last.y,
                            color:last.color
                        });
                    }
                }
                // 2.走到边界,游戏结束
                if(headX < 0 || headX >= maxX || headY < 0 || headY >= maxY){
                    // 停止定时器
                    clearInterval(timer);
                    alert("Game over");
                }
            },150);
        }
        // 将构造函数通过window暴露
        window.Game = Game;
    })(window,undefined);
    // ==================main================
    // 将执行代码放在单独的js页面中
    (function (window,undefined){
        var stage = document.getElementById("stage");
        var game = new Game(stage);
        game.start();
    })(window,undefined);
    // 压缩文件会:1.去除注释、空格、换行 2.简化标识符
    

    起源地下载网 » 贪吃蛇游戏实现思路、优化方法与源码

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元