先看效果图
思路
ts编程思想时面向对象。所以我们也要以面向对象的形式来开发。
首先我们需要三个类,蛇的类,食物的类,记分牌的类,然后还有创建一个类,将这三个类关联再一起。
看代码
先看下布局结构
食物是一个div,蛇是一个大div,他的每一段都是一个小div,下面是记分牌
看最简单的食物的类
在构造函数中,获取食物的div,然后通过get X Y 方法获取到食物的坐标,接着需要定义一个方法来改变食物的位置,因为食物是随机的嘛。
接着看记分牌的类
记分牌的类也容易,主要是没得一分就调用addScore方法来基数,并且判断如果分数到达一定阶段时,调用Leveup方法来升级等级。
接着看蛇的类
// 蛇类
class Snake {
element: HTMLElement;
head: HTMLElement;
bodies: HTMLCollection;
constructor() {
this.element = document.getElementById("snake")!;
this.head = document.querySelector("#snake >div") as HTMLElement;
this.bodies = document.getElementById("snake")!.getElementsByTagName("div");
}
get X() {
//蛇头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 (this.X > value) value = this.X + 10;
//新值小于旧值,证明想转头向左走
else value = this.X - 10; //否则就是转头向右走
}
this.moveBody();
this.head.style.left = value + "px";
this.checkhead()
}
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 (this.Y > value) value = this.Y + 10;
//新值小于旧值,证明想转头向上走
else value = this.Y - 10; //否则就是转头向下走
}
this.moveBody(); //跟下面一条位置不能乱,否则会出现当只有两节蛇时,蛇头的坐标先赋予最新值的话,再来赋予第二段的值,第二段会跟舌头叠在一起
this.head.style.top = value + "px";
this.checkhead()
}
//蛇增加身体方法
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";
}
}
checkhead() {
//检查是否撞到自己身体
for (let i = 1; i < this.bodies.length; i++) {
if (
(this.bodies[0] as HTMLElement).offsetLeft ===
(this.bodies[i] as HTMLElement).offsetLeft &&
(this.bodies[0] as HTMLElement).offsetTop ===
(this.bodies[i] as HTMLElement).offsetTop
) {
throw new Error("游戏结束");
}
}
}
}
蛇的类是最复杂的,先一点点看
在构造函数中就要获取到蛇的盒子,蛇头,还有蛇的每一个div。
然后我们主要要获取蛇头的位置以及设置舌头的位置,
这里逻辑比较复杂的是,在设置蛇头的位置的时候,首先,如果当给蛇设的新值,<0,或者大于整个盒子的宽度时,就抛出错误,说明撞墙了。
接着要判断,蛇是否调头了,这个思路也比较容易,就是当蛇头与蛇的第二节位置相同时,就证明调头了。首先得判断第二节在不在,
如果相等证明掉头,所以我们要进行操作,当新值大于旧值时,证明蛇是往左走然后突然往右调头,此时要把新值设小。反之设大,然后再把新值赋给蛇头。
接着,因为我们是没吃到一个食物增加一个蛇的div,所以要让蛇动起来的话,就得做处理,蛇的每一节我们放在了this.bodies里面,所以我们只需要遍历,从后往前设,将上一个的位置,作为下一个的位置,比如倒数第二个的位置,就是最后一个下一次走到的位置,以此类推。
接着还要判断会不会撞到自己,
这个也容易,只要遍历。然后判断第一个是否会撞到其他的就行。
这里的位置有讲究的,我们必须将前一个的值给了后面后,才能赋予新值,否则先赋予新值,再将该值给后面的话,会引起有的蛇两节都是在一起的,因为他是先设新值,在获取。
最后检查撞到自己的时候,也必须是赋予值后再判断,否则没赋予新值判断的时候,肯定是会撞到自己的啦,因为蛇头的值还没改变,第二节的值此时通过moveBody是跟蛇头一样的,所以要给蛇头新值才能检查。
接着就是最后一个类,将三个类关联起来的类
//关联其他的类
class GameControl {
snake: Snake;
food: Food;
scorePanel: ScorePanel;
direction: string = "d";
isLive: boolean = true;
constructor() {
this.snake = new Snake();
this.food = new Food();
this.scorePanel = new ScorePanel();
this.init();
}
//游戏初始化
init() {
//绑定键盘事件
document.addEventListener("keydown", (e: KeyboardEvent) => {
this.keydownHandle(e);
});
this.run();
}
//检查是否吃到食物
checkEct(X: number, Y: number) {
if (Boolean(X === this.food.X && Y === this.food.Y)) {
this.snake.addBody();
this.food.change();
this.scorePanel.addScore();
}
}
keydownHandle(e: KeyboardEvent) {
this.direction = e.key;
}
run() {
let x = this.snake.X;
let y = this.snake.Y;
switch (this.direction) {
case "w":
case "ArrowUp":
y -= 10;
break;
case "s":
case "ArrowDown":
y += 10;
break;
case "a":
case "ArrowLeft":
x -= 10;
break;
case "d":
case "ArrowRight":
x += 10;
break;
default:
break;
}
this.checkEct(x, y);
try {
this.snake.X = x;
this.snake.Y = y;
} catch (e) {
alert(e.message);
this.isLive = false;
}
this.isLive &&
setTimeout(() => {
this.run();
}, 100 - this.scorePanel.level * 20);
}
}
const gameControl = new GameControl();
其实也很容易,
首先看构造函数,我们先把三个类的实例都获取到了作为最后一个类的实例属性,那么就可以在这个类对其他三个类的实例进行操作。
游戏初始化,我们得监听键盘事件,
获取到我们按下键盘的值,然后判断
先获取到当前舌头的位置,然后通过上下左右设置每一个新值,最后还要判断,是否吃到了食物,
如果吃到了食物,就直接通过每个类的实例来操作,很方便,增加身体,改变食物位置,增加分数。
当判断完后,我们还要将新值赋予蛇头,
使用try,catche来俘获我们刚才设下的跑出来的错误。吃措施,设置this,isLive为false,证明蛇已死。
然后通过this,isLive来判断是否在几秒后继续执行run方法。因为这个setTimeout是在run里面调用的,所以会一直调用,除非蛇死了。
因为我们按下的值是设在一个变量的,就算我们不按,下次启动run方法还是会根据上一个我们按下的按键来判断蛇应该怎么走,所以达到一个蛇自己动的效果。
以上就是四个类,通过ts完成贪吃蛇小项目。