通过h5的canvas手写一个俄罗斯方块小游戏

38次阅读

共计 7306 个字符,预计需要花费 19 分钟才能阅读完成。

开始自己手写一个好玩的俄罗斯方块吧,上变形,左右移动,下加速,空格瞬移等功能,无聊的时候学习下 canvas,f12 修改分数,体验金手指的快乐吧

1、定义界面,和按钮

<div id="by">
        <div id="title">
            <!--// 定义游戏界面 -->
            <canvas id="myCanvas" height="600" width="400" style="border: 2px solid #3c763d"></canvas>
            <!--// 定义下一个方块的预知框 -->
            <div id="title2">
                <canvas id="Canvas" height="150" width="150" style="border: 2px solid #3c763d"></canvas>
            </div>
        </div>
        <!-- 背景图 -->
        <div id="">
            <img src="http://cnd.yinglingxuan.cn/75Z58PICq67_1024.jpg"  id="img" width="405px" height="602px" />
        </div>
    </div>
    <!-- 按钮 -->
    <div class="aj">
        <span onclick="tops()"> 上 </span>
        <span onclick="under()"> 下 </span>
        <span onclick="lefts()"> 左 </span>
        <span onclick="rights()"> 右 </span>
    </div>

2、js 部分

1、先定义每个图形的形状和变化的形状,这里是使用多维数组的方式去保存它图形每个方块的位置(当然这里也可以用循环的方式)

        var data=[[[[1,0,0,0],[1,0,0,0],[1,1,0,0],[0,0,0,0]],[[1,1,1,0],[1,0,0,0],[0,0,0,0],[0,0,0,0]],[[1,1,0,0],[0,1,0,0],[0,1,0,0],[0,0,0,0]],[[0,0,1,0],[1,1,1,0],[0,0,0,0],[0,0,0,0]]],
                   [[[1,0,0,0],[1,1,0,0],[1,0,0,0],[0,0,0,0]],[[1,1,1,0],[0,1,0,0],[0,0,0,0],[0,0,0,0]],[[0,0,1,0],[0,1,1,0],[0,0,1,0],[0,0,0,0]],[[0,0,0,0],[0,1,0,0],[1,1,1,0],[0,0,0,0]]],
                   [[[0,1,0,0],[0,1,1,0],[0,0,1,0],[0,0,0,0]],[[0,1,1,0],[1,1,0,0],[0,0,0,0],[0,0,0,0]],[[0,1,0,0],[0,1,1,0],[0,0,1,0],[0,0,0,0]],[[0,0,0,0],[0,1,1,0],[1,1,0,0],[0,0,0,0]]],
                   [[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]]],
                   [[[0,0,1,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]],[[1,1,0,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],[[0,0,1,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]],[[1,1,0,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]]],
                   [[[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]],[[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],[[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]],[[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]]],
                   [[[1,1,0,0],[1,0,0,0],[1,0,0,0],[0,0,0,0]],[[1,1,1,0],[0,0,1,0],[0,0,0,0],[0,0,0,0]],[[0,0,0,0],[0,0,1,0],[0,0,1,0],[0,1,1,0]],[[0,0,0,0],[0,0,0,0],[1,0,0,0],[1,1,1,0]]]
          ];

2、定义图形的位置和每帧下落的速度

        var newX=120;// 记录这个图形的左右位置
        var count=0;   // 记录下落的速度
        var shape=0; // 记录形状
        var finallys=new Array(); // 记录落下后停止的位置
        var re=6;   // 获取随机数方块
        var fat=1;  // 每帧的速度
        var xyg=0;  // 下一个方块
        var dfen=0;// 分数
           // 定义游戏界面
        var c=document.getElementById("myCanvas");
        var ctx=c.getContext("2d");
        // 下个方格的画布
        var c2=document.getElementById("Canvas");
        var ctx2=c2.getContext("2d");

3、定义组成图形的方法

        // 组成图形的方法
          function constitute(){var Arr=new Array(); // 存储当前下来的方块位置
              var y=0;   // 记录每个小方块组成图形的小方块 y 下标 -- 相加
              for (var i=0;i<data[re][shape].length;i++) {
                  var x=0; // 记录每个小方块组成图形的小方块 x 下标
                  for (var j=0;j<data[re][shape][i].length;j++) {
                      x+=20;
                      if(data[re][shape][i][j]==1&&Arr.length<4){ // 每个图形都是四个小方块组成
                           ctx.strokeStyle = "#000";                
                           ctx.strokeRect(x+newX,y+count,20,20);  
                           ctx.fillStyle="#000000";  // 定义颜色
                           ctx.fillRect(x+newX,y+count,19,19);    // 绘图 
                           var a={
                               xs:x+newX,
                               ys:y+count
                           };
                           Arr.push(a);   // 记录图形位置
                           if(Arr.length==4){ // 图形形成后调用掉落完成后重新调用
                               anew(Arr);
                               return Arr;
                           }
                      }
                  }
                  y+=20;
              };
          }

3、判断掉落是否到底或者碰撞到上一个底部的方块

          // 掉落完成后重新调用
          function anew (arr){var da=bottom(arr); // 调用判断是否到底
              if(da==1){  // 如果返回 1 则已经是到底部,finallys.push(arr); // 保存在底部的方块,方便碰撞判断
                  count=0;  // 恢复成原来的上下位置
                  newX=120;  // 恢复原来的左右位置
                  re=xyg;     // 当前的方块变成上一个的下一个
                  xyg=Math.floor(Math.random()*7);  // 重新随机获取当前的下一个
                  ctx2.clearRect(0, 0, c.width,c.height);  // 清除画布
                  xygs();// 重新绘制预知框的}
          }
          
          
          
          // 判断是否碰到下面的障碍物
          function bottom(arr){   // 判断是否掉落底部 // 返回 1 表示可以保存起来
              for (var i =0;i<arr.length;i++) {if(arr[i].ys>=c.height-20){// 首先判断是否到界面的底部
                      /*console.log(arr);*/
                      return 1;
                  }
                  for (var t=0;t<finallys.length;t++) {// 再到保存的底部图形里面遍历出来判断是否与其他图形发生了碰撞
                      for (var j=0;j<finallys[t].length;j++) {if(finallys[t][j].ys==arr[i].ys+20&&finallys[t][j].xs==arr[i].xs){/*console.log(arr);*/
                            return 1;
                          }
                      }
                  }
              }
              return 0;
          }

4、下落到底部后的方块要保存起来,定义重新渲染的方法

          // 用来组成下标已经堆积的格子
          function  ground(finallys){deletes();// 判断是否到达顶部了,顶部了就直接清除重新开始
              for (var i=0;i<finallys.length;i++) {   // 新图形开始的时候重新渲染已经到底部保存的图形
                  for (var j=0;j<finallys[i].length;j++) {
                       ctx.strokeStyle = "#000";
                        ctx.strokeRect(finallys[i][j].xs,finallys[i][j].ys,20,20);
                        ctx.fillStyle="#000000";  // 定义颜色
                       ctx.fillRect(finallys[i][j].xs,finallys[i][j].ys,19,19);    // 画图 
                       if(finallys[i][j].ys<=20){this.finallys=new Array();
                           count=0;
                           constitute();   // 调用组成画布的方法
                           /*ctx.clearRect(0, 0, c.width,c.height);  // 清除画布 */
                           return;
                       }         
                  }
              }
          }    

5、当横向一行满了后就可以清楚并得到对应的分数

      // 判断是否组满一行达到清除的位置
      function deletes(){
        var c=600;// 当前横向的宽度
        var add=new Array;// 记录宽度的每个方块
        for (var i=0;i<c/20;i++) {// 除以当前方块的宽度获得每个横度的方块位置
            c=c-20;
            add.push(c);
        }
        for (var a=0;a<add.length;a++) {// 判断每个横向的方块是否堆积满了
            var cou=0;
            for (var t=0;t<finallys.length;t++) {for (var j=0;j<finallys[t].length;j++) {if(add[a]==finallys[t][j].ys){cou++;}
                  }
              }
            if(cou>=20){ // 如果横向满了调用清除的方法
                qc(add[a]);//
                cou=0;
            }
        }
    }
      // 一行满了后清除和上面的向下增加移动
      function qc(add){for (var t=0;t<finallys.length;t++) {for (var j=0;j<finallys[t].length;j++) {if(add==finallys[t][j].ys){finallys[t].splice(j,1);
                    dfen=dfen+10;
                    j--;
                }
              }
          }
          for (var t=0;t<finallys.length;t++) {for (var j=0;j<finallys[t].length;j++) {if(finallys[t][j].ys<add){finallys[t][j].ys=finallys[t][j].ys+20;
                }
              }
          }
      }

6、碰撞检测

          // 判断不能掉出围起来的范围
          function crash(count,e){ //count 下落的位置,e 表示当前是什么按钮操作
              var newCount=0;  // 判断是否有下降的数据有就下降
              if(count!=0){newCount=count;}
              var y=0;   // 记录组成形状的位置 -- 相加
              var r=0;
              var make=-1;
              for (var i=0;i<data[re][shape].length;i++) {
                  var x=0; 
                  for (var j=0;j<data[re][shape][i].length;j++) {
                      x+=20;
                      if(data[re][shape][i][j]==1){
                          // 防止变形的时候溢出
                          if(e==38&&x+newX<0||x+newX>c.width-20){if(x+newX<0){
                                  newX=newX+20;
                                  r=1;
                              }else{
                                  newX=newX-20;
                                  r=2;
                              }
                          }
                          if(e==38){if(y+count>c.height-20){if(shape!=0){shape=shape-1;}else{shape=3;}
                              }
                          }
                          
                          //// 防止突出右边
                          if(e==39&&c.width-20<x+newX+20){make=1;}
                          // 防止突出左边
                          if(e==37&&x+newX-20<0){make=1;}
                          if(y+newCount>c.height-20){count=count-20;}
                          var ys=(y+count)%20; // 下落的时候可能会一直按着 速度快会直接插到下面,所以这里要求余,当到这个方块的位置的时候取整判断
                          var es=20-ys;
                          var newCount=y+count+es;
                          
                          for (var t=0;t<finallys.length;t++) {for (var j1=0;j1<finallys[t].length;j1++) {if(finallys[t][j1].ys==newCount&&finallys[t][j1].xs==x+newX+20&&e==39){newX=newX-20;}
                                if(finallys[t][j1].ys==newCount&&finallys[t][j1].xs==x+newX-20&&e==37){newX=newX+20;}
                                if(finallys[t][j1].ys+20==newCount&&finallys[t][j1].xs==x+newX&&e==38){if(shape!=0){shape=shape-1;}else{shape=3;}
                                    if(r==1){newX=newX-20;}
                                    if(r==2){newX=newX+20;}
                                }
                              }
                          }
                      }
                  }
                  y+=20;
              };
              
              if(e==39&&make==-1){newX=newX+20;}
              if(e==37&&make==-1){newX=newX-20;}
          }

7、按钮操作

        $(document).keydown(function(e){  // 电脑键盘的
            if(e.keyCode==39){  // 右
                crash(count,e.keyCode);// 判断是否可以加到右边
            }else if(e.keyCode==37){  // 左
                // 判断是否可以加到左边
                crash(count,e.keyCode);
            }else if(e.keyCode==40){   // 下
                var i=count%10;
                count=count-i;
                fat=10;
                
            }else if(e.keyCode==38){   // 上
               shape++;
               if(shape>3){shape=0;}
               crash(count,e.keyCode);  // 防止变形溢出
            }else if(e.keyCode==32){
                var i=count%20;
                count=count-i;
                for (var t=0;t<400;t++) {
                    count=count+10;
                    var arr=constitute();
                    var e=bottom(arr);
                    if(e==1){break;}
                }
            }
        });
        //////////////////////////// 手机端的按钮
        function under(){
              var i=count%10;
            count=count-i;
            fat=10;
            setTimeout(function(){fat=1;},100)
          }
          document.onkeyup=function(){fat=1;}
          function tops(){
             shape++;
           if(shape>3){shape=0;}
           crash(count,38);  // 防止变形溢出
          }
          function lefts(){
              // 判断是否可以加到左边
            crash(count,37);
          }
          function rights(){crash(count,39);// 判断是否可以加到右边
          }

8、定义绘制预知框的方法,如上面绘制的方法一样,只是对应的 canvas 不同

          // 预知框里面的绘制
          function xygs(){
              var y=0;   // 记录组成形状的位置 -- 相加
              for (var i=0;i<data[xyg][shape].length;i++) {
                  var x=0; 
                  for (var j=0;j<data[xyg][shape][i].length;j++) {
                      x+=20;
                      if(data[xyg][shape][i][j]==1){
                           ctx2.strokeStyle = "#000";
                           ctx2.strokeRect(x+30,y+50,20,20);
                           ctx2.fillStyle="#000000";  // 定义颜色
                           ctx2.fillRect(x+30,y+50,19,19);    // 画图 
                      }
                  }
                  y+=20;
              };
          }

9、开始运行的方法

      function  dy(){ctx.clearRect(0, 0, c.width,c.height);  // 清除画布
            constitute();   // 调用组成画布的方法
            count=count+fat;   // 下落的速度
            ground(finallys);    // 调用已经在底部的方块的方法
            ctx.font="20px 微软雅黑";
            ctx.fillText("得分:"+dfen+"",20,30);
            requestAnimationFrame(dy);   // 调用循环运行
        }
        requestAnimationFrame(dy);  // 开始运行每一帧
        
        if(finallys.length==0){ // 获取第一个预知框
            xyg=Math.floor(Math.random()*7);// 随机获取
            xygs();// 调用预知框的渲染}
        constitute()// 开始绘制 

正文完
 0