zoukankan      html  css  js  c++  java
  • JavaScript 进阶教程(2)---面向对象实战之贪吃蛇小游戏

    目录

    1 引言

    2 游戏地图

    3 游戏对象

    3.1 食物对象

    3.2 小蛇对象

    3.3 游戏对象

    4 游戏的逻辑

    4.1小蛇的移动

    4.2 让蛇自己动起来

    4.2.1 自动移动

    4.2.2 自调用函数

    4.2.3 私有方法

    4.3 判断蛇是否吃到食物

    5 代码

    6 其它处理

    6.1 index.js

    6.2 自调用函数的参数

    6.2 多个自调用函数


    1 引言

    上篇文章https://blog.csdn.net/qq_23853743/article/details/108034430

    上篇文章中讲解了JavaScript中的面向对象编程,今天带大家使用面向对象的思想,实现一个大家小时候应该都玩过的小游戏贪吃蛇,游戏的目的是为了让大家进一步体会JavaScript面向对象编程的使用。

    源码:https://github.com/AlbertYang666/snake
    演示地址:https://www.albertyy.com/2020/snake/index.html

    2 游戏地图

    使用一个div容器盛放游戏场景,即游戏地图 div.map,并设置地图样式。

    div:

    <!--游戏地图-->
    <div class="map"></div>
    

    css

    .map {
         800px;
        height: 600px;
        background-color: #CCC;
        position: relative;
        margin: auto;
    }
    

    3 游戏对象

    经过分析可知,参与本游戏的对象有食物对象,小蛇对象和游戏对象。

    3.1 食物对象

    此处的食物对象为一个小方块,它的属性有横纵坐标 x, y, width,height, color 。
    食物对象的方法: init() 用于随机创建一个食物对象,并渲染到map上。

    创建Food的构造函数,并设置属性:

        // 食物的构造函数,食物是一个小方块,有宽,高,颜色,横纵坐标等属性
        function Food(x, y, width, height, color) {
            // 宽和高
            this.width = width || 20;
            this.height = height || 20;
            // 背景颜色
            this.color = color || "green";
             // 横纵坐标
             this.x = x || 0;
             this.y = y || 0;
        }
    

    通过原型设置init方法,实现随机产生食物对象,并渲染到map上:

        // 为食物对象原型添加初始化食物的方法(作用:在页面上显示这个食物)
        // 因为食物要在地图上显示,所以,需要用到地图这个参数
        Food.prototype.init = function (map) {
            // 创建div
            var div = document.createElement("div");
            // 把div加到map中
            map.appendChild(div);
            // 设置div的样式
            div.style.width = this.width + "px";
            div.style.height = this.height + "px";
            div.style.backgroundColor = this.color;
            // 脱离文档流
            div.style.position = "absolute";
            // 随机食物的位置,map.宽度/food.宽度,总共有多少份food的宽度,随机一下。然后再乘以food的宽度
            this.x = parseInt(Math.random() * (map.offsetWidth / this.width)) * this.width;
            this.y = parseInt(Math.random() * (map.offsetHeight / this.height)) * this.height;
            div.style.left = this.x + "px";
            div.style.top = this.y + "px";
    
            // 把div加入到数组elements中
            elements.push(div);
        };
    

    通过自调用函数,进行封装,把Food暴露给Window,以便外部可以使用

    // 把Food暴露给Window,以便外部可以使用
     window.Food = Food;
    

    3.2 小蛇对象

    小蛇(Snake)由一个个小方块组成,它的属性有,width (组成小蛇的小方块的宽 默认为20),height(组成小蛇的小方块的高 默认为20),body (数组,蛇的头部和身体,第一个位置是蛇头), direction(小蛇的运动方向 默认right ,可以是 left top bottom)。

    小蛇的方法: init() 把蛇渲染到map上。

    Snake构造函数:

       // 小蛇的构造函数,小蛇由一个个小方块组成
            function Snake(width, height, direction) {
                    // 小蛇每个部分的宽和高
                    this.width = width || 20;
                    this.height = height || 20;
                    // 小蛇的身体
                    this.body = [{
                                    x: 3,
                                    y: 2,
                                    color: "red"
                            }, // 头
                            {
                                    x: 2,
                                    y: 2,
                                    color: "orange"
                            }, // 身体
                            {
                                    x: 1,
                                    y: 2,
                                    color: "orange"
                            } // 身体
                    ];
                    // 小蛇的运动方向
                    this.direction = direction || "right";
            }
    

    init方法

           // 为小蛇对象原型添加小蛇初始化方法
            Snake.prototype.init = function(map) {
                    
                    // 循环遍历创建div
                    for (var i = 0; i < this.body.length; i++) {
                            // 数组中的每个数组元素都是一个对象
                            var obj = this.body[i];
                            // 创建div
                            var div = document.createElement("div");
                            // 把div加入到map地图中
                            map.appendChild(div);
                            // 设置div的样式
                            div.style.position = "absolute";
                            div.style.width = this.width + "px";
                            div.style.height = this.height + "px";
                            // 横纵坐标
                            div.style.left = obj.x * this.width + "px";
                            div.style.top = obj.y * this.height + "px";
                            // 背景颜色
                            div.style.backgroundColor = obj.color;
                            // 方向暂时不定
                            // 把div加入到elements数组中---目的是为了删除
                            elements.push(div);
                    }
            };
    

    在自调用函数中暴露Snake对象

       //把Snake暴露给window,以便外部可以使用
       window.Snake = Snake;
    

    3.3 游戏对象

    游戏对象,用来管理游戏中的所有对象和开始游戏。游戏对象的属性有,food,snake,map。
    游戏对象的方法:init()开始游戏(绘制所有游戏对象)。

    构造函数

       // 游戏对象的构造函数
        function Game(map) {
            this.food = new Food();// 食物对象
            this.snake = new Snake();// 小蛇对象
            this.map = map;// 地图
            that = this;// 保存当前的实例对象到that变量中
        }

    开始游戏,渲染食物对象和蛇对象

    	// 为游戏对象的原型添加初始化游戏的方法---让小蛇和食物显示出来
    	Game.prototype.init = function() {
    		// 食物初始化
    		this.food.init(this.map);
    		// 小蛇初始化
    		this.snake.init(this.map);
    		// 调用自动移动小蛇的方法
    		this.runSnake(this.food, this.map);
    		// 调用按键的方法
    		this.bindKey();
    	};

    4 游戏的逻辑

    4.1小蛇的移动

    在蛇对象(Snake.js)中,在Snake的原型上新增move方法:

    1. 让蛇移动起来,把蛇身体的每一部分往前移动一下
    2. 蛇头部分根据不同的方向决定 往哪里移动
    	// 为小蛇对象原型添加小蛇移动方法
    	Snake.prototype.move = function(food, map) {
    		// 改变小蛇身体的坐标位置
    		var i = this.body.length - 1; // 第一个位置为头,身体从第二个位置开始
    		for (; i > 0; i--) {
    			this.body[i].x = this.body[i - 1].x;
    			this.body[i].y = this.body[i - 1].y;
    		}
    		// 判断方向---改变小蛇头的坐标位置
    		switch (this.direction) {
    			case "right":
    				this.body[0].x += 1;
    				break;
    			case "left":
    				this.body[0].x -= 1;
    				break;
    			case "top":
    				this.body[0].y -= 1;
    				break;
    			case "bottom":
    				this.body[0].y += 1;
    				break;
    		}
    
    	}

    4.2 让蛇自己动起来

    4.2.1 自动移动

    在Game.js中 添加runSnake方法,开启定时器调用蛇的move和init方法,让蛇动起来。

        // 为游戏对象的原型添加小蛇自动移动的方法
    	Game.prototype.runSnake = function(food, map) {
    
    		// 通过定时器让小蛇自动的去移动
    		var timeId = setInterval(function() {
    			// 此时的this是window,通过bind(that),把this变成游戏对象
    			// 移动小蛇
    			this.snake.move(food, map);
    			// 初始化小蛇
    			this.snake.init(map);
    			// 横坐标的最大值
    			var maxX = map.offsetWidth / this.snake.width;
    			// 纵坐标的最大值
    			var maxY = map.offsetHeight / this.snake.height;
    			// 小蛇的头的坐标
    			var headX = this.snake.body[0].x;
    			var headY = this.snake.body[0].y;
    			// 横坐标
    			if (headX < 0 || headX >= maxX) {
    				//撞墙了,停止定时器
    				clearInterval(timeId);
    				alert("游戏结束");
    			}
    			//纵坐标
    			if (headY < 0 || headY >= maxY) {
    				//撞墙了,停止定时器
    				clearInterval(timeId);
    				alert("游戏结束");
    			}
    		}.bind(that), 200);
    	};

    在Game中通过键盘控制蛇的移动方向,在init方法中调用。

    // 添加原型方法---设置用户按键,改变小蛇移动的方向
    	Game.prototype.bindKey = function() {
    
    		// 获取用户的按键,改变小蛇的方向
    		document.addEventListener("keydown", function(e) {
    			//这里的this应该是触发keydown的事件的对象---document,
    			//所以,这里的this就是document,可以通过bind(that),把this变成当前游戏对象
    			//获取按键的值
    			switch (e.keyCode) {
    				case 37:
    					this.snake.direction = "left";
    					break;
    				case 38:
    					this.snake.direction = "top";
    					break;
    				case 39:
    					this.snake.direction = "right";
    					break;
    				case 40:
    					this.snake.direction = "bottom";
    					break;
    			}
    		}.bind(that), false);
    	};

    4.2.2 自调用函数

    函数声明的同时,直接调用了

       (function () {
          console.log("函数");
       })();
    

    自调用函数里的变量为局部变量,如果想把局部变量变成全局变量,把局部变量给window就可以了。

    (function (win) {
        var num=10;// 局部变量
        win.num=num;
     })(window);
     console.log(num);
    

    关于局部变量,全局变量可以看一下我这篇文章:https://blog.csdn.net/qq_23853743/article/details/106946100

    4.2.3 私有方法

    什么是私有方法?
      不能被外部直接访问的方法为私有方法。
    如何创建私有方法?
      使用自调用函数包裹要创建的方法。

    在Food中添加删除食物的私有方法,在init中调用:

        // 删除食物---私有函数外部无法访问
    	function remove() {
    		// elements数组中有这个食物
    		for (var i = 0; i < elements.length; i++) {
    			var ele = elements[i];
    			// 找到这个子元素的父级元素,然后删除这个子元素
    			ele.parentNode.removeChild(ele);
    			// 把elements中的这个子元素也要删除
    			elements.splice(i, 1);
    		}
    	}
    

    在Snake中添加删除蛇的私有方法,在init中调用:

        // 删除小蛇---私有函数外部无法访问
    	function remove() {
    		// 删除map中的小蛇的每个div,同时删除elements数组中的每个元素,从蛇尾向蛇头方向删除div
    		var i = elements.length - 1;
    		for (; i >= 0; i--) {
    			// 从当前的子元素中找到该子元素的父级元素,然后再删除这个子元素
    			var ele = elements[i];
    			// 从map地图上删除这个子元素
    			ele.parentNode.removeChild(ele);
    			// 从elements数组中删除这个子元素
    			elements.splice(i, 1);
    		}
    	}

    4.3 判断蛇是否吃到食物

    在Snake的move方法中判断小蛇是否吃到食物。

                    // 判断有没有吃到食物
                    // 小蛇头的坐标和食物的坐标一致则吃到食物
                    var headX = this.body[0].x * this.width;
                    var headY = this.body[0].y * this.height;
                    // 判断小蛇头的坐标和食物的坐标是否相同
                    if (headX == food.x && headY == food.y) {
                            // 获取小蛇的最后的尾巴
                            var last = this.body[this.body.length - 1];
                            // 把最后的蛇尾复制一个,重新的加入到小蛇的body中
                            this.body.push({
                                    x: last.x,
                                    y: last.y,
                                    color: last.color
                            });
                            // 把食物删除,重新初始化食物
                            food.init(map);
                    }
    
    

    5 代码

    index.html

    <!DOCTYPE html>
    <html>
    	<head>
    		<meta charset="UTF-8">
    		<title>贪吃蛇小游戏:公众号AlbertYang</title>
    		<style>
    			.map {
    				 800px;
    				height: 600px;
    				background-color: #CCC;
    				position: relative;
    				margin: auto;
    			}
    		</style>
    	</head>
    	<body>
    		<!--画出地图,设置样式-->
    		<div class="map"></div>
    		<script src="food.js"></script>
    		<script src="Snake.js"></script>
    		<script src="Game.js"></script>
    		<script>
    			//初始化游戏对象
    			var gm = new Game(document.querySelector(".map"));
    
    			//初始化游戏---开始游戏
    			gm.init();
    		</script>
    	</body>
    </html>
    

    Food.js

    /**
     * 食物对象
     * Created by AlbertYang on 2020-08-16.
     */
    (function() {
    	var elements = []; // 用来保存食物
    	// 食物的构造函数,食物是一个小方块,有宽,高,颜色,横纵坐标等属性
    	function Food(x, y, width, height, color) {
    		// 宽和高
    		this.width = width || 20;
    		this.height = height || 20;
    		// 背景颜色
    		this.color = color || "green";
    		// 横纵坐标
    		this.x = x || 0;
    		this.y = y || 0;
    	}
    
    	// 为食物对象原型添加初始化食物的方法(作用:在页面上显示这个食物)
    	// 因为食物要在地图上显示,所以,需要用到地图这个参数
    	Food.prototype.init = function(map) {
    		// 删除已经存在地图上的食物
    		// 此处remove为私有函数,外部无法访问
    		remove();
    
    		// 创建div
    		var div = document.createElement("div");
    		// 把div加到map中
    		map.appendChild(div);
    		// 设置div的样式
    		div.style.width = this.width + "px";
    		div.style.height = this.height + "px";
    		div.style.backgroundColor = this.color;
    		// 脱离文档流
    		div.style.position = "absolute";
    		// 随机横纵坐标
    		this.x = parseInt(Math.random() * (map.offsetWidth / this.width)) * this.width;
    		this.y = parseInt(Math.random() * (map.offsetHeight / this.height)) * this.height;
    		div.style.left = this.x + "px";
    		div.style.top = this.y + "px";
    
    		// 把div加入到数组elements中
    		elements.push(div);
    	};
    
    	// 删除食物---私有函数外部无法访问
    	function remove() {
    		// elements数组中有这个食物
    		for (var i = 0; i < elements.length; i++) {
    			var ele = elements[i];
    			// 找到这个子元素的父级元素,然后删除这个子元素
    			ele.parentNode.removeChild(ele);
    			// 把elements中的这个子元素也要删除
    			elements.splice(i, 1);
    		}
    	}
    
    	// 把Food暴露给Window,以便外部可以使用
    	window.Food = Food;
    }()); //自调用函数
    

    Snake.js

    /**
     * 小蛇对象
     * Created by AlbertYang on 2020-08-16.
     */
    (function() {
    	var elements = []; // 存放小蛇身体的每个部分
    	// 小蛇的构造函数,小蛇由一个个小方块组成
    	function Snake(width, height, direction) {
    		// 小蛇每个部分的宽和高
    		this.width = width || 20;
    		this.height = height || 20;
    		// 小蛇的身体
    		this.body = [{
    				x: 3,
    				y: 2,
    				color: "red"
    			}, // 头
    			{
    				x: 2,
    				y: 2,
    				color: "orange"
    			}, // 身体
    			{
    				x: 1,
    				y: 2,
    				color: "orange"
    			} // 身体
    		];
    		// 小蛇的运动方向
    		this.direction = direction || "right";
    	}
    
    	// 为小蛇对象原型添加小蛇初始化方法
    	Snake.prototype.init = function(map) {
    		// 删除之前的小蛇
    		remove();
    
    		// 循环遍历创建div
    		for (var i = 0; i < this.body.length; i++) {
    			// 数组中的每个数组元素都是一个对象
    			var obj = this.body[i];
    			// 创建div
    			var div = document.createElement("div");
    			// 把div加入到map地图中
    			map.appendChild(div);
    			// 设置div的样式
    			div.style.position = "absolute";
    			div.style.width = this.width + "px";
    			div.style.height = this.height + "px";
    			// 横纵坐标
    			div.style.left = obj.x * this.width + "px";
    			div.style.top = obj.y * this.height + "px";
    			// 背景颜色
    			div.style.backgroundColor = obj.color;
    			// 方向暂时不定
    			// 把div加入到elements数组中---目的是为了删除
    			elements.push(div);
    		}
    	};
    
    	// 为小蛇对象原型添加小蛇移动方法
    	Snake.prototype.move = function(food, map) {
    		// 改变小蛇身体的坐标位置
    		var i = this.body.length - 1; // 第一个位置为头,身体从第二个位置开始
    		for (; i > 0; i--) {
    			this.body[i].x = this.body[i - 1].x;
    			this.body[i].y = this.body[i - 1].y;
    		}
    		// 判断方向---改变小蛇头的坐标位置
    		switch (this.direction) {
    			case "right":
    				this.body[0].x += 1;
    				break;
    			case "left":
    				this.body[0].x -= 1;
    				break;
    			case "top":
    				this.body[0].y -= 1;
    				break;
    			case "bottom":
    				this.body[0].y += 1;
    				break;
    		}
    
    		// 判断有没有吃到食物
    		// 小蛇头的坐标和食物的坐标一致则吃到食物
    		var headX = this.body[0].x * this.width;
    		var headY = this.body[0].y * this.height;
    		// 判断小蛇头的坐标和食物的坐标是否相同
    		if (headX == food.x && headY == food.y) {
    			// 获取小蛇的最后的尾巴
    			var last = this.body[this.body.length - 1];
    			// 把最后的蛇尾复制一个,重新的加入到小蛇的body中
    			this.body.push({
    				x: last.x,
    				y: last.y,
    				color: last.color
    			});
    			// 把食物删除,重新初始化食物
    			food.init(map);
    		}
    	}
    	// 删除小蛇---私有函数外部无法访问
    	function remove() {
    		// 删除map中的小蛇的每个div,同时删除elements数组中的每个元素,从蛇尾向蛇头方向删除div
    		var i = elements.length - 1;
    		for (; i >= 0; i--) {
    			// 从当前的子元素中找到该子元素的父级元素,然后再删除这个子元素
    			var ele = elements[i];
    			// 从map地图上删除这个子元素
    			ele.parentNode.removeChild(ele);
    			// 从elements数组中删除这个子元素
    			elements.splice(i, 1);
    		}
    	}
    
    	//把Snake暴露给window,以便外部可以使用
    	window.Snake = Snake;
    }()); //自调用函数
    

    Game.js

    /**
     * 游戏对象
     * Created by AlbertYang on 2020-08-16.
     */
    (function() {
    
    	var that = null; // 该变量的目的就是为了保存游戏Game的实例对象
    
    	// 游戏对象的构造函数
    	function Game(map) {
    		this.food = new Food(); // 食物对象
    		this.snake = new Snake(); // 小蛇对象
    		this.map = map; // 地图
    		that = this; // 保存当前的实例对象到that变量中
    	}
    
    	// 为游戏对象的原型添加初始化游戏的方法---让小蛇和食物显示出来
    	Game.prototype.init = function() {
    		// 食物初始化
    		this.food.init(this.map);
    		// 小蛇初始化
    		this.snake.init(this.map);
    		// 调用自动移动小蛇的方法
    		this.runSnake(this.food, this.map);
    		// 调用按键的方法
    		this.bindKey();
    	};
    
    	// 为游戏对象的原型添加小蛇自动移动的方法
    	Game.prototype.runSnake = function(food, map) {
    
    		// 通过定时器让小蛇自动的去移动
    		var timeId = setInterval(function() {
    			// 此时的this是window,通过bind(that),把this变成游戏对象
    			// 移动小蛇
    			this.snake.move(food, map);
    			// 初始化小蛇
    			this.snake.init(map);
    			// 横坐标的最大值
    			var maxX = map.offsetWidth / this.snake.width;
    			// 纵坐标的最大值
    			var maxY = map.offsetHeight / this.snake.height;
    			// 小蛇的头的坐标
    			var headX = this.snake.body[0].x;
    			var headY = this.snake.body[0].y;
    			// 横坐标
    			if (headX < 0 || headX >= maxX) {
    				//撞墙了,停止定时器
    				clearInterval(timeId);
    				alert("游戏结束");
    			}
    			//纵坐标
    			if (headY < 0 || headY >= maxY) {
    				//撞墙了,停止定时器
    				clearInterval(timeId);
    				alert("游戏结束");
    			}
    		}.bind(that), 200);
    	};
    
    	// 添加原型方法---设置用户按键,改变小蛇移动的方向
    	Game.prototype.bindKey = function() {
    
    		// 获取用户的按键,改变小蛇的方向
    		document.addEventListener("keydown", function(e) {
    			//这里的this应该是触发keydown的事件的对象---document,
    			//所以,这里的this就是document,可以通过bind(that),把this变成当前游戏对象
    			//获取按键的值
    			switch (e.keyCode) {
    				case 37:
    					this.snake.direction = "left";
    					break;
    				case 38:
    					this.snake.direction = "top";
    					break;
    				case 39:
    					this.snake.direction = "right";
    					break;
    				case 40:
    					this.snake.direction = "bottom";
    					break;
    			}
    		}.bind(that), false);
    	};
    
    	// 把Game暴露给window,以便外部能够使用
    	window.Game = Game;
    }()); // 自调用函数
    

    6 其它处理

    6.1 index.js

    可以把html中的js代码放到index.js中,以避免html中出现js代码。

    6.2 自调用函数的参数

    (function (window, undefined) {
      var document = window.document;
    
    }(window, undefined))
    

    传入window对象

    代码压缩的时候,可以把 function (window) 压缩成 function (w)。

    传入undefined

    在有的老版本的浏览器中 undefined可以被重新赋值,防止undefined 被重新赋值。

    6.2 多个自调用函数

    一个JS文件中如果存在多个自调用函数要用分号分割,否则会发生语法错误,下面代码会报错。

    (function () {
    }())
    
    (function () {
    }())
    

    所以在代码规范中建议在自调用函数之前加上分号,下面代码没有问题。

    ;(function () {
    }())
    
    ;(function () {
    }())
    

    当自调用函数的前面有函数声明时,会把自调用函数作为参数,所以建议自调用函数前,加上; 。

    var a = function() {
    	console.dir(arguments);
    	console.log('11');
    }
    
    (function() {
    	console.log('22');
    }())
    

    今天的学习就到这里,你可以使用今天学习的技巧来改善一下你曾经的代码,如果想继续提高,欢迎关注我,每天学习进步一点点,就是领先的开始。如果觉得本文对你有帮助的话,欢迎点赞,评论,转发!!!

  • 相关阅读:
    netcore使用IdentityServer在nginx下502错误的解决
    更新到.netcore3.0后找不到dotnet-ef的解决办法
    openssh-win64 on windows2016 ssh pub key config
    405 Method Not Allowed error with PUT or DELETE Request on IIS Server
    minikube windows hyperx填坑记
    angular当router使用userhash:false时路由404问题
    内网gitlab11.2升级至11.4.5
    Angular7上手体验
    动态规划之背包问题
    Union-Find算法详解
  • 原文地址:https://www.cnblogs.com/yangxianyang/p/13675538.html
Copyright © 2011-2022 走看看