前端时间有空, 还是想找点事干, 正好那天听一个朋友说写一个 俄罗斯方块, 于是就想尝试一下. 经过两天的思考, 我终于在完全不借助外力的情况下想到了一个自己的实现方法. 当然, 如果网上早就有类似方法纯属巧合.
因为以前写过点DOM动画, 这次遇到俄罗斯方块自然也想到了碰撞检测
, 让元素移动起来并不难, 难得是在碰撞时候的各种检测, 主要是当前下落方块和已有的复杂结构方块的碰撞, 因为当前下落方块还可以旋转位移.
简单的碰撞检测
通常也就是找到元素的Bounding Box
, 然后根据Bounding Box
四边位置和边界或其他元素进行判断. 比如碰撞动画
.
但是, 俄罗斯方块的形状比普通圆形或矩形复杂, 而且它包含的旋转中心也不同. 所以光是直接通过外形判断是否碰撞(尤其是形态不同的方块之间)比较麻烦.
然后我想到了如下数据结构表示整个游戏舞台:
var stage = [
// 总共20行, 10列
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// ...
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
方块矩阵, 如:
var block = [
[0, 1, 0],
[1, 1, 1]
]
方块走到哪里, 就和stage相加到哪里, 如果某个位置之前已经有方块存在, 则这个位置会>1, 因此可以判断方块之间发生了碰撞.
可以说, 想到这个方法, 整个俄罗斯方块已经相当于完成了1/3了.
但事情并没完...
如果使用数组表示矩阵, 那么水平移动, 向下移动, 检测碰撞 会添加大量的代码. 直接用二进制数表示岂不是更方便?
我只需要添加一个pad函数, 即可将10进制数字展开成二进制数, 且前面补0. 比如pad(3, 10)
得到0000000011
而且可以直接使用<<
和>>
对数字进行移位, 如果使用数组表示还需要单独添加一大坨函数来做
使用数字表示舞台:
// 因为俄罗斯方块是20行
var stage = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
方块例子:
var block = [32, 112]; // 因为方块默认是从舞台顶部中间位置出现
又会有问题...
上一步中block已经和stage对应行数相加(add
函数实现)了, 但下一步动画block已经变了一格位置. 根据上面的<=1法则, 难道算是碰撞了? 然而方块并没有和其他方块或墙壁碰撞
所以, 我想到了一个mix
函数, mix
函数内部先执行minus
, 然后进行某些操作, 最后再add
.
中间的"某些操作" 可以是平移,向下移动... 如果不操作, 则下一步位置不变.
中间还需要判断方块在边界是否可变换, 方块在方块之间是否可变换....
最终每一步都得到一个修改后的stage都是最终要呈现的model, 然后将model直接投射到dom或者canvas(使用render
), over!
上面说的检测碰撞算是小坑.
检测边界稍微大点, 直接平移的话边界当然非常容易检测, 但一个奇葩的形状在边界还想变换呢? 有可能一部分就跑到边界之外了. 上面使用的是10个二进制数来表示一行, 在左边界自然可以通过判断是否>=1024
, 因为第11位如果为1说明方块已经有一部分在左边界之外. 但右边界就不好弄了, 因为不管怎么向右平移得到都是0.
最终只能使用11或12位的二进制表示一行. 虽然展示的只有10列.
- 涉及到复杂碰撞的效果, 可以使用矩阵(3D空间可以扩展到三维矩阵), 如果矩阵直接相加大于指定的值, 说明已经碰撞.
- 展示型的效果(不一定必须是游戏, 可以是一个动画, 展示面板等等), 虚拟舞台最好都比实际显示舞台大, 才方便检测各种效果. 不然坑大了...
- 视图渲染和模型分离, model出来了, render方法无论呈现给DOM还是Canvas都已经非常简单了.