手把手教你写贪吃蛇👩🔧原生JS
超详细的教程,一步一步和我来,你一定会学会的~
绘制界面
设置分数和速度
<h1>score:</h1>
<button>快</button>
<button>中</button>
<button>慢</button>
分数字体大,采用h1
速度需要用户点击按钮进行选择,所以采用button
画运动界面
- 用表格显示格子,作为刻度(十行十列),每个格子的宽为40px,高为40px;
table>tr*10>th*10
; 快捷方式生成十行十列的表格
样式:
①在html的table标签中加入样式:
border=“1” (表格的边框长度为1px) cellpadding=“0”(表格的内边距为0px) cellspacing="0"这个别忘了设定,因为我这里默认是2px,就导致对不齐
②在css中给每一个td都设置长和宽为38px(因为border四边还有1px占用
③position:absolute 使表格浮动起来,不占用空间,以便div重叠上去
用一个大div设置实际运动场所
用一个大的div来供JS插入新的div(主战场)
样式:
①宽高:均为400px
②背景颜色也是在div上设置,不是在表格上设置
封装产生div的方法
通过在大的div后面附加子div的方式,来显示蛇头,蛇身子和食物。
封装一个函数还创建小div
var map=document.getElementById("map");
function creatDiv(color){
var div = document.createElement("div");
map.appendChild(div);
}
给小div设置大小
function creatDiv(color){
var div = document.createElement("div");
div.style.width="38px";
div.style.height="38px";
map.appendChild(div);
}
给小div根据传入参数的不同而设置颜色的不同
var map=document.getElementById("map");
function creatDiv(color){
var div = document.createElement("div");
div.style.width="40px";
div.style.height="40px";
div.style.backgroundColor=color;
map.appendChild(div);
}
creatDiv("red"); //创建一个蛇头
creatDiv("red"); //创建一个食物
当创建好蛇头和食物后,要让他们的位置也变成absolute。目的是要让他们浮动起来,并且如果生成在同一行的时候,一个不会把另一个挤下去
div.style.position="absolute";
利用随机数产生蛇头和食物div
Math.random();
随机产生一个[0,1)的随机数
我们的要求:div的left和top应该位于[0,360]之间,并且必须是40的倍数
方法:
Math.random()*10 =>产生一个[0,10)的随机数,即[0,9]的随机数。
parseInt(Math.random()*10) =>将其转化为[0,9]的整数
parseInt(Math.random()*10) *40 =>产生一个[0,360],且为40的倍数的整数
div.style.left = parseInt(Math.random() * 10) * 40 + "px";
div.style.top = parseInt(Math.random() * 10) * 40 + "px";
利用返回值得到通过函数创建的div
因为之后要获取蛇头和食物的位置,但因是在函数中创建的,所以函数已结束就临时变量销毁了,无法获取,所以通过设置函数的返回值为div,并var一个div承接,来得到蛇头和食物
function creatDiv(color) {
XXXXX
return div;
}
var headNode = creatDiv("red");
var foodNode = creatDiv("blue");
利用定时器让蛇头移动起来
给headNode一个value属性,规定他的移动方向(默认初始为向上移动)
headNode.value="up";
利用定时器函数,让蛇头移动(move是个移动的函数)
var t = setInterval(move, 1000);
定义move函数(注意这里的变换坐标形式)
function move() {
switch (headNode.value) {
case "up":
headNode.style.top = parseInt(headNode.style.top) - 40 + "px";
break;
case "down":
headNode.style.top = parseInt(headNode.style.top) + 40 + "px";
break;
case "right":
headNode.style.left = parseInt(headNode.style.left) + 40 + "px";
break;
case "left":
headNode.style.left = parseInt(headNode.style.left) - 40 + "px";
break;
}
}
通过键盘的按键来改变蛇头的移动方向
-
利用键盘按下事件: document.onkeydown()。利用上下左右键来更改蛇头的移动方向
-
通过获取键盘的键值,对应不同的方向,那应该怎么获取呢?
在本例的事件编程中,由
document.onkeydown
可得:事件源是我的键盘,事件就是键盘按下。function(){ }
是事件处理函数。在事件处理函数当中其实存在一个默认的参数event(可写可不写,用的时候需要写),表示事件对象,所以获取键盘的键值,就可以通过event.keyCode来获取document.onkeydown=function(event){ event=event||window.event; }
-
监听键值,并根据键盘上敲击的键更改蛇头的移动方向
并且,在蛇有身体之后,不能让蛇头在向一个方向移动的时候向相对方向移动(自噬)。
document.onkeydown = function (event) { event = event || window.event; switch (event.keyCode) { case 37: if(headNode.value!="right"||arr.length==0) headNode.value = "left"; break; case 38: if(headNode.value!="down" || arr.length == 0) headNode.value = "up"; break; case 39: if(headNode.value!="left" || arr.length == 0) headNode.value = "right"; break; case 40: if(headNode.value!="up" || arr.length == 0) headNode.value = "down"; break; } }
检测是否吃到食物并改变食物位置
-
首先需要思考把监测步骤放到哪里?
应该放到move函数里,每移动一次就判断时候吃到食物
-
如何判断呢?
当蛇头的top等于食物的top,蛇头的left等于食物的left时,就相等于吃到了食物
-
如果吃到食物后该怎么更新食物的位置呢?
我本来想的是利用原来createDiv函数重新生成一个div,但是这样不仅执行的代码量多,而且还需要删除原来的食物div,所以正确方法应该是重新刷新食物的top和left值即可
function move() { switch (headNode.value) { xxxx... } if(foodNode.style.top==headNode.style.top&& foodNode.style.left == headNode.style.left){ foodNode.style.top == parseInt(Math.random() * 10) * 40 + "px"; foodNode.style.left == parseInt(Math.random() * 10) * 40 + "px"; } }
产生新的身体
-
通过什么产生新的身体? createNode函数
var newNode=createNode("yellow");
-
将新身体跟在什么的后面? 跟在最后一节的后面。
所以🐍的身体应该用数组保存。
在全局中:
var arr=[];//保存身体的数组
还要分情况讨论:
如果只有一个蛇头,最后一节就是蛇头
如果蛇头后面还有身体,即length>0,通过arr[length-1]来获取蛇的最后一节
//吃到了食物 if (foodNode.style.top == headNode.style.top && foodNode.style.left == headNode.style.left) { foodNode.style.top = parseInt(Math.random() * 10) * 40 + "px"; foodNode.style.left = parseInt(Math.random() * 10) * 40 + "px"; var newNode=creatDiv("yellow"); var lastNode; //得到最后一节 if(arr.length>0){ lastNode=arr[length-1]; } else{ lastNode=headNode; } }
-
给新的一节设置value(移动方向)
新的一节的移动方向应该和lastNode的移动方向相同
newNode.value=lastNo
-
将新身体也附在身体数组中
arr.push(newNode);
根据最后一块的运动方向确定新产生一节的位置
-
如何确定新产生的一节的位置? 需要找到最后一节的移动方向,如果向右,新的一节在它左侧出现,如果向左,新的一节在他右侧出现
-
如何找到最后一节的移动方向? 找到最后一节的value
如果最后一节向右,那么如何让新的一节在最后一节的左侧出现? 让newNode的left = lastNode的left + 40px, 新一节的top和最后一节的top相同。其他方向同理。
身体的移动
-
身体移动写在哪?
身体移动也是移动的一部分,所以应该写在move函数中
-
是先让蛇头移动还是先让身体移动?
先让身体移动
-
每一节应该是先移动身体再调整方向,还是想调整方向再移动身体?
因为每一节身体应该与他上一节上一次的运动方向相同。所以应该是先移动身体(本次移动的时候的方向还是和上一节上一次移动方向保持一致),在调整方向。
-
要移动每一节,应该用for循环遍历身体arr数组,那遍历时是从第一节开始还是从最后一节开始?
如果从第一节开始,那么会出现:第一节的运动方向=头,第二节的运动方向=第一节=头,即所有的节的运动方向都等于头的运动方向,所以不对。应该是从最后一节开始遍历。
所以,整个蛇的移动应该是从最后一节开始,不断向前的。
function move() {
//遍历整个蛇身子,从最后一节开始
for(var i=arr.length-1;i>=0;i--){
//蛇的一节移动
switch (arr[i].value) {
case "up":
arr[i].style.top = parseInt(arr[i].style.top) - 40 + "px";
break;
case "down":
arr[i].style.top = parseInt(arr[i].style.top) + 40 + "px";
break;
case "right":
arr[i].style.left = parseInt(arr[i].style.left) + 40 + "px";
break;
case "left":
arr[i].style.left = parseInt(arr[i].style.left) - 40 + "px";
break;
}
//蛇的一节调整方向,方向与蛇的上一节的上一次的移动方向一致
if(i==0){
arr[i].value=headNode.value;
}
else{
arr[i].value=arr[i-1].value;
}
}
//蛇头移动
switch (headNode.value) {
xxxx....
}
//移动过程中吃到了食物
if (foodNode.style.top == headNode.style.top && foodNode.style.left == headNode.style.left) {
var newNode=creatDiv("yellow"); //身体增加一节
var lastNode;
//得到最后一节
//给新节点的移动方向赋值
newNode.value = lastNode.value;
//通过原来最后一个节点的方向更改新节点的位置
//将新的一节附在身体数组
//更新食物位置
}
}
判断蛇死亡
情况1:超出边界,撞墙死
情况2:碰到自己的身体,即for循环判断每一块身体是否和头重合
if (parseInt(headNode.style.top) < 0 || parseInt(headNode.style.left) < 0 || parseInt(headNode.style.top) > 360 || parseInt(headNode.style.left) > 360) {
clearInterval(t);
alert("蛇撞墙了,游戏结束");
}
//碰到自己身体死了
if (arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].style.top == headNode.style.top && arr[i].style.left == headNode.style.left) {
clearInterval(t);
alert("蛇碰到自己身体了,游戏结束");
}
}
}
更新食物位置的时候防止其在身体部位生成
//更新食物位置
px= parseInt(Math.random() * 10) * 40 + "px";
py= parseInt(Math.random() * 10) * 40 + "px";
//遍历整个身子
for(var i=0;i<arr.length;i++){
//如果身子的某一节和食物
if(parseInt(arr[i].left)==px&&parseInt(arr[i].top)==py){
//重新生成蛇的位置
px = parseInt(Math.random() * 10) * 40 + "px";
py = parseInt(Math.random() * 10) * 40 + "px";
//重置i重新从头判断
i=-1;
}
}
foodNode.style.left =px+"px";
foodNode.style.top=py+"px";
吃到食物后增加分数
html中:
<span>
<h1 id="Score">score:</h1>
</span>
设置全局变量:
var score = 0;//计分
var span = document.getElementById("Score");
吃到食物后:
score += 10;
span.innerHTML = "<h1>Score:" + score + "</h1>";
点击按钮控制速度
js中以点击按钮事件传入参数进行控制速度:
/将不变的速度改成传参可以控制的速度,绑定按钮进行传参
原来:
var t = setInterval(move, 500);
现在:
function changeTime(speed){
var t = setInterval(move, speed);
}
html中:
<button onclick="changeTime('200')">快</button>
<button onclick="changeTime('300')">中</button>
<button onclick="changeTime('500')">慢</button>