TS项目《贪吃蛇》

本文最后更新于:2023年7月19日 晚上

一、项目简介

在Web端,利用Html、CSS、Typescript语言,实现贪吃蛇游戏的功能

  1. 界面的搭建 HTML、CSS
  2. 系统功能的完成
    • 类型:蛇的实现
      • 蛇(头)的坐标
      • 移动功能
      • 转向功能
      • 蛇身体变长功能
      • 检测蛇是否撞到墙的功能
      • 检测蛇是否撞到自己的功能
    • 类型:记分板的实现
      • 分数增加功能
      • 等级(难度)增加功能
    • 类型:食物的实现
      • 随机生成食物坐标功能
    • 类型:游戏控制的实现(核心类)
      • 游戏初始化功能
      • 键盘按下的响应功能
      • 根据键盘响应改变蛇的方向功能
      • 检测是否吃到食物功能

二、界面搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- index.html界面-->
<!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>贪吃蛇</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="leve">1</span>
</div>
</div>
</div>
</body>
</html>

问题:div下嵌套div,下面的DOM的获取方式没见过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// 设置变量
@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: 40px;

// 开启弹性盒模型
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;
// background-color: red;
display: flex; //父亲开启弹性盒
flex-flow: row wrap; //设置主轴横向排列 wrap表示会换行
justify-content: space-between;//主轴的空白空间分配到元素之间
align-content: space-between; //让四个小方块在四周

left: 40px;
top: 40px;
&>div{
width: 4px;
height: 4px;
background-color: #000;
transform: rotate(45deg); //设置每个div旋转45度
}
//不知道这边和 直接div有什么区别
}

}

// 记分牌
#score-panel{
width: 300px;
display: flex;
// 设置主轴的对齐方式
justify-content: space-between;
}
}

//例如div span得到的是div下所有的span元素,而div>span则是取得的div下第一级的span元素。

三、系统功能的完成

1. 蛇类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
class Snake {
//表示蛇头的元素
head: HTMLElement
//蛇的身体,包括蛇头
bodies: HTMLCollection
//获取蛇的容器
element: HTMLElement

constructor() {
this.element = document.getElementById('snake')! //获取蛇在html中的容器标签
this.head = document.querySelector('#snake > div') as HTMLElement //获取蛇头 #snake下的div标签
// <div id="snake">
// <div></div> //‘#snake > div 获取的就是这个,id是snake,子元素 div标签
//</div>
this.bodies = this.element.getElementsByTagName('div')! //获取所有的div蛇身 //获取蛇下的所有div
}

//获取蛇头的坐标

get X() {
return this.head.offsetLeft //距离左边的距离
}
get Y() {
return this.head.offsetTop //距离上边的距离
}

//设置蛇头坐标 构造函数
set X(value: number) {
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) {
value = this.X - 10
} else {
value = this.X + 10
}
}
this.moveBody()
this.head.style.left = value + 'px'
this.checkHeadBody()
}

set Y(value: number) {
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) {
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>")
//在结束标签之前的位置插入一个HTML 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; //最后记得把类暴露出去 后面就可以import

补充:get和set (C#都忘光了。。。)

1
2
3
4
5
6
7
8
9
10
11
12
class person{
private _name:string = '' //私有属性,外部访问不到
constructor(){}

public get name(){ //get 使得该类具有了访问name属性的方法 ,后面就可以直接 person.name
return this._name //实现的对私有属性的“访问”
}

public set name(inputName:string){
this._name = inputName //实现对私有属性赋值
}
}

2.得分窗口类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//定义表示记分牌的类
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.score++
this.scoreEle.innerHTML = this.score + ''
//判断分数是多少
if (this.score % this.upScore === 0) {
this.levelUp()
}
}
levelUp() {
//实现分数自增
if (this.level < this.maxLevel) {
this.level++
this.levelEle.innerHTML = this.level + ''
}
}
}
export default ScorePanel;

3.食物类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Food{
//定义一个属性来表示食物所对应的元素
element:HTMLElement
constructor(){
//获取页面中的food元素并赋值给Element
this.element = document.getElementById('food')!
}
//定义一个获取食物x轴坐标的方法
get X(){
return this.element.offsetLeft //使得外部可以通过 food.X 和 food.Y 访问到 Food的坐标
}
get Y(){
return this.element.offsetTop
}
// 修改食物位置的方法
change(){
//生成一个随机的位置
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'
}
}

export default Food;

4.游戏控制类

```typescript //控制其他的所有类 //引入其他类 import Snake from "./snake"; import Food from "./food"; import ScorePanel from "./scorePanel";

class GameControl { //定义三个属性 snake: Snake; food: Food; scorePanel: ScorePanel; //创建属性 存储蛇的移动方向(按键方向) direction: string = ""; //创建属性 记录游戏是否结束 isLive = true; constructor() { this.snake = new Snake(); this.food = new Food(); this.scorePanel = new ScorePanel(); this.init(); }

//游戏的初始化方法,调用后游戏开始 init() { document.addEventListener("keydown", this.keyDownloadHandler.bind(this)); this.run();

} //创建一个键盘按下的响应函数 keyDownloadHandler(event: KeyboardEvent) { //赋值之前需要检查方向 event.key 用户是否按了正确的按键 this.direction = event.key; //用户按下按键的时候,方向值存到direction中

}

run() { //根据方向,使得蛇的位置改变 //向上 top值减小 向下 top值增加 左left减少 右 left增加 //获取蛇现在的坐标 let X = this.snake.X; let Y = this.snake.Y; //根据按键的方向修改 X Y switch (this.direction) { 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)

try {
  this.snake.X = X;
  this.snake.Y = Y;
} catch (error) {
  alert(error)
  this.isLive = false
}


//开启定时调用
this.isLive && setTimeout(this.run.bind(this), 100 - (this.scorePanel.level - 1) * 10);

}

//检查蛇是否吃到了食物 checkEat(X: number, Y: number) { if (X === this.food.X && Y === this.food.Y) { console.log('吃到食物') //食物的位置要重置 this.food.change() this.scorePanel.addScore() this.snake.addBody() }

} }

export default GameControl;


TS项目《贪吃蛇》
https://anonymouslosty.ink/2022/08/08/透视:TS项目《贪吃蛇》/
作者
Ling yi
发布于
2022年8月8日
更新于
2023年7月19日
许可协议