用html写游戏,Html5写一个简单的俄罗斯方块小游戏
导言
在一个风和日丽的一天,看完了疯狂HTML 5+CSS 3+JavaScript讲义,跟着做了书里最后一章的俄罗斯方块小游戏,并做了一些改进,作为自己前端学习的第一站。
游戏效果:
制作思路
因为书里的俄罗斯方块比较普通,太常规了,不是很好看,所以我在网上找了上面那张图片,打算照着它来做。(请无视成品和原图的差距)
然后便是游戏界面和常规的俄罗斯方块游戏逻辑。
接着便是游戏结束界面了。
原本想做个弹出层,但觉得找图片有点麻烦,所以就在网上找了文字特效,套用了一下。
代码实现:
首先是html文件和css文件,主要涉及了布局方面。作为新手,在上面真的是翻来覆去的踩坑。o(╥﹏╥)o
index.html
俄罗斯方块
/*导入外部的字体文件*/
@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;
}
速度:1
当前分数:0
最高分数:0
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
function changeWidthAndHeight(w, h){
//通过jquery设置css
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");
$(".bg").css({
"width":w,
"height":h,
"top":0, "bottom":0, "right":0, "left":0,
"margin":"auto"
});
}
change width and height
//draw blocks
function drawBlocks(){
//清空地图
for(let i=0; i
for(let j=0;j
canvasCtx.clearRect(j*CELL_SIZE+1, i*CELL_SIZE+1, CELL_SIZE-2, CELL_SIZE-2);
}
//绘制地图
for(let i=0; i
for(let j=0;j
if(tetris_status[i][j]!=NO_BLOCK)
canvasCtx.fillRect(j*CELL_SIZE+1, i*CELL_SIZE+1, CELL_SIZE-2, CELL_SIZE-2);//中间留点缝隙
}
}
//绘制currentFall
for(let i=0;i
canvasCtx.fillRect(currentFall[i].x*CELL_SIZE+1, currentFall[i].y*CELL_SIZE+1, CELL_SIZE-2,CELL_SIZE-2);
}
draw block
游戏逻辑
function rotate(){
// 定义记录能否旋转的旗标
var canRotate = true;
for (var i = 0 ; i < currentFall.length ; i++)
{
var preX = currentFall[i].x;
var preY = currentFall[i].y;
// 始终以第三个方块作为旋转的中心,
// i == 2时,说明是旋转的中心
if(i != 2)
{
// 计算方块旋转后的x、y坐标
var afterRotateX = currentFall[2].x + preY - currentFall[2].y;
var afterRotateY = currentFall[2].y + currentFall[2].x - preX;
// 如果旋转后所在位置已有方块,表明不能旋转
if(tetris_status[afterRotateY][afterRotateX + 1] != NO_BLOCK)
{
canRotate = false;
break;
}
// 如果旋转后的坐标已经超出了最左边边界
if(afterRotateX < 0 || tetris_status[afterRotateY - 1][afterRotateX] != NO_BLOCK)
{
moveRight();
afterRotateX = currentFall[2].x + preY - currentFall[2].y;
afterRotateY = currentFall[2].y + currentFall[2].x - preX;
break;
}
if(afterRotateX < 0 || tetris_status[afterRotateY-1][afterRotateX] != NO_BLOCK)
{
moveRight();
break;
}
// 如果旋转后的坐标已经超出了最右边边界
if(afterRotateX >= TETRIS_COLS - 1 ||
tetris_status[afterRotateY][afterRotateX+1] != NO_BLOCK)
{
moveLeft();
afterRotateX = currentFall[2].x + preY - currentFall[2].y;
afterRotateY = currentFall[2].y + currentFall[2].x - preX;
break;
}
if(afterRotateX >= TETRIS_COLS - 1 ||
tetris_status[afterRotateY][afterRotateX+1] != NO_BLOCK)
{
moveLeft();
break;
}
}
}
if(canRotate){
for (var i = 0 ; i < currentFall.length ; i++){
var preX = currentFall[i].x;
var preY = currentFall[i].y;
if(i != 2){
currentFall[i].x = currentFall[2].x +
preY - currentFall[2].y;
currentFall[i].y = currentFall[2].y +
currentFall[2].x - preX;
}
}
localStorage.setItem("currentFall", JSON.stringify(currentFall));
}
}
旋转
//按下 下 或 interval到了
function next(){
if(moveDown()){
//记录block
for(let i=0;i
tetris_status[currentFall[i].y][currentFall[i].x]=HAVE_BLOCK;
//判断有没有满行的
for(let j=0;j
for(let i=0;i
if(tetris_status[currentFall[j].y][i]==NO_BLOCK)
break;
//最后一行满了
if(i==TETRIS_COLS-1){
//消除最后一行
for(let i=currentFall[j].y; i>0;i--){
for(let j=0;j
tetris_status[i][j]=tetris_status[i-1][j];
}
//分数增加
curScore+=5;
localStorage.setItem("curScore", curScore);
if(curScore>maxScore){
//超越最高分
maxScore=curScore;
localStorage.setItem("maxScore", maxScore);
}
//加速
curSpeed+=0.1;
localStorage.setItem("curSpeed", curSpeed);
//ui输出
curScoreEle.innerHTML=""+curScore;
maxScoreEle.innerHTML=""+maxScore;
curSpeedEle.innerHTML=curSpeed.toFixed(1);//保留两位小数
clearInterval(timer);
timer=setInterval(function(){
next();
}, 500/curSpeed);
}
}
}
//判断是否触顶
for(let i=0;i
if(currentFall[i].y==0){
gameEnd();
return;
}
}
localStorage.setItem("tetris_status", JSON.stringify(tetris_status));
//新的block
createBlock();
localStorage.setItem("currentFall", JSON.stringify(currentFall));
}
drawBlocks();
}
//右移
function moveRight(){
for(let i=0;i
if(currentFall[i].x+1>=TETRIS_ROWS || tetris_status[currentFall[i].y][currentFall[i].x+1]!=NO_BLOCK)
return;
}
for(let i=0;i
currentFall[i].x++;
localStorage.setItem("currentFall", JSON.stringify(currentFall));
return;
}
//左移
function moveLeft(){
for(let i=0;i
if(currentFall[i].x-1<0 || tetris_status[currentFall[i].y][currentFall[i].x-1]!=NO_BLOCK)
return;
}
for(let i=0;i
currentFall[i].x--;
localStorage.setItem("currentFall", JSON.stringify(currentFall));
return;
}
//judge can move down and if arrive at end return 1, if touch other blocks return 2, else, return 0
function moveDown(){
for(let i=0;i
if(currentFall[i].y>=TETRIS_ROWS-1 || tetris_status[currentFall[i].y+1][currentFall[i].x]!=NO_BLOCK)
return true;
}
for(let i=0;i
currentFall[i].y+=1;
return false;
}
上下左右移动
function gameKeyEvent(evt){
switch(evt.keyCode){
//向下
case 40://↓
case 83://S
next();
drawBlocks();
break;
//向左
case 37://←
case 65://A
moveLeft();
drawBlocks();
break;
//向右
case 39://→
case 68://D
moveRight();
drawBlocks();
break;
//旋转
case 38://↑
case 87://W
rotate();
drawBlocks();
break;
}
}
keydown事件监听
keydown事件监听
其他的详细情况可以看源代码,我就不整理了。
接下来我们看游戏结束时的特效。因为我也不是很懂,所以在这里整理的会比较详细。当做学习。
//game end
function gameEnd(){
clearInterval(timer);
//键盘输入监听结束
window.οnkeydοwn=function(){
//按任意键重新开始游戏
window.οnkeydοwn=gameKeyEvent;
//初始化游戏数据
initData();
createBlock();
localStorage.setItem("currentFall", JSON.stringify(currentFall));
localStorage.setItem("tetris_status", JSON.stringify(tetris_status));
localStorage.setItem("curScore", curScore);
localStorage.setItem("curSpeed", curSpeed);
//绘制
curScoreEle.innerHTML=""+curScore;
curSpeedEle.innerHTML=curSpeed.toFixed(1);//保留两位小数
drawBlocks();
timer=setInterval(function(){
next();
}, 500/curSpeed);
//清除特效
this.stage.removeAllChildren();
this.textStage.removeAllChildren();
};
//特效,游戏结束
setTimeout(function(){
initAnim();
//擦除黑色方块
for(let i=0; i
for(let j=0;j
canvasCtx.clearRect(j*CELL_SIZE+1, i*CELL_SIZE+1, CELL_SIZE-2, CELL_SIZE-2);
}
}, 200);
//推迟显示Failed
setTimeout(function(){
if(textFormed) {
explode();
setTimeout(function() {
createText("FAILED");
}, 810);
} else {
createText("FAILED");
}
}, 800);
}
上面代码里的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的位置来初始化小圆点。
function initCircles() {
circles = [];
var p=[];
var count=0;
for(let i=0; i
for(let j=0;j
if(tetris_status[i][j]!=NO_BLOCK)
p.push({'x':j*CELL_SIZE+2, 'y':i*CELL_SIZE+2, 'w':CELL_SIZE-3, 'h':CELL_SIZE-4});
for(var i=0; i<250; i++) {
var circle = new createjs.Shape();
var r = 7;
//x和y范围限定在黑色block内
var x = p[count]['x']+p[count]['w']*Math.random();
var y = p[count]['y']+p[count]['h']*Math.random();
count++;
if(count>=p.length)
count=0;
var color = colors[Math.floor(i%colors.length)];
var alpha = 0.2 + Math.random()*0.5;
circle.alpha = alpha;
circle.radius = r;
circle.graphics.beginFill(color).drawCircle(0, 0, r);
circle.x = x;
circle.y = y;
circles.push(circle);
stage.addChild(circle);
circle.movement = 'float';
tweenCircle(circle);
}
}
initCircles
然后再讲显示特效Failed的createText()。先将FAILED的text显示在textstage里,然后ctx.getImageData.data获取像素数据,并以此来为每个小圆点定义位置。
function createText(t) {
curText=t;
var fontSize = 500/(t.length);
if (fontSize > 80) fontSize = 80;
text.text = t;
text.font = "900 "+fontSize+"px 'Source Sans Pro'";
text.textAlign = 'center';
text.x = TETRIS_COLS*CELL_SIZE/2;
text.y = 0;
textStage.addChild(text);
textStage.update();
var ctx = document.getElementById('text').getContext('2d');
var pix = ctx.getImageData(0,0,600,200).data;
textPixels = [];
for (var i = pix.length; i >= 0; i -= 4) {
if (pix[i] != 0) {
var x = (i / 4) % 600;
var y = Math.floor(Math.floor(i/600)/4);
if((x && x%8 == 0) && (y && y%8 == 0)) textPixels.push({x: x, y: y});
}
}
formText();
textStage.clear();//清楚text的显示
}
CreateText
跟着代码的节奏走,我们现在来到了formtext.
function formText() {
for(var i= 0, l=textPixels.length; i
circles[i].originX = offsetX + textPixels[i].x;
circles[i].originY = offsetY + textPixels[i].y;
tweenCircle(circles[i], 'in');
}
textFormed = true;
if(textPixels.length < circles.length) {
for(var j = textPixels.length; j
circles[j].tween = TweenLite.to(circles[j], 0.4, {alpha: 0.1});
}
}
}
formtext
explode()就是讲已组成字的小圆点给重新遣散。
动画实现是使用了tweenlite.
function tweenCircle(c, dir) {
if(c.tween) c.tween.kill();
if(dir == 'in') {
/*TweenLite.to 改变c实例的x坐标,y坐标,使用easeInOut弹性函数,透明度提到1,改变大小,radius,总用时0.4s*/
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() {
c.movement = 'jiggle';/*轻摇*/
tweenCircle(c);
}});
} else if(dir == 'out') {
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() {
c.movement = 'float';
tweenCircle(c);
}});
} else {
if(c.movement == 'float') {
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,
onComplete: function() {
tweenCircle(c);
}});
} else {
c.tween = TweenLite.to(c, 0.05, {x: c.originX + Math.random()*3, y: c.originY + Math.random()*3, ease:Quad.easeInOut,
onComplete: function() {
tweenCircle(c);
}});
}
}
}
TweenLite.to函数第一个参数,要做动画的实例,第二个参数,事件,第三个参数,动画改变参数。
Quad.easeInOut()意思是在动画开始和结束时缓动。onComplete动画完成时调用的函数。易得,在我们的应用中,我们将开始下一次动画。
个人感言
其实刚开始没想做这么复杂,所以文件排的比较随意,然后就导致了后期项目完成时那副杂乱无章的样子。^_^,以后改。等我等看懂动画效果时在说,现在用的有点半懵半懂。
这篇博客写得有点乱。新手之作,就先这样吧。同上,以后改。因为不知道这个项目会不会拿来直接当我们计算机职业实践的作业。要是的话,我就彻改,连同博客。
以下是源代码地址。
还在审核。明天再说。相信我,我在小番茄里做了记录。
总结
以上所述是小编给大家介绍的Html5写一个简单的俄罗斯方块小游戏,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
用html写游戏,Html5写一个简单的俄罗斯方块小游戏相关推荐
- python简单小游戏代码_一个简单的python小游戏---七彩同心圆
本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 用pygame做一个简单的python小游戏-七彩同心圆 玩法:每次点击鼠标时,会以鼠标为圆心,不断 ...
- Friends——一个简单的Processing小游戏
Friends--一个简单的Processing小游戏 背景前言 人类永远是矛盾的个体,我们一边喜爱着无垠的夜空,一边又恐惧着深邃的孤独- 在无边无际的的黑夜中,每一个光点都是一个孤独的个体,他们本应 ...
- 原生JS实现一个简单的打字小游戏
原生JS实现一个简单的打字小游戏 利用javascript制作一个简单的打字小游戏 之前写了一个贪吃蛇小游戏好像反响不错 今天我来写一个比贪吃蛇更low更简单的打字小游戏 打字小游戏原理 接下来咋们直 ...
- 一个简单的纸牌小游戏
一个简单的纸牌小游戏 初始化页面布局 function initView(){var html = html2 = '';for(var i=1;i<=10;i++){html += '< ...
- 用pygame做一个简单的python小游戏---贪吃蛇
用pygame做一个简单的python小游戏-贪吃蛇 贪吃蛇游戏博客链接:(方法一样,语言不一样) c++贪吃蛇:https://blog.csdn.net/weixin_46791942/artic ...
- 用pygame做一个简单的python小游戏---七彩同心圆
用pygame做一个简单的python小游戏-七彩同心圆 这个小游戏原是我同学python课的课后作业,并不是很难,就简单实现了一下,顺便加强一下pygame库的学习. 玩法:每次点击鼠标时,会以鼠标 ...
- 用pygame做一个简单的python小游戏---生命游戏
用pygame做一个简单的python小游戏-生命游戏 生命游戏(Game of Life) 生命游戏(Game of Life)是剑桥大学约翰·何顿·康威(John Horton Conway)教授 ...
- python七彩同心圆_用pygame做一个简单的python小游戏---七彩同心圆
用pygame做一个简单的python小游戏---七彩同心圆 用pygame做一个简单的python小游戏-七彩同心圆 这个小游戏原是我同学python课的课后作业,并不是很难,就简单实现了一下,顺便 ...
- 做一个简单的java小游戏--单机版五子棋
做一个简单的java小游戏–单机版五子棋 学了java有一段时间了,今天就来搞一个简单的单机版五子棋游戏. 实现功能:那必须能进行基础的输赢判断.还有重新开始的功能,悔棋的功能,先手设置的功能和退出的 ...
- 制作一个简单的switch小游戏
好的,那么,我们可以这样来制作一个简单的 switch 小游戏: 首先,我们需要先引入所需的库,如 stdio.h 和 stdlib.h. 接着,我们可以使用 printf 和 scanf 函数来输出 ...
最新文章
- 工作300:处理预览界面
- (王道408考研操作系统)第三章内存管理-第一节6-4:非连续分配管理方式之基本分页存储管理之两级页表
- python已知有列表_python 列表常用方法
- python基础知识学习笔记(2)
- HTTP 如何传输大文件
- python语法糖怎么用_程序中的奇技淫巧之语法糖-释然
- Linux文件描述符和输入输出重定向
- springboot vue导出excel 使用easypoi
- 2011天涯上令人心酸至极的微瞬间
- 删除只读属性的文件夹及其子文件
- 陆奇天团二期成团,清北硕博超七成
- caffe cmake matlab,编译caffe时候抛出的错误
- 在CentOS7上源码安装MongoDB 3.2.7
- 【问题记录】ABP框架模板页面样式加载不完全
- Linux常用环境软件安装(提供对应安装包)
- VBA颜色转换 中英互译
- encode decode
- 全数字OQPSK调制解调的基本算法,包括成形滤波器、NCO模型、载波恢复
- 数字信号与图像处理实验一:信号处理基础
- h61 nvme硬盘_NVMe和PCIE谁对固态硬盘的影响更大?
热门文章
- adprw指令教程_三菱FX5U模拟量,通信,运动控制详解
- Unity5.6——VideoPlayer播放
- excel 将日期转换为8位数字
- python爬视频网站数据_Python爬虫:B站排行榜视频播放量,视频评论量等数据采集...
- 如何用计算机术语写论文,计算机毕业论文结论怎么写?
- 2020年全国大学生数学建模竞赛应该如何准备
- 弘玑Cyclone上榜36氪中国超自动化先锋企业
- ORA-1652: unable to extend temp segment by 128 in tablespace TEMP
- 炫界 (667) -(回应骑两小)_西番莲小棚架高产优质栽培技术,既能高产,又能保证质量...
- Win10 快速检查修复系统方法