前言
ts是一门面向对象的语言,在学习完后,可以拿贪吃蛇小游戏来当作一个练手项目。
项目效果图
snake
一、项目详细介绍
1、主面板
<!--创建游戏的主容器-->
<div id="main">
<!-- 设置游戏的舞台-->
<div id="stage">
<!--设置蛇-->
<div id="snake">
<!--snake内部的div 表示蛇的各部分-->
<div></div>
</div>
<!--设置食物-->
<div id="food">
<!--添加四个小div来设置食物的样式-->
<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>
2、食物Food类
一个对象所包含的主要是属性和方法两点。
食物所具有的属性是位置,而方法则是改变坐标位置。
// 定义食物类Food
class Food {
// 定义一个属性表示食物所对应的元素
element: HTMLElement;
constructor() {
// 获取页面中的food元素并将其赋值给element
this.element = document.getElementById("food") as HTMLElement;
}
// 定义一个获取食物X轴坐标的方法
get X() {
return this.element.offsetLeft;
}
// 定义一个获取食物Y轴坐标的方法
get Y() {
return this.element.offsetTop;
}
// 修改食物位置的方法
change() {
// 生成一个随机的位置
// 食物的位置最小是0,最大是290(减去自身)
// 蛇移动一次就是一格,一格的大小就是10,所以就要求食物的坐标必须是10的倍数
let left = Math.round(Math.random() * 29) * 10;
let top = Math.round(Math.random() * 29) * 10;
this.element.style.left = left + 'px';
this.element.style.top = top + 'px';
}
}
export default Food;
3、计分盘ScorePanel类
计分盘应包含分数和等级2个属性。
实现此类的思路是:蛇每吃到一个食物就加一分,当加到多少分(该分数自己设定的)时就等级+1。
// 定义表示记分牌的类
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") as HTMLElement;
this.levelEle = document.getElementById("level") as HTMLElement;
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.levelEle.innerHTML = ++this.level + '';
}
}
}
export default ScorePanel;
4、蛇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) {
// 如果新值和旧值相同,则直接返回不再修改
if(this.X === value) {
return;
}
// X的值的合法范围0-290之间
if(value < 0 || value > 290) {
// 进入判断说明蛇撞墙了,抛出一个异常
throw new Error('蛇撞墙了');
}
// 修改x时,是在修改水平坐标,蛇在左右移动。蛇在向左移动时,不能向右掉头,反之亦然。
// 蛇头的位置等于第二个身体的位置时,说明掉头了
if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
// console.log('水平方向发生了掉头!');
// 如果发生了掉头,让蛇向反方向继续移动
if(value > this.X) {
// 如果新增value大于旧值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时,是在修改垂直坐标,蛇在上下移动。蛇在向上移动时,不能向下掉头,反之亦然。
// 蛇头的位置等于第二个身体的位置时,说明掉头了
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() {
// 向element中添加一个div(方法一)
this.element.insertAdjacentHTML('beforeend',"<div></div>");
// 向element中添加一个div(方法二)
// let newBody = document.createElement('div');
// this.element.appendChild(newBody);
}
// 添加一个蛇身体移动的方法
moveBody() {
/*
将后边的身体设置为前边的身体的位置
举例子:
第4节 = 第3节的位置
第3节 = 第2节的位置
第2节 = 蛇头的位置
*/
// 遍历获取所有的身体
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;
5、游戏控制GameControl类
控制上述的三个类,实现游戏的运行。
定义一个可以通过使用键盘方向键来控制蛇移动方向的方法。
以及判断蛇是否吃到食物的方法。
// 引入其他的类
import Snake from './Snake';
import Food from './Food';
import ScorePanel from "./ScorePanel";
// 游戏控制器,控器其他的所有类
class GameControl {
// 定义三个属性
// 蛇
snake: Snake;
// 食物
food: Food;
// 记分牌
scorePanel: ScorePanel;
// 创建一个属性来存储蛇的移动方向(也就是按键的方向)
direction: string = 'Right';
// 创建一个属性用来记录游戏是否结束
isLive = true;
constructor() {
this.snake = new Snake();
this.food = new Food();
// this.scorePanel = new ScorePanel(10,2);
this.scorePanel = new ScorePanel();
this.init();
}
// 游戏的初始化方法,调用后游戏即开始
init() {
// 绑定键盘按键按下的事件
document.addEventListener('keydown',this.keydownHandler.bind(this));
this.run();
}
/*
chrome:
ArrowUp
ArrowDown
ArrowLeft
ArrowRight
ie:
Up
Down
Left
Right
*/
// 创建一个键盘按下的响应函数
keydownHandler(event: KeyboardEvent) {
// console.log(event.key);
// 需要检查event.key的值是否合法(用户是否按了正确的按键)
// 修改direction属性
this.direction = event.key;
}
// 创建一个控制蛇移动的方法
run() {
/*
向上 Y减少
向下 Y增加
向左 X减少
向右 X增加
*/
// 获取蛇现在的坐标
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);
// 修改蛇的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 GameControl;
二、项目源码
本人已将项目传至本人的github上了,有需要的小伙伴可以下载。
github项目开源地址:https://github.com/syj-github-hub/snake_project
项目打包命令:npm run build
项目运行命令:npm start