视频直播网站建设,招聘信息网站建设,界面设计师,代运营服务方案之前学习了几篇的ts基础#xff0c;今天我们就使用ts来完成一个贪吃蛇的小游戏。
游戏拆解
我们将我们的任务进行简单拆解分析。
首先我们应该有一个窗口#xff0c;我们叫做屏幕。让蛇在里面移动#xff0c;所有我们应该想到要设计一个大盒子当作地图。考虑到食物以及蛇…之前学习了几篇的ts基础今天我们就使用ts来完成一个贪吃蛇的小游戏。
游戏拆解
我们将我们的任务进行简单拆解分析。
首先我们应该有一个窗口我们叫做屏幕。让蛇在里面移动所有我们应该想到要设计一个大盒子当作地图。考虑到食物以及蛇的绘制我们可以使用canvas来实现。其次我们还会在地图随机投放食物这里还可以考虑食物该不该出现在蛇的身体节点上本文不做考虑所以我们大概率会创建一个类这个类用来诞生一个随机方块也就是食物。接着我们考虑蛇蛇在最开始应该也是一个随机方块然后通过移动吃到食物长长。
代码实现
接下来我们根据上面的拆解做详细的需求梳理以及代码实现。 屏幕的实现 屏幕的实现是最为简单的我们决定了使用canvas来绘制食物与蛇那么我们直接创建一个canvas标签当作屏幕即可。
canvas width500 height500/canvas食物的实现 接下来我们思考食物应该如何实现。既然决定在canvas绘制食物那么最简单的方式就是把食物绘制会一个矩形。而矩形的绘制需要四个参数分别是起始点坐标以及宽高食物的宽高我们就设定为10所以不确定的也就至于起始点的坐标了。这个坐标决定了他会出现在屏幕的哪个位置。 还需要注意的是他的起始位置一定要在蛇的移动路径上例如我们蛇的宽度为10如果你的食物起始点在1111这个坐标上那么他就无法一次吃掉这个食物。 所以食物的坐标应该是10的倍数且不能超过屏幕的边界。 我们还要思考到食物被吃掉后应该就会自动消失所以蛇这个类还应该有个清除方法可以清除掉自己。
代码展示
class Drop {width: number 10height: number 10x: numbery: numbercolor: stringconstructor(x: number Math.floor(Math.random() * 49) * 10,y: number Math.floor(Math.random() * 49) * 10,color: string black) {this.color colorthis.x xthis.y y}del() {const ctx: CanvasRenderingContext2D canvasEle.getContext(2d)!ctx.clearRect(this.x, this.y, this.width, this.height)}
}蛇的实现 蛇的实现相对来说就要复杂很多。
首先我们思考蛇的身体应该是怎样的为了他的灵活转向最简单的方式就是他的身体应该是一个一个的矩形拼接起来的。既然如此我们就可以直接使用上面的食物类这也是我将上面类的名字叫做Drop而不是Food的原因并且我在类里面添加了颜色进行蛇与食物的区别。接下来我们想到蛇既然是多个矩形拼接起来的那么应该有一个容器来有序的存放这些矩形所以我们定义个数组list来进行存放身体的数据。class Snake {list: ArrayDropconstructor() {this.list [new Drop(250, 250, red)]}}我们让他在地图中心点生成并使用红色进行与食物进行区分。接下来我们思考移动方法。蛇在移动的时候首先需要确认方法我们可以设定一个方向属性初始化的时候默认一个方向值。接着就是朝着方向移动如何移动呢如果简单的使用平移会发现蛇好像并不能灵活的转向蛇的身体也不会发生弯曲。这个时候我们就需要换一个思路。既然蛇是有一个个的矩形组成那么我们只需要控制里面的矩形就行了。当然也不是控制里面的矩形平移而是进行矩形的增加与删除操作。想象一下当蛇向上走一格这里我们设定基础格子就是10 x 10单位的是不是意味着我们将这个矩形的起点坐标的y值减去10所以我们直接创建一个蛇头部盒子的起点坐标y值减去10的盒子然后在直接删除蛇的最后一个盒子是不是就可以看作移动了一格。 当然我们也要考虑到吃到食物的情况这种情况下我们是不需要删除尾部矩形的。然后我们的类就补充成这样class Snake {list: ArrayDropdirection: stringconstructor(direction: string ArrowUp, speed: number 100) {this.list [new Drop(250, 250, red)]this.direction direction}move() {let newHeader JSON.parse(JSON.stringify(this.list[0]))const { x: newHeaderX, y: newHeaderY } newHeaderconst { x: foodX, y: foodY } foodlet isEatFood: boolean falseif (newHeaderX foodX foodY newHeaderY) {isEatFood true}switch (this.direction) {case ArrowUp:newHeader.y - 10breakcase ArrowDown:newHeader.y 10breakcase ArrowLeft:newHeader.x - 10breakcase ArrowRight:newHeader.x 10break}this.addHead(newHeader)// 判断是否吃到食物if (isEatFood) {food.del()food new Drop()renderDorp(food)} else {this.delFooter()}}addHead(dorp: Drop) {this.list.unshift(dorp)}delFooter() {const endDrop: Drop this.list.pop()!const { x, y, width, height } endDropconst ctx: CanvasRenderingContext2D canvasEle!.getContext(2d)!ctx.clearRect(x, y, width, height)}
}我还还应该考虑一些特殊情况例如移动到屏幕边缘会不会吃到自己得身体我们新增一个状态属性来判断他是否出局所以我们继续填充这个方法class Snake {list: ArrayDropdirection: stringisOut: booleanconstructor(direction: string ArrowUp, speed: number 100) {this.list [new Drop(250, 250, red)]this.direction directionthis.boolean false}move() {let newHeader JSON.parse(JSON.stringify(this.list[0]))const { x: newHeaderX, y: newHeaderY } newHeaderconst { x: foodX, y: foodY } foodlet isEatFood: boolean falseif (newHeaderX foodX foodY newHeaderY) {isEatFood true}if (this.direction) {}switch (this.direction) {case ArrowUp:newHeader.y - 10breakcase ArrowDown:newHeader.y 10breakcase ArrowLeft:newHeader.x - 10breakcase ArrowRight:newHeader.x 10break}// 是否吃到自己const isEatSelf this.list.some(({ x, y }) {if (x newHeader.x y newHeader.y) {return true}})if (isEatSelf) {alert(吃到自己了)return }this.addHead(newHeader)// 判断是否吃到食物if (isEatFood) {food.del()food new Drop()renderDorp(food)} else {this.delFooter()}// 判断是否达到边界if (newHeaderX 500 ||newHeaderY 500 ||newHeaderX 0 ||newHeaderY 0) {return alert(撞墙了)}renderDorp(this.list)}addHead(dorp: Drop) {this.list.unshift(dorp)}delFooter() {const endDrop: Drop this.list.pop()!const { x, y, width, height } endDropconst ctx: CanvasRenderingContext2D canvasEle!.getContext(2d)!ctx.clearRect(x, y, width, height)}
}渲染蛇与食物 我们写了食物与蛇的类但是还没有真正在canvas上进行绘制。接下来我们使用ts的重载进行渲染类的绘制。
// 创建渲染函数
function renderDorp(dorp: Drop): void
function renderDorp(dorps: ArrayDrop): void
function renderDorp(dorps: Drop | ArrayDrop) {if (Array.isArray(dorps)) {dorps.forEach((element: Drop) {const { x, y, width, height, color } elementconst ctx: CanvasRenderingContext2D canvasEle!.getContext(2d)!ctx.fillStyle colorctx.fillRect(x, y, width, height)})} else {const { x, y, width, height, color } dorpsconst ctx: CanvasRenderingContext2D canvasEle!.getContext(2d)!ctx.fillStyle colorctx.fillRect(x, y, width, height)}
}键盘监听 我么使用方向键来控制蛇的移动那么就需要监听键盘事件。需要注意的是我们在身体长度为1的时候通常是可以随意移动的比如直接从右往左或者从上到下但是当身体长度不为1的时候我们的有了头尾的定义就不应该在随意的上下或者左右移动了。毕竟他不像火车一样前后都有一个车头。
window.addEventListener(keydown, function (e) {const { code } econst keys: string[] [ArrowUp, ArrowDown, ArrowLeft, ArrowRight]if (keys.includes(code)) {if (snake.list.length 1) {snake.direction codereturn}if (snake.direction ArrowUp code ArrowDown) {return}if (snake.direction ArrowDown code ArrowUp) {return}if (snake.direction ArrowLeft code ArrowRight) {return}if (snake.direction ArrowRight code ArrowLeft) {return}snake.direction code}
})最后补充完整的实现代码
const canvasEle document.querySelector(canvas)!
let food: Drop
let snake: Snake
class Drop {width: number 10height: number 10x: numbery: numbercolor: stringconstructor(x: number Math.floor(Math.random() * 49) * 10,y: number Math.floor(Math.random() * 49) * 10,color: string black) {this.color colorthis.x xthis.y y}del() {const ctx: CanvasRenderingContext2D canvasEle.getContext(2d)!ctx.clearRect(this.x, this.y, this.width, this.height)}
}class Snake {list: ArrayDropdirection: stringisOut: booleanconstructor(direction: string ArrowUp, speed: number 100) {this.list [new Drop(250, 250, red)]this.direction directionthis.isOut false}move() {let newHeader JSON.parse(JSON.stringify(this.list[0]))const { x: newHeaderX, y: newHeaderY } newHeaderconst { x: foodX, y: foodY } foodlet isEatFood: boolean falseif (newHeaderX foodX foodY newHeaderY) {isEatFood true}if (this.direction) {}switch (this.direction) {case ArrowUp:newHeader.y - 10breakcase ArrowDown:newHeader.y 10breakcase ArrowLeft:newHeader.x - 10breakcase ArrowRight:newHeader.x 10break}// 是否吃到自己const isEatSelf this.list.some(({ x, y }) {if (x newHeader.x y newHeader.y) {return true}})if (isEatSelf) {this.isOut truereturn alert(吃到自己了)}this.addHead(newHeader)// 判断是否吃到食物if (isEatFood) {food.del()food new Drop()renderDorp(food)} else {this.delFooter()}// 判断是否达到边界if (newHeaderX 500 ||newHeaderY 500 ||newHeaderX 0 ||newHeaderY 0) {this.isOut truereturn alert(撞墙了)}renderDorp(this.list)}addHead(dorp: Drop) {this.list.unshift(dorp)}delFooter() {const endDrop: Drop this.list.pop()!const { x, y, width, height } endDropconst ctx: CanvasRenderingContext2D canvasEle!.getContext(2d)!ctx.clearRect(x, y, width, height)}
}// 创建渲染函数
function renderDorp(dorp: Drop): void
function renderDorp(dorps: ArrayDrop): void
function renderDorp(dorps: Drop | ArrayDrop) {if (Array.isArray(dorps)) {dorps.forEach((element: Drop) {const { x, y, width, height, color } elementconst ctx: CanvasRenderingContext2D canvasEle!.getContext(2d)!ctx.fillStyle colorctx.fillRect(x, y, width, height)})} else {const { x, y, width, height, color } dorpsconst ctx: CanvasRenderingContext2D canvasEle!.getContext(2d)!ctx.fillStyle colorctx.fillRect(x, y, width, height)}
};(function () {food new Drop()snake new Snake()renderDorp(food)let timer setInterval(() {snake.move()if (snake.isOut) {clearInterval(timer)}}, 100)window.addEventListener(keydown, function (e) {const { code } econst keys: string[] [ArrowUp, ArrowDown, ArrowLeft, ArrowRight]if (keys.includes(code)) {if (snake.list.length ! 1) {if (snake.direction ArrowUp code ArrowDown) {return}if (snake.direction ArrowDown code ArrowUp) {return}if (snake.direction ArrowLeft code ArrowRight) {return}if (snake.direction ArrowRight code ArrowLeft) {return}}snake.direction code}})
})() 这只是一个简单版贪吃蛇效果没有经过严格测试肯定会有bug希望可以留言交流 再推一个自己插件element-ui的拓展组件库还在不断完善希望大家支持