之前学习了几篇的ts基础,今天我们就使用ts来完成一个贪吃蛇的小游戏。
我们将我们的任务进行简单拆解分析。
canvas
来实现。接下来我们根据上面的拆解做详细的需求梳理以及代码实现。
屏幕的实现
屏幕的实现是最为简单的,我们决定了使用canvas来绘制食物与蛇,那么我们直接创建一个canvas标签当作屏幕即可。
<canvas width="500" height="500"></canvas>
食物的实现
接下来我们思考食物应该如何实现。既然决定在canvas绘制食物,那么最简单的方式就是把食物绘制会一个矩形。而矩形的绘制需要四个参数,分别是起始点坐标以及宽高,食物的宽高我们就设定为10,所以不确定的也就至于起始点的坐标了。这个坐标决定了他会出现在屏幕的哪个位置。
还需要注意的是他的起始位置一定要在蛇的移动路径上,例如我们蛇的宽度为10,如果你的食物起始点在(11,11)这个坐标上,那么他就无法一次吃掉这个食物。
所以食物的坐标应该是10的倍数,且不能超过屏幕的边界。
我们还要思考到食物被吃掉后应该就会自动消失,所以蛇这个类还应该有个清除方法可以清除掉自己。
代码展示
class Drop {
width: number = 10
height: number = 10
x: number
y: number
color: string
constructor(
x: number = Math.floor(Math.random() * 49) * 10,
y: number = Math.floor(Math.random() * 49) * 10,
color: string = 'black'
) {
this.color = color
this.x = x
this.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: Array<Drop>
constructor() {
this.list = [new Drop(250, 250, 'red')]
}
}
我们让他在地图中心点生成,并使用红色进行与食物进行区分。class Snake {
list: Array<Drop>
direction: string
constructor(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 } = newHeader
const { x: foodX, y: foodY } = food
let isEatFood: boolean = false
if (newHeaderX === foodX && foodY === newHeaderY) {
isEatFood = true
}
switch (this.direction) {
case 'ArrowUp':
newHeader.y -= 10
break
case 'ArrowDown':
newHeader.y += 10
break
case 'ArrowLeft':
newHeader.x -= 10
break
case 'ArrowRight':
newHeader.x += 10
break
}
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 } = endDrop
const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
ctx.clearRect(x, y, width, height)
}
}
class Snake {
list: Array<Drop>
direction: string
isOut: boolean
constructor(direction: string = 'ArrowUp', speed: number = 100) {
this.list = [new Drop(250, 250, 'red')]
this.direction = direction
this.boolean = false
}
move() {
let newHeader = JSON.parse(JSON.stringify(this.list[0]))
const { x: newHeaderX, y: newHeaderY } = newHeader
const { x: foodX, y: foodY } = food
let isEatFood: boolean = false
if (newHeaderX === foodX && foodY === newHeaderY) {
isEatFood = true
}
if (this.direction) {
}
switch (this.direction) {
case 'ArrowUp':
newHeader.y -= 10
break
case 'ArrowDown':
newHeader.y += 10
break
case 'ArrowLeft':
newHeader.x -= 10
break
case 'ArrowRight':
newHeader.x += 10
break
}
// 是否吃到自己
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 } = endDrop
const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
ctx.clearRect(x, y, width, height)
}
}
渲染蛇与食物
我们写了食物与蛇的类,但是还没有真正在canvas上进行绘制。接下来我们使用ts的重载进行渲染类的绘制。
// 创建渲染函数
function renderDorp(dorp: Drop): void
function renderDorp(dorps: Array<Drop>): void
function renderDorp(dorps: Drop | Array<Drop>) {
if (Array.isArray(dorps)) {
dorps.forEach((element: Drop) => {
const { x, y, width, height, color } = element
const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
ctx.fillStyle = color
ctx.fillRect(x, y, width, height)
})
} else {
const { x, y, width, height, color } = dorps
const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
ctx.fillStyle = color
ctx.fillRect(x, y, width, height)
}
}
键盘监听
我么使用方向键来控制蛇的移动,那么就需要监听键盘事件。需要注意的是,我们在身体长度为1的时候通常是可以随意移动的,比如直接从右往左或者从上到下,但是当身体长度不为1的时候,我们的有了头尾的定义,就不应该在随意的上下或者左右移动了。毕竟他不像火车一样前后都有一个车头。
window.addEventListener('keydown', function (e) {
const { code } = e
const keys: string[] = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']
if (keys.includes(code)) {
if (snake.list.length === 1) {
snake.direction = code
return
}
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 = 10
height: number = 10
x: number
y: number
color: string
constructor(
x: number = Math.floor(Math.random() * 49) * 10,
y: number = Math.floor(Math.random() * 49) * 10,
color: string = 'black'
) {
this.color = color
this.x = x
this.y = y
}
del() {
const ctx: CanvasRenderingContext2D = canvasEle.getContext('2d')!
ctx.clearRect(this.x, this.y, this.width, this.height)
}
}
class Snake {
list: Array<Drop>
direction: string
isOut: boolean
constructor(direction: string = 'ArrowUp', speed: number = 100) {
this.list = [new Drop(250, 250, 'red')]
this.direction = direction
this.isOut = false
}
move() {
let newHeader = JSON.parse(JSON.stringify(this.list[0]))
const { x: newHeaderX, y: newHeaderY } = newHeader
const { x: foodX, y: foodY } = food
let isEatFood: boolean = false
if (newHeaderX === foodX && foodY === newHeaderY) {
isEatFood = true
}
if (this.direction) {
}
switch (this.direction) {
case 'ArrowUp':
newHeader.y -= 10
break
case 'ArrowDown':
newHeader.y += 10
break
case 'ArrowLeft':
newHeader.x -= 10
break
case 'ArrowRight':
newHeader.x += 10
break
}
// 是否吃到自己
const isEatSelf = this.list.some(({ x, y }) => {
if (x === newHeader.x && y === newHeader.y) {
return true
}
})
if (isEatSelf) {
this.isOut = true
return 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 = true
return alert('撞墙了!')
}
renderDorp(this.list)
}
addHead(dorp: Drop) {
this.list.unshift(dorp)
}
delFooter() {
const endDrop: Drop = this.list.pop()!
const { x, y, width, height } = endDrop
const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
ctx.clearRect(x, y, width, height)
}
}
// 创建渲染函数
function renderDorp(dorp: Drop): void
function renderDorp(dorps: Array<Drop>): void
function renderDorp(dorps: Drop | Array<Drop>) {
if (Array.isArray(dorps)) {
dorps.forEach((element: Drop) => {
const { x, y, width, height, color } = element
const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
ctx.fillStyle = color
ctx.fillRect(x, y, width, height)
})
} else {
const { x, y, width, height, color } = dorps
const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
ctx.fillStyle = color
ctx.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 } = e
const 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的拓展组件库,还在不断完善,希望大家支持