TypeScript实现贪吃蛇效果
项目参考教程:贪吃蛇案例
1.项目搭建
1.1 项目结构搭建
创建一个名为xxx的项目
项目初始化
npm init -y
安装后面需要用到的依赖,在package.json中查看

项目根目录创建名为tsconfig.json的文件并更改内容如下:
{
"compilerOptions": {
"module": "ES2015",
"target": "ES2015",
"strict": true,
"noEmitOnError": true
}
}
项目根目录创建名为twebpack.config.js的文件并更改内容如下:
//引入一个包
const path = require("path");
//引入html插件
const HTMLWebpackPlugin = require("html-webpack-plugin");
//引入clean插件
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const {options} = require("less");
//webpack中的所有配置信息都应该写在module.exports中
module.exports = {
mode: "development",
//指定入口文件
entry: "./src/index.ts",
//指定打包文件所在目录
output: {
//打包后文件的目录
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
//告诉我们webpack不使用箭头函数
environment: {
arrowFunction: false,
},
},
//指定webpack打包时要使用的模块
module: {
//指定要再加载的规则
rules: [
{
//test规则生效的文件
test: /\.ts$/,
use: [
//配置babel
{
//指定加载器
loader: "babel-loader",
//设置babel
options: {
//设置预定义的环境
presets: [
[
//指定环境的插件
"@babel/preset-env",
//配置信息
{
//要兼容的目标浏览器
targets: {
chrome: "88",
ie: "11",
},
//指定corejs的版本
corejs: "3",
//按需使用
useBuiltIns: "usage",
},
],
],
},
},
"ts-loader",
],
//要排除的文件
exclude: /node-modules/,
},
//设置less文件的类型
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
//引入postcss
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [[
"postcss-preset-env",
{
browsers: 'last 2 versions'
}
]
]
}
}
},
"less-loader"
]
}
],
},
//配置webpack插件
plugins: [
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
// title: "这是一个自定义的title"
template: "./src/index.html",
}),
],
//用来设置引用模块
resolve: {
extensions: [".ts", ".js"],
},
};
创建如下目录

编写index.html文件内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>贪吃蛇</title>
</head>
<body>
<!--主容器-->
<div id="main">
<!--游戏舞台-->
<div id="stage">
<!--蛇-->
<div id="snake">
<!--蛇的内部div,表示蛇的各个部分-->
<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>
编写index.less文件内容
@bg-color: #b7d4a8;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font: bold 20px "Courier";
}
//主窗口的样式
#main {
width: 360px;
height: 420px;
background-color: @bg-color;
margin: 100px auto;
border: 10px solid black;
border-radius: 15px;
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-flow: row wrap;
justify-content: space-between;
align-content: space-between;
transform: rotate(45deg);
& > div {
width: 4px;
height: 4px;
background-color: black;
}
}
}
//积分牌
#score-panel {
width: 300px;
display: flex;
justify-content: space-between;
}
}
编写index.ts文件内容
import './style/index.less';
最终搭建项目如图所示(项目名称不做严格要求)

1.2 项目启动
在终端运行npm run build编译项目
运行npm start启动项目,如下为项目启动成功、

效果如图

2.编写Food类
index.ts添加如下内容
//定义食物Food
class Food {
//定义一个属性表示食物对应的元素
element: HTMLElement;
constructor() {
//获取页面中的food元素并赋值给element
this.element = document.getElementById('food')!;
}
//获取食物x轴坐标的方法
get x() {
return this.element.offsetLeft;
}
//获取食物y轴坐标的方法
get y() {
return this.element.offsetTop;
}
//修改食物位置
change() {
//生成随机位置
//食物的随机位置最小是0,最大是290
//蛇移动一次就是一格,一格就是10,所以食物的坐标必须是整10
let top = Math.round(Math.random() * 30) * 10;
let left = Math.round(Math.random() * 30) * 10;
this.element.style.left = top + 'px';
this.element.style.top = left + 'px'
}
}
3.编写ScorePanel类
在index.ts添加代码
class ScorePanel {
//score和level用来记录分数和等级
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(100, 2);
for (let i = 0; i < 200; i++) {
scorePanel.addScore();
}
运行代码效果如图

4.模块化
在src下创建名为modules的文件夹

创建Food.ts文件
将index.ts的Food类代码复制到Food.ts文件中
在Food.ts添加代码将模块暴露出去
export default Food;
ScorePanel类进行同样操作
在index.ts中引入模块并调用

5.编写snake类
class Snake {
//蛇头的元素
head: HTMLElement;
bodies: HTMLCollection;
//获取蛇的容器
element: HTMLElement;
constructor() {
this.element = document.getElementById('snake')!;
this.head = document.querySelector('#snake > div') as HTMLElement;
this.bodies = this.element.getElementsByTagName('div');
}
//获取蛇的坐标(蛇头坐标)
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
//设置蛇头的坐标
set X(value) {
this.head.style.left = value + 'px';
}
set Y(value) {
this.head.style.top = value + 'px';
}
//设置蛇增加身体的方法
addBody() {
this.element.insertAdjacentHTML("beforeend","<div></div>")
}
}
6.GameControoller实现
src下创建GamaController类,用来管理Food类、scorePanel类和Snake类,并将其暴露,并在index.ts中引入该模块

import Snake from "./snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
//游戏控制器 控制其他所有类
class GameController {
//定义三个属性
snake: Snake;
food: Food;
scorePanel: ScorePanel;
//创建一个属性来存储移动方向(按键的方向)
dirdction: string = '';
constructor() {
this.snake = new Snake();
this.food = new Food();
this.scorePanel = new ScorePanel();
this.init();
}
//游戏初始化方法,调用后游戏开始
init() {
document.addEventListener('keydown', this.keydownhandler.bind(this));
}
//创建一个键盘按下的响应函数
keydownhandler(event: KeyboardEvent) {
//需要检查event.key的值是否合法(是否按了四个方向键)
//修改direction属性
this.dirdction = event.key;
}
}
export default GameController;
6.1 GameController实现蛇的移动
在GameController中添加代码实现跑
import Snake from "./snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
//游戏控制器 控制其他所有类
class GameController {
//定义三个属性
snake: Snake;
food: Food;
scorePanel: ScorePanel;
//创建一个属性来存储移动方向(按键的方向)
dirdction: string = '';
//创建一个属性来记录游戏是否结束
isLive = true;
constructor() {
this.snake = new Snake();
this.food = new Food();
this.scorePanel = new ScorePanel();
this.init();
}
//游戏初始化方法,调用后游戏开始
init() {
document.addEventListener('keydown', this.keydownhandler.bind(this));
//调用方法使蛇移动
this.run();
}
//创建一个键盘按下的响应函数
keydownhandler(event: KeyboardEvent) {
//需要检查event.key的值是否合法(是否按了四个方向键)
//修改direction属性
this.dirdction = event.key;
}
//控制蛇移动
run() {
/*
* 根据方向(this.diretion)来使蛇的位置改变
* 向上 top 减少
* 向上 top 增加
* 向左 left 减少
* 向右 left 增加
* */
//获取蛇现在坐标
let X = this.snake.X;
let Y = this.snake.Y;
//根据按键方向修改X值和Y值
switch (this.dirdction) {
case "ArrowUp":
case "Up":
Y -= 10;
break;
case "ArrowDown":
case "Down":
Y += 10;
break;
case "ArrowLeft":
case "Left":
X -= 10;
break;
case "ArrowRight":
case "Right":
X += 10;
break;
}
//修改蛇的x和Y
this.snake.X = X;
this.snake.Y = Y;
//开启定时调用
this.isLive && setTimeout(this.run.bind(this), 300 * (this.scorePanel.level - 1) * 30);//等级越高速度越快
}
}
export default GameController;
6.2 蛇撞墙和吃食的检测
snake.ts做如下修改

snake.ts
class Snake {
//蛇头的元素
head: HTMLElement;
bodies: HTMLCollection;
//获取蛇的容器
element: HTMLElement;
constructor() {
this.element = document.getElementById('snake')!;
this.head = document.querySelector('#snake > div') as HTMLElement;
this.bodies = this.element.getElementsByTagName('div');
}
//获取蛇的坐标(蛇头坐标)
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
//设置蛇头的坐标
set X(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) {
//如果新值和旧值相等,则直接返回不修改
if (this.Y === value) {
return;
}
//Y的合法值为0~290之间
if (value < 0 || value > 290) {
//进入判断说明蛇撞墙了
throw new Error('蛇撞墙了!')
}
this.head.style.top = value + 'px';
}
//设置蛇增加身体的方法
addBody() {
this.element.insertAdjacentHTML("beforeend", "<div></div>")
}
}
export default Snake;
GameController.ts做如下修改
import Snake from "./snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
//游戏控制器 控制其他所有类
class GameController {
//定义三个属性
snake: Snake;
food: Food;
scorePanel: ScorePanel;
//创建一个属性来存储移动方向(按键的方向)
dirdction: string = '';
//创建一个属性来记录游戏是否结束
isLive = true;
constructor() {
this.snake = new Snake();
this.food = new Food();
this.scorePanel = new ScorePanel();
this.init();
}
//游戏初始化方法,调用后游戏开始
init() {
document.addEventListener('keydown', this.keydownhandler.bind(this));
//调用方法使蛇移动
this.run();
}
//创建一个键盘按下的响应函数
keydownhandler(event: KeyboardEvent) {
//需要检查event.key的值是否合法(是否按了四个方向键)
//修改direction属性
this.dirdction = event.key;
}
//控制蛇移动
run() {
/*
* 根据方向(this.diretion)来使蛇的位置改变
* 向上 top 减少
* 向上 top 增加
* 向左 left 减少
* 向右 left 增加
* */
//获取蛇现在坐标
let X = this.snake.X;
let Y = this.snake.Y;
//根据按键方向修改X值和Y值
switch (this.dirdction) {
case "ArrowUp":
case "Up":
Y -= 10;
break;
case "ArrowDown":
case "Down":
Y += 10;
break;
case "ArrowLeft":
case "Left":
X -= 10;
break;
case "ArrowRight":
case "Right":
X += 10;
break;
}
//检查蛇是否吃到了食物
this.checkEat(X, Y);
//修改蛇的x和Y
try {
this.snake.X = X;
this.snake.Y = Y;
} catch (e) {
//进入catch,说明出现异常,游戏结束,弹出提示信息
alert((e as Error).message + 'GAME OVER!');
//将isLive设置为false
this.isLive = false;
}
//开启定时调用
this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30);//等级越高速度越快
}
//检查蛇是否吃到了食物
checkEat(X: number, Y: number) {
if( X === this.food.X && Y === this.food.Y){
//重置食物
this.food.change();
//分数相加
this.scorePanel.addScore();
//蛇要增加一节
this.snake.addBody();
}
}
}
export default GameController;
6.2 设置蛇吃食后增加身体长度

在snake.ts中修改
class Snake {
//蛇头的元素
head: HTMLElement;
bodies: HTMLCollection;
//获取蛇的容器
element: HTMLElement;
constructor() {
this.element = document.getElementById("snake")!;
this.head = document.querySelector("#snake > div") as HTMLElement;
this.bodies = this.element.getElementsByTagName("div");
}
//获取蛇的坐标(蛇头坐标)
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
//设置蛇头的坐标
set X(value) {
//如果新值和旧值相等,则直接返回不修改
if (this.X === value) {
return;
}
//X的合法值为0~290之间
if (value < 0 || value > 290) {
//进入判断说明蛇撞墙了
throw new Error("蛇撞墙了!");
}
//移动身体
this.moveBody();
this.head.style.left = value + "px";
}
set Y(value) {
//如果新值和旧值相等,则直接返回不修改
if (this.Y === value) {
return;
}
//Y的合法值为0~290之间
if (value < 0 || value > 290) {
//进入判断说明蛇撞墙了
throw new Error("蛇撞墙了!");
} //移动身体
this.moveBody();
this.head.style.top = value + "px";
}
//设置蛇增加身体的方法
addBody() {
this.element.insertAdjacentHTML("beforeend", "<div></div>");
}
//添加一个蛇身体移动的方法
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';
}
}
}
export default Snake;
6.3 优化
蛇在向右走走时,不能按←键,反之亦然
6.3.1 移动方向的优化

snake.ts代码修改
class Snake {
//蛇头的元素
head: HTMLElement;
bodies: HTMLCollection;
//获取蛇的容器
element: HTMLElement;
constructor() {
this.element = document.getElementById("snake")!;
this.head = document.querySelector("#snake > div") as HTMLElement;
this.bodies = this.element.getElementsByTagName("div");
}
//获取蛇的坐标(蛇头坐标)
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
//设置蛇头的坐标
set X(value) {
//如果新值和旧值相等,则直接返回不修改
if (this.X === value) {
return;
}
//X的合法值为0~290之间
if (value < 0 || value > 290) {
//进入判断说明蛇撞墙了
throw new Error("蛇撞墙了!");
}
//修改X时,在修改水平坐标,蛇在左右移动,Y轴同理,反之亦然
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
// console.log('水平方向掉头')
//如果发生掉头,继续向反方向掉头
if (value > this.X) {
//如果新值大于旧值。则说明蛇在向右走。此时发生掉头,继续向左走
value = this.X - 10;
} else {
value = this.X + 10;
}
}
//移动身体
this.moveBody();
this.head.style.left = value + "px";
}
set Y(value) {
//如果新值和旧值相等,则直接返回不修改
if (this.Y === value) {
return;
}
//Y的合法值为0~290之间
if (value < 0 || value > 290) {
//进入判断说明蛇撞墙了
throw new Error("蛇撞墙了!");
}
//修改Y时,在修改水平坐标,蛇在上下移动,X轴同理,反之亦然
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
// console.log('水平方向掉头')
//如果发生掉头,继续向反方向掉头
if (value > this.Y) {
//如果新值大于旧值。则说明蛇在向右走。此时发生掉头,继续向左走
value = this.Y - 10;
} else {
value = this.Y + 10;
}
}
//移动身体
this.moveBody();
this.head.style.top = value + "px";
}
//设置蛇增加身体的方法
addBody() {
this.element.insertAdjacentHTML("beforeend", "<div></div>");
}
//添加一个蛇身体移动的方法
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';
}
}
}
export default Snake;
6.3.2 穿越身体的优化
穿越身体撞到自己游戏结束

snake.ts修改
class Snake {
//蛇头的元素
head: HTMLElement;
bodies: HTMLCollection;
//获取蛇的容器
element: HTMLElement;
constructor() {
this.element = document.getElementById("snake")!;
this.head = document.querySelector("#snake > div") as HTMLElement;
this.bodies = this.element.getElementsByTagName("div");
}
//获取蛇的坐标(蛇头坐标)
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
//设置蛇头的坐标
set X(value) {
//如果新值和旧值相等,则直接返回不修改
if (this.X === value) {
return;
}
//X的合法值为0~290之间
if (value < 0 || value > 290) {
//进入判断说明蛇撞墙了
throw new Error("蛇撞墙了!");
}
//修改X时,在修改水平坐标,蛇在左右移动,Y轴同理,反之亦然
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
// console.log('水平方向掉头')
//如果发生掉头,继续向反方向掉头
if (value > this.X) {
//如果新值大于旧值。则说明蛇在向右走。此时发生掉头,继续向左走
value = this.X - 10;
} else {
value = this.X + 10;
}
}
//移动身体
this.moveBody();
this.head.style.left = value + "px";
//检查有没有撞到自己
this.checkHeadBody();
}
set Y(value) {
//如果新值和旧值相等,则直接返回不修改
if (this.Y === value) {
return;
}
//Y的合法值为0~290之间
if (value < 0 || value > 290) {
//进入判断说明蛇撞墙了
throw new Error("蛇撞墙了!");
}
//修改Y时,在修改水平坐标,蛇在上下移动,X轴同理,反之亦然
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
// console.log('水平方向掉头')
//如果发生掉头,继续向反方向掉头
if (value > this.Y) {
//如果新值大于旧值。则说明蛇在向右走。此时发生掉头,继续向左走
value = this.Y - 10;
} else {
value = this.Y + 10;
}
}
//移动身体
this.moveBody();
this.head.style.top = value + "px";
//检查有没有撞到自己
this.checkHeadBody();
}
//设置蛇增加身体的方法
addBody() {
this.element.insertAdjacentHTML("beforeend", "<div></div>");
}
//添加一个蛇身体移动的方法
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';
}
}
//检查是否撞到身体
checkHeadBody() {
//获取所有的身体,检查是否和蛇头的坐标重叠
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('撞到自己了!!!')
}
}
}
}
export default Snake;
7.得分测试
为了便于测试,加快升级速度,修改GameController代码

至此,完结撒花!!!

浙公网安备 33010602011771号