Html5 小游戏 俄罗斯方块,


导言

在一个风和日丽的一天,看完了疯狂HTML 5+CSS 3+JavaScript讲义,跟着做了书里最后一章的俄罗斯方块小游戏,并做了一些改进,作为自己365bet体育在线手机_365bet _365bet娱乐平台官网学习的第一站。

游戏效果:

 

 

 

制作思路

 

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

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

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

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

代码实现:

 

 首先是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">
        
        <div class="ui_bg">
            <div style="float:left;margin-right:4px;">
                速度:<span id="cur_speed">1span>
            div>
            <div style="float:left;">
                当前分数:<span id="cur_points">0span>
            div>
            <div style="float:right;">
                最高分数:<span id="max_points">0span>
            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){ canvasCtx.moveTo(i*CELL_SIZE, 0); canvasCtx.lineTo(i*CELL_SIZE, myCanvas.height); } for(let i=1; 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"; } draw canvas 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 } change width and height 1 //draw blocks 2 function drawBlocks(){ 3 //清空地图 4 for(let i=0; i<>){ 5 for(let j=0;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<>){ 10 for(let j=0;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<>) 17 canvasCtx.fillRect(currentFall[i].x*CELL_SIZE+1, currentFall[i].y*CELL_SIZE+1, CELL_SIZE-2,CELL_SIZE-2); 18 } draw block

游戏逻辑

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<>) 6 tetris_status[currentFall[i].y][currentFall[i].x]=HAVE_BLOCK; 7 //判断有没有满行的 8 for(let j=0;j<>){ 9 for(let i=0;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<>) 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<>){ 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<>){ 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<>) 63 currentFall[i].x++; 64 localStorage.setItem("currentFall", JSON.stringify(currentFall)); 65 return; 66 } 67 //左移 68 function moveLeft(){ 69 for(let i=0;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<>) 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<>){ 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<>) 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 } keydown事件监听

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

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

 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<>){
31             for(let j=0;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插件。要引入几个文件。

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(); } initAnim

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

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

1 function initCircles() { 2 circles = []; 3 var p=[]; 4 var count=0; 5 for(let i=0; i<>) 6 for(let j=0;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 } initCircles

然后再讲显示特效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 } CreateText

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

1 function formText() { 2 for(var i= 0, l=textPixels.length; 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) { 10 circles[j].tween = TweenLite.to(circles[j], 0.4, {alpha: 0.1}); 11 } 12 } 13 } formtext

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动画完成时调用的函数。易得,在我们的应用中,我们将开始下一次动画。

 个人感言

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

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

以下是源代码地址。(我还以为csdn的下载要的积分是自己定的。等我下次彻改的时候我传到github上。现在将就一下)

https://download.csdn.net/download/qq_26136211/12011640

 

相关文章

    暂无相关文章