贪吃蛇小游戏体验点击体验通过画布实现的贪吃蛇小游戏。
游戏效果如下:
小游戏的源码如下:
<!DOCTYPE HTML> <html> <head> <meta charset='utf-8'/> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>贪吃蛇</title> <style> body{margin:0;background:#000;} #mycanvas{position:fixed;z-Index:2;top:0;bottom:0;background:transparent;} #canvasbg{position:fixed;z-Index:1;top:0;bottom:0;} .disbtn{position:fixed;z-Index:5;right:5px;top:5px;} .open_div{position:fixed;z-Index:5;left:0;top:0;bottom:35px;right:0;background:rgba(255,255,255,0.5);white-space:nowrap;text-align:center;} .open_div:after{content:"";display:inline-block;height:100%;vertical-align:middle;width:0;font-size:0;} .open_tip{display:inline-block;vertical-align:middle;} .tip{line-height:25px;font-size:13px;} .start-cont{text-align:center;margin-top:25px;} .startbtn{display:inline-block;padding:3px 8px;font-size:14px;color:#fff;background:blue;cursor:pointer;} .loadresource{position:fixed;z-Index:15;left:0;top:0;width:100%;height:100%;background:#F9A73D;} </style> </head> <body> <div class='loadresource' id='loadimg'>loading</div> <div class='open_div' id='opentip'> <div class='open_tip'> <div class='tip'>左拐弯:向左滑动</div> <div class='tip'>右拐弯:向右滑动</div> <div class='tip'>上拐弯:向上滑动</div> <div class='tip'>下拐弯:向下滑动</div> <div class='start-cont' ><span class='startbtn ' id='start'>开始游戏</span></div> </div> </div> <canvas id='mycanvas'> </canvas> <canvas id='canvasbg'> </canvas> <div class='open_div' id='gameover' style='display:none;'> <div class='open_tip'> <div class='tip'>是否重新开始游戏?</div> <div class='start-cont'><span class='startbtn' id='restart'>重新开始</span></div> </div> </div> </body> <script> function start_snake(){ document.getElementById("opentip").style.display='none'; flag=true; //paint(); } function restart(){ document.getElementById("gameover").style.display='none'; a=-1;b=-1;score=0; fangxiang=2;last=2; len=5; clearMap(); count=0; flag=true; gameover=false; //paint(); } var canvas=document.getElementById("mycanvas"); var canvasbg=document.getElementById("canvasbg"); var ctx=canvas.getContext("2d"); var ctxbg=canvasbg.getContext("2d"); //变量初始化 //缓冲画布变量 var buffer,buffer_ctx; var a=-1,b=-1,score=0,speed=14,minspeed=5,initspeed=14,speednum=8, fangxiang=2,last=2, //画布格子对应的二维数组,1表示蛇身,2表示食物。0表示空白。 map=[], //蛇身位置数组, row,col, orow,ocol, //蛇身初始化长度 len=5; //游戏是否进行中。 var flag=true; var gameover=false; var ttop=35; var tbottom=35; var _ww=document.documentElement.clientWidth; var _hh=document.documentElement.clientHeight-tbottom-ttop; var _thh=document.documentElement.clientHeight; //蛇每一格的大小 var swd=32; //画布分的格子数目 var hsum=Math.floor(_ww/swd); var vsum=Math.floor(_hh/swd); // console.log("hsum:"+hsum+",vsum:"+vsum) var hleft=(_ww-(hsum*swd))/2; var vtop=(_hh-(vsum*swd))/2; for(var i=0;i<hsum;i++){ map[i]=new Array(); for(var j=0;j<vsum;j++){ map[i][j]=0; } } var basepath="demo/"; //需要加载的图片数组。 var imgs=["food1.png","food2.png","food3.png","food4.png","coupon.png","doller.png","bg.png","bg2.png","left.png","right.png","blank.png"]; var foods=["food1.png","food2.png","food3.png","food4.png"]; var foodsimg=new Array(); var bg=["bg.png","bg2.png"]; var blank; var prizearr=[]; var prize=0; var dollerarr=[]; var doller=0; var pa=-1,pb=-1; var pflag=false; var prize_doller=1; var prizeimg=new Image(); var dollerimg=new Image(); var prize_score=3; var lastscore=0; //var sbody=["food1.png","food2.png","food3.png","food4.png"]; var lenarr=[]; var imgcount=0; for(var i=0;i<imgs.length;i++){ var img=new Image(); img.onload=loadoneImg; img.onerror=loadoneImg; img.src=basepath+imgs[i]; } //图片加载完成后进行初始化调用。 function loadoneImg(){ imgcount++; if(imgcount==imgs.length){ for(var i=0;i<shead.length;i++){ var img=new Image(); img.src=basepath+shead[i]; sheadimg.push(img); } for(var i=0;i<foods.length;i++){ var img=new Image(); img.src=basepath+foods[i]; foodsimg.push(img); } blank=new Image(); blank.src=basepath+"blank.png"; prizeimg.src=basepath+"coupon.png"; dollerimg.src=basepath+"doller.png"; init(); var load=document.getElementById("loadimg"); load.parentNode.removeChild(load); } } //初始化画布信息 function init(){ var _w=document.documentElement.clientWidth; var _h=document.documentElement.clientHeight; canvas.width=_w; canvas.height=_h; canvasbg.width=_w; canvasbg.height=_h; buffer=document.createElement("canvas"); //document.body.appendChild(buffer); buffer.width=_w; buffer.height=_h; buffer_ctx=buffer.getContext("2d"); ctx.clearRect(0,0,canvas.width,canvas.height); buffer_ctx.clearRect(0,0,buffer.width,buffer.height); initEvent(); adjustwin(); newSnake(); snake_action(); //绘制背景画布,背景画布不用刷新,可以避免大面积刷新引起的闪烁。 var bgimg2=new Image(); bgimg2.src=basepath+bg[1]; ctxbg.drawImage(bgimg2,hleft,vtop+ttop,(hsum*swd),(vsum*swd)); ctx.clearRect(0,0,canvas.width,canvas.height); ctx.drawImage(buffer,0,0); flag=false; paint(); } var count=0; //绘制动画画布 function paint(){ requestAnimationFrame(paint); //count++; //if(count==10){ //count=0; snake_action(); ctx.clearRect(0,0,canvas.width,canvas.height); ctx.drawImage(buffer,0,0); //} } var istouch=!!(("ontouchstart" in window) || window.DocumentTouch && document instanceof DocumentTouch); var touchstart=istouch?'touchstart':'mousedown', touchmove=istouch?'touchmove':'mousemove', touchend=istouch?'touchend':'mouseup'; function adjustwin(){ document.body.addEventListener(touchstart,function(evt){ evt.preventDefault(); },false); document.body.addEventListener(touchmove,function(evt){ evt.preventDefault(); },false); document.body.addEventListener(touchend,function(evt){ evt.preventDefault(); },false); } function paint_snake(){ } //绑定事件 function initEvent(){ canvas.addEventListener(touchstart,recordstart,false); canvas.addEventListener(touchmove,recordmove,false); canvas.addEventListener(touchend,recordend,false); window.addEventListener("keydown",keyrecord,false); document.getElementById("start").addEventListener(touchend,start_snake,false); document.getElementById("restart").addEventListener(touchend,restart,false); } var startX,startY,endX,endY; //根据滑动方向确定蛇运动的方向 function recordstart(evt){ evt.preventDefault(); evt.stopPropagation(); var ee=('ontouchend' in document)?evt.touches[0]:evt; startX=endX=(ee.clientX==undefined)?ee.screenX:ee.clientX; startY=endY=(ee.clientY==undefined)?ee.screenY:ee.clientY; } function recordmove(evt){ evt.preventDefault(); evt.stopPropagation(); var ee=('ontouchend' in document)?evt.touches[0]:evt; endX=(ee.clientX==undefined)?ee.screenX:ee.clientX; endY=(ee.clientY==undefined)?ee.screenY:ee.clientY; } function recordend(evt){ evt.stopPropagation(); if(Math.abs(endX-startX)>Math.abs(endY-startY)){ if(endX>startX){ fangxiang=4; }else{ fangxiang=3; } }else{ if(endY>startY){ fangxiang=2; }else{ fangxiang=1; } } } function keyrecord(evt){ if(flag){ var keycode=evt.keyCode; switch(keycode){ case 37:fangxiang=3;break; case 38:fangxiang=1;break; case 39:fangxiang=4;break; case 40:fangxiang=2;break; } } } //初始化画布清空数组 function clearMap() { score=0; fangxiang=2; last=2; len=5; a=-1; b=-1; lenarr=[]; prize=0; doller=0; pa=-1,pb=-1; pflag=false; speed=20; for(var i=0;i<hsum;i++) { for(var j=0;j<vsum;j++) { map[i][j]=0; } } row=[hsum*vsum]; col=[hsum*vsum]; orow=[hsum*vsum]; ocol=[hsum*vsum]; for(var i=0;i<len;i++) { row[i]=Math.floor(hsum/3); orow[i]=Math.floor(hsum/3); col[i]=len-i; ocol[i]=len-i; } for(var i=0;i<len;i++) { map[row[i]][col[i]]=1; } } //新建蛇 function newSnake() { a=-1; b=-1; row=[hsum*vsum]; col=[hsum*vsum]; orow=[hsum*vsum]; ocol=[hsum*vsum]; for(var i=0;i<len;i++) { row[i]=Math.floor(hsum/3); orow[i]=Math.floor(hsum/3); col[i]=len-i; ocol[i]=len-i; } for(var i=0;i<len;i++) { map[row[i]][col[i]]=1; } } var foodindex=0; //得到随机食物,并将食物序列保存变量。 function getFoodimg(){ var rd=foodsimg[Math.floor(Math.random()*foodsimg.length)]; foodindex=rd; return rd; } var foodimg; //蛇头对应的图片 var shead=["left.png","right.png","left.png","right.png"]; var sheadimg=new Array(); var olda=-1,oldb=-1; //绘制画布 var wordleft; function paintimage() { if(flag) { buffer_ctx.clearRect(0, 0,buffer.width,buffer.height); //buffer_ctx.strokeStyle='#F06128'; //buffer_ctx.strokeRect(hleft,vtop+ttop,(hsum*swd),(vsum*swd)); /*var bgimg2=new Image(); bgimg2.src=basepath+bg[1]; buffer_ctx.drawImage(bgimg2,hleft,vtop+ttop,(hsum*swd),(vsum*swd));*/ //绘制得分信息。 buffer_ctx.fillStyle='#FFFFFF'; buffer_ctx.font="16px 微软雅黑"; var scorestr="得分:"+(score*20); var fontw=buffer_ctx.measureText(scorestr); buffer_ctx.fillText(scorestr,hleft+15,30); buffer_ctx.drawImage(prizeimg,hleft+25+fontw.width,8,32,32); var prizestr=prize; var prizew=buffer_ctx.measureText(prizestr); buffer_ctx.fillText(prizestr,hleft+30+fontw.width+32,30); buffer_ctx.drawImage(dollerimg,hleft+45+fontw.width+32+prizew.width,8,32,32); buffer_ctx.fillText(doller,hleft+45+fontw.width+69+prizew.width,30); buffer_ctx.beginPath(); //var bgimg=new Image(); //bgimg.src=basepath+bg[0]; /*for(var i=0;i<hsum;i+=2) for(var j=0;j<vsum;j+=2) { buffer_ctx.strokeStyle='#D7CE3A'; buffer_ctx.strokeRect(i*swd+hleft+1,j*swd+vtop+ttop+1,swd-2,swd-2); buffer_ctx.fillStyle="#dddddd"; buffer_ctx.fillRect(i*swd+hleft+3,j*swd+vtop+ttop+3,swd-6,swd-6); buffer_ctx.drawImage(bgimg,i*swd+hleft,j*swd+vtop+ttop,2*swd,2*swd); }*/ for(var i=0;i<hsum;i++) for(var j=0;j<vsum;j++) { if(map[i][j]==1) { /*if(row[0]==i&&col[0]==j){ buffer_ctx.fillStyle='#d4f2e8'; }else{ buffer_ctx.fillStyle='#d4f2e8'; } buffer_ctx.fillRect(i*swd+hleft+1, j*swd+vtop+1, swd-2, swd-2);*/ } else if(map[i][j]==2) { /*buffer_ctx.fillStyle='#158B05'; buffer_ctx.arc(i*swd+swd/2+hleft, j*swd+swd/2+vtop+ttop, swd/2, 0,2*Math.PI); buffer_ctx.fill();*/ //绘制可以吃的食物。 buffer_ctx.drawImage(foodimg,i*swd+hleft-2,j*swd+vtop+ttop-2,swd+4,swd+4); }else if(map[i][j]==3){ var img=prize_doller==1?prizeimg:dollerimg; buffer_ctx.drawImage(img,i*swd+hleft-2,j*swd+vtop+ttop-2,swd+4,swd+4); } /*else{ buffer_ctx.strokeStyle='#D7CE3A'; buffer_ctx.strokeRect(i*swd+hleft+1,j*swd+vtop+ttop+1,swd-2,swd-2); buffer_ctx.fillStyle="#dddddd"; buffer_ctx.fillRect(i*swd+hleft+3,j*swd+vtop+ttop+3,swd-6,swd-6); }*/ } for(var k=0;k<len;k++){ var lec=0,lecv=0; //根据旧位置和新位置将每一步分成speed步进行流畅动画。 if(!(row[k]==orow[k]&&col[k]==ocol[k])){ if(orow[k]==undefined||ocol[k]==undefined){ orow[k]=row[k];ocol[k]=col[k]; lec=(row[k-1]-orow[k])*swd*(count/speed); lecv=(col[k-1]-ocol[k])*swd*(count/speed); }else{ lec=(row[k]-orow[k])*swd*(count/speed); lecv=(col[k]-ocol[k])*swd*(count/speed); } } if(k==0){ //绘制蛇头,根据方向改变蛇头的图片 var img=sheadimg[fangxiang-1]; buffer_ctx.drawImage(img,orow[k]*swd+hleft+lec,ocol[k]*swd+vtop+ttop+lecv,swd,swd); }else{ //绘制蛇身,如果没有吃食物的身体用blank图片绘制。 var img=lenarr[k-1]==undefined?blank:lenarr[k-1]; buffer_ctx.drawImage(img,orow[k]*swd+hleft+lec,ocol[k]*swd+vtop+ttop+lecv,swd,swd); } } } else { if(gameover){ buffer_ctx.fillStyle='#267322'; buffer_ctx.font="40px 微软雅黑"; var txt="游戏结束" var textw=buffer_ctx.measureText(txt).width; buffer_ctx.fillText(txt,buffer.width/2-textw/2,buffer.height/2); document.getElementById("gameover").style.display='block'; } } var prizeuser="恭喜小明中得了一个小苹果奖品,恭喜小王中得了小香蕉奖品"; buffer_ctx.fillStyle='#BE5454'; buffer_ctx.font="16px 微软雅黑"; var userstr=buffer_ctx.measureText(prizeuser); if(wordleft==undefined){ wordleft=_ww; } wordleft--; if(wordleft<=-userstr.width){ wordleft=_ww; } //console.log(userstr.); var wordtop=_hh+20+ttop; // console.log(wordleft+"--"); //console.log(wordtop); //console.log(((_thh-_hh)/2-userstr.height)); buffer_ctx.clearRect(0, _hh+ttop,buffer.width,(_thh-_hh)); buffer_ctx.fillText(prizeuser,wordleft,wordtop); } function getpflag(){ //pflag=Math.random()>0.8; prize_doller=Math.random()>0.5?1:2; //return pflag; } var prizelast=0; //蛇的运动 function snake_action(){ count++; if(count==speed){ count=0; if(flag==true) { for(var i=0;i<len;i++){//保存蛇的旧位置。 orow[i]=row[i]; ocol[i]=col[i]; } var oldlen=len; if(a==-1&&b==-1) { //创建蛇吃的食物 a=Math.floor(Math.random()*hsum); b=Math.floor(Math.random()*vsum); while(map[a][b]==1) { a=Math.floor(Math.random()*hsum); b=Math.floor(Math.random()*vsum); } map[a][b]=2; olda=a;oldb=b; foodimg=getFoodimg(); } else { //蛇吃到食物时的处理 if(row[0]==a&&col[0]==b) { map[a][b]=0; //如果是空气泡时不增加长度。 if(lenarr.length+1>=len){ len++; } a=-1; b=-1; score++; lastscore++; speed=initspeed-Math.floor(score/speednum); if(speed<minspeed){ speed=minspeed; } //将对应的食物图片名称放到数组中 lenarr.push(foodindex); } } if(pa==-1&&pb==-1&&lastscore>=prize_score){ lastscore=lastscore-prize_score; prize_score+=1; getpflag(); pa=Math.floor(Math.random()*hsum); pb=Math.floor(Math.random()*vsum); while(map[pa][pb]==1||map[pa][pb]==2) { pa=Math.floor(Math.random()*hsum); pb=Math.floor(Math.random()*vsum); } map[pa][pb]=3; prizelast=0; }else{ if(row[0]==pa&&col[0]==pb) { map[pa][pb]=0; pa=-1; pb=-1; prize_doller==1?prize++:doller++; }else{ prizelast++; if(prizelast>=120&&pa!=-1&&pb!=-1){ map[pa][pb]=0; pa=-1; pb=-1; } } } //先请清空最后一个,再将前一个位置赋值给后一个位置。最后再改变第一个的位置。 map[row[oldlen-1]][col[oldlen-1]]=0; for(var i=len-1;i>0;i--) { row[i]=row[i-1]; col[i]=col[i-1]; } switch(fangxiang) { case 1: if(last==2) { col[0]+=1; } else { col[0]-=1; last=1; } break; case 2: if(last==1) { col[0]-=1; } else { col[0]+=1; last=2; } break; case 3: if(last==4) { row[0]+=1; } else { row[0]-=1; last=3; } break; case 4: if(last==3) { row[0]-=1; } else { row[0]+=1; last=4; } break; } //蛇撞墙或者咬到自己的尾巴时游戏结束 if(row[0]>=hsum||row[0]<0||col[0]>=vsum||col[0]<0||map[row[0]][col[0]]==1) { flag=false; gameover=true; paintimage(); }else{ for(var i=0;i<len;i++) { map[row[i]][col[i]]=1; } //paintimage(); } } } paintimage(); } //更新得分 </script> </html>
此小游戏使用canvas的双缓存,以及requestAnimationFrame刷新,移动端事件以及键盘事件操作,蛇身移动的运算。
代码中已有详细的注释,接下来的几篇文章,我们将逐点讲解此小游戏中使用的canvas的知识点。