学完TS基础,用一个贪吃蛇的小案例来巩固一下自己学习的知识点。 coding,可以看一哈视频: www.bilibili.com/video/BV1Xy…
1. 项目搭建
1.1 项目配置信息
- 使用webpack打包工具管理项目
- 创建新项目安装项目所需的依赖
npm install --save-dev webpack@5.6.0
npm install --save-dev webpack-cli@4.10.0
npm install --save-dev typescript@4.1.2
npm install --save-dev ts-loader@8.0.1
npm install --ave-dev html-webpack-plugin@4.5.0 // 自定编译html的插件
npm install --save-dev webpack-dev-server@3.11.0 // 用于配置自动启动浏览器打包
npm install --save-dev clean-webpack-plugin@4.0.0 // 每次编译前将dist文件夹清空
// 用于支持Babel的插件,做浏览器兼容处理
npm install --save-dev @babel/core@7.12.9
npm install --save-dev @babel/preset-env@7.12.7
npm install babel-loader@8.2.2 --save-dev
npm install core-js@3.8.0 --save-dev
// 安装预处理css的sass-loader
npm install --save-dev @node-sass@7.0.1
npm install --save-dev @sass-loader@13.0.2
npm install --save-dev @style-loader@3.3.1
npm install --save-dev @css-loader@6.7.1
// 对css属性兼容性的loader
npm install --save-dev postcss@8.4.14
npm install --save-dev @postcss-loader@7.0.1
npm install --save-dev @postcss-preset-env@7.7.2
- webpack.config.js配置文件
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: "production",
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [
{
test: /\.ts$/,
use: [
// 配置Babel
{
// 指定加载器
loader: "babel-loader",
// 设置babel
options: {
// 设置预定于的环境
presets: [
[
// 指定环境插件
"@babel/preset-env",
// 配置信息
{
targets: {
edge: "12",
firefox: "60",
chrome: "60",
safari: "11.1",
ie: "11",
},
// 使用corejs的方式, usage表示按需加载
useBuiltIns: "usage",
// 指定corejs的版本
corejs: "3",
},
],
],
},
},
"ts-loader",
],
exclude: /node_modules/,
},
// 配置sass
{
test: /\.scss$/,
use: [
"style-loader",
"css-loader",
// 配置 postcss 处理css兼容性问题
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
browsers: "last 2 versions",
},
],
],
},
},
},
"sass-loader",
],
},
],
},
plugins: [
new htmlWebpackPlugin({
title: "my app",
filename: "index.html",
template: "./src/index.html",
}),
new CleanWebpackPlugin(),
],
// 用来设置引用模块
resolve: {
extensions: [".js", ".ts"],
},
};
- 项目tsconfig.js文件的配置
{
"compilerOptions": {
"target": "ES2015",
"module": "ES2015",
"strict": true
},
"include": [
"src/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
2.项目界面
- 搭建项目静态的html文件
<!--路径: ./src/index.html -->
<div class="main">
<!-- 设置游戏的区域 -->
<div class="game_area">
<!-- 设置蛇 -->
<div class="snake">
<div class="snake_item"></div>
</div>
<!-- 设置食物 -->
<div class="foods" id="foods">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<!-- 设置游戏的提示区域 -->
<div class="game_prompt">
<div class="prompt_left">
<span>SCORE:</span>
<span class="score" id="score">0</span>
</div>
<div class="prompt_right">
<span>LEVEL:</span>
<span class="level" id="level">1</span>
</div>
</div>
</div>
- 项目scss样式文件
// 设置变量
$bg-color: #b7d4a8;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.main {
width: 360px;
height: 420px;
background-color: $bg-color;
margin: 100px auto;
border: 10px solid black;
border-radius: 40px;
.game_area {
width: 304px;
height: 304px;
border: 2px solid black;
margin: 10px auto;
position: relative;
.snake {
.snake_item {
width: 10px;
height: 10px;
background-color: #000;
border: 1px solid $bg-color;
position: absolute;
}
}
#foods {
width: 10px;
height: 10px;
position: absolute;
left: 40px;
top: 100px;
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
&>div {
width: 4px;
height: 4px;
background-color: #000;
transform: rotate(45deg);
}
}
}
.game_prompt {
display: flex;
justify-content: space-between;
padding: 30px;
span {
font-size: 18px;
font-weight: 600;
}
}
}
- 页面效果图
3. 项目中所需类的定义
3.1 定义页面中小方块食物类foods
// 页面路径 ./src/module/foods.ts
// 添加一个食物类
class Food {
// 定义一个属性表示食物所对应的元素
element: HTMLElement;
constructor() {
// 将页面的foods元素给类的属性
this.element = document.getElementById("foods")!;
}
// 定义食物元素 在页面的X Y 坐标
get X() {
return this.element.offsetLeft;
}
get Y() {
return this.element.offsetTop;
}
// 食物的位置是需要改变的,定义修改食物位置的方法
/**
* 生成随机的数字需要考虑的问题:
* 1. 生成的随机数应该是10的倍数
* 2. 蛇身体每次移动应该是以10px的距离移动
*/
change() {
const stepX = Math.round(Math.random() * 29) * 10;
const stepY = Math.round(Math.random() * 29) * 10;
this.element.style.left = stepX + "px";
this.element.style.top = stepY + "px";
}
}
export { Food };
3.2 定义页面中积分,等级类scorePanel
// 路径: ./src/module/scorePanel.ts
// 定义一个类,用于表示分数,等级
class Panel {
score = 0;
level = 1;
scoreElement: HTMLElement;
levelElement: HTMLElement;
// 设置一个限制等级的变量
maxLevel: number;
// 设置一个到达分数升级的变量
upLevel: number;
constructor(maxLevel: number = 10, upLevel: number = 10) {
this.scoreElement = document.getElementById("score")!;
this.levelElement = document.getElementById("level")!;
// this.scoreElement = document.querySelector(".score")!;
// this.levelElement = document.querySelector(".level")!;
this.maxLevel = maxLevel;
this.upLevel = upLevel;
}
// 获取元素内容的方法
get scoreValue() {
return this.scoreElement.innerHTML;
}
get levelValue() {
return this.levelElement.innerHTML;
}
// 修改元素内容的方法
changeScore() {
this.score++;
this.scoreElement.innerHTML = this.score + "";
// 判断如果分数是10的倍数时,升一级
if (this.score % this.upLevel === 0) {
this.changeLevel();
}
}
changeLevel() {
if (this.level < this.maxLevel) {
this.level++;
this.levelElement.innerHTML = this.level + "";
}
}
}
export { Panel };
3.4 定义面板中snake类
class Snake {
// 蛇的容器
element: HTMLElement;
// 定义蛇头
head: HTMLElement;
// 身体
body: HTMLCollection;
constructor() {
this.element = document.getElementById("snake")!;
this.head = document.querySelector(".snake>div") as HTMLElement;
this.body = document.getElementById("snake")!.getElementsByTagName("div");
}
// 获取蛇头的X,Y坐标
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
// 设置蛇头的X,Y坐标
set X(value: number) {
this.head.style.left = value + "px";
}
set Y(value: number) {
this.head.style.top = value + "px";
}
// 增加身体的方法
addBody() {
// 向蛇的容器中添加一个元素
/**
* insertAdjacentHTML方法
* 方法名称:insertAdjacentHTML(where,html)
* where:插入位置。包括beforeBegin,beforeEnd,afterBegin,afterEnd。
* html:要插入的html代码
*/
this.element.insertAdjacentHTML("beforeend", "<div class='snake_item'></div>");
}
}
export { Snake };
3.5 控制游戏区域,控制所有其他类
// 文件路径: ./src/module/gameControl.ts
// 引入其他类
import { Foods } from "./foods";
import { Snake } from "./snake";
import { Panel } from "./scorePanel";
// 定义游戏控制的类
class GameControl {
// 定义蛇的属性 , 定义键盘按下要存储的值
snake: Snake;
foods: Foods;
panel: Panel;
direction: string = "";
// 构造方法
constructor() {
this.snake = new Snake();
this.foods = new Foods();
this.panel = new Panel();
// 调用游戏开始的方法
this.init();
}
// 游戏的初始化方法,调用后游戏开始
init() {
// 绑定键盘按键按下的事件
document.addEventListener("keydown", this.keydownHandler.bind(this));
}
// 键盘按下的回调函数
keydownHandler(event: KeyboardEvent) {
// event.key 可以获取到那个键盘按键按下
this.direction = event.key;
}
// init() {
// document.addEventListener("keydown", (event) => {
// this.direction = event.key;
// console.log(this);
// });
// }
}
export { GameControl };
3.5 在入口index.ts文件中引入
// 路径: ./src/index.ts
import "../style/css/index.scss";
import { GameControl } from "./module/gameControl";
3.6 游戏面板中蛇移动
- 根据键盘按下的方向,this.direction来使蛇位置改变
- ArrowUp top值减少
- ArrowDown top值增加
- ArrowLeft left值减少
- ArrowRight left值增加
- 设置定时器,调用蛇移动的函数
- 随着游戏等级的提高,增加调用蛇移动的函数
- 在定时器调用蛇移动的函数时要先判断游戏有没有over
// 文件路径: ./src/module/gameControl.ts
// 引入其他类
import { Foods } from "./foods";
import { Snake } from "./snake";
import { Panel } from "./scorePanel";
// 定义游戏控制的类
class GameControl {
// 定义蛇的属性 , 定义键盘按下要存储的值
snake: Snake;
foods: Foods;
panel: Panel;
direction: string = "";
// 创建一个属性用来标记游戏是不是结束
isLive: boolean = false;
// 构造方法
constructor() {
this.snake = new Snake();
this.foods = new Foods();
this.panel = new Panel();
// 调用游戏开始的方法
this.init();
}
// 游戏的初始化方法,调用后游戏开始
init() {
// 绑定键盘按键按下的事件
document.addEventListener("keydown", this.keydownHandler.bind(this));
// 调用蛇移动的方法
this.run();
}
// 键盘按下的回调函数
keydownHandler(event: KeyboardEvent) {
this.direction = event.key;
}
/**
* 创建一个函数,控制蛇移动的方法
* 根据键盘按下的方向,this.direction来使蛇位置改变
* ArrowUp top值减少
* ArrowDown top值增加
* ArrowLeft left值减少
* ArrowRight left值增加
*/
run() {
// 获取蛇当前的坐标
let X = this.snake.X;
let Y = this.snake.Y;
// 通过键盘按下获取的值判断
switch (this.direction) {
case "ArrowUp":
// 设置snake的top值
this.snake.Y -= 10;
break;
case "ArrowDown":
this.snake.Y += 10;
break;
case "ArrowLeft":
this.snake.X -= 10;
break;
case "ArrowRight":
this.snake.X += 10;
break;
}
// 修改蛇移动的位置
this.snake.X = X
this.snake.Y = Y
// 设置定时器
setTimeout(() => {
// 判断游戏是不是结束
if (this.isLive) {
this.run();
}
// 随着等级的提升,速度加快
}, 300 - (this.panel.level - 1) * 30);
}
}
export { GameControl };
3.7 蛇移动过程中撞墙和吃东西检测
- 在snake类中对蛇头属性X,Y也就是(X轴坐标和Y轴坐标)的值进行判断
- 判断蛇移动过程中传入的X值没有改变,则不修改X的值
- 判断蛇移动过程中传入的Y值没有改变,则不修改Y的值
- 判断移动过程中传入的X,Y值(X轴Y轴坐标)合不合法,也就是说是不是在游戏区域内。(0-290之间为合法)
- 如果不合法就是撞墙了,抛出异常
- snake类中的改变,文件路径: ./src/module/snake.ts
- gameControl类中的改变,文件路径: ./src/module/snake.ts
- 检查蛇是不是吃到了食物
- 在gameControl类中增加一个检查有没有吃到食物的方法
- 通过传入的参数X,Y,来判断有没有和食物X,Y坐标重合,重合,说明吃到了食物
- 吃到食物后需要做三件事情
- (1) 生成新食物 (2) 蛇身体增加一截 (3) 当前记分板分数+1
- 更改代码截图,路径: ./src/module/gameControl.ts
3.8 处理蛇移动过程中X,Y方向掉头问题及蛇头碰到蛇身问题
- 判断掉头的问题
- 当蛇有两截且第二截移动的距离等于蛇头移动的距离坐标时,说明掉头了
- 若发生掉头,让蛇反方向移动
- 改变当前的坐标
- 修改代码 路径 ./src/module/snake.ts
)
- 判断蛇头碰到蛇身问题
- 定义一个检查碰撞的函数
- 循环遍历身体的长度
- 通过判断每一截身体的坐标与当前蛇头的坐标有没有重叠
- 有重叠说明蛇头已经碰到身体的某一截
- 最后,在修改蛇头坐标后调用检查碰撞的函数 4.代码更改,路径./src/module/snake.ts