2D场景-实现鼠标选中场景物体(二)

DinS          Written on 2018/2/14

上一节我们完成了基本功能,但是那只是特例情况:我们没有移动camera。
如果不移动camera,直接用UI做游戏是更简单的方式,大费周折就是因为我们要使用camera移动功能。

但是一旦移动camera,之前的规则就有问题了。这是因为不管如何移动camera,屏幕坐标永远是那个,但是场景坐标实际上是变化了。不光是移动,camera缩放也会带来变化。

在这一节,我们攻坚克难,实现自由移动缩放camera的情况下依然能够通过click点获取对应node。

一、移动camera后坐标变化的数学探讨

这部分主要是数学和思路问题,实现已经在上一节做好了,只是需要微调即可。
先从数学上看看。这是一个通用的例子:

2D场景本质上是一个无限延展的平面,camera是平面上的一小块区域,这一小块区域就是屏幕上显示的内容。
这里红点是场景的中点,也是camera的中点。换成坐标表示,红点的场景坐标是(0,0),屏幕坐标是(480,320)     //假设屏幕大小为960*640
现在考虑移动camera后的情况:

红点仍然是场景中点,因为移动camera并不影响场景,场景坐标系是绝对的。
使用坐标表示,红点的场景坐标是(0,0),屏幕坐标是(0,640),这是因为红点在绿框中位于左下角。绿点成为了camera的中点,这是用户现在看到的中点。使用坐标表示,绿点的场景坐标是(4.8, 3.2),屏幕坐标是(480,320)。

切入点在哪里?注意到绿点相对红点移动了(Δx,Δy)。移动的值可正可负。这有什么意义呢?可以这样理解:
首先不管camera实际在哪里,我们都假设camera在原点,即蓝框位置。这样的好处是我们已经解决过camera在原点的问题,应用之前的坐标转换公式将屏幕点转为场景点。
之后将得到的场景点坐标(Sx,Sy)加上Δ,即得到最终的点(Sx+Δx, Sy+Δy)。
为什么可以呢?因为场景坐标是绝对的,移动camera只是相对原点增加了偏移量而已。

结论是转换公式如下:
Scene.x = (Screen.x – Screen.width/2)*PIXEL_SIZE + offset.x
Scene.y = -(Screen.y – Screen.height/2)*PIXEL_SIZE + offset.y

应用这个公式试试看。
对于camera位于原点的情况,offset为0,公式退化为之前的公式,正确性已得到验证。这也可以看出之前的公式是新公式的特例。
以上面的情况为例,camera向右上移动,offset.x = 4.8, offset.y = 3.2
加入click点为屏幕中点,即(480,320),那么
Scene.x = (480 – 960/2)*0.01 + 4.8 = 4.8
Scene.y = -(320 – 640/2)*0.01 + 3.2 = 3.2

原点有些特殊,来试一个更一般的情况,假设click点是(333, 478),这个点大概在屏幕中点的左下方。应用公式得到
Scene.x = (333 – 960/2)*0.01 + 4.8 = 3.33
Scene.y = -(478 – 640/2)*0.01 + 3.2 = 1.62
从数值可以看出这个点在蓝框的右上里面,符合图上得到的直觉

如果读者觉得还不放心,可以多试几种情况,将camera移动到任意位置,再试验数据。其实如果从原理上理解了上述公式,就知道一定是对的。

二、移动camera后坐标助手的实现

下面修改代码,private里增加两个变量记录offset,初始化为0.0:


然后改坐标转换公式:

大图点这里

再增加更新offset的接口。注意是累加:

然后Helper类就改完了,剩下是在App中略作修改。
主要问题是如何确定camera偏移量。

大图点这里

又回到了这里,MOVE_SPEED就是camera偏移量吗?
让我们先看看这个:

这些是urho3d预先定义的各个方向的单位向量,所以拿MOVE_SPEED*UP实际上执行的是向量的scaler乘法,最后得到的就是node的移动量,于是MOVE_SPEED应该就是camera偏移量。
保险起见可以试一次。我们把MOVE_SPEED设置为0.8,因为屏幕高为320,所以场景中就是3.2,于是我们如果按w键4次应该整好将camera向上移动一半,于是我们看到的屏幕下半部分是花,屏幕上半部分是黑的。
试试看:

确实如此,掌握了这一点,就可以修改代码了:

用switch-case替代原来的if-else,更清晰一点
剩下的A和D键类似。注意正负号。

然后制造一个场景来检测:

大图点这里

相当于在左上制造一个相同的布局:

右下角是原始camera场景,左上角是新增加的图片。
不管如何移动camera,不管点哪个布局,点击都可以得到正确的对应图片。
成功!

三、缩放camera后坐标助手的实现

将移动camera解决后,解决最后一个问题:缩放camera.
这个问题相当有难度,并不是简单的乘除缩放量的问题。

the easy way out:使用urho3d现有的函数帮助我们转换坐标.
Camera类提供了场景坐标(也称世界坐标)和屏幕坐标(正则化后的)互换的函数。所以之前讨论的所有关于坐标转换的问题都可以掩盖掉,直接用Camera来实现。至于找node的算法,还是使用之前的multimap。
所以可以把Helper类改造成这样:

大图点这里

比之前的更简单,实现如下:

大图点这里

直接使用camera提供的方法,有保证。
注意屏幕坐标要正则化,即化为(0-1)之间,这是调用规则。
使用的时候都一样,注意初始化camera就好。

运行效果如下:

不管如何移动、缩放camera,都可以找到对应的node,至此我们的任务终于结束了。