时间:凌晨2:36

历时:两晚+半个下午

代码:约300行

这两天发烧,头痛脑壳昏的,没想到竟然还能有动力去做小游戏,其实这不是我第一次想要弄个俄罗斯方块,前几个星期的时候就尝试着去弄一个,然后被凌乱的代码带进了无尽的深渊,最后忍痛把几天的代码一键删除。这次从头梳理思路,重新设计,结果也正好印证了某人的一句话:有时候重新开始比修bug更省事。

在正文开始之前还要啰嗦一下,标题中所谓自给自足,是在没有参考任何设计思路的前提下去开发这游戏的,你可能会不解,如果参考优秀的思路,岂不是事半功倍,当然,参考与不参考都有利,我只说不参考的利,当我煞费苦心、历经数十个BUG修改,终于完成一件作品的时候,我可以很自豪地对别人说:“看,我开发的游戏!”当然,创意不是我的,但这并不影响自己那份“虚荣心”,为一款经典的游戏赋予自我的理解,并将它融入游戏中,岂不是一件有意思的事,而且,回过头来再看一看别人的思路,有时会拍案而起,“这个我当初怎么就没想到呢?”,“原来这个问题可以这样解决”,“这个设计思路比我的思路好多了!”,诸如此类总比开始就直接看别人的思路而阻塞自己的思考要强得多,对吧?

好叻,正文开始~

想先看效果的,先跳转试玩一下吧!

俄罗斯方块,主游戏界面应该由一个一个的方块组成,如下图,当然成品里面这些网格是看不到的,这里只是助于理解,主界面尺寸为400×500,设定每块砖(网格)的尺寸为20×20,则每行有20个砖块,每列有25个砖块。相关代码:

brickWidth = 20,    //砖块大小
width = 400, height = 500; //画布宽高,20X25

提到主界面的网格,就要提到一个非常重要的变量了,它就是BOARD,一个二维数组,形象化地说其尺寸是20×26,存储的值为0或1,0表示该位置没有砖块,1表示该位置有砖块,在接下来的一些判定中有重要作用,游戏细心的同学可能发现,为什么是20×26,而不是对应主界面网格的20×25,我在一开始的时候也是设定为20×25的,后来注意到如果加一行而且这一行的值都为1就可以很容易判断砖块是否到触及主界面底部了。相关代码:

// 初始化BOARD,注意纵向有26个,最后一排用来判断是否触底
for(i=0;i<20;i++){
BOARD[i] = [];
for(j=0;j<26;j++) {
if(j==25) {
BOARD[i][j] = 1
} else {
BOARD[i][j] = 0;
}
}
}

接下来看由4个砖块组成的“形状”,有五种,为了好描述,我把它们为别命名,Tian(田),Chu(锄头),Tu(凸起来),Thunder(闪电),Line(一横),哈哈有趣的名字,原谅我没找到它们的英文名字吧。

首先定义一个砖头类Brick:

function Brick() { }

其下有几个原型变量和方法:

Brick.prototype.embattle = null;    //砖块的布局(需重载)
Brick.prototype.isOverturn = 0; //是否翻转
Brick.prototype.originX = 9; //砖头的绘制起点X
Brick.prototype.originY = -3; //砖头的绘制起点Y
Brick.prototype.direction = 0; //砖头朝向
Brick.prototype.autoMoveTimer = null; //自动移动计时器
Brick.prototype.draw = function() { …… } //画砖块的方法
Brick.prototype.move = function(moveX, moveY) { …… } //移动的方法
Brick.prototype.autoMove = function() { …… } //自动移动的方法
Brick.prototype.change = function() { …… } //变换砖头朝向

Brick的子类有:Tian,Chu,Tu,Thunder,Line五个,每个子类中都重载Brick的embattle变量,embattle是什么,英译中的意思是布阵,这个阵是个什么阵呢?首先,同学们要理解我的思路,用Tu的embattle来举例,其代码如下:

this.embattle = [
[ [0,4,5,8], [1,4,5,6], [1,4,5,9], [0,1,2,5] ], //布局表为4X4表格,数字为砖头位置
[ [0,4,5,8], [1,4,5,6], [1,4,5,9], [0,1,2,5] ] //次行为翻转的情况
];

embattle是一个三维数组,第一维是是否翻转isOverturn(形象来说就像图片的水平翻转),第二维是方向direction(上左下右),第三维是形状的4个砖块分布情况,我把每个新形状对象定义在一个4×4的阵中,例如,Tu的this.embattle[0][0]为[0,4,5,8],数字即该砖块的所在位置,如下图:

所以要确定一个形状的位置和样子,需要isOverturn确定是否翻转,需要direction确定其方向,需要originX和originY确定“阵”的位置。

接下来,分别解释Brick的4个原型方法。

Brick.prototype.draw

ctx.fillStyle = 'rgb('+Math.floor(Math.random()*256)+','+Math.floor(Math.random()*256)+','+Math.floor(Math.random()*256)+')';
for(i=0;i<4;i++) {
tmp = this.embattle[this.isOverturn][this.direction][i];
ctx.fillRect((this.originX+tmp%4)*brickWidth, (this.originY+Math.floor(tmp/4))*brickWidth, brickWidth, brickWidth);
ctx.strokeRect((this.originX+tmp%4)*brickWidth+1, (this.originY+Math.floor(tmp/4))*brickWidth+1, brickWidth-2, brickWidth-2); //注意+1和减2
}

有上面说的确定形状的位置和样子的方法,之后就是纯粹canvas画图,4个砖块一个一个地画,不要看代码很长其实就是那么一点点,originX、originY和砖块在阵中的位置就可以确定画砖块的起点了。注意到代码的注释了没有,画边框的时候,它是从起点向外面画的,就像我把一个塑料袋套在另一个塑料袋的外面,为了以后的清除的方便且不影响其他的砖块,把边框画进fillRect的领土,就像我现在把这个塑料袋不套在外面而是放进这另一个塑料袋里面一样,就这个意思。

Brick.prototype.move

这是最长的一个了,移动的时候,moveX和moveY表示横纵的增量,没有同时非0的情况(这是人为的设定,要么横向移动要么纵向移动),当然要判断即将移动到的位置是否违规:

横向:

如果阵贴靠主界面左侧则不能向左移即moveX不能为-1

(this.originX==0 && moveX==-1)

判断右边时比较麻烦,因为不能直接用阵来判断是否贴靠右侧(看前面的图就知道阵的右边和下边可能没有砖块的),这时要一个个地判断4个砖块是否有至少有一个在最右,这时不能向右移动

|| (this.originX+tmp[0]%4==19 && moveX==1)
|| (this.originX+tmp[1]%4==19 && moveX==1)
|| (this.originX+tmp[2]%4==19 && moveX==1)
|| (this.originX+tmp[3]%4==19 && moveX==1)

最后还要判断即将到达的位置是否已经有砖块了。

|| (BOARD[this.originX+tmp[0]%4+moveX][this.originY+Math.floor(tmp[0]/4)]==1)
|| (BOARD[this.originX+tmp[1]%4+moveX][this.originY+Math.floor(tmp[1]/4)]==1)
|| (BOARD[this.originX+tmp[2]%4+moveX][this.originY+Math.floor(tmp[2]/4)]==1)
|| (BOARD[this.originX+tmp[3]%4+moveX][this.originY+Math.floor(tmp[3]/4)]==1)

纵向:

即将到达的位置是否已经有砖块了,注意到下面的代码的&& moveX==0,原来是没有的,后来发现每次砖块怎么刚刚靠上下面堆着的砖块就不能再移动了,原来横向移动的时候也进行了这个判断,即刚刚靠上下面的砖块,如果这时想左右移动,但下方有砖块,但是问题来了,下面有没有砖块跟我左右移动有什么关系呢?是吧。

if((as==1 || bs==1 || cs==1 || ds==1) && moveX==0) { …… }

纵向终止判断里面主要做了几件事:清除autoMoveTimer,设置BOARD在该形状当前位置的值为1,有可以消除的整行就消除,加分改分,判断胜利/失败,删除当前对象,召唤下一个形状。

横纵都没违规时:

这时,把该形状前一个位置的砖块清除,更新originX和originY,再画出来。

for(i=0;i<4;i++) {
tmp = this.embattle[this.isOverturn][this.direction][i];
ctx.clearRect((this.originX+tmp%4)*brickWidth, (this.originY+Math.floor(tmp/4))*brickWidth, brickWidth, brickWidth);
}
this.originX += moveX;
this.originY += moveY;
this.draw();

Brick.prototype.autoMove

只做一件事,设置计时器,定时向下移动。

var status, self = this;
this.autoMoveTimer = setInterval(function() {
status = self.move(0,1);
},speed);

Brick.prototype.change

改变形状的朝向,很好办啊,不是有embattle数组了吗?当然没有那么简单,不只是换个数组这么简单。要考虑改变方向之后占用的位置是否已经有砖块了,如果形状是贴着主界面右边界就更糟糕了,比如原来是竖着的Line,改变其方向变为横,占用阵的0、1、2、3,如果Line贴着右边界,originX为19,变为横向,占用阵的0、1、2、3,后面三个砖块已经溢出了主界面。

解决方案是:如果有越界的砖块就把阵往左挪一挪,直到不再越界。

while(ox+tmp[0]%4 > 19 || ox+tmp[1]%4 > 19 || ox+tmp[2]%4 > 19 || ox+tmp[3]%4 > 19) {
ox -= 1;
}

最后,如果都没事,就可以清除原位置,画出改变方向之后的形状了。

并不是太完美,因为有些卡位的情况没考虑进来,什么是卡位,看下图,你知道Line实例调用change方法的结果是什么了吗?事实上,它不应该成功改变方向的,对吧?还有其他一些卡位的情况。

Brick的4个原型方法就介绍到这里了。现在如果我要在右边的信息界面显示下一个的形状,最直接的方法就是,通过该形状的构造函数实例化一个对象,为防止其自动调用autoMove,为构造函数添加了isModel来判断是不是供提示用的。

还有按键事件监听、NextBrick函数和deleteObj自己看看吧,很容易看懂,游戏的入口就是NextBrick函数。

还有就是,我无法确定deleteObj是否真的成功让GC把对象回收了。

还有就是,我本想增加关卡功能,因为可以自由设置速度(speed变量),就把这功能放一放了。

----------20141114更新---

初始化的东西放进一个init函数里了。

还是把关卡功能实现了,速度会越来越快哦,你能挑战多少关呢?←_←

------------------

其他:

在线试玩:http://xzh-loop.github.io/Manji/lab/html5game/20141114-tetris.html

访问这个项目的GitHub位置:https://github.com/xzh-loop/Manji/tree/gh-pages/lab/html5game

访问我的GitHub Pages:http://xzh-loop.github.io/

-----------------------------------------------

你可能感兴趣:自己动手打造html5星际迷航

自给自足:动手打造html5俄罗斯方块的更多相关文章

  1. 自己动手打造html5星际迷航!

    学习html5的canvas第三天,觉得还没过瘾,转眼就忘,于是趁着有空,准备弄个小游戏来玩!游戏应该需要注意性能,还有一些逻辑需要斟酌,我想还需要用户可修改性,也就是用户配置.好,开始我们简单但有趣 ...

  2. 青瓷引擎之纯JavaScript打造HTML5游戏第二弹——《跳跃的方块》Part 10(排行榜界面&界面管理)

    继上一次介绍了<神奇的六边形>的完整游戏开发流程后(可点击这里查看),这次将为大家介绍另外一款魔性游戏<跳跃的方块>的完整开发流程. (点击图片可进入游戏体验) 因内容太多,为 ...

  3. 动手打造轻量web服务器(二)路由

    tomcat启动慢?自己动手打造轻量web服务器(一) 上篇讲了怎么做一个最简单的web服务器,这篇就是在上篇加上URL路由功能(什么是路由?) 首先,根据http获得请求行 val scanner ...

  4. 打造 html5 文件上传组件,实现进度显示及拖拽上传,支持秒传+分片上传+断点续传,兼容IE6+及其它标准浏览器

    老早就注册了博客园帐号,昨天才发现,连博客都没开,Github也是一样,深觉惭愧,赶紧潜个水压压惊`(*∩_∩*)′ 言归正传.大概许多人都会用到文件上传的功能,上传的库貌似也不少,比如(jQuery ...

  5. 一起动手打造个人娱乐级linux

    我们使用电脑,一直以来用的都是windows,但是对于像我这种爱折腾的人来说,尝试使用linux系统应该是一种不错的体验.说到linux,许多人可能都没听过,或者知道的人对它印象是这样的: 然而,li ...

  6. 自己动手打造WEB服务器 Windows + Apache + PHP + MySQL

    XWAMP并不打算打造一个多功能,零配置,方便调试的工具.XWAMP只是把原程序简单的组合在一起,利用CMD命令控制,真正的绿色版,只为了多学习点Windows + Apache + PHP + My ...

  7. 全栈开发——动手打造属于自己的直播间(Vue+SpringBoot+Nginx)

    前言 大学的学习时光临近尾声,感叹时光匆匆,三年一晃而过.同学们都忙着找工作,我也在这里抛一份简历吧,欢迎各位老板和猎手诚邀.我们进入正题.直播行业是当前火热的行业,谁都想从中分得一杯羹,直播养活了一 ...

  8. tomcat启动慢?自己动手打造轻量web服务器(一)

    废话少说,直接上代码. 编程语言:kotlin import java.net.ServerSocketimport java.net.Socket fun main(args:Array<St ...

  9. 自己动手打造基于 WKWebView 的混合开发框架(一)WKWebView 上手

    http://www.cocoachina.com/ios/20150911/13301.html 代码示例:https://github.com/johnlui/Swift-On-iOS/tree/ ...

随机推荐

  1. SharePoint各版本信息

    参考网页http://blogs.msdn.com/b/erica/archive/2013/05/30/sharepoint-server-2010-version-reference.aspx

  2. 关于JS变量和作用域

    ECMAScript 变量:1.基本类型值(简单数据段) 2.引用类型值(可能由过个值构成的对象) → 保存在内存中的对象 动态属性: 只能给引用型值动态添加新属性,以便将来使用. 复制变量值 : 基 ...

  3. Github android客户端源代码分析之一:环境搭建

    1.下载相应的包及项目,参考https://github.com/github/android/wiki/Building-From-Eclipse. 2.若需查看某些包的源文件或者javadoc,则 ...

  4. Laravel Packages 开发

    Packages是向Laravel中添加功能最重要的途径.composer.json中require的都是包.关于包的详细说明请查看 API . 下面一起创建一个简单的Package : 1. 环境配 ...

  5. 【maven学习笔记】 01 初见

    想学maven,maven是ant的替代品. 1:下载 maven是apache的顶级项目,在http://maven.apache.org/可以直接下载. 2:环境变量 下载完要配置环境变量,把bi ...

  6. autofac使用笔记

    在之前的项目中用来解耦的使用的轻型IOC框架是unity,它的使用也是很方便的提供在之前的文章的也提到过它的使用方式,但是使用久了之后发现了它的不足之处就是需要配置xml文件来对应的接口和实现的关系. ...

  7. JS常见操作

    //第一篇博文,希望大家多多支持 /***** BasePage.js 公共的 脚本文件 部分方法需引用jquery库 *****/ //#region 日期操作 //字符串转化为时间. functi ...

  8. Oracle EBS-SQL (MRP-5):重起MRP Manager.sql

    UPDATE fnd_concurrent_processes SET process_status_code = 'K' WHERE process_status_code not in ('K', ...

  9. STL set 使用小结

    这是微软帮助文档中对集合(set)的解释: “描述了一个控制变长元素序列的对象(注:set中的key和value是Key类型的,而map中的key和value是一个pair结构中的两个分 量)的模板类 ...

  10. codility上的练习(3)

    今天发现又出了lesson 3... 不过题目都很简单…… (1) Min-avg-slice 给定一个长度为n的整数数组,找到一个连续的子数组,数组元素的平均值最小. 数据范围N [1..10^5] ...