欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Html5 小游戏 俄罗斯方块

程序员文章站 2022-03-21 18:15:45
导言 在一个风和日丽的一天,看完了疯狂HTML 5+CSS 3+JavaScript讲义,跟着做了书里最后一章的俄罗斯方块小游戏,并做了一些改进,作为自己前端学习的第一站。 游戏效果: 制作思路 因为书里的俄罗斯方块比较普通,太常规了,不是很好看,所以我在网上找了上面那张图片,打算照着它来做。(请无 ......

导言

在一个风和日丽的一天,看完了疯狂html 5+css 3+javascript讲义,跟着做了书里最后一章的俄罗斯方块小游戏,并做了一些改进,作为自己前端学习的第一站。

游戏效果:

Html5 小游戏 俄罗斯方块

 

 

 Html5 小游戏 俄罗斯方块

制作思路

Html5 小游戏 俄罗斯方块

 

因为书里的俄罗斯方块比较普通,太常规了,不是很好看,所以我在网上找了上面那张图片,打算照着它来做。(请无视成品和原图的差距)

然后便是游戏界面和常规的俄罗斯方块游戏逻辑。

接着便是游戏结束界面了。

原本想做个弹出层,但觉得找图片有点麻烦,所以就在网上找了文字特效,套用了一下。

代码实现:

 

 首先是html文件和css文件,主要涉及了布局方面。作为新手,在上面真的是翻来覆去的踩坑。o(╥﹏╥)o

index.html

<!doctype html>
<html>
<head>
    <title>俄罗斯方块</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8"/>
    <link rel=stylesheet type="text/css" href="teris.css">
    <style type="text/css">
        /*导入外部的字体文件*/
        @font-face{
            font-family:tmb;/*为字体命名为tmb*/
            src:url("ds-digib.ttf") format("truetype");/*format为字体文件格式,truetype为ttf*/
        }
        div>span{
            font-family:tmb;
            font-size:18pt;
            color:green;
        }
    </style>
</head>

<body>
    <div id="container" class="bg">
        <!--ui-->
        <div class="ui_bg">
            <div style="float:left;margin-right:4px;">
                速度:<span id="cur_speed">1</span>
            </div>
            <div style="float:left;">
                当前分数:<span id="cur_points">0</span>
            </div>
            <div style="float:right;">
                最高分数:<span id="max_points">0</span>
            </div>
        </div>
        <canvas id="text" width="500" height="100" style="position:absolute;"></canvas>
        <canvas id="stage" width="500" height="100" style="position:absolute;"></canvas>
    </div>
    <script src='easepack.min.js'></script>
    <script src='tweenlite.min.js'></script>
    <script src='easeljs-0.7.1.min.js'></script>
    <script src='requestanimationframe.js'></script>
    <script type="text/javascript" src="jquery-3.4.1.min.js"></script>
    <script type="text/javascript" src="teris.js"></script>
</body>
</html>

teris.css

*{
    margin:0;
    padding:0;
}
html, body{
    width:100%;
    height:100%;
}

.bg{
    font-size:13pt;
    background-color:rgb(239, 239, 227);
    /*好看的渐变色*/
    background-image:radial-gradient(rgb(239, 239, 227), rgb(230, 220, 212));
    /*阴影*/
    box-shadow:#cdc8c1 -1px -1px 7px 0px;
    padding-bottom:4px;
}

.ui_bg{
    border-bottom:1px #a69e9ea3 solid;
    padding-bottom:2px;
    overflow:hidden;/*没有这句的话因为子div都设置了float,所以是浮在网页上的,所以父div就没有高度,这句清除了浮动,让父div有了子div的高度*/
}

然后是重头戏,teris.js

游戏变量

//游戏设定
var tetris_rows = 20;
var tetris_cols = 14;
var cell_size = 24;
var no_block=0;
var have_block=1;
// 定义几种可能出现的方块组合
var blockarr = [
    // z
    [
        {x: tetris_cols / 2 - 1 , y:0},
        {x: tetris_cols / 2 , y:0},
        {x: tetris_cols / 2 , y:1},
        {x: tetris_cols / 2 + 1 , y:1}
    ],
    // 反z
    [
        {x: tetris_cols / 2 + 1 , y:0},
        {x: tetris_cols / 2 , y:0},
        {x: tetris_cols / 2 , y:1},
        {x: tetris_cols / 2 - 1 , y:1}
    ],
    // 田
    [
        {x: tetris_cols / 2 - 1 , y:0},
        {x: tetris_cols / 2 , y:0},
        {x: tetris_cols / 2 - 1 , y:1},
        {x: tetris_cols / 2 , y:1}
    ],
    // l
    [
        {x: tetris_cols / 2 - 1 , y:0},
        {x: tetris_cols / 2 - 1, y:1},
        {x: tetris_cols / 2 - 1 , y:2},
        {x: tetris_cols / 2 , y:2}
    ],
    // j
    [
        {x: tetris_cols / 2  , y:0},
        {x: tetris_cols / 2 , y:1},
        {x: tetris_cols / 2  , y:2},
        {x: tetris_cols / 2 - 1, y:2}
    ],
    // □□□□
    [
        {x: tetris_cols / 2 , y:0},
        {x: tetris_cols / 2 , y:1},
        {x: tetris_cols / 2 , y:2},
        {x: tetris_cols / 2 , y:3}
    ],
    // ┴
    [
        {x: tetris_cols / 2 , y:0},
        {x: tetris_cols / 2 - 1 , y:1},
        {x: tetris_cols / 2 , y:1},
        {x: tetris_cols / 2 + 1, y:1}
    ]
];

// 记录当前积分
var curscore=0;
// 记录曾经的最高积分
var maxscore=1;
var curspeed=1;
//ui元素
var curspeedele=document.getelementbyid("cur_speed");
var curscoreele=document.getelementbyid("cur_points");
var maxscoreele=document.getelementbyid("max_points");

var timer;//方块下落控制

var mycanvas;
var canvasctx;
var tetris_status;//地图数据
var currentfall;//当前下落的block

游戏界面的完善

//create canvas
function createcanvas(){
    mycanvas=document.createelement("canvas");
    mycanvas.width=tetris_cols*cell_size;
    mycanvas.height=tetris_rows*cell_size;
    //绘制背景
    canvasctx=mycanvas.getcontext("2d");
    canvasctx.beginpath();
    //tetris_cos
    for(let i=1; i<tetris_cols; i++){
        canvasctx.moveto(i*cell_size, 0);
        canvasctx.lineto(i*cell_size, mycanvas.height);
    }
    for(let i=1; i<tetris_rows; i++){
        canvasctx.moveto(0, i*cell_size);
        canvasctx.lineto(mycanvas.width, i*cell_size);
    }
    canvasctx.closepath();
    canvasctx.strokestyle="#b4a79d";
    canvasctx.linewidth=0.6;
    canvasctx.stroke();
    //第一行,最后一行,第一列,最后一列粗一点。
    canvasctx.beginpath();
    canvasctx.moveto(0, 0);
    canvasctx.lineto(mycanvas.width, 0);
    canvasctx.moveto(0, mycanvas.height);
    canvasctx.lineto(mycanvas.width, mycanvas.height);
    canvasctx.moveto(0, 0);
    canvasctx.lineto(0, mycanvas.height);
    canvasctx.moveto(mycanvas.width, 0);
    canvasctx.lineto(mycanvas.width, mycanvas.height);
    canvasctx.closepath();
    canvasctx.strokestyle="#b4a79d";
    canvasctx.linewidth=4;
    canvasctx.stroke();
    //设置绘制block时的style
    canvasctx.fillstyle="#201a14";
}
 1 function changewidthandheight(w, h){
 2     //通过jquery设置css
 3     h+=$("ui_bg").css("height")+$("ui_bg").css("margin-rop")+$("ui_bg").css("margin-bottom")+$("ui_bg").css("padding-top")+$("ui_bg").css("padding-bottom");
 4     $(".bg").css({
 5         "width":w,
 6         "height":h,
 7         "top":0, "bottom":0, "right":0, "left":0,
 8         "margin":"auto"
 9     });
10 }
 1 //draw blocks
 2 function drawblocks(){
 3     //清空地图
 4     for(let i=0; i<tetris_rows;i++){
 5         for(let j=0;j<tetris_cols;j++)
 6             canvasctx.clearrect(j*cell_size+1, i*cell_size+1, cell_size-2, cell_size-2);
 7     }
 8     //绘制地图
 9     for(let i=0; i<tetris_rows;i++){
10         for(let j=0;j<tetris_cols;j++){
11             if(tetris_status[i][j]!=no_block)
12                 canvasctx.fillrect(j*cell_size+1, i*cell_size+1, cell_size-2, cell_size-2);//中间留点缝隙
13         }
14     }
15     //绘制currentfall
16     for(let i=0;i<currentfall.length;i++)
17         canvasctx.fillrect(currentfall[i].x*cell_size+1, currentfall[i].y*cell_size+1, cell_size-2,cell_size-2);
18 }

游戏逻辑

 1 function rotate(){
 2     // 定义记录能否旋转的旗标
 3     var canrotate = true;
 4     for (var i = 0 ; i < currentfall.length ; i++)
 5     {
 6         var prex = currentfall[i].x;
 7         var prey = currentfall[i].y;
 8         // 始终以第三个方块作为旋转的中心,
 9         // i == 2时,说明是旋转的中心
10         if(i != 2)
11         {
12             // 计算方块旋转后的x、y坐标
13             var afterrotatex = currentfall[2].x + prey - currentfall[2].y;
14             var afterrotatey = currentfall[2].y + currentfall[2].x - prex;
15             // 如果旋转后所在位置已有方块,表明不能旋转
16             if(tetris_status[afterrotatey][afterrotatex + 1] != no_block)
17             {
18                 canrotate = false;
19                 break;
20             }
21             // 如果旋转后的坐标已经超出了最左边边界
22             if(afterrotatex < 0 || tetris_status[afterrotatey - 1][afterrotatex] != no_block)
23             {
24                 moveright();
25                 afterrotatex = currentfall[2].x + prey - currentfall[2].y;
26                 afterrotatey = currentfall[2].y + currentfall[2].x - prex;
27                 break;
28             }
29             if(afterrotatex < 0 || tetris_status[afterrotatey-1][afterrotatex] != no_block)
30             {
31                 moveright();
32                 break;
33             }
34             // 如果旋转后的坐标已经超出了最右边边界
35             if(afterrotatex >= tetris_cols - 1 || 
36                 tetris_status[afterrotatey][afterrotatex+1] != no_block)
37             {
38                 moveleft();
39                 afterrotatex = currentfall[2].x + prey - currentfall[2].y;
40                 afterrotatey = currentfall[2].y + currentfall[2].x - prex;
41                 break;
42             }
43             if(afterrotatex >= tetris_cols - 1 || 
44                 tetris_status[afterrotatey][afterrotatex+1] != no_block)
45             {
46                 moveleft();
47                 break;
48             }
49         }
50     }
51     if(canrotate){
52         for (var i = 0 ; i < currentfall.length ; i++){
53             var prex = currentfall[i].x;
54             var prey = currentfall[i].y;
55             if(i != 2){
56                 currentfall[i].x = currentfall[2].x + 
57                     prey - currentfall[2].y;
58                 currentfall[i].y = currentfall[2].y + 
59                     currentfall[2].x - prex;
60             }
61         }
62         localstorage.setitem("currentfall", json.stringify(currentfall));
63     }
64 }
 1 //按下 下 或 interval到了
 2 function next(){
 3     if(movedown()){
 4         //记录block
 5         for(let i=0;i<currentfall.length;i++)
 6             tetris_status[currentfall[i].y][currentfall[i].x]=have_block;
 7         //判断有没有满行的
 8         for(let j=0;j<currentfall.length;j++){
 9             for(let i=0;i<tetris_cols; i++){
10                 if(tetris_status[currentfall[j].y][i]==no_block)
11                     break;
12                 //最后一行满了
13                 if(i==tetris_cols-1){
14                     //消除最后一行
15                     for(let i=currentfall[j].y; i>0;i--){
16                         for(let j=0;j<tetris_cols;j++)
17                             tetris_status[i][j]=tetris_status[i-1][j];
18                     }
19                     //分数增加
20                     curscore+=5;
21                     localstorage.setitem("curscore", curscore);
22                     if(curscore>maxscore){
23                         //超越最高分
24                         maxscore=curscore;
25                         localstorage.setitem("maxscore", maxscore);
26                     }
27                     //加速
28                     curspeed+=0.1;
29                     localstorage.setitem("curspeed", curspeed);
30                     //ui输出
31                     curscoreele.innerhtml=""+curscore;
32                     maxscoreele.innerhtml=""+maxscore;
33                     curspeedele.innerhtml=curspeed.tofixed(1);//保留两位小数
34                     clearinterval(timer);
35                     timer=setinterval(function(){
36                         next();
37                     }, 500/curspeed);
38                 }
39             }
40         }
41         //判断是否触顶
42         for(let i=0;i<currentfall.length;i++){
43             if(currentfall[i].y==0){
44                 gameend();
45                 return;
46             }
47         }
48         localstorage.setitem("tetris_status", json.stringify(tetris_status));
49         //新的block
50         createblock();
51         localstorage.setitem("currentfall", json.stringify(currentfall));
52     }
53     drawblocks();
54 }
55 
56 //右移
57 function moveright(){
58     for(let i=0;i<currentfall.length;i++){
59         if(currentfall[i].x+1>=tetris_rows || tetris_status[currentfall[i].y][currentfall[i].x+1]!=no_block)
60             return;
61     }
62     for(let i=0;i<currentfall.length;i++)
63         currentfall[i].x++;
64     localstorage.setitem("currentfall", json.stringify(currentfall));
65     return;
66 }
67 //左移
68 function moveleft(){
69     for(let i=0;i<currentfall.length;i++){
70         if(currentfall[i].x-1<0 || tetris_status[currentfall[i].y][currentfall[i].x-1]!=no_block)
71             return;
72     }
73     for(let i=0;i<currentfall.length;i++)
74         currentfall[i].x--;
75     localstorage.setitem("currentfall", json.stringify(currentfall));
76     return;
77 }
78 //judge can move down and if arrive at end return 1, if touch other blocks return 2, else, return 0
79 function movedown(){
80     for(let i=0;i<currentfall.length;i++){
81         if(currentfall[i].y>=tetris_rows-1 || tetris_status[currentfall[i].y+1][currentfall[i].x]!=no_block)
82             return true;
83     }
84 
85     for(let i=0;i<currentfall.length;i++)
86         currentfall[i].y+=1;
87     return false;
88 }
 1 function gamekeyevent(evt){
 2     switch(evt.keycode){
 3         //向下
 4         case 40://↓
 5         case 83://s
 6             next();
 7             drawblocks();
 8             break;
 9         //向左
10         case 37://←
11         case 65://a
12             moveleft();
13             drawblocks();
14             break;
15         //向右
16         case 39://→
17         case 68://d
18             moveright();
19             drawblocks();
20             break;
21         //旋转
22         case 38://↑
23         case 87://w
24             rotate();
25             drawblocks();
26             break;
27     }
28 }

其他的详细情况可以看源代码,我就不整理了。

接下来我们看游戏结束时的特效。因为我也不是很懂,所以在这里整理的会比较详细。当做学习。

 1 //game end
 2 function gameend(){
 3     clearinterval(timer);
 4     //键盘输入监听结束
 5     window.onkeydown=function(){
 6         //按任意键重新开始游戏
 7         window.onkeydown=gamekeyevent;
 8         //初始化游戏数据
 9         initdata();
10         createblock();
11         localstorage.setitem("currentfall", json.stringify(currentfall));
12         localstorage.setitem("tetris_status", json.stringify(tetris_status));
13         localstorage.setitem("curscore", curscore);
14         localstorage.setitem("curspeed", curspeed);
15         //绘制
16         curscoreele.innerhtml=""+curscore;
17         curspeedele.innerhtml=curspeed.tofixed(1);//保留两位小数
18         drawblocks();
19         timer=setinterval(function(){
20             next();
21         }, 500/curspeed);
22         //清除特效
23         this.stage.removeallchildren();
24         this.textstage.removeallchildren();
25     };
26     //特效,游戏结束
27     settimeout(function(){
28         initanim();
29         //擦除黑色方块
30         for(let i=0; i<tetris_rows;i++){
31             for(let j=0;j<tetris_cols;j++)
32                 canvasctx.clearrect(j*cell_size+1, i*cell_size+1, cell_size-2, cell_size-2);
33         }
34     }, 200);
35     //推迟显示failed
36     settimeout(function(){
37         if(textformed) {
38             explode();
39             settimeout(function() {
40                 createtext("failed");
41             }, 810);
42         } else {
43             createtext("failed");
44         }
45     }, 800);
46 }

上面代码里的localstorage是html5的本地数据存储。因为不是运用很难,所以具体看代码。

整个特效是运用了createjs插件。要引入几个文件。

Html5 小游戏 俄罗斯方块

easeljs-0.7.1.min.js, easepacj.min.js, requestanimationframe.js和tweenlite.min.js
游戏重新开始就要清除特效。我看api里我第一眼望过去最明显的就是removeallchildren(),所以就选了这个。其他的改进日后再说。

        //清除特效
        this.stage.removeallchildren();
        this.textstage.removeallchildren();
function initanim() {
    initstages();
    inittext();
    initcircles();
    //在stage下方添加文字——按任意键重新开始游戏.
    tmp = new createjs.text("t", "12px 'source sans pro'", "#54555c");
    tmp.textalign = 'center';
    tmp.x = 180;
    tmp.y=350;
    tmp.text = "按任意键重新开始游戏";
    stage.addchild(tmp);
    animate();
}

上面初始化了一个stage,用于存放特效,一个textstage,用于形成“failed”的像素图片。还有一个按任意键重新游戏的提示。同时开始每隔一段时间就刷新stage。

根据block的位置来初始化小圆点。

 1 function initcircles() {
 2     circles = [];
 3     var p=[];
 4     var count=0;
 5     for(let i=0; i<tetris_rows;i++)
 6         for(let j=0;j<tetris_cols;j++)
 7             if(tetris_status[i][j]!=no_block)
 8                 p.push({'x':j*cell_size+2, 'y':i*cell_size+2, 'w':cell_size-3, 'h':cell_size-4});
 9     for(var i=0; i<250; i++) {
10         var circle = new createjs.shape();
11         var r = 7;
12         //x和y范围限定在黑色block内
13         var x = p[count]['x']+p[count]['w']*math.random();
14         var y = p[count]['y']+p[count]['h']*math.random();
15         count++;
16         if(count>=p.length)
17             count=0;
18         var color = colors[math.floor(i%colors.length)];
19         var alpha = 0.2 + math.random()*0.5;
20         circle.alpha = alpha;
21         circle.radius = r;
22         circle.graphics.beginfill(color).drawcircle(0, 0, r);
23         circle.x = x;
24         circle.y = y;
25         circles.push(circle);
26         stage.addchild(circle);
27         circle.movement = 'float';
28         tweencircle(circle);
29     }
30 }

然后再讲显示特效failed的createtext()。先将failed的text显示在textstage里,然后ctx.getimagedata.data获取像素数据,并以此来为每个小圆点定义位置。

 1 function createtext(t) {
 2     curtext=t;
 3     var fontsize = 500/(t.length);
 4     if (fontsize > 80) fontsize = 80;
 5     text.text = t;
 6     text.font = "900 "+fontsize+"px 'source sans pro'";
 7     text.textalign = 'center';
 8     text.x = tetris_cols*cell_size/2;
 9     text.y = 0;
10     textstage.addchild(text);
11     textstage.update();
12 
13     var ctx = document.getelementbyid('text').getcontext('2d');
14     var pix = ctx.getimagedata(0,0,600,200).data;
15     textpixels = [];
16     for (var i = pix.length; i >= 0; i -= 4) {
17         if (pix[i] != 0) {
18             var x = (i / 4) % 600;
19             var y = math.floor(math.floor(i/600)/4);
20             if((x && x%8 == 0) && (y && y%8 == 0)) textpixels.push({x: x, y: y});
21         }
22     }
23 
24     formtext();
25     textstage.clear();//清楚text的显示
26 }

跟着代码的节奏走,我们现在来到了formtext.

 1 function formtext() {
 2     for(var i= 0, l=textpixels.length; i<l; i++) {
 3         circles[i].originx = offsetx + textpixels[i].x;
 4         circles[i].originy = offsety + textpixels[i].y;
 5         tweencircle(circles[i], 'in');
 6     }
 7     textformed = true;
 8     if(textpixels.length < circles.length) {
 9         for(var j = textpixels.length; j<circles.length; j++) {
10             circles[j].tween = tweenlite.to(circles[j], 0.4, {alpha: 0.1});
11         }
12     }
13 }

explode()就是讲已组成字的小圆点给重新遣散。

动画实现是使用了tweenlite.

 1 function tweencircle(c, dir) {
 2     if(c.tween) c.tween.kill();
 3     if(dir == 'in') {
 4         /*tweenlite.to 改变c实例的x坐标,y坐标,使用easeinout弹性函数,透明度提到1,改变大小,radius,总用时0.4s*/
 5         c.tween = tweenlite.to(c, 0.4, {x: c.originx, y: c.originy, ease:quad.easeinout, alpha: 1, radius: 5, scalex: 0.4, scaley: 0.4, oncomplete: function() {
 6             c.movement = 'jiggle';/*轻摇*/
 7             tweencircle(c);
 8         }});
 9     } else if(dir == 'out') {
10         c.tween = tweenlite.to(c, 0.8, {x: window.innerwidth*math.random(), y: window.innerheight*math.random(), ease:quad.easeinout, alpha: 0.2 + math.random()*0.5, scalex: 1, scaley: 1, oncomplete: function() {
11             c.movement = 'float';
12             tweencircle(c);
13         }});
14     } else {
15         if(c.movement == 'float') {
16             c.tween = tweenlite.to(c, 5 + math.random()*3.5, {x: c.x + -100+math.random()*200, y: c.y + -100+math.random()*200, ease:quad.easeinout, alpha: 0.2 + math.random()*0.5,
17                 oncomplete: function() {
18                     tweencircle(c);
19                 }});
20         } else {
21             c.tween = tweenlite.to(c, 0.05, {x: c.originx + math.random()*3, y: c.originy + math.random()*3, ease:quad.easeinout,
22                 oncomplete: function() {
23                     tweencircle(c);
24                 }});
25         }
26     }
27 }

tweenlite.to函数第一个参数,要做动画的实例,第二个参数,事件,第三个参数,动画改变参数。

quad.easeinout()意思是在动画开始和结束时缓动。
oncomplete动画完成时调用的函数。易得,在我们的应用中,我们将开始下一次动画。

 个人感言

其实刚开始没想做这么复杂,所以文件排的比较随意,然后就导致了后期项目完成时那副杂乱无章的样子。^_^,以后改。等我等看懂动画效果时在说,现在用的有点半懵半懂。

这篇博客写得有点乱。新手之作,就先这样吧。同上,以后改。因为不知道这个项目会不会拿来直接当我们计算机职业实践的作业。要是的话,我就彻改,连同博客。

以下是源代码地址。

还在审核。明天再说。相信我,我在小番茄里做了记录。