HTML5+Canvas贪吃蛇

最近前端以及JavaScript很火,再加上上课web的需要,感觉是时候系统的学学HTML5了,之前和同学合作用html5的canvas开发了一个贪吃蛇改进版的小游戏,很有趣,当时的核心代码是同学敲得,而我也在忙别的事情一直没有回过头来研究一下。好在快一年的时间了,自己也学了很多种UI的皮毛,见得多了,再看canvas感觉就是只要有文档就能直接用了。

这里分享一下开发的过程吧。

效果图。



还有穿墙功能。




前置技能

使用canvas。

所需工具以及前置技能:支持HTML5的浏览器,html+css, javascript,Jquery

canva是HTML5中新特性的一种,直接可以进行绘图,和其他UI差别不大。


布局

首先,我们写个index.html,并进行简单的布局及样式。

设置整个画布为720 x 480 的大小。每个蛇身、食物的半径为12,所以一行能放720 / 24 = 30个,每列能放 480 / 24  = 20个元素

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <title>Snake</title>
    <style type="text/css"> //简单的定义了一下样式
        body{
            background: #dddddd;
        }
        #canvas1{
            border:#8b4513 solid 5px;
            border-radius:5px;
            margin: :10px;
            background: #ffffff;
            display:block;
        }
    </style>
</head>
<body>
    <div class="container" id=container>  //简单的布局
        <center>
             <button  id=startGame>Start Game</button>
            <canvas id="canvas1" width="720" height="480">
                 canvas not supported
             </canvas>
             <span >Score</span> //得分
              <input type="text" id=score name=score disabled="disabled">

            <span >Level</span> //关卡
            <input type="text"  id=level name=level disabled="disabled">
        </center>
    </div>
</body>
 <script src="../bootstrap/js/jquery.min.js"></script> //引入jquery
</html>


主逻辑

然后我们编写主逻辑。

首先获取Canvas对象,然后设置网页的键盘监听器(接受上下左右的按键),接着通过Jquery获取StartGame这个按钮的事件。

在引入Jquery的代码下面插入一段JS脚本。

<script type="text/javascript">
         var canvas = document.getElementById('canvas1'); //获取canvas画布
         var ctxt = canvas.getContext('2d'); //以后就用ctxt来在canvas画布上绘画
         document.addEventListener('keydown',keyDownCheck,false); //设置键盘按下的监听事件,通过keyDownCheck这个函数处理,
        $("#startGame").click(function(){
            beginGame(1); // 通过beginGame(1)这个函数来开始游戏,1代表了开始的关卡
        });
    </script>


好,这样我们游戏的外部框架出来了,接着我们往里深入,来实现贪吃蛇的主体逻辑。


Snake类

首先,我们先创建一个Snake的类,JavaScript的类用function就可以实现。

贪吃蛇需要有身子,身子由什么组成呢?  因为是2D游戏,所以由一个个相连的二位坐标点组成。

到这里我们想到,蛇身由点组成,那么食物也可由点组成,于是这里我们把点写成一个类,num属性代表食物的得分。

function Pos(x,y,num = 2){
  this.x = x;
  this.y = y;
  this.num = num;
}
this.body = [new Pos(20,15), new Pos(21,15),new Pos(22,15),
  new Pos(23,15), new Pos(24,15),new Pos(25,15)]; //定义蛇身,从20 ~ 25相邻的一串点

当然食物(障碍物)obstacle,我们也用一样的方法定义了。

function Obstacle(){
  this.body = [new Pos(0,0),new Pos(0,1),new Pos(1,0)
  ,new Pos(1,1),new Pos(29,19),new Pos(10,10),new Pos(11,11)];
}

有了蛇身,还要定义蛇当前行进的方向。

这里我们规定 1 ,2 ,3 ,4 分别为上下左右

  this.dir = 3; // 初始向左


有了身子方向,接着我们要想怎么去移动move。


这里提供一个思路。

首先,我们通过蛇头,和方向判断出蛇下一步要走的那个点的坐标。我们记做head,记录下这一坐标。然后从蛇尾遍历到蛇头,每一个的坐标赋值为其前一个位置的坐标,而蛇头就用head进行赋值,这样就能实现移动了。

当然,有了head我们还能做更多的事,判断head所在位置是不是墙,是不是蛇身,是不是食物,我们就能实现判断是否游戏结束,以及得分情况了。

this.move = function(level){
    var isEat = false;
    var head;
    if(this.dir == 1)
        head = new Pos(this.body[0].x,this.body[0].y-1);
    else if(this.dir == 2)
        head = new Pos(this.body[0].x,this.body[0].y+1);
    else if(this.dir == 3)
        head = new Pos(this.body[0].x-1,this.body[0].y);
    else if(this.dir == 4)
        head = new Pos(this.body[0].x+1,this.body[0].y);
    //判定是否死亡
    if(level == 1){ //可以穿墙 ,这里设置第一关可以穿越围墙到另一面
      if(head.x < 0) head.x = 29;
      if(head.x > 29) head.x = 0;
      if(head.y < 0) head.y = 19;
      if(head.y > 19) head.y = 0;
    }
    else if (head.x <0 || head.x > 29 || head.y < 0 || head.y > 20) //碰到边界
      this.dead = true;
    if(!this.dead){ //吃到自身
      for(i=0;i<snake.body.length;i++){
        if(snake.body[i].x == head.x && snake.body[i].y == head.y ){
          this.dead = true ; 
          break;
        }
      }
    }
    if(this.dead){
      GameOver(); return; //如果死亡,则执行GameOver函数。
    }
    //判定是否吃到食物
    for(i = 0;i < obstacle.body.length;i++){
          if(head.x == obstacle.body[i].x && head.y == obstacle.body[i].y){
             this.score +=  obstacle.body[i].num; //增加得分
             this.body.splice(0,0,obstacle.body[i]); //在头部增加元素。
             obstacle.body.splice(i,1); // 删除i位置的一个元素
             isEat = true;
          }
     }
     if(!isEat){ //没有吃到就每个蛇身往前移动一步,直接用前一个蛇身赋值即可。
        for(i = this.body.length-1;i>=0;i--){
          if(i == 0)
            this.body[i].x = head.x , this.body[i].y = head.y;
          else
            this.body[i].x = this.body[i-1].x,this.body[i].y = this.body[i-1].y;
        }
/*  另一种移动方法     this.body.splice(0,0,head); // 吃掉头
          this.body.pop(); // 删除尾部 ,这样就移动了!*/
     }

生成食物

吃到食物之后,为了让游戏进行下去,我们还要不断地生成食物。

直接用Math.Random()函数生成待生成食物的x,y坐标,然后判断是否当前坐标已经被占用,如果占用就再生成一次。

   if(isEat){ // 添加食物
        var isOcupy = false;
        var tmp;
        do{
          isOcupy = false;
          tmp = createObstacle();
          for(i=0;i<snake.body.length;i++){
              if(snake.body[i].x == tmp.x && snake.body[i].y == tmp.y){
                isOcupy = true;break;
              }
          }
          if(!isOcupy){
             for(i=0;i<obstacle.body.length;i++){
              if(obstacle.body[i].x == tmp.x && obstacle.body[i].y == tmp.y){
                isOcupy = true;break;
              }
             }
          }
        }while(isOcupy);
        obstacle.body.push(tmp);
     }
  }

function createObstacle(){
  var tmp = new Pos(Math.floor(Math.random()*30), Math.floor(Math.random()*20));
  tmp.num = Math.random() < 0.1 ? 4: 2; // 出现4和2的比例 1 : 9
  return tmp;
}

这样,整体的游戏逻辑算是基本实现了。

接下来完成剩余的工作。

接受键盘数据

主要是上下左右,除了方向键,还增添了wasd,同时保证了按相反的方向无效,因为贪吃蛇不会直接回头走。

function keyDownCheck(event){
  var e=event||window.event||arguments.callee.caller.arguments[0]; //各种兼容下获取event
 // alert(e.keyCode);
  if(e.keyCode == 87 || e.keyCode == 38){ // up
    if(snake.dir == 2)
      return;
    snake.dir = 1;
  }
  else if(e.keyCode == 83 || e.keyCode == 40){ // down
    if(snake.dir == 1)
      return;
    snake.dir = 2;
  }
  else if(e.keyCode == 65 || e.keyCode == 37){ // left
    if(snake.dir == 4)
      return;
    snake.dir = 3;
  }
  else if(e.keyCode == 68 || e.keyCode == 39){ //right
    if(snake.dir == 3)
      return;
    snake.dir = 4;
  }
}


主体循环以及绘图

2D游戏应该都是这样,游戏数据不停的的变化,界面也在不停的刷新。我们用setInterval来设立一个定时器,每隔一段时间刷新一下屏幕。再刷新的同时执行snake的move函数,这样就能让贪吃蛇不停的往前走了。当然最重要的,是要让这个后台数据变化在屏幕上显示出来。这里就用到了canvas的绘图技术了。


首先是开始游戏的函数。

function beginGame(level){
  if(gameTimer)
  clearInterval(gameTimer); //清空计时器
  snake = new Snake(); //为贪吃蛇申请对象
  obstacle = new Obstacle(); //为障碍物申请对象
  gameTimer = setInterval(function(){ //设置定时器
    snake.move(level);
    draw(level); //绘图函数。
    $("#score").val(snake.score); //更新成绩
    $("#level").val(level);  
  },gameSpeed[level-1]);
}
var gameSpeed = [240,210,180,150]; // 游戏速度

接着draw函数。

function draw(level){
// 画布大小 720 x 480, 每个元素半径12
// X = 0 ~ 29 , Y = 0 ~ 19
  ctxt.clearRect(0,0,canvas.width,canvas.height); //清空我们的画布
  ctxt.beginPath();  //开始画
  ctxt.rect(0,0,canvas.width,canvas.height);
  ctxt.fillStyle = "black";
  ctxt.closePath();
  ctxt.fill(); //填充

  for(i=0;i<snake.body.length;i++){ //绘制蛇身
    ctxt.beginPath();
    //arc(X,Y,Radius,StartAngle,endAngle,anticlockwise)
    ctxt.arc(snake.body[i].x*24+12,snake.body[i].y*24+12,12,0,Math.PI*2,true); //画圆
    ctxt.fillStyle = "green"; //颜色
    ctxt.closePath();
    ctxt.fill(); //填充
  }
 for(i=0;i<obstacle.body.length;i++){ //画食物(障碍物)
    ctxt.beginPath();
    ctxt.arc(obstacle.body[i].x*24+12,obstacle.body[i].y*24+12,12,0,Math.PI*2,true);
    ctxt.fillStyle = "red";
    ctxt.closePath();
    ctxt.fill();

  }
  
}

GameOver函数。

function GameOver(){
  alert('GameOver!'); 
  clearInterval(gameTimer); //为了保险,清空一下计数器
  $("#score").val(0); //得分置为0
  $("#level").val(1); //关卡置为1
  snake.body = null; //蛇身置为空
}


接下来大家还可以为这个游戏加上音效,背景音乐,或者改造成其他版本的贪吃蛇,例如2048版贪吃蛇,相信如果你仔细学习了这个代码,扩充下去不难做到。大笑

这个项目我已经放到了github上,欢迎fork

https://github.com/chaiwenjun000/webSnake

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值