HTML5游戏实战之俄罗斯方块

先说几句废话,写这篇文章前也思考了很久,最近这几年很少在博客上写文章了,感觉年龄越大越懒得写东西了,不过思前想好,还是决定写出来。还是以前的心态,做份记录,未来可以回顾。同时也希望能给初学者写一篇好的教程。

现在HTML5炒得的确很火,我就不解释为什么了,文章里会有很多处用“不解释,百度吧", 大家别见怪。这篇文章主要是讲解如何设计和实现一款经典的俄罗斯方块游戏, 并且是基于HTML5的Canvas技术实现出来的。 希望读者对涉及到的技术能有个大概的了解, 本文的目的还是游戏设计的思路,不是编程语言的教程。

好,进入正题。俄罗斯方块是很经典的一款小游戏。有没玩过的吗?^_^  其实我玩得真不多, 按玩家级别算, 我也就是初级类型的。 甚至在设计这款游戏的时候, 我还在计算传统的俄罗斯方块有几种图形。  下面这张图是我用windows画图做的。 我大概设想就用这么几种图形。
(图1)


我再重申一下,本文只是讲解游戏设计的思路,至于最后做出来的游戏规则跟真正的俄罗斯方块还有多大的出入,本人概不负责 ^_^

 

一, 界面设计

 


Ok, 先说游戏界面吧, 我设想的界面也非常简单, 或者叫没界面。就是画一个矩形框。外边加点说明文字。还有就是那几种图形的样式。 就这么多。

这里要重点介绍的是实现游戏界面的是HTML5中的Canvas负责的。 Canvas就是一块画布,这个概念很像J2ME中的Canvas. 在一个HTML页面里,可以存在多个Canvas。

 

 

 

 

 

 

<canvas id="VC" width="104" height="204">This is Canvas!</canvas>

 


就这么简单的一句话,就在HTML的代码里,实现了一块画布。 这也是HTML5新特性之一。 大家可是对它给予厚望啊。 因为以前要想用Javascript画点什么,那可费劲了。

好,大家别被我扯远了。 我们还在讨论界面, 仅仅是在HTML代码里加这么一句,当然不能画出来什么,现在真正要实现功能的就是Javascript代码。不解释Javascript了,自己百度GOOGLE吧。 看下面代码:

 

var canvas = document.getElementById('VC');
var context = canvas.getContext('2d');


这两句话很简单, 但却非常重要, 第一句是通过document对象得到ID为VC的Canvas对象。。。有点绕口。多念几遍,呵呵。这里的VC就是上面那个canvas里的ID。 第二句话是利用这个得到的canvas对象,再获取它的context, 什么叫context呢。 就是相当于J2ME里的Graphics g;这个对象, 也就是拿到它才能真正的去做画图动作。它里面需要一个参数。
传入2d 就表示得到的是一个具有画2D图形的句柄,传入3d就可以得到画3D图形的句柄了。 是不是很清晰? 这么来看这个Canvas的确很强大。 貌似可以2D,3D同时画出来。我还没试过。

好,我们拿到了“画笔”。 先小试牛刀,画个游戏外框吧,矩形。 我查了HTML5的文档,貌似没有提供类似 drawRect 这种函数。 只有fillRect. 我没更多研究,应该是通过别的函数来实现画线。  没关系, 我们的界面简单, 就用fillRect 也可以, 我就用它涂两遍矩形, 一大,一小。 这样就显示出了一个边框。

 

 

 

context.fillStyle = "#00f";
context.fillRect(map_x, map_y, canvas_width, canvas_height);
context.fillStyle = "#fff";
context.fillRect(map_x + 1, map_y + 1, canvas_width - 2, canvas_height - 2);

 


里面传入的参数其实就是(x, y, width, height) 都是数字类型的。  fillStyle 就是当前填充的颜色。 过多的就不解释了。  好,界面就大概这么来画。 因为其它的图形也都是小方块组成, 举一反三的话, 也就是说我们整个游戏, 就只用这个fillRect就可以搞定了。 


二, 游戏规则


规则很简单, 就那么几种图形, 有些图形可以旋转成四种不同方向的图形。 而有些图形,比如 横条, 只能转成 竖条。 程序会自动生成不同形状的图形,玩家通过操作键盘来旋转,移动图形。  只能左右移动,向下加速,而不能向上。 图形一旦碰到了下面的边框,或者是最底层方格, 就不能再移动。 新的图形又被生成。  当某一个行满格后, 要自动消掉, 同时上层的格子按照原来的位置下移。。。。  就这么简单,,,


三, 程序设计


好, 按照软件工程的设计流程, 我们用了不到10分钟的时间, 就进入编码阶段了, 前期的10分钟里包括技术调研,需求分析,市场分析等等。。。界面设计这个得单加钱^_^
这部分是重点, 如果看完这部分还没看懂, 那请看第二遍,第三遍,,。。。。 希望你还能跟着我的思路在走。 本人表达能力很一般,凑活看吧。一般我们设计一款小游戏, 主体的部分,其实就是一个不断循环的过程, 俗称loop.   我们索性写一个方法, 就叫 gameLoop() 

 

 

 

 

function gameLoop()
{
  ...
}

 


循环都要干什么事呢? 首先就是绘图, 因为如果想让一个物件动起来, 在程序中,只能不断的改变它的坐标点。 那么每改变一次, 界面就需要更新。 也就是画它的那段代码就要被执行一次,  这就需要反复执行绘制代码。  其次需要反复执行的就是游戏逻辑判断, 包括碰撞检测。 因为物体坐标点改变了, 需要从新对所有物体进行坐标检测,来判断碰撞。 这么说清楚吗?

那么Javascript如何实现反复执行呢? 请看下面两个函数。

 

 

 

 

setTimeout(gameLoop, 100);
setInterval(gameLoop, 100);
 

这两个函数都可以实现每隔一段执行某个函数。 区别在于setInterval是真正的每隔一段时间,执行一个动作。  而setTimeout顾名思义,时间到了就会执行,而且仅执行一次。 那么如果需要反复执行, 就需要在gameLoop这个函数里, 再写一遍setTimeout(gameLoop, 100) 这样就可以反复的递归调用下去。 实现的效果跟setInterval一样。  具体他们在执行效率上有什么不同, 没深入研究。 感兴趣的可以查查。好, 我们的程序界面有了, 反复执行的逻辑也实现了, 是不是挺简单, 我废话一堆,其实现在代码还没超过10行。  另一个重要的模块就是用户的输入处理。  对应咱们这个游戏,其实就是需要接收键盘事件。 因为我们需要方向键来操作。  如何实现接收键盘事件呢。看代码:

function keyEvent(e)
{
   var c = String.fromCharCode(e.which);
   if (c == "1") {
     //do something
   }
}
document.onkeydown = keyEvent;


想要接收按键事件, 就要用到document的onkeydown, 当然还有onkeyup 他们什么含义,不解释,百度吧。   这里的重点就是我们把一个写好的方法赋值给onkeydown, 表示我们要接收所有按键当按下释放后的事件。  参数e表示一个事件对象, 我们要想取得当前到底按下的是哪个按键, 还需要用它的which属性来获得。然后再转成字符。 键盘上有些按键是没有值的, 这点注意。

好了, 我们现在可以干的事情就很多了, 因为我们可以画图, 可以循环执行逻辑, 可以接收到按键事件。 那下一步就是最关键的,如何用面向对象的思想来实现游戏的逻辑。在设计这个游戏时,我最先考虑的就是如何消方块, 消掉方块后, 如何让上面的可以落下来。  从面向对象的角度来看, 每一个方块应该是一个对象。 因为他们都有自己的属性,位置,颜色信息。 那么各种图形是什么呢? 当然也是对象, 而且每一种图形对象内, 都包含4个小方块对象。 因为我们的所有图形,都只用到4个方格。 这样想, 是不是就简单多了?也就是说我们的程序会有两种对象类,暂且这么叫吧, 因为Javascript没有真正的类概念。

 

 

 

function Square(ix, iy)
{
   this.x = ix;
   this.y = iy;
   this.setXY = function(nx, ny)
   {
     this.x = nx;
     this.y = ny;
   }
}


function Shape(t, px, py)
{
   this.x = px;
   this.y = py;
   this.tp = t;
   
   this.square_1 = new Square(1, 1);
   this.square_2 = new Square(1, 1);
   this.square_3 = new Square(1, 1);
   this.square_4 = new Square(1, 1);


   this.render = function(c)
   {
      //draw this shape
   }
}

 


好, 这就是两个类的最简单代码, Square表示一个最小方块,  Shape呢, 表示一种图形, 它包括4个小方块对象。  有了这两个类的结构概念,继续实现就思路清晰了。因为
需要很多种不同的图形, 我把概念简化一下, 就是用这同一个Shape表示各种图形, 那么就需要一个标识符来 标识每一种不同的类型。  比如 横条,竖条图形, 可以用 "heng", "shu" 来标识。 这个标识字符 需要在构造Shape对象时传入给构造函数。 像下面这样。

 

 

 

 

var heng = new Shape("hang", 20, 20);

 

这样我们就创造一个横条的图形, 在坐标20, 20的位置上,举一反三,其它的图形也都这么创造出来。 不同点在于, 每个图形内部的四个小方块的初始化坐标位置是不同的。因为形状不同嘛, 就是我上面代码中所有用数字1临时写的坐标,,这些坐标每个图形都不同。 真正的代码应该类似这样:

 

 

if (this.tp == "heng")
{
  this.square_1 = new Square(2, 2);
   this.square_2 = new Square(12, 2);
   this.square_3 = new Square(22, 2);
   this.square_4 = new Square(32, 2);
}
if (this.tp == "shu")
{
   this.square_1 = new Square(2, 2);
   this.square_2 = new Square(2, 12);
   this.square_3 = new Square(2, 22);
   this.square_4 = new Square(2, 32);  
}


因为每一个Shape都有自己的x,y属性, 每一个Square也有自己的x,y属性, 又因为square是组成shape的最小单位, 那么如果移动了shape图形,里面的4个方块也要相应的做调整, 这就是在移动坐标点时需要注意的。

另一个关键的设计就是,我们还需要一个缓存容器, 来保存摆放好的小方块, 比如一个横条落下来后, 它这个Shape对象的功能就不存在了, 因为不需要对它移动了。 而真正有用处的还是它里面的四个小方格, 我们需要把每一个掉落后的图形的小方格都保存在一个容器内。 因为我们要对 这些小方格做遍历,查找同一个水平面上的是否满格, 如果满了, 我们要从这个容器内 删除这行的方格, 并且让它上面的所有方格下落一行。容器就用Javascript的数组就行

 

 

 

var square_cache = new Array();

 


似乎我们讲到这里, 聪明的你大概能设想到整体的游戏逻辑是什么样的。  下面就是一个分步骤的逻辑描述。

1, 随机生成一种图形
2, 按固定速度自动下移
3, 下移过程中,允许用户左右移动图形,旋转图形。 这些动作需要对周围做碰撞检测。 
4, 图形下落到下部边际时, 这个图形的生命周期就结束了, 它内部的小方格被放入一个容器内。 
5, 检测容器内所有小方格在某一行是否同时存在, 表示满行, 如果有满行, 就删除掉这行所有小方格。 
6, 再次遍历方格容器,把刚才删除的所有行之上的小格整体下移。
7, 重复步骤1

好,就写到这里, 完整的代码在游戏的网页中可以找到, 里面的名称可能跟我讲的有些不同,但是代码很简单,很容易理解。 希望初学者能有一个全新的认识,写个小游戏原来还是很简单的事情。 我用了3天的时间写完所有代码, 当然还有很多BUG。 老鸟们就多给我提提意见, 同时我讲得不对,不细的,大家继续补充。  HTML5的诞生,尤其是canvas的功能,在未来的小游戏领域绝对会火一把。 FLASH前景堪忧。。。呵呵。  尤其是现在基于webkit浏览器内核的移动设备上,HTML5更能够大展才艺。


下面是这个游戏,欢迎大家试玩!
http://www.sj11.net/games/eluosi/index.html

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝壳鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值