基于TypeScript的贪吃蛇游戏开发详解
本文详细解析使用TypeScript开发贪吃蛇游戏的完整流程,涵盖tsconfig.json编译配置、CSS变量布局技巧以及核心Class设计。重点讲解食物、蛇身移动、碰撞检测及键盘事件监控的实现逻辑,提供完整源码参考。
首先展示游戏的运行效果预览。

下文将逐步讲解具体的配置细节与代码实现,完整的源代码已托管在GitHub仓库中,链接位于文章末尾。
技术实现解析
1. tsconfig.json 编译配置
{
"compilerOptions": {
"target": "ES2015",
"module": "ES2015",
"strict": true,
"noEmitOnError": true
}
}
该文件主要用于设定TypeScript的编译选项。
- target:指定将TypeScript代码转译为JavaScript的目标版本。
- module:定义使用的模块化规范版本。
- strict:控制是否开启所有相关的严格类型检查模式。
- noEmitOnError:配置当编译过程中出现错误时,是否阻止输出文件的生成。
2. HTML 结构与 CSS 样式布局
首先查看整体页面的布局结构。
<!DOCTYPE html>
<html lang="en">
<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">
<title>Document</title>
</head>
<body scroll="no">
<!-- 创建游戏的主容器 -->
<div id="main">
<!-- 设置游戏舞台 -->
<div id="stage">
<!-- 设置蛇 -->
<div id="snack">
<!-- snack内部的div表示蛇的各部分 -->
<div></div>
</div>
<div id="food">
<!-- 设置食物的样式 -->
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<!-- 设置游戏的积分牌 -->
<div id="scoreStage">
<div>SCORE:<span id="score">0</span></div>
<div>LEVEL:<span id="level">1</span></div>
</div>
</div>
</body>
</html>以下是相关的CSS样式代码。
// 设置变量
@bgColor: #b7d4a8;
// 清除默认样式清除默认样式
* {
margin: 0;
padding: 0;
// 怪异模式
box-sizing: border-box;
}
body {
font: bold 20px "Courier";
width: 100%;
height: 100%;
overflow: hidden;
}
// 设置主窗口的样式
#main {
width: 360px;
height: 420px;
background-color: @bgColor;
margin: 100px auto;
border: 10px solid #000;
border-radius: 40px;
display: flex;
flex-direction: column;
align-items: center;
// 主轴对齐方式
justify-content: space-around;
}
#stage {
width: 304px;
height: 304px;
border: 2px solid black;
position: relative;
}
#scoreStage {
width: 300px;
display: flex;
justify-content: space-between;
}
#snack {
&>div {
width: 10px;
height: 10px;
background-color: #000;
border: 1px solid @bgColor;
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;
&>div {
width: 4px;
height: 4px;
background-color: #000;
transform: rotate(45deg);
// border: 1px solid @bgColor;
}
}
body {
scroll-behavior: unset;
}
其中较为实用的特性是利用 @ 符号定义CSS变量。
例如定义背景颜色变量:@bgColor: #b7d4a8;。
3. TypeScript 核心逻辑实现
TypeScript的核心优势在于类(Class)的使用,因此我们需要定义多个类来对贪吃蛇游戏进行模块化分析。
首先梳理贪吃蛇游戏包含的主要功能模块。
- 食物模块
- 蛇身模块
- 分数显示模块
- 游戏控制模块
食物模块详解
食物模块包含几个核心逻辑点。
首先是定义Food类。
我们需要通过getter方法获取食物当前的横坐标(X)与纵坐标(Y)。
其次,当蛇吃到食物后,需要调用change()方法重新生成食物的随机位置。
class Food {
// 属性 & 方法
// 定义食物所对应的元素
element: HTMLElement;
constructor() {
// 加一个 “!”表示这玩意不会为空
this.element = document.getElementById('food')!;
}
// 方法
// 1. 获取食物的x坐标的方法
get X() {
return this.element.offsetLeft;
}
// 2. 获取食物的y坐标的方法
get Y() {
return this.element.offsetTop;
}
// 修改食物位置的方法
change() {
// 使用random,生成随机位置
// 蛇移动一次就是一格,大小为10
const left = Math.round(Math.random()*29)*10;
const top = Math.round(Math.random()*29)*10;
this.element.style.left = left + 'px';
this.element.style.top = top + 'px';
}
}
export default Food
蛇身模块详解
蛇身模块的逻辑相对复杂,涉及多个方面。
首先,需要获取蛇头的横纵坐标,并提供设置坐标的方法。
其次,需要实现addBody方法用于增加蛇的身体节点。
同时,需要实现moveBody方法处理身体节点的移动逻辑。
此外,必须包含碰撞检测机制,确保蛇头不会与自己的身体发生重叠。
这个比较复杂,我们分而析之
1. 元素初始化与构造函数
// 表示蛇的元素
head: HTMLElement;
// 蛇的身体,包括蛇头
bodies: HTMLCollection;
// 获取蛇的容器
element: HTMLElement;
constructor() {
// 断言一下 | 找到蛇头
this.head = document.querySelector('#snack > div') as HTMLElement;
this.element = document.getElementById('snack')!
this.bodies = this.element.getElementsByTagName('div');
}
代码中的 ! 符号用于断言,确保id为snack的元素一定存在。
2. 增加身体节点
addBody() {
this.element.insertAdjacentHTML("beforeend", "<div></div>")
}
3. 移动身体节点
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';
}
}4. 碰撞检测机制
checkHeadBody() {
// 获取所有的身体,检查其是否和蛇头的坐标发生重叠
for(let i=1;i<this.bodies.length;i++) {
const bd = (this.bodies[i] as HTMLElement)
if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
// 说明出现了碰撞
throw Error('撞到自己了~~~')
}
}
}
5. 完整代码展示
class Snack {
// 表示蛇的元素
head: HTMLElement;
// 蛇的身体,包括蛇头
bodies: HTMLCollection;
// 获取蛇的容器
element: HTMLElement;
constructor() {
// 断言一下 | 找到蛇头
this.head = document.querySelector('#snack > div') as HTMLElement;
this.element = document.getElementById('snack')!
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
if(value < 0 || value > 290) {
throw new Error('您撞墙了')
}
// 蛇在往左走,不能往右走
if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
// 让蛇向反方向继续移动
if(value > this.X) {
// 如果新值大于旧值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
if(value < 0 || value > 290) {
throw new Error('您撞墙了')
}
if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
// 让蛇向反方向继续移动
if(value > this.Y) {
// 如果新值大于旧值X,说明蛇在向右走,此时发生掉头,应该使蛇继续向左走
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++) {
const bd = (this.bodies[i] as HTMLElement)
if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
// 说明出现了碰撞
throw Error('撞到自己了~~~')
}
}
}
}
export default Snack
得分面板模块
该模块逻辑相对简单,主要包含增加得分的方法以及提升等级的方法。
class scorePanel {
// score和level用来记录分数和等级
score: number = 0;
level: number = 1;
scoreSpan: HTMLElement;
levelSpan: HTMLElement;
// 设置等级
maxLevel: number;
// 设置一个变量表示多少分升级
upScore: number;
// 给两个需要修改的元素赋值
constructor(maxLevel: number = 10, upScore: number = 2) {
this.scoreSpan = document.getElementById('score')!;
this.levelSpan = document.getElementById('level')!;
this.maxLevel = maxLevel
this.upScore = upScore
}
// method
// 设置加分的方法
addScore() {
// 分数自增
this.score += 1
this.scoreSpan.innerHTML = this.score + '';
// 判断一下分数是多少
if(this.score % this.upScore === 0) {
this.levelUp()
}
}
// 提升等级的方法
levelUp() {
if(this.level < this.maxLevel) {
this.level += 1
this.levelSpan.innerHTML = this.level + '';
}
}
}
export default scorePanel
控制面板模块
该逻辑的核心在于整合各个模块,并监控键盘的keydown事件。
关于整合:我们将蛇、分数面板、食物等模块整合到该类中,作为启动游戏的总开关。
snack: Snack;
food: Food;
scorePanel: scorePanel;
arrowDirection: string = '';
// 创建一个属性用来记录游戏是否结束
isLeave: boolean = true
constructor() {
this.snack = new Snack();
this.food = new Food();
this.scorePanel = new scorePanel();
this.init();
}
关于键盘事件监控
核心逻辑是监听按键,判断是上下左右中的哪一个方向,并据此改变蛇的移动方向。
由于蛇的移动需要持续调用run函数,因此使用isLeave作为开关标志,通过递归方式多次调用run函数以实现持续运行。
import Snack from './snack';
import Food from "./Food";
import scorePanel from './scorePanel';
class GameControl {
snack: Snack;
food: Food;
scorePanel: scorePanel;
arrowDirection: string = '';
// 创建一个属性用来记录游戏是否结束
isLeave: boolean = true
constructor() {
this.snack = new Snack();
this.food = new Food();
this.scorePanel = new scorePanel();
this.init();
}
// 游戏的初始化方法
init() {
// 绑定键盘按下的时间
// const _this = this
// 如果不改变这个this,则会绑定到document上面
// document.addEventListener('keydown', _this.keyDownHandler)
document.addEventListener('keydown', this.keyDownHandler.bind(this));
// 调用run方法
this.run();
}
// 创建一个键盘按下的响应函数
/**
* ArrowRight Right
ArrowLeft Left
ArrowDown Down
ArrowUp Up
*/
keyDownHandler(event: KeyboardEvent) {
this.arrowDirection = event.key
// this.run();
}
// 创建一个控制蛇移动的方法
run() {
// 根据方向(this.direction)来使蛇的位置改变
// 向上 top -
// 向下 top +
// 向左 left -
// 向右 left +
let X = this.snack.X;
let Y = this.snack.Y;
// 根据按键方向修改值
switch (this.arrowDirection) {
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
}
try {
this.snack.X = X;
this.snack.Y = Y;
} catch(e: any) {
alert(e.message)
this.isLeave = false
}
this.checkEat(X, Y)
// 开启定时调用
// 这是递归调用
this.isLeave && 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.snack.addBody()
}
}
}
export default GameControl
项目源码获取
GitHub仓库地址
以上即为利用TypeScript编写贪吃蛇游戏的完整内容,更多TypeScript游戏开发资料欢迎查阅本站其他文章。
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53








