【原创】《矩阵的史诗级玩法》连载九:用矩阵反推45度地图直角坐标在斜坐标下的位置
我是个比较啰嗦的人,每次打算写一篇文章的时候都会发现太长了,从而被迫拆成两篇甚至更多。也好吧,至少让大家消化的容易一些。
上篇我们在给定斜坐标的情况下算出了直角坐标下的位置,从而实现了正方形砖块斜铺的效果。这篇我们要根据给定的直角坐标(如鼠标位置)算出它在斜坐标的哪个位置,从而确定鼠标位于哪个砖块上。
可以说这是一个逆向的操作,其做法已在连载四中提及,我们一起来回忆下:
横向缩小50%->负旋转45度->等比缩放根号2倍
也就是如下代码:
var matrixInvert = new Matrix();
MatrixUtil.scale(matrixInvert, 0.5, 1);
MatrixUtil.rotate(matrixInvert, -Math.PI / 4);
MatrixUtil.scale(matrixInvert, Math.sqrt(2), Math.sqrt(2));
忽略平移的部分,我们来看看上篇的matrix:
var matrix = new Matrix();
MatrixUtil.scale(matrix, Math.sqrt(2) / 2, Math.sqrt(2) / 2);
MatrixUtil.rotate(matrix, Math.PI / 4);
MatrixUtil.scale(matrix, 2, 1);
你会发现,第一个矩阵的第n条变换都跟第二个矩阵的倒数第n条相对应,并且变换刚好互逆,也就是说,两个变换走的是同一条路,只是顺着走和反着走的区别。
自然而然的,为了对应x方向400的偏移,我们应该给matrixInvert在最开始加上如下的平移变换:
MatrixUtil.translate(matrixInvert, -400, 0);
接下来,我们监听一下canvas的鼠标移动事件mousemove,并获取它的直角坐标位置。
在js代码块追加如下代码:
canvas.addEventListener("mousemove", function(event)
{
//鼠标位置
var mouseX = event.clientX-canvas.getBoundingClientRect().left;
var mouseY = event.clientY-canvas.getBoundingClientRect().top;
});
大家可以先console.log一下这个值是否正确再继续往下阅读。
然后我们用matrixInvert变换一下吧!
var transformedMouse = matrixInvert.transformPoint(new Point(mouseX, mouseY));
我们前面给砖块设置了40像素的宽高,所以要获取到它是第几块砖,就可以对算出来的xy进行%unitSize的操作得到。
var transformedMouse = matrixInvert.transformPoint(new Point(mouseX, mouseY));
var xIndex = Math.floor(transformedMouse.x / unitSize);
var yIndex = Math.floor(transformedMouse.y / unitSize);
最后用console.log输出一下看是否跟砖块的坐标对应上了。
console.log(xIndex, yIndex);
测试发现,能对上号了,不过这样看未免有点单调,我们不妨为它加上变色效果。当然了,这些已经不是dom对象,无法通过css神马的操作单个对象进行变色,必须重新绘制。为了不把话题扯远,我就不做类似于脏矩形的优化了,直接清空全部重画,大家不要在这事情上吐槽我哦。
我们先把for循环部分封装成一个方法。
function draw(xIndex, yIndex)
{
for(var j = 0; j < gridNumY; j ++)
{
for(var i = 0; i < gridNumX; i ++)
{
var x = i * unitSize;
var y = j * unitSize;
//左上
var leftTop = new Point(x, y);
//右上
var rightTop = new Point(x + unitSize, y);
//右下
var rightBottom = new Point(x + unitSize, y + unitSize);
//左下
var leftBottom = new Point(x, y + unitSize);
//中间
var center = new Point(x + unitSize * 0.5, y + unitSize * 0.5);
//让编号对应上的砖块变成黄色
context.fillStyle = (i == xIndex && j == yIndex) ? "#ffeecc" : "#ccccff";
context.beginPath();
var transformedLeftTop = matrix.transformPoint(leftTop);
context.moveTo(transformedLeftTop.x, transformedLeftTop.y);
var transformedRightTop = matrix.transformPoint(rightTop);
context.lineTo(transformedRightTop.x, transformedRightTop.y);
var transformedRightBottom = matrix.transformPoint(rightBottom);
context.lineTo(transformedRightBottom.x, transformedRightBottom.y);
var transformedLeftBottom = matrix.transformPoint(leftBottom);
context.lineTo(transformedLeftBottom.x, transformedLeftBottom.y);
context.closePath();
context.stroke();
context.fill();
context.fillStyle = "#000";
var transformedCenter = matrix.transformPoint(center);
context.fillText(i + "," + j, transformedCenter.x - 5, transformedCenter.y + 5);//根据字号做了个粗糙的修正
}
}
}
//一开始要先绘制一次,并且不让任何编号的砖块都高亮
draw(-1, -1);
然后,mousemove事件函数追加draw的调用。
draw(xIndex, yIndex);
再次运行,移动鼠标,效果如下图所示。
至此,45度地图砖块的铺贴和选择功能就完成了。
45度地图由于广泛应用于游戏等热门领域,所以相信大家能百度到很多比本文简单很多的方法。没错,对于这个案例来说,用矩阵求解确实是大材小用。大家如果还记得连载四给出的最终结果,就会发现转换公式很简单,矩阵法似乎绕了很大一个圈子,就好比非要用puremvc做一个很简单的小游戏一样。
然而真的有不少人就是用puremvc来做小游戏,还理直气壮地说我这是练手,在这过程学习puremvc的原理和应用。本文自然也不例外,45度地图是矩阵的一个练手之作,抱着这样的心态,大家应该就能理解我的用意了吧。
另一方面,细看那些45度地图的文章,我们会发现,由于变换公式简单,所以他们往往都想当然地一笔带过。然后上层开发的朋友把公式拷过来,放到项目上发现能用就不求甚解,继续开发新的功能去。下次再用到,要么重新百度,要么从旧项目里拷过来,好点的封装成类库或者模板来重用,而在此过程中,可能大家都未必搞得懂他们的变换公式到底是怎么来的。
也有的文章使用向量的方法进行推导,在后续的教程中我也会给出向量的版本,不过最后我还是会转换到矩阵中进行变换。原因在于,后续的一些史诗级应用,矩阵的威力要比向量强大很多。至于是什么样的史诗级应用,我先卖个关子。饭要一口一口地吃,路要一步一步地走,步子大了容易扯着蛋,对吧。
本文提到了互逆地变换,在知道具体变换步骤地情况下,我们可以很轻松地找到逆变换的方法,但是在实际应用中,这些步骤并不会像这个demo一样清晰,已知的只有一个最终的变换矩阵,这时候想进行逆变换,要么想办法分解步骤,要么尝试找到可以直接进行逆变换的方法。显然后者要科学得多,因为前者不但计算繁琐,难以理解,而且容易造成精度损失,性能也不好,那我们何苦不选择后者呢?
而后者用到的,是一个可能大家都听过的玩意儿——逆矩阵,我会在下一篇文章中给出逆矩阵的推导方法并封装到我们的类里面,敬请期待!