上周突然想做个小游戏,想起2016年年底做过一个2048体验一把,GitHub:https://github.com/YutLee/game-2048
现在就做一个简单的贪吃蛇,来一局,GitHub:https://github.com/YutLee/snake好了,于是某天下午构思并完成了第一个版本,前天晚上想起还有bug,又花点时间修复
5BEA6381-87E4-46F3-86BD-79E0C90876FD.GIF
下面说说制作思路:
QQ截图20210714105702.png
整个游戏有什么呢,首先是场地、蛇、食物,然后可以控制蛇转向。
第一步:生成场地,场地我们用是一个10*10的二维数组坐标来标记
/**
* 生成场地
*/
let maxX = 10, maxY = 10
function createSpace () {
var space = []
for (var i = 0; i < maxY; i++) {
var xs = []
for (var j = 0; j < maxX; j++) {
xs.push([j, i])
}
space.push(xs)
}
return space
}
蛇和食物都是坐标上的点,那么我们需要一个方法生成一个随机整数
/**
* 生成一个随机整数
*/
function createInt (max) {
return Math.floor(Math.random() * max)
}
然后需要一个在场地范围内生成随机点的方法
/**
* 在场地内生成随机点
*/
function createPoint (existed = []) {
let empty, idx = 0
empty = emptySpace.filter(item => existed.find(entry => entry[0] === item[0] && entry[1] === item[1]) === undefined)
idx = createInt(empty.length)
return empty[idx]
}
第二步:生成蛇,为了一开始可以区分蛇和食物,蛇需要用相连的两个点来表示,所以生成一个随机点代表蛇头,然后在该点上下左右依次判断是否在场地内,如果是则该点视为蛇尾
/**
* 生成蛇
*/
let snakes = []
function createSnake () {
const snake = createPoint()
snakes.push(snake)
if (snake[0] + 1 < maxX) {
snakes.push([snake[0] + 1, snake[1]])
} else if (snake[0] - 1 < maxX) {
snakes.push([snake[0] - 1, snake[1]])
} else if (snake[1] + 1 < maxY) {
snakes.push([snake[0], snake[1] + 1])
} else if (snake[1] - 1 < maxY) {
snakes.push([snake[0], snake[1] - 1])
}
}
第三步:生成食物
/**
* 生成食物
*/
let food
function createFood (snakes) {
food = createPoint(snakes)
}
第四步:对边界的判断,墙壁、蛇身、没有空间了(赢了)
/**
* 判断是否墙壁
*/
function isWall (x, y) {
return x < 0 || y < 0 || x >= maxX || y >= maxY
}
/**
* 判断是否咬到自己
*/
function isSelf (x, y) {
const size = snakes.length
let self = false
for (var i = 4; i < size; i++) {
if (snakes[i][0] === x && snakes[i][1] === y) {
self = true
break
}
}
return self
}
/**
* 判断是否赢了(没有空间了)
*/
const maxSize = maxX * maxY
function isWin () {
return snakes.length === maxSize
}
/**
* 游戏结束
*/
let isOver
function gameOver (text) {
isOver = true
clearTimeout(timer)
clearEvent(text)
}
第五步:实体都准备好了,那么需要蛇可以移动
/**
* 获取下一步坐标
*/
function nextPoint (point = [], step = 1) {
const steps = getStep(step)[directionIdx]
return {
x: point[0] + steps[0],
y: point[1] + steps[1]
}
}
/**
* 蛇移动
*/
let timer
const speed = 500 // 每500ms前进一步
function move () {
const { x, y } = nextPoint(snakes[0])
let last
clearTimeout(timer)
if (isOver) return
if (isWall(x, y)) {
gameOver('蛇撞墙而亡')
return
}
if (isSelf(x, y)) {
gameOver('蛇吃了自己')
return
}
if (isWin()) {
gameOver('蛇吃饱了')
return
}
snakes.unshift([x, y])
if (food[0] === x && food[1] === y) {
score += 10
createFood(snakes)
} else {
last = snakes.pop()
}
// snakeMove(last) //界面上移动效果
timer = setTimeout(() => {
move()
}, speed)
}
第六步:上面都是数据,那么还需要在界面上显示,加入界面更新方法
/**
* 创建空间UI
*/
function createSpaceUI () {
const space = createSpace()
const dom = document.getElementById('space')
let html = ''
space.forEach(item => {
html += `<div class="row flex">`
item.forEach(entry => {
emptySpace.push(entry)
html += `<div class="column" id="${entry[0]}-${entry[1]}"></div>`
})
html += '</div>'
})
dom.innerHTML = html
}
/**
* 获取移动步伐
*/
function getStep (step = 1) {
return [[-step, 0], [0, -step], [step, 0], [0, step]]
}
/**
* 蛇移动UI
*/
function snakeMove (last = []) {
let lastDom
if (Array.isArray(last)) {
lastDom = document.getElementById(`${last[0]}-${last[1]}`)
if (lastDom) {
lastDom.className = 'column'
}
}
snakes.forEach(item => {
const snakesDom = document.getElementById(`${item[0]}-${item[1]}`)
if (snakesDom) {
snakesDom.className = 'column snake'
}
})
}
第七步:加入事件,键盘上下左右键,WSAD键和界面按钮事件
window.addEventListener('keydown', handleKeyDown, false)
document.getElementById('handle').addEventListener('click', handleClick, false)
function clearEvent (text) {
document.getElementById('alert-title').innerHTML = text
document.getElementById('this-score').innerHTML = score
document.getElementById('shadow').className = 'shadow open'
document.removeEventListener('keydown', handleKeyDown, false)
}
function handleClick (event) {
const id = event.target.id
const keyCode = Number(id.replace('key-', ''))
if (id === 'handle') return
handleMove(keyCode)
}
function handleKeyDown (event) {
let keyCode = event.which || event.keyCode
if (keyCode === 65) { // A
keyCode = 37
} else if (keyCode === 87) { // W
keyCode = 38
} else if (keyCode === 68) { // D
keyCode = 39
} else if (keyCode === 83) { // S
keyCode = 40
}
handleMove(keyCode)
}
function handleMove (keyCode) {
const currentIdx = keyCode - 37
const isForward = directionIdx === currentIdx
const isBack = directionIdx === currentIdx + (keyCode > 38 ? -2 : 2)
const isTurn = !isForward && !isBack && /^(37|38|39|40)$/.test(keyCode)
const pauseDom = document.getElementById('key-32')
if (keyCode === 32) { // 空格
if (isPause === true) {
isPause = false
move()
pauseDom.innerHTML = '暂时'
} else {
isPause = true
clearTimeout(timer)
pauseDom.innerHTML = '开始'
}
return
}
if (isForward) {
clearTimeout(timer)
move()
}
if (isTurn) {
clearTimeout(timer)
directionIdx = currentIdx
direction = directions[directionIdx]
move()
}
}
OK,游戏开始
/**
* 游戏开始
*/
function start () {
createSpaceUI()
createSnake()
createFood(snakes)
snakeMove()
move()
}