贪吃蛇JS实现(超详细,万字解析版)

Posted 步踟蹰于码海

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了贪吃蛇JS实现(超详细,万字解析版)相关的知识,希望对你有一定的参考价值。

前言

小伙伴们好呀,本人这次呕心沥血为大家带来一个做了绝对收获满满的项目。
分析到位,也是第一次写这么长的文章,十分期望大家的反馈✨✨

本文是对B站尚硅谷TypeScript贪吃蛇项目的完整解析。
原视频对TypeScript讲的很好,很建议有时间去观看。对于时间不充裕的小伙伴
此文的目的就是让大家以少的时间得到大的收获。
项目环境搭建是一个难点,小伙伴们看不明白可以视频研究,保证视频中会提到相关知识点。因为这对于代码的编写过程起到较大的帮助,但是不会影响到最终代码的产出。(意思就是看不懂直接拿我整合好的代码往上扔就行)。
可以直接评论区留言,或者私信我直接发你完整项目方便对照研究。

1.准备阶段:

编写程序用的是VSCode。
需要大致了解基本的html,css,javascript相关知识。
对Typescript,node.js有初步理解。
掌握预处理器less
打包工具webpack整理代码
该项目不会对过旧浏览器进行兼容,尽量使用chrome浏览器验证效果

2.项目分析:

总共有三个类对象:蛇,食物,场景。并设置控制模块
要准备4个js子模块,最后在设置一个index模块做整合

Sound.js是我设置的BGM,大家也可以自己设置一个作为点缀

1.场景模块需求

样式和结构:
1.具有长和宽的容器,容器内分成蛇移动的场景记分牌上下两个板块
2.蛇移动的场景设置边界线
3.记分牌记录蛇吃到食物的分数以及等级(等级越高蛇速度越快)
逻辑:
1.蛇吃到食物分数涨一分,每涨一定分数等级提高一级
2.等级设有上限
3.等级和蛇移动速度有关

2.食物模块需求

样式和结构:处于蛇移动的场景中
逻辑:
1.开局自动生成在场景中任意位置
2.蛇吃掉以后消失并刷新在新的位置

3.蛇模块需求

样式和结构:
1.蛇整体在移动的场景中
2.蛇分成蛇头和蛇身体两个部分
逻辑:
1.开局只有蛇头,蛇身体每吃一个食物涨一节
2.蛇头碰到自己身体或者场景模块中的边界线游戏结束
3.蛇持续前进只能改变方向,不能停下。

4.控制模块需求

逻辑:
1.按任意键开始游戏
2.只能通过四个方向键改变蛇前进的方向
3.能检测蛇死亡,蛇是否吃到食物
4.控制分数和等级是否相应增长,食物是否刷新等逻辑

以上是大致要实现的需求,作者这里给出一些优化方向,各位可自行实现:
1.蛇,食物,场景,页面自身的美化
2.加入暂停功能,
3.加入BGM,音效等
4.场景复杂度提升:如随机生成地刺,动态调整场景大小等
5.按空格建加速功能
6.加入自动重开功能
7.随着分数的提高出现鼓励文字,死亡出现游戏结束文字样式
8.改成吃豆人

3.项目搭建

1.TS转JS的配置文件

 //拥有了该文件,一个tsc指令即可直接编译所有ts文件,该文件就是ts的配置文件
    //extends定义继承哪个
    //路径:**表示任意目录*表示任意文件

  //include(指定哪些ts文件需要被编译)
  //路径:**表示任意目录,*表示任意文件
    "include":[
        "./'你要被编译的ts的文件根目录路径'/**/*"
    ],
    //编译器的选项
    "compilerOptions":
        //指定要使用的模块化的规范
        "module":"ES2015",
        //target:用来被编译的ES的版本
        "target":"ES2015",
        //严格模式开启
        "strict":true,
        //错误代码不进行编译
        "noEmitOnError":false
    

要点:
1.我们要在项目中创建一个名称为tsconfig.json的文件作为配置文件
2.其作用是当我们以集成终端形式打开项目并输入tsc指令时,将项目下我们所有的ts文件转换成js文件。
3.该文件主要是指定一些ts转译js的规范
4.如果不写任何内容,光创建这个文件也可以,因为这个文件不创建就无法使用tsc命令。
5.tsc命令只能编译一次, tsc -w表示持续监视,后续会自动编译
6.使用webpack不必用tsc命令进行编译。下面会讲到

2.准备Node.js

在node.js官网上下载Node.js放入和项目相同的硬盘中,
效果如下:

并建议node_modules文件夹复制一份放入项目中
要点:
1.我们需要通过node.js下载webpack以及其它模块
2.国内下载可能网速缓慢,可以尝试使用一些镜像服务器下载。

3.package.json包配置文件

使用webpack打包工具同样需要名为package.json的文件,进行打包的配置。
如告诉该项目需要哪些依赖。
首先


    "name": "snake",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": 
        "test": "echo \\"Error: no test specified\\" && exit 1",
        "build": "webpack",
        "start": "node E:/VScode/贪吃蛇项目/list/bundle.js"
    ,
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": 
        "express": "^4.17.1"
    ,
    # 下列显示的是开发依赖
    "devDependencies": 
    	# css-loader用于整合css和webpack
        "css-loader": "^6.5.1",
        "html-webpack-plugin": "^4.5.0",
        # style-loader用于整合css和webpack
        "style-loader": "^3.3.1",
        # ts-loader用于整合typescript和webpack
        "ts-loader": "^9.2.6",
        # ts核心包
        "typescript": "^4.4.4",
        # 下载完后可以在集成终端中使用webpack的命令
        "webpack": "^5.64.0",
        "webpack-cli": "^4.9.0"
    


要点:
1.确保你的Node.js安装好了
2.在Vscode中右键本项目选择在集成终端中打开并输入代码npm init -y 进行项目初始化,一般这个时候就会在你的项目中生成一个初步的package.json文件,然后我们进一步完善
3.该json文件是不能写注释的,粘贴代码时,请删去我的注释
4.在集成终端中输入指令npm i -D webpack webpack-cli typescript ts-loader用来下载相关依赖(如果可以看见package.json的depDependencies中更新了你下载的依赖表示下载成功)。i表示install下载的意思,-D意思是下载的作为依赖使用
5.继续输入指令npm i -D css-loader 等依赖,这些后面都有用
6.请注意上述代码中scripts中的"build": "webpack"键值对,这个设置说明我们可以用npm run build的代码来启用webpack打包工具(详情见视频09的15分钟到20分钟)

4.webpack.config.js打包工具配置

// 引入一个包
const path = require('path')
//引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin')
//webpack中所有的配置信息都要写在module.exports中
module.exports = 
    //指定入口文件
    entry:"./src/index.ts",
    
    
    
    //指定打包文件所在的目录
    output:
        //指定打包文件的目录
        path:path.resolve("E:/VScode/贪吃蛇项目/list"),
        //filename打包后文件的名字一般叫bundle.js
        filename:"bundle.js",

    ,
    //指定webpack打包时要使用模块
    module:
        //指定要加载的数据
        rules:[
            
                //test指定规则生效的文件 即所有以.ts结尾的文件
                test:/\\.ts$/,
                use:'ts-loader',
                //指定要排除的文件
                exclude:/node-modules/
            ,
            
                //匹配哪些文件
                test: /\\.css$/,
                //使用哪些loader进行处理
                use:[
     //use数组中的执行顺序是从后往前依次执行
     //创建一个style标签将js中的样式资源插入进行,添加到head中生效
                    'style-loader',
      //将css文件变成commonjs模块加载js中,里面内容是样式字符串
                    'css-loader'
                ]
            ,
            
        ]
        
    ,
    plugins:[
        new HTMLWebpackPlugin(
                template:'./src/index.html'
            ) ,
          ],
    //告诉webpack哪些文件可以作为模块被引入
    resolve:
        extensions:['.ts','.js']
    ,
    mode:'development'

要点:
1.最后是关于webpack打包工具的相关配置,同样命名一个webpack.config.js
的文件并粘贴上述代码
2.由于webpack相关使用不是本文重点,不过我在几个重要的配置上做了注释,感兴趣的小伙伴可以参考视频09-11
3.output中放入的是打包输出后的文件

至此,TypeScript编译配置,Node.js配置和webpack配置完毕,可以正式撸代码啦!O(∩_∩)O

4.CSS,HTML搭建

1.HTML

<!DOCTYPE html>
<html lang="zh">
<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">
     //这里引入下面的css
    <link rel="stylesheet" href="./style/index.css">
    <title>贪吃蛇</title>
</head>
<body>
    <!--游戏主容器-->
    <div id="main">
        <p>按下任意方向键开始游戏</p>
        <!--设置游戏的舞台-->
        <div id="stage">
            <!--设置蛇-->
            <div id="snake">
                <!--snake 的身体各个部分-->
                <div id="head"></div>
            </div>
        <!--设置食物-->
        <div id="food">
            <!--添加四个div设置食物样式-->
            <div></div>
            <div></div>
            <div></div>
            <div></div>
        </div>
        </div>
        <!--设置游戏的积分盘-->
        <div id="score-panel">
            <div>
                SCORE: <span id="score">0</span>
            </div>
            <div>
                LEVEL: <span id="level">1</span>
            </div>
        </div>
    </div>
    // 以下为作者添加的BGM代码
    <audio autoplay="autopaly" loop="loop" id="audios">
        <source src="./bgm.mp3" type="audio/mp3" />
    </audio>
</body>
</html>

层级顺序:
主容器 => 蛇 =>蛇头,蛇身
      =>食物
       =>积分盘=>积分,等级

2.CSS

1.首先要记得清除浏览器默认样式,这个网上一抓一大把。
2.本来是用less预处理器编写的然后转译成CSS,这里就直接给转译好的CSS
3.样式方面只要注意长宽是10px的倍数,因为后面蛇一次的位移格数默认是10px。
4.此CSS样式中默认开局蛇头居中,食物位置固定

* 
  margin: 0;
  padding: 0;
  box-sizing: border-box;

body 
  font: bold 20px "Comic Sans MS";
  position: absolute;
  display: flex;
  flex-direction: column;
  background-color: red;
  flex: 1;
  width: 100%;
  height: 100%;
  # 背景图片任意选择图源
  background: url('bg.jpg') no-repeat;
  background-size: 100% 100%;
  background-attachment: fixed;

#main 
  width: 360px;
  height: 420px;
  background-color: #b7d4a8;
  margin: 100px auto;
  border: 10px solid black;
  border-radius: 40px;
  display: flex;
  flex-flow: column;
  align-items: center;
  justify-content: space-around;

#main #stage 
  width: 304px;
  height: 304px;
  border: 2px solid black;
  position: relative;

#main #stage #food 
  width: 10px;
  height: 10px;
  background-color: black;
  left: 100px;
  top: 100px;
  position: absolute;
  display: flex;
  flex-flow: row wrap;
  justify-content: space-between;
  align-content: space-between;

#main #stage #food > div 
  width: 4px;
  height: 4px;
  border: 1px solid #b7d4a8;

#main #stage #snake > div 
  width: 10px;
  height: 10px;
  background-color: #fff;
  border: 1px solid #b7d4a8;
  position: absolute;
  left: 140px;
  top: 140px;

#main #stage #snake #head 
  background-color: black;

#main #score-panel 
  width: 250px;
  display: flex;
  justify-content: space-between;


此时,页面基本样式已经搭建完毕如下图:

5.JavaScript注入灵魂

现在只要我们照着项目分析中的需求分别对几个模块的逻辑编写代码即可。
不过首先我们要用ts语言更加严谨的编写。
首先我们还要理解class 类名 是构建一个对象模板,该模板中我们能够编写一些属性和方法,这样别的js文件调用该模板对象时就能创建一个符合模板规范的对象。可以类比构造函数和创建实例对象

1.食物模块

//定义食物类Food作为模板对象
class Food
//定义元素类型为HTMLElement,这样就具备了一些特性
    element: HTMLElement;
    
    constructor()
//我们根据ID属性值去网页中拿div
//这里加!是因为我们百分之百能够在html中获取到food
        this.element = document.getElementById('food')!;
        this.element.style.left = Math.round(Math.random()*29) * 10 + 'px';
        this.element.style.top = Math.round(Math.random()*29) * 10 + 'px'
    
    //定义一个获取食物X轴坐标的方法
    get X()
        return this.element.offsetLeft
    
    //定义一个获取食物Y轴坐标的方法
    get Y()
        return this.element.offsetTop
    
    //定义一个方法能够修改食物的位置
    change()
 //生成一个随机的位置
 //Math.random()会生成一个0-1的随机数,但是不包含0和1 
 //Math.round()方法表示四舍五入
        let left = Math.round(Math.random()*29) * 10;
        let top = Math.round(Math.random()*29) * 10;
        this.element.style.left = left + 'px'
        this.element.style.top = top + 'px'
    

export default Food;//暴露Food这个类

要点:
1.由于在配置typescript时我们设置了strict模式,因此
this.element = document.getElementById('food')!中如果我们不加!会让程序不确定我们是否一定会获取到food而报错
2.这个之后不会再提,constructor方法中的所有代码都会在该模板对象被实例化的时候执行一次,也就是说,我们运用了随机数random在页面刚刚刷新的时候的食物不会像css中定义的那样处在固定位置,而是一开始就调用了这里的代码进行了一次随机刷新。让游戏更真一点。
3.准备了get()方法可以在控制模块中随时获取food的具体定位
4.change()方法为随机刷新一次food
5.这个之后不会再提,export default Food代码绝对要加在最后。为的是把food这个模板对象暴露给全局,否则别的模块引用都引用不了

2.计分板模块

//定义表示记分牌的类
class ScorePanel
    score = 0;
    level = 1;
    scoreEle:HTMLElement;
    levelEle:HTMLElement;
    //设置变量限制等级
    maxLevel :number;
    //设置一个变量表示多少分时升级
    upScore:number;
    constructor(maxLevel:number = 10,upScore:number = 10)
        this.scoreEle = document.getElementById('score')!;
        this.levelEle = document.getElementById('level')!;
        this.maxLevel = maxLevel;
        this.upScore = upScore;
    
    //设置一个加分的方法
    addScore()
        //使分数自增
        this.scoreEle.innerHTML = ++this.score + ''//需要字符串形式
        //判断分数是多少
        if(this.score % 3 == 0)
            this.levelUp()
        
    
    levelUp()
        //还要设计一个等级上限
        if(this.level < this.maxLevel)
            this.levelEle.innerHTML = ++this.level + ''
        
    

export default ScorePanel;//暴露ScorePanel这个类

要点:
1.需要两个方法,调用就能提升分数以及等级
2.我们设定了等级的上限不能超过十级,大家可自凭喜好修改
3.如果能大致明白food模块,计分板模块不难明白

3.蛇模块

这是逻辑最复杂的模块,但是大部分都打上了注释,搞定它,就离成功不远啦!

class Snake
    head:HTMLElement;
//蛇的身体,包括蛇头,HTMLCollection的特性是会实施刷新,
//如果添加新元素,它会自动获取新元素
    bodies:HTMLCollection;
//获取蛇的容器
    element:HTMLElement
//获取p
    p:HTMLElement
    constructor()
 //querySelctor只会取一个,所以把第一个div当蛇头
 //as HEMLElement类型断言因为querySelector获取到的是元素
 //要断言HTMLElement
        this.head = document.querySelector('#snake > div') as HTMLElement;
        this.bodies = document.getElementById('snake')!.getElementsByTagName('div');
        this.element = document.getElementById('snake')!
        this.p = document.querySelector('#main > p')!
    ;
     //获取蛇头的坐标
    get X()
        return this.head.offsetLeft;
    
    get Y()
        return this.head.offsetTop;
    
    set X(value:number)
 //优化:因为本游戏蛇的移动一次只会改变x,y中的一个值
 //所以可以做一个判断机制,如果值和之前一样就不重新改变CSS样式了
        if(this.X === value)
            return
        
       
// X的合法值范围在0-290px之间(蛇自身占据stage的10px)
        if(value < 0 以上是关于贪吃蛇JS实现(超详细,万字解析版)的主要内容,如果未能解决你的问题,请参考以下文章

100行Python代码实现贪吃蛇小游戏(超详细)

10分钟教你用python打造贪吃蛇超详细教程

超详细C语言带你吃透贪吃蛇游戏之精髓

C语言项目实战:《自动版贪吃蛇》零基础项目

代码解析双向链表实现贪吃蛇游戏!简单易学,开发自己第一个游戏!

代码解析双向链表实现贪吃蛇游戏!简单易学,开发自己第一个游戏!