• 原生JS实现《贪吃蛇-snake》项目总结及代码分析展示


    项目展示图


     

     

     

     基本功能描述

    1.开始游戏功能

    当用户进入游戏主界面时,可在界面中下方显眼的位置找到“start”按钮,点击后用户可进行新游戏。

    2.运动功能

    2.1.默认运动功能

    当用户点击”开始游戏“按钮后,蛇的方向默认从左到右方向移动。

    2.2.键盘控制方向运动功能

    用户可通过使用键盘上的上下左右方位键控制蛇的移动方向,蛇在控制的方向上进行直线前进。

    3.吃食物功能

    当界面任意位置出现食物,用户使用方位键控制蛇移动到食物周围,当蛇头碰到食物时则表示贪吃蛇吃到此食物,界面上会在任意位置出现下一个食物,用户再次控制蛇去吃这一食物。

    4.死亡判定功能

    当蛇头在前进方向上撞到墙壁或蛇头吃到蛇身时,给出死亡判定,并弹框给出用户本次游戏得分。

    5.暂停/继续游戏功能

    当用户使用软件时,由于个人原因需要暂停游戏进程,用户可点击游戏界面时及时“暂停”游戏,此时会出现“游戏暂停”的按钮,当再次点击“暂停”按钮则继续之前的游戏。

    准备工作

    1. 建立新文件夹snake,新建出images CSS JS 三个文件夹,并在根目录下创建出snake.html
    2. 将素材图片转到images文件中
    3. 接下来可以开始着手操作了 


    HTML结构

    1. 游戏内容区域 (content)
    2. 游戏开始按钮 (btn startBtn)
    3. 游戏暂停按钮 (btn pauseBtn)
    4. 游戏进行区域 (snakeWrap)

    可以构造出如下的结构

    1. <div class="content">
    2. <div class="btn startBtn"><button>button>div>
    3. <div class="btn pauseBtn"><button>button>div>
    4. <div id="snakeWrap">div>
    5. div>

    在head标签内引入CSS文件夹内的style.css

    <link rel="stylesheet" href="css/style.css">

    在body标签的最末尾,添加script,引入JS文件夹内的snake.js

    <script src="js/snake.js">script>

     

    CSS样式

    首先,要让游戏内容区域 居中显示

    1. .content {
    2. width: 640px;
    3. height: 640px;
    4. margin: 50px auto;
    5. position: relative;
    6. }

    左右margin为 auto 使其自适应居中显示

    给两个按钮以及其蒙层添加属性

    1. .btn {
    2. width: 100%;
    3. height: 100%;
    4. position: absolute;
    5. left: 0;
    6. top: 0;
    7. background-color: rgba(0, 0, 0, 0);
    8. z-index: 1;
    9. }
    10. .btn button {
    11. background: none;
    12. border: none;
    13. background-size: 100% 100%;
    14. cursor: pointer;
    15. outline: none;
    16. position: absolute;
    17. left: 50%;
    18. top: 50%;
    19. }

    btn 是两个按钮的蒙层属性,完全覆盖了游戏内容区域,而btn 中 的 button 给它设置了一些相应的属性,例如水平垂直居中(在下面获取具体宽高的时候,给予负 的 margin)background-size 平铺背景图片 等属性

    1. .startBtn button {
    2. background-image: url(../images/btn1.gif);
    3. width: 200px;
    4. height: 150px;
    5. margin-left: -100px;
    6. margin-top: -75px;
    7. }
    8. .pauseBtn {
    9. display: none;
    10. }
    11. .pauseBtn button {
    12. background-image: url(../images/btn4.png);
    13. width: 70px;
    14. height: 70px;
    15. margin-left: -35px;
    16. margin-top: -35px;
    17. }

    给予开始按钮 以及 暂停按钮宽高,并设置背景图片,让其居中,并让暂停按钮不显示

    给游戏进行区域添加样式

    1. #snakeWrap {
    2. position: relative;
    3. width: 600px;
    4. height: 600px;
    5. border: 20px solid deeppink;
    6. background: pink;
    7. }

    游戏内容区域宽高为640px 游戏进行区域的宽高为600px 加上边框border 刚好就是640px,并且给予其相对定位

    预定义蛇的样式

    首先,我们的蛇的身体,头部,包括我们的食物苹果,其实都是一个一个的小方块构成(这部分我们会在js里面实现)

    我们先预定义蛇头样式

    1. .snakeHead {
    2. background-image: url(../images/snake.png);
    3. background-size: cover;
    4. }

    引入蛇头图片,让其图片平铺方块,再将其初始的蛇头方向圆角修饰,让其蛇头朝右 

    蛇身体样式

    1. .snakeBody {
    2. background-color: #9CCC65;
    3. border-radius: 50%;
    4. }

    蛇的身体其实是一个小圆点,直接设置背景颜色和 border-radius 即可

    预定义食物样式

    1. .food {
    2. background: url(../images/food2.png) no-repeat;
    3. background-size: 100% 100%;
    4. }

    食物将背景图片引入,设置背景尺寸即可

    JS逻辑 && 代码

    游戏逻辑

     

     

     蛇逻辑

    食物逻辑 

     


    在开始代码之前,先在全局定义几个变量

    1. var sw = 20, // 一个方块的宽度
    2. sh = 20, // 高度
    3. tr = 30, // 行数
    4. td = 30; // 列数
    5. var snake = null, // 蛇的实例
    6. food = null, // 食物的实例
    7. game = null; // 游戏的实例

    接下来开始着手主要的核心代码

    上面说到 蛇 和 食物 可以看成是小方块,所以我们可以先写小方块的构造函数

    小方块

    1. // 方块构造函数
    2. function Square(x, y, classname) {
    3. this.x = x * sw; // x坐标 * 小方块宽度
    4. this.y = y * sh; // y坐标 * 小方块高度
    5. this.classname = classname; // 赋值classname
    6. this.viewContent = document.createElement('div'); // 方块对应的DOM元素
    7. this.viewContent.className = this.class;
    8. this.parent = document.getElementById('snakeWrap'); // 方块的父级
    9. }

    接下来,我们在Square的原型链上添加两个方法,创建和移除 ;并且给小方块设置样式

    1. Square.prototype.create = function() { // 创建方块dom, 并添加到页面里
    2. this.viewContent.style.position = 'absolute';
    3. this.viewContent.style.width = sw + 'px';
    4. this.viewContent.style.height = sh + 'px';
    5. this.viewContent.style.left = this.x + 'px';
    6. this.viewContent.style.top = this.y + 'px';
    7. this.parent.appendChild(this.viewContent); // 把方块添加到snake里
    8. }
    9. Square.prototype.remove = function() {
    10. this.parent.removeChild(this.viewContent); // 从父级里面清除方块元素
    11. }

    首先,先写出蛇的构造函数

    1. // 蛇
    2. function Snake() {
    3. this.head = null; // 存一下蛇头的信息
    4. this.tail = null; // 蛇尾
    5. this.pos = []; // 存储蛇身上的每一个方块的位置
    6. this.directionNum = { // 存储蛇走的方向,用一个对象来表示
    7. left: {
    8. x: -1,
    9. y: 0,
    10. rotate: 180 // 蛇头旋转角度 蛇头在不同方向中应该进行旋转,要不始终是向右
    11. },
    12. right: {
    13. x: 1,
    14. y: 0,
    15. rotate: 0
    16. },
    17. up: {
    18. x: 0,
    19. y: -1,
    20. rotate: -90
    21. },
    22. down: {
    23. x: 0,
    24. y: 1,
    25. rotate: 90
    26. }
    27. }
    28. }

    其中呢,rotate是为了蛇头转向时候,蛇头的朝向设置的

    接下来是 蛇的 初始换函数 (最开始的两个蛇身 以及 蛇头)

    1. Snake.prototype.init = function() {
    2. // 创建蛇头
    3. var snakeHead = new Square(2, 0, 'snakeHead');
    4. snakeHead.create(); // 在(2,0)处创建蛇头
    5. this.head = snakeHead; // 存储蛇头信息
    6. this.pos.push([2, 0]) // 将蛇头所在的位置,用数组pos存储起来
    7. // 创建蛇身体
    8. var snakeBody1 = new Square(1, 0, 'snakeBody');
    9. snakeBody1.create();
    10. this.pos.push([1, 0]) // 存储蛇身1的位置
    11. var snakeBody2 = new Square(0, 0, 'snakeBody');
    12. snakeBody2.create();
    13. this.tail = snakeBody2; // 存储蛇尾信息
    14. this.pos.push([0, 0]) // 存储蛇身2的位置
    15. // 形成链表关系
    16. snakeHead.last = null;
    17. snakeHead.next = snakeBody1;
    18. snakeBody1.last = snakeHead;
    19. snakeBody1.next = snakeBody2;
    20. snakeBody2.last = snakeBody1;
    21. snakeBody2.next = null;
    22. // 给蛇添加一个属性,用来表示蛇走的方向
    23. this.direction = this.directionNum.right; // 默认往右走
    24. };

    注释写的挺清楚了 ,也就不做过多的解释,创建链表关系是为了更方便我们写蛇移动的函数

    接下来是蛇的下一个点的判断,也就是四块逻辑,撞墙,撞自己,撞食物,未碰撞

    1. // 这个方法用来获取蛇头的下一个位置对应的元素,要根据元素做不同的事情
    2. Snake.prototype.getNextPos = function() {
    3. var nextPos = [ // 蛇头要走的下一个点的坐标
    4. this.head.x / sw + this.direction.x, // 蛇头的x坐标 + 蛇走向的下一个坐标
    5. this.head.y / sh + this.direction.y
    6. ]
    7. // 下个点是自己,代表撞到了自己,游戏结束
    8. var selfCollied = false; //是否撞到了自己
    9. this.pos.forEach(function(value) {
    10. if (value[0] == nextPos[0] && value[1] == nextPos[1]) {
    11. // 如果数组中的两个数据都相等,就说明下一个点在蛇身上里面能找到,代表撞到自己了
    12. selfCollied = true;
    13. }
    14. });
    15. if (selfCollied) {
    16. console.log('撞到自己了!');
    17. this.strategies.die.call(this); // 把this.strategies(属性)转换成this实例对象
    18. return;
    19. }
    20. // 下个点是围墙,游戏结束
    21. if (nextPos[0] < 0 || nextPos[1] < 0 || nextPos[0] > td - 1 || nextPos[1] > tr - 1) {
    22. console.log('撞到墙了!');
    23. this.strategies.die.call(this); // 把this.strategies(属性)转换成this实例对象
    24. return;
    25. }
    26. // 下个点是食物,吃
    27. if (food && food.pos[0] == nextPos[0] && food.pos[1] == nextPos[1]) {
    28. // 如果食物的点和蛇头走的下一个点一致,就说明蛇吃到了食物
    29. console.log('吃到食物了!');
    30. this.strategies.eat.call(this); // 把this.strategies(属性)转换成this实例对象
    31. return;
    32. }
    33. // 下个点什么都不是,继续走
    34. this.strategies.move.call(this);
    35. };

    书写四种逻辑,每种逻辑内有三种处理情况 游戏结束(die) 蛇吃食物(eat) 蛇移动(move)

    接下来就是书写碰撞后三种处理情况的逻辑

    1. // 处理碰撞后要做的事
    2. Snake.prototype.strategies = {
    3. move: function () {
    4. },
    5. die: function() {
    6. },
    7. eat: function() {
    8. }
    9. }

    首先是 move 函数

    蛇移动的思路是如下

    • 蛇朝着对应的方向移动的,首先先在蛇头处创建一个蛇身
    • 再在蛇头下一个移动的坐标上创建蛇头
    • 最后根据是否有吃食物,来进行最后一个蛇身是否消失
    1. move: function(format) { // 该参数用于决定是否删除蛇尾,当传了这个参数后就表示要做的事情是吃
    2. // 创建新身体,在旧蛇头的位置
    3. var newBody = new Square(this.head.x / sw, this.head.y / sh, 'snakeBody');
    4. // 更新链表的关系
    5. newBody.next = this.head.next;
    6. newBody.next.last = newBody;
    7. newBody.last = null;
    8. this.head.remove(); // 把旧蛇头从原来的位置删除
    9. newBody.create(); // 创建一个新蛇头
    10. // 新蛇头要走的下一个点
    11. var newHead = new Square(this.head.x / sw + this.direction.x, this.head.y / sh + this.direction.y, 'snakeHead');
    12. // 更新链表的关系
    13. newHead.next = newBody;
    14. newHead.last = null;
    15. newBody.last = newHead;
    16. newHead.viewContent.style.transform = 'rotate(' + this.direction.rotate + 'deg)';
    17. newHead.create();
    18. // 更新蛇身上每一个方块的坐标
    19. this.pos.splice(0, 0, [this.head.x / sw + this.direction.x, this.head.y / sh + this.direction.y]); // 在索引为0的位置,不需要替换,所以有第二个零,然后插入一个新蛇头的坐标值
    20. this.head = newHead; //还要更新this.head的信息
    21. if (!format) { // 如果format的值为false: 表示需要删除(处理吃之外的操作)
    22. this.tail.remove();
    23. this.tail = this.tail.last;
    24. this.pos.pop(); // 删除数组中的最后一个
    25. }
    26. },

    eat 这块逻辑,首先,吃了食物,蛇身体变长,但其实,蛇的身体是变长可以通过给move函数传递参数true来实现。
    蛇吃完了食物,就要创建新的食物,这个食物不和蛇的身体重叠,这块逻辑,就交给食物对象来处理。

    1. eat: function() {
    2. this.strategies.move.call(this, true);
    3. createFood();
    4. game.score++;
    5. },

    die 这块逻辑,我们用游戏结束来代替,通过game对象的 over 来结束游戏,并打印分数等

    1. die: function() {
    2. game.over();
    3. }

    吃了食物之后,游戏的分数会增加,通过game对象来处理

    到此,蛇的逻辑基本完成了

    这时开始游戏时,就要新建一个蛇,不要忘记新建蛇

    snake = new Snake();

    食物

    1. // 创建食物
    2. function createFood() {
    3. // 食物的随机坐标
    4. var x = null;
    5. var y = null;
    6. var include = true; // 循环跳出的条件,true表示食物的坐标在蛇身上(需要继续循环), false表示食物的坐标不在蛇身上(不循环了)
    7. while (include) {
    8. x = Math.round(Math.random() * (td - 1));
    9. y = Math.round(Math.random() * (tr - 1));
    10. snake.pos.forEach(function(value) {
    11. if (x != value[0] && y != value[1]) { // 条件成立时说明现在这个随机出来的食物坐标不在蛇身上
    12. include = false;
    13. }
    14. });
    15. }
    16. // 生成食物
    17. food = new Square(x, y, 'food');
    18. food.pos = [x, y]; // 存储食物的坐标,用于跟蛇头下一个走的点作对比
    19. var foodDom = document.querySelector('.food');
    20. if (foodDom) {
    21. foodDom.style.left = x * sw + 'px';
    22. foodDom.style.top = y * sh + 'px';
    23. } else {
    24. food.create();
    25. }
    26. }

    game 对象

    首先,是Game 的构造函数

    1. // 创建游戏逻辑
    2. function Game() {
    3. this.timer = null;
    4. this.score = 0;
    5. }

    然后是game 的初始化函数

    1. Game.prototype.init = function() {
    2. snake.init();
    3. createFood();
    4. document.onkeydown = function(ev) {
    5. // 用户按下左键, 蛇不能是正在往右走的
    6. if (ev.which == 37 && snake.direction != snake.directionNum.right) { // 左键
    7. snake.direction = snake.directionNum.left;
    8. } else if (ev.which == 38 && snake.direction != snake.directionNum.down) { // 上键
    9. snake.direction = snake.directionNum.up;
    10. } else if (ev.which == 39 && snake.direction != snake.directionNum.left) { // 右键
    11. snake.direction = snake.directionNum.right;
    12. } else if (ev.which == 40 && snake.direction != snake.directionNum.up) { // 下键
    13. snake.direction = snake.directionNum.down;
    14. }
    15. }
    16. this.start();
    17. }

    开始游戏

    1. Game.prototype.start = function() {
    2. // 开始游戏
    3. this.timer = setInterval(function() {
    4. snake.getNextPos();
    5. }, 200);
    6. }

    暂停游戏

    1. Game.prototype.pause = function() {
    2. clearInterval(this.timer);
    3. }

    游戏结束

    1. Game.prototype.over = function() {
    2. clearInterval(this.timer);
    3. alert('你的得分为:' + this.score);
    4. // 游戏回到最初始的状态 蛇和食物都清空
    5. var snakeWrap = document.getElementById('snakeWrap');
    6. snakeWrap.innerHTML = '';
    7. snake = new Snake();
    8. game = new Game();
    9. // 开始按钮显示出来
    10. var startBtnWrap = document.querySelector('.startBtn');
    11. startBtnWrap.style.display = 'block';
    12. }

    game逻辑内的 两个按钮 (开始游戏,暂停游戏,继续游戏)

    1. // 开启游戏
    2. game = new Game();
    3. var startBtn = document.querySelector('.startBtn button');
    4. startBtn.onclick = function() {
    5. startBtn.parentNode.style.display = 'none';
    6. game.init();
    7. }
    8. // 暂停游戏
    9. var snakeWrap = document.getElementById('snakeWrap');
    10. var pauseBtn = document.querySelector('.pauseBtn button');
    11. snakeWrap.onclick = function() {
    12. game.pause();
    13. pauseBtn.parentNode.style.display = 'block';
    14. }
    15. pauseBtn.onclick = function() {
    16. game.start();
    17. pauseBtn.parentNode.style.display = 'none';
    18. }

  • 相关阅读:
    爬虫中Selenium和DrissionPage的区别
    Android 启动流程及 init 进程解析
    Android Studio导入,删除第三方库
    从CNN到Transformer:基于PyTorch的遥感影像、无人机影像的地物分类、目标检测、语义分割和点云分类
    一篇案例读懂国央企如何实现数字化管控
    手动证书管理与自动证书管理
    华为云云耀云服务器L实例评测|拉取创建canal镜像配置相关参数 & 搭建canal连接MySQL数据库 & spring项目应用canal初步
    C站专家圈分享-低代码构建WebAPI的原理与体验
    SQL-Labs靶场“32-33”关通关教程
    Google Earth Engine——使用geetool批量下载单景影像以Landsat 8 反演后的NDSI结果
  • 原文地址:https://blog.csdn.net/DIUDIUjiang/article/details/126140064