预览:

第一章:项目搭建
1、创建一个名为 practice 文件夹
2、配置文件
2.1 package.json
javascript
{
"name": "practice",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve --open chrome.exe"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.27.1",
"@babel/preset-env": "^7.27.1",
"babel-loader": "^8.4.1",
"clean-webpack-plugin": "^3.0.0",
"core-js": "^3.42.0",
"html-webpack-plugin": "^4.5.0",
"ts-loader": "^8.4.0",
"typescript": "^4.9.5",
"webpack": "^5.99.7",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^3.11.3"
}
}
2.2 tsconfig.json
javascript
{
"compilerOptions": {
"module": "ES2015",
"target": "ES2015",
"strict": true,
"noEmitOnError": false
}
}
2.3 webpack.config.js
javascript
// 引入一个包
const path = require('path');
// 引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
// 引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// webpack中的所有的配置信息都应该写在module.exports中
module.exports = {
// 指定入口文件
entry: "./src/index.ts",
// 指定打包文件所在目录
output: {
// 指定打包文件的目录
path: path.resolve(__dirname, 'dist'),
// 打包后文件的文件
filename: "bundle.js",
// 告诉webpack不使用箭头
environment:{
arrowFunction: false
}
},
// 指定webpack打包时要使用模块
module: {
// 指定要加载的规则
rules: [
{
// test指定的是规则生效的文件
test: /\.ts$/,
// 要使用的loader
use: [
// 配置babel
{
// 指定加载器
loader:"babel-loader",
// 设置babel
options: {
// 设置预定义的环境
presets:[
[
// 指定环境的插件
"@babel/preset-env",
// 配置信息
{
// 要兼容的目标浏览器
targets:{
"chrome":"58",
"ie":"11"
},
// 指定corejs的版本
"corejs":"3",
// 使用corejs的方式 "usage" 表示按需加载
"useBuiltIns":"usage"
}
]
]
}
},
'ts-loader'
],
// 要排除的文件
exclude: /node-modules/
}
]
},
// 配置Webpack插件
plugins: [
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
// title: "这是一个自定义的title"
template: "./src/index.html"
}),
],
// 用来设置引用模块
resolve: {
extensions: ['.ts', '.js']
}
};
3、下载依赖:在终端输入 npm i
4、配置less依赖
- 在终端输入 npm i -D less less-loader css-loader style-loader
- 更改 webpack.config.js 配置文件的 module 下的 rules 后增加以下内容
javascript
//设置less文件处理
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
"less-loader"
]
}
5、配置 postcss 依赖
- npm i -D postcss postcss-loader postcss-preset-env
- 更改 webpack.config.js 配置文件,在设置 less 文件处理中的 "css-loader", 后加入
javascript
//引入postcss
{
loader:"postcss-loader",
options:{
postcssOptions:{
plugins:[
["postcss-preset-env", {
browsers: "last 2 versions"
}]
]
}
}
},
6、打包: npm run build
第二章:项目界面
1、 index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇</title>
</head>
<body>
<!-- 创建游戏主容器 -->
<div id="main">
<!-- 设置游戏的舞台 -->
<div id="stage">
<!-- 设置蛇 -->
<div id="snake">
<!-- 设置蛇的身体 -->
<div></div>
</div>
<!-- 设置食物 -->
<div id="food">
<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>
</body>
</html>
2、 index.less(记得在index.ts内引入样式)
css
//设置变量
@bg-color: #b7d4a8;
// 清除默认样式
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
font: bold 20px "Courier New";
}
// 设置主窗口样式
#main {
width: 360px;
height: 420px;
background-color: @bg-color;
margin: 100px auto;
border: 10px solid black;
border-radius: 20px;
// 设置主窗口的子元素为弹性布局
display: flex;
// 设置主轴的方向
flex-flow: column;
// 设置侧轴的对齐方式
align-items: center;
// 设置主轴的对齐方式
justify-content: space-around;
}
// 设置游戏舞台样式
#stage {
width: 304px;
height: 304px;
border: 2px solid black;
position: relative;
// 设置蛇样式
#snake {
&>div {
width: 10px;
height: 10px;
background-color: #000;
border: 1px solid @bg-color;
// 开启绝对定位
position: absolute;
}
}
// 设置食物样式
#food {
width: 10px;
height: 10px;
// 开启绝对定位
position: absolute;
left: 40px;
top: 100px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
&>div {
width: 4px;
height: 4px;
background-color: #555;
}
}
}
// 设置游戏得分样式
#score-panel {
width: 304px;
display: flex;
justify-content: space-between;
}
3、效果

第三章:完成类的定义
1、创建 moduls 文件夹,用于装不同的类
2、完成 Food 类
typescript
//定义食物链Food
class Food {
// 定义一个属性表示食物所对应的食物
element : HTMLElement
constructor() {
this.element = document.getElementById("food")!
}
// 定义一个方法,获取食物的坐标
get X() {
return this.element.offsetLeft
}
get Y() {
return this.element.offsetTop
}
// 修改食物的位置
change() {
// 生成一个随机位置
// 食物的位置最小是0,最大是290
// 蛇移动一次就是一格,一格的大小是10,所以食物的坐标必须是10的倍数
let top = Math.round(Math.random() * 29) * 10
let left = Math.round(Math.random() * 29) * 10
this.element.style.left = left + "px"
this.element.style.top = top + "px"
}
}
//测试代码
// const food = new Food()
// console.log(food.X, food.Y)
// food.change()
// console.log(food.X, food.Y)
export default Food
3、完成 ScorePanel 类
typescript
// 表示记分牌的类
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 % this.upScore === 0) {
this.levelUp()
}
}
// 定义一个方法,显示等级
levelUp() {
if (this.level < this.maxLevel) {
this.levelEle.innerHTML = ++this.level + ""
}
}
}
//测试代码
// const scorePanel = new ScorePanel()
// scorePanel.addScore()
// scorePanel.addScore()
// scorePanel.addScore()
// scorePanel.addScore()
// console.log(scorePanel.score)
export default ScorePanel
4、初步完成 Snake 类
typescript
class Snake {
// 定义一个属性表示蛇的头部
head : HTMLElement
// 定义一个属性表示蛇的身体(包括蛇头)
bodies : HTMLCollection
// 获取蛇的容器
element : HTMLElement
constructor() {
// querySelector 只会取一个 就取的是第一个
this.head = document.querySelector("#snake > div") as HTMLElement;
this.bodies = document.getElementById("snake")!.getElementsByTagName("div")
this.element = document.getElementById("snake")!
}
// 获取蛇的坐标
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
//设置蛇的坐标
set X(value : number) {
// 如果蛇的坐标和value相等,则不进行设置
if(this.X === value) {
return
}
this.head.style.left = value + "px"
}
set Y(value : number) {
// 如果蛇的坐标和value相等,则不进行设置
if(this.Y === value) {
return
}
this.head.style.top = value + "px"
}
// 添加蛇的身体
addBody() {
this.element.insertAdjacentHTML("beforeend", "<div></div>")
}
}
export default Snake
第四章:游戏控制器
1、创建 GameControl.ts
2、GameControl 键盘控制
typescript
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
//游戏控制器,控制其他所有类
class GameControl {
// 定义三个属性
snake : Snake
food : Food
scorePanel : ScorePanel
// 创建一个属性来存储蛇的移动方向(默认是向右移动)
direction : string = "Right"
constructor() {
this.snake = new Snake()
this.food = new Food()
this.scorePanel = new ScorePanel()
this.init()
}
// 创建一个方法来初始化游戏
init() {
// 绑定键盘按下的事件
document.addEventListener("keydown", this.keydownHandler.bind(this))
}
/*
ArrowUp Up w
ArrowDown Down s
ArrowLeft Left a
ArrowRight Right d
*/
// 创建一个键盘按下的响应函数
keydownHandler(event : KeyboardEvent) {
this.direction = event.key
}
}
export default GameControl
3、GameControl 使蛇移动
typescript
// 创建一个属性用来记录游戏是否结束
isLive : boolean = true
// 创建一个控制蛇移动的方法
run() {
// 获取蛇现在的坐标
let X = this.snake.X
let Y = this.snake.Y
// 根据方向来移动
switch (this.direction) {
case "ArrowUp":
case "Up":
case "w":
Y -= 10;
break;
case "ArrowDown":
case "Down":
case "s":
Y += 10;
break;
case "ArrowLeft":
case "Left":
case "a":
X -= 10;
break;
case "ArrowRight":
case "Right":
case "d":
X += 10;
break;
}
this.snake.X = X
this.snake.Y = Y
// 设置定时调用
this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30)
}
4、蛇撞墙和吃食检测
4.1 蛇撞墙
4.1.1 在 Snack 类的设置蛇的坐标内判断并的抛出异常
typescript
//设置蛇的坐标
set X(value : number) {
// 如果蛇的坐标和value相等,则不进行设置
if(this.X === value) {
return
}
// x 的值的合法范围是 0-290
if(value < 0 || value > 290) {
throw new Error("撞墙了")
}
this.head.style.left = value + "px"
}
set Y(value : number) {
// 如果蛇的坐标和value相等,则不进行设置
if(this.Y === value) {
return
}
// y 的值的合法范围是 0-290
if(value < 0 || value > 290) {
throw new Error("撞墙了")
}
this.head.style.top = value + "px"
}
4.1.2 在 GameControl 类内捕获异常
typescript
this.snake.X = X
this.snake.Y = Y
修改为
typescript
// 检查蛇是否撞墙
try {
this.snake.X = X
this.snake.Y = Y
} catch (e) {
// 如果蛇撞墙了,则游戏结束
alert(e.message)
this.isLive = false
}
4.2 蛇吃食
4.2.1 在检查蛇是否撞墙之前检查蛇是否吃到食物
typescript
// 检查蛇是否吃到食物
this.checkEat(X, Y)
4.2.2 在 GameControl 内定义方法
typescript
// 定义一个方法,用来检查蛇是否撞墙
checkEat(X : number, Y : number) {
if(X === this.food.X && Y === this.food.Y) {
this.snake.addBody()
this.food.change()
this.scorePanel.addScore()
}
}
5、蛇的身体移动
5.1 在 Snack 内添加一个方法
typescript
// 添加一个蛇身体移动的方法
moveBody() {
for(let i = this.bodies.length-1;i>0;i--) {
let X = (this.bodies[i-1] as HTMLElement).offsetLeft;
let Y = (this.bodies[i-1] as HTMLElement).offsetTop;
(this.bodies[i] as HTMLElement).style.left = X + 'px';
(this.bodies[i] as HTMLElement).style.top = Y + 'px';
}
}
5.2 在 set X()和 set Y()内引用 moveBody 方法并处理掉头
typescript
//设置蛇的坐标
set X(value : number) {
// 如果蛇的坐标和value相等,则不进行设置
if(this.X === value) {
return
}
// x 的值的合法范围是 0-290
if(value < 0 || value > 290) {
throw new Error("撞墙了")
}
// 判断蛇是否掉头
if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
// 如果发生掉头,让蛇反方向继续移动
if(value > this.X) {
//如果新值value大于旧值,则说明蛇在向右走,此时应该发生掉头,应该使蛇向左走
value = this.X - 10
} else {
value = this.X + 10
}
}
this.moveBody()
this.head.style.left = value + "px"
}
set Y(value : number) {
// 如果蛇的坐标和value相等,则不进行设置
if(this.Y === value) {
return
}
// y 的值的合法范围是 0-290
if(value < 0 || value > 290) {
throw new Error("撞墙了")
}
// 判断蛇是否掉头
if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
// 如果发生掉头,让蛇反方向继续移动
if(value > this.Y) {
//如果新值value大于旧值,则说明蛇在向下走,此时应该发生掉头,应该使蛇向左走
value = this.Y - 10
} else {
value = this.Y + 10
}
}
this.moveBody()
this.head.style.top = value + "px"
}
6、 检查碰撞自己
6.1 定义一个 checkBody 方法
typescript
// 检查头和身体相撞
checkBody() {
for(let i = 1;i<this.bodies.length;i++) {
let bd = this.bodies[i] as HTMLElement;
if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
throw new Error("撞到自己了")
}
}
}