JavaScript 进阶教程(2)---面向对象实战之贪吃蛇小游戏

x33g5p2x  于2022-03-06 转载在 其他  
字(12.3k)|赞(0)|评价(0)|浏览(557)

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:

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

css

  1. .map {
  2. width: 800px;
  3. height: 600px;
  4. background-color: #CCC;
  5. position: relative;
  6. margin: auto;
  7. }

3 游戏对象

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

3.1 食物对象

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

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

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

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

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

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

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

3.2 小蛇对象

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

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

Snake构造函数:

  1. // 小蛇的构造函数,小蛇由一个个小方块组成
  2. function Snake(width, height, direction) {
  3. // 小蛇每个部分的宽和高
  4. this.width = width || 20;
  5. this.height = height || 20;
  6. // 小蛇的身体
  7. this.body = [{
  8. x: 3,
  9. y: 2,
  10. color: "red"
  11. }, // 头
  12. {
  13. x: 2,
  14. y: 2,
  15. color: "orange"
  16. }, // 身体
  17. {
  18. x: 1,
  19. y: 2,
  20. color: "orange"
  21. } // 身体
  22. ];
  23. // 小蛇的运动方向
  24. this.direction = direction || "right";
  25. }

init方法

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

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

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

3.3 游戏对象

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

构造函数

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

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

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

4 游戏的逻辑

4.1小蛇的移动

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

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

4.2 让蛇自己动起来

4.2.1 自动移动

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

  1. // 为游戏对象的原型添加小蛇自动移动的方法
  2. Game.prototype.runSnake = function(food, map) {
  3. // 通过定时器让小蛇自动的去移动
  4. var timeId = setInterval(function() {
  5. // 此时的this是window,通过bind(that),把this变成游戏对象
  6. // 移动小蛇
  7. this.snake.move(food, map);
  8. // 初始化小蛇
  9. this.snake.init(map);
  10. // 横坐标的最大值
  11. var maxX = map.offsetWidth / this.snake.width;
  12. // 纵坐标的最大值
  13. var maxY = map.offsetHeight / this.snake.height;
  14. // 小蛇的头的坐标
  15. var headX = this.snake.body[0].x;
  16. var headY = this.snake.body[0].y;
  17. // 横坐标
  18. if (headX < 0 || headX >= maxX) {
  19. //撞墙了,停止定时器
  20. clearInterval(timeId);
  21. alert("游戏结束");
  22. }
  23. //纵坐标
  24. if (headY < 0 || headY >= maxY) {
  25. //撞墙了,停止定时器
  26. clearInterval(timeId);
  27. alert("游戏结束");
  28. }
  29. }.bind(that), 200);
  30. };

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

  1. // 添加原型方法---设置用户按键,改变小蛇移动的方向
  2. Game.prototype.bindKey = function() {
  3. // 获取用户的按键,改变小蛇的方向
  4. document.addEventListener("keydown", function(e) {
  5. //这里的this应该是触发keydown的事件的对象---document,
  6. //所以,这里的this就是document,可以通过bind(that),把this变成当前游戏对象
  7. //获取按键的值
  8. switch (e.keyCode) {
  9. case 37:
  10. this.snake.direction = "left";
  11. break;
  12. case 38:
  13. this.snake.direction = "top";
  14. break;
  15. case 39:
  16. this.snake.direction = "right";
  17. break;
  18. case 40:
  19. this.snake.direction = "bottom";
  20. break;
  21. }
  22. }.bind(that), false);
  23. };

4.2.2 自调用函数

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

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

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

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

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

4.2.3 私有方法

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

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

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

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

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

4.3 判断蛇是否吃到食物

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

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

5 代码

index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>贪吃蛇小游戏:公众号AlbertYang</title>
  6. <style>
  7. .map {
  8. width: 800px;
  9. height: 600px;
  10. background-color: #CCC;
  11. position: relative;
  12. margin: auto;
  13. }
  14. </style>
  15. </head>
  16. <body>
  17. <!--画出地图,设置样式-->
  18. <div class="map"></div>
  19. <script src="food.js"></script>
  20. <script src="Snake.js"></script>
  21. <script src="Game.js"></script>
  22. <script>
  23. //初始化游戏对象
  24. var gm = new Game(document.querySelector(".map"));
  25. //初始化游戏---开始游戏
  26. gm.init();
  27. </script>
  28. </body>
  29. </html>

Food.js

  1. /**
  2. * 食物对象
  3. * Created by AlbertYang on 2020-08-16.
  4. */
  5. (function() {
  6. var elements = []; // 用来保存食物
  7. // 食物的构造函数,食物是一个小方块,有宽,高,颜色,横纵坐标等属性
  8. function Food(x, y, width, height, color) {
  9. // 宽和高
  10. this.width = width || 20;
  11. this.height = height || 20;
  12. // 背景颜色
  13. this.color = color || "green";
  14. // 横纵坐标
  15. this.x = x || 0;
  16. this.y = y || 0;
  17. }
  18. // 为食物对象原型添加初始化食物的方法(作用:在页面上显示这个食物)
  19. // 因为食物要在地图上显示,所以,需要用到地图这个参数
  20. Food.prototype.init = function(map) {
  21. // 删除已经存在地图上的食物
  22. // 此处remove为私有函数,外部无法访问
  23. remove();
  24. // 创建div
  25. var div = document.createElement("div");
  26. // 把div加到map中
  27. map.appendChild(div);
  28. // 设置div的样式
  29. div.style.width = this.width + "px";
  30. div.style.height = this.height + "px";
  31. div.style.backgroundColor = this.color;
  32. // 脱离文档流
  33. div.style.position = "absolute";
  34. // 随机横纵坐标
  35. this.x = parseInt(Math.random() * (map.offsetWidth / this.width)) * this.width;
  36. this.y = parseInt(Math.random() * (map.offsetHeight / this.height)) * this.height;
  37. div.style.left = this.x + "px";
  38. div.style.top = this.y + "px";
  39. // 把div加入到数组elements中
  40. elements.push(div);
  41. };
  42. // 删除食物---私有函数外部无法访问
  43. function remove() {
  44. // elements数组中有这个食物
  45. for (var i = 0; i < elements.length; i++) {
  46. var ele = elements[i];
  47. // 找到这个子元素的父级元素,然后删除这个子元素
  48. ele.parentNode.removeChild(ele);
  49. // 把elements中的这个子元素也要删除
  50. elements.splice(i, 1);
  51. }
  52. }
  53. // 把Food暴露给Window,以便外部可以使用
  54. window.Food = Food;
  55. }()); //自调用函数

Snake.js

  1. /**
  2. * 小蛇对象
  3. * Created by AlbertYang on 2020-08-16.
  4. */
  5. (function() {
  6. var elements = []; // 存放小蛇身体的每个部分
  7. // 小蛇的构造函数,小蛇由一个个小方块组成
  8. function Snake(width, height, direction) {
  9. // 小蛇每个部分的宽和高
  10. this.width = width || 20;
  11. this.height = height || 20;
  12. // 小蛇的身体
  13. this.body = [{
  14. x: 3,
  15. y: 2,
  16. color: "red"
  17. }, // 头
  18. {
  19. x: 2,
  20. y: 2,
  21. color: "orange"
  22. }, // 身体
  23. {
  24. x: 1,
  25. y: 2,
  26. color: "orange"
  27. } // 身体
  28. ];
  29. // 小蛇的运动方向
  30. this.direction = direction || "right";
  31. }
  32. // 为小蛇对象原型添加小蛇初始化方法
  33. Snake.prototype.init = function(map) {
  34. // 删除之前的小蛇
  35. remove();
  36. // 循环遍历创建div
  37. for (var i = 0; i < this.body.length; i++) {
  38. // 数组中的每个数组元素都是一个对象
  39. var obj = this.body[i];
  40. // 创建div
  41. var div = document.createElement("div");
  42. // 把div加入到map地图中
  43. map.appendChild(div);
  44. // 设置div的样式
  45. div.style.position = "absolute";
  46. div.style.width = this.width + "px";
  47. div.style.height = this.height + "px";
  48. // 横纵坐标
  49. div.style.left = obj.x * this.width + "px";
  50. div.style.top = obj.y * this.height + "px";
  51. // 背景颜色
  52. div.style.backgroundColor = obj.color;
  53. // 方向暂时不定
  54. // 把div加入到elements数组中---目的是为了删除
  55. elements.push(div);
  56. }
  57. };
  58. // 为小蛇对象原型添加小蛇移动方法
  59. Snake.prototype.move = function(food, map) {
  60. // 改变小蛇身体的坐标位置
  61. var i = this.body.length - 1; // 第一个位置为头,身体从第二个位置开始
  62. for (; i > 0; i--) {
  63. this.body[i].x = this.body[i - 1].x;
  64. this.body[i].y = this.body[i - 1].y;
  65. }
  66. // 判断方向---改变小蛇头的坐标位置
  67. switch (this.direction) {
  68. case "right":
  69. this.body[0].x += 1;
  70. break;
  71. case "left":
  72. this.body[0].x -= 1;
  73. break;
  74. case "top":
  75. this.body[0].y -= 1;
  76. break;
  77. case "bottom":
  78. this.body[0].y += 1;
  79. break;
  80. }
  81. // 判断有没有吃到食物
  82. // 小蛇头的坐标和食物的坐标一致则吃到食物
  83. var headX = this.body[0].x * this.width;
  84. var headY = this.body[0].y * this.height;
  85. // 判断小蛇头的坐标和食物的坐标是否相同
  86. if (headX == food.x && headY == food.y) {
  87. // 获取小蛇的最后的尾巴
  88. var last = this.body[this.body.length - 1];
  89. // 把最后的蛇尾复制一个,重新的加入到小蛇的body中
  90. this.body.push({
  91. x: last.x,
  92. y: last.y,
  93. color: last.color
  94. });
  95. // 把食物删除,重新初始化食物
  96. food.init(map);
  97. }
  98. }
  99. // 删除小蛇---私有函数外部无法访问
  100. function remove() {
  101. // 删除map中的小蛇的每个div,同时删除elements数组中的每个元素,从蛇尾向蛇头方向删除div
  102. var i = elements.length - 1;
  103. for (; i >= 0; i--) {
  104. // 从当前的子元素中找到该子元素的父级元素,然后再删除这个子元素
  105. var ele = elements[i];
  106. // 从map地图上删除这个子元素
  107. ele.parentNode.removeChild(ele);
  108. // 从elements数组中删除这个子元素
  109. elements.splice(i, 1);
  110. }
  111. }
  112. //把Snake暴露给window,以便外部可以使用
  113. window.Snake = Snake;
  114. }()); //自调用函数

Game.js

  1. /**
  2. * 游戏对象
  3. * Created by AlbertYang on 2020-08-16.
  4. */
  5. (function() {
  6. var that = null; // 该变量的目的就是为了保存游戏Game的实例对象
  7. // 游戏对象的构造函数
  8. function Game(map) {
  9. this.food = new Food(); // 食物对象
  10. this.snake = new Snake(); // 小蛇对象
  11. this.map = map; // 地图
  12. that = this; // 保存当前的实例对象到that变量中
  13. }
  14. // 为游戏对象的原型添加初始化游戏的方法---让小蛇和食物显示出来
  15. Game.prototype.init = function() {
  16. // 食物初始化
  17. this.food.init(this.map);
  18. // 小蛇初始化
  19. this.snake.init(this.map);
  20. // 调用自动移动小蛇的方法
  21. this.runSnake(this.food, this.map);
  22. // 调用按键的方法
  23. this.bindKey();
  24. };
  25. // 为游戏对象的原型添加小蛇自动移动的方法
  26. Game.prototype.runSnake = function(food, map) {
  27. // 通过定时器让小蛇自动的去移动
  28. var timeId = setInterval(function() {
  29. // 此时的this是window,通过bind(that),把this变成游戏对象
  30. // 移动小蛇
  31. this.snake.move(food, map);
  32. // 初始化小蛇
  33. this.snake.init(map);
  34. // 横坐标的最大值
  35. var maxX = map.offsetWidth / this.snake.width;
  36. // 纵坐标的最大值
  37. var maxY = map.offsetHeight / this.snake.height;
  38. // 小蛇的头的坐标
  39. var headX = this.snake.body[0].x;
  40. var headY = this.snake.body[0].y;
  41. // 横坐标
  42. if (headX < 0 || headX >= maxX) {
  43. //撞墙了,停止定时器
  44. clearInterval(timeId);
  45. alert("游戏结束");
  46. }
  47. //纵坐标
  48. if (headY < 0 || headY >= maxY) {
  49. //撞墙了,停止定时器
  50. clearInterval(timeId);
  51. alert("游戏结束");
  52. }
  53. }.bind(that), 200);
  54. };
  55. // 添加原型方法---设置用户按键,改变小蛇移动的方向
  56. Game.prototype.bindKey = function() {
  57. // 获取用户的按键,改变小蛇的方向
  58. document.addEventListener("keydown", function(e) {
  59. //这里的this应该是触发keydown的事件的对象---document,
  60. //所以,这里的this就是document,可以通过bind(that),把this变成当前游戏对象
  61. //获取按键的值
  62. switch (e.keyCode) {
  63. case 37:
  64. this.snake.direction = "left";
  65. break;
  66. case 38:
  67. this.snake.direction = "top";
  68. break;
  69. case 39:
  70. this.snake.direction = "right";
  71. break;
  72. case 40:
  73. this.snake.direction = "bottom";
  74. break;
  75. }
  76. }.bind(that), false);
  77. };
  78. // 把Game暴露给window,以便外部能够使用
  79. window.Game = Game;
  80. }()); // 自调用函数

6 其它处理

6.1 index.js

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

6.2 自调用函数的参数

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

传入window对象

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

传入undefined

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

6.2 多个自调用函数

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

  1. (function () {
  2. }())
  3. (function () {
  4. }())

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

  1. ;(function () {
  2. }())
  3. ;(function () {
  4. }())

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

  1. var a = function() {
  2. console.dir(arguments);
  3. console.log('11');
  4. }
  5. (function() {
  6. console.log('22');
  7. }())

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

相关文章