该游戏的主要思想:将目标区域或者说盒子网格化,通过网格化来对蛇进行生成、移动等操作
html和css很少,主要是规划盒子的将要用到的开始按钮、暂停按钮以及蛇的头和身体、食物的大小、定位和类名。
html:
<div class="game">
<div class="game_start">
<img src="img/btn1.gif">
</div>
<div class="game_pause">
<img src="img/btn4.png">
</div>
</div>
css:
* {
padding: 0;
margin: 0;
}
.game {
position: relative;
width: 600px;
height: 600px;
background-color: #fce4ec;
margin: 50px auto;
border: 20px solid #f8bbd0;
}
.game_start,
.game_pause {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
}
.game_pause {
display: none;
}
.game_start img {
width: 300px;
}
.game_pause img {
width: 100px;
}
.snake_hd {
position: absolute;
width: 20px;
height: 20px;
top: 0;
left: 0px;
background-image: url(img/snake.png);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.snake_bd {
position: absolute;
top: 0;
left: 0px;
width: 20px;
height: 20px;
background-color: #649c49;
border-radius: 50%;
}
.food {
position: absolute;
width: 20px;
height: 20px;
left: 0px;
top: 0px;
background-image: url(img/food2.png);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
以下js中:建议将每一块的第一部分放在所有方法的前面(因为他们都是全局配置,放前面预防出差错),我文中这样放置是为了方便大家观看。
一、初始化游戏(initGame)
1、地图的初始化
这个盒子是600x600 px的盒子,我将其分成30x30 的网格,每个格子是20x20 px大小
用双重for循环对地图进行初始化,用对象将获取的地图储存
var gridData = []; //存储地图对象
// 整个网格的行与列
var tr = 30;
var td = 30;
function initGame() {
// 1.初始化地图
for (var i = 0; i < tr; i++) {
for (var j = 0; j < td; j++) {
gridData.push({
x: j,
y: i
})
}
}
// console.log(gridData);
// 2.绘制蛇
drawSnake(snake);
// 3.绘制食物
drawFood();
}
2.绘制蛇(因为初始化需要调用绘制蛇的方法,所有书写时应该在initGame前面)
// 蛇的身体大小
var snakeBody = 20;
// 蛇相关的配置信息
var snake = {
// 蛇一开始移动的方向
direction: directionNum.right, //一开始向右边移动
// 蛇的初始位置
snakePos: [
{ x: 0, y: 0, domContent: '', flag: 'body' },
{ x: 1, y: 0, domContent: '', flag: 'body' },
{ x: 2, y: 0, domContent: '', flag: 'body' },
{ x: 3, y: 0, domContent: '', flag: 'head' },
]
}
// 绘制蛇的方法
function drawSnake(snake) {
for (var i = 0; i < snake.snakePos.length; i++) {
if (!snake.snakePos[i].domContent) {
// 进入此说明第一次创建蛇
snake.snakePos[i].domContent = document.createElement('div');
snake.snakePos[i].domContent.style.left = snake.snakePos[i].x * snakeBody + 'px';
snake.snakePos[i].domContent.style.top = snake.snakePos[i].y * snakeBody + 'px';
if (snake.snakePos[i].flag === 'head') {
// 进入此if说明是蛇头
snake.snakePos[i].domContent.className = 'snake_hd';
// 根据方向进行旋转
switch (snake.direction.flag) {
case 'top':
{
snake.snakePos[i].domContent.style.transform = 'rotate(-90deg)';
break;
}
case 'bottom':
{
snake.snakePos[i].domContent.style.transform = 'rotate(90deg)';
break;
}
case 'left':
{
snake.snakePos[i].domContent.style.transform = 'rotate(180deg)';
break;
}
case 'right':
{
snake.snakePos[i].domContent.style.transform = 'rotate(0deg)';
break;
}
}
} else {
// 说明是蛇身
snake.snakePos[i].domContent.className = 'snake_bd';
}
}
// 需要将创建的 DOM元素添加到container 容器上面
document.querySelector('.game').append(snake.snakePos[i].domContent);
}
}
3.绘制食物(因为初始化需要调用绘制食物的方法,所有书写时应该在initGame前面)
//食物相关的配置信息
var food = {
x: 0,
y: 0,
domContent: ''
}
// 绘制食物的方法
function drawFood() {
// 1.食物的坐标是随机的
// 2.食物不能生成在蛇头或者蛇身上面
while (true) {
// 构成一个死循环,直到生成符合要求的食物坐标才能退出该循环
var isRepeat = false; //默认生成的坐标是符合要求的
// 随机生成一个坐标
food.x = Math.floor(Math.random() * tr);
food.y = Math.floor(Math.random() * tr);
// 查看坐标是否符合要求(遍历蛇)
for (var i = 0; i < snake.snakePos.length; i++) {
if (snake.snakePos[i].x === food.x && snake.snakePos[i].y === food.y) {
// 进入此if 说明当前生成的食物坐标和蛇的坐标冲突了
isRepeat = true;
break;
}
}
if (!isRepeat) {
// 跳出while循环
break;
}
}
// 跳出while循环说明食物的坐标符合要求
if (!food.domContent) {
food.domContent = document.createElement('div');
food.domContent.className = 'food';
document.querySelector('.game').append(food.domContent);
}
food.domContent.style.left = food.x * snakeBody + 'px';
food.domContent.style.top = food.y * snakeBody + 'px';
}
二、碰撞测试
//游戏分数
var score = 0;
// 碰撞检测
function isCollide(newHead) {
var collideCheckInfo = {
isCollide: false, //是否碰撞墙壁、蛇身
isEat: false //是否吃到食物
}
// 1、检测是否碰到墙壁
if (newHead.x < 0 || newHead.x > td - 1 || newHead.y < 0 || newHead.y > tr - 1) {
collideCheckInfo.isCollide = true;
return collideCheckInfo;
}
// 2、检测是否碰到自己
for (var i = 0; i < snake.snakePos.length; i++) {
if (snake.snakePos[i].x === newHead.x && snake.snakePos[i].y === newHead.y) {
collideCheckInfo.isCollide = true;
return collideCheckInfo;
}
}
// 3、检测是否吃到东西
if (newHead.x === food.x && newHead.y === food.y) {
collideCheckInfo.isEat = true;
score++; //分数自增
}
return collideCheckInfo;
}
三、蛇移动
蛇移动的核心方法就是:获取旧头的位置,通过旧头确定新头的位置(旧头位置+键盘绑定事件)
再将旧头改成身体。
// 明确新的蛇头和旧的蛇头之间的位置关系
// 在明确新的蛇头坐标的时候,会拿下面的对象和旧的蛇头做计算
//得到新的蛇头的坐标
var directionNum = {
left: { x: -1, y: 0, flag: 'left' },
right: { x: 1, y: 0, flag: 'right' },
top: { x: 0, y: -1, flag: 'top' },
bottom: { x: 0, y: 1, flag: 'bottom' },
}
function snakeMove() {
var oldHead = snake.snakePos[snake.snakePos.length - 1];
// 根据方向计算出新的蛇头的坐标
var newHead = {
domContent: '',
x: snake.snakePos[snake.snakePos.length - 1].x + snake.direction.x,
y: snake.snakePos[snake.snakePos.length - 1].y + snake.direction.y,
flag: 'head'
}
// 碰撞游戏结束检测
// 看新的蛇头是否碰上食物、身体、墙壁
var collideCheckResult = isCollide(newHead);
if (collideCheckResult.isCollide) {
//进入此if,说明碰墙了
alert('游戏结束,您当前的分数为' + score + ' 分,是否重新开始游戏?')
clearInterval(timerStop);
}
// 将旧的头改成身体
oldHead.flag = 'body';
oldHead.domContent.className = 'snake_bd';
snake.snakePos.push(newHead);
// 判断是否吃到东西
if (collideCheckResult.isEat) {
// 1.重新生成新的食物
drawFood();
} else {
//说明没有吃到食物
// 移除最后一个元素
document.querySelector('.game').removeChild(snake.snakePos[0].domContent);
snake.snakePos.shift();
}
// 重新绘制蛇
drawSnake(snake);
}
四、蛇自动移动
// 停止计时器
var timerStop = null;
var time = 100;
// 自动移动
function startGame() {
timerStop = setInterval(function() {
snakeMove();
}, time)
}
五、绑定事件
// 绑定事件
function bindEvent() {
// 1.首先是键盘事件,用户按上下左右,蛇能够移动
document.onkeydown = function(e) {
if ((e.key === 'ArrowUp' || e.key.toLocaleLowerCase() === 'w') && snake.direction.flag !== 'bottom') {
// 用户按的是上
snake.direction = directionNum.top;
}
if ((e.key === 'ArrowDown' || e.key.toLocaleLowerCase() === 's') && snake.direction.flag !== 'top') {
// 用户按的是下
snake.direction = directionNum.bottom;
}
if ((e.key === 'ArrowLeft' || e.key.toLocaleLowerCase() === 'a') && snake.direction.flag !== 'right') {
// 用户按的是左
snake.direction = directionNum.left;
}
if ((e.key === 'ArrowRight' || e.key.toLocaleLowerCase() === 'd') && snake.direction.flag !== 'left') {
// 用户按的是右
snake.direction = directionNum.right;
}
}
// 2. 计时器自动调用蛇移动的方法
startGame();
// 3.点击整个容器的时候,可以暂停
document.querySelector('.game').onclick = function() {
document.querySelector('.game_pause').style.display = 'block';
clearInterval(timerStop);
}
// 4.给暂停按钮绑定事件
document.querySelector('.game_pause').onclick = function(e) {
e.stopPropagation();
document.querySelector('.game_pause').style.display = 'none';
startGame();
}
}
六、游戏主方法
function main() {
// 用户点击了开始游戏后才开始做后续
game_start.onclick = function(e) {
e.stopPropagation(); //阻止冒泡
game_start.style.display = 'none';
// 1.首先第一步:初始化游戏
initGame();
// 2.绑定事件
bindEvent();
}
}
main();