2D场景概述

DinS          Written on 2018/2/12

本专题我们深入研究2D的相关内容。
为什么研究2D呢?不是叫Urho3D吗?因为本人对2D更感兴趣,想做2D项目,仅此而已。

一、设置2D场景

2D场景是3D场景的一个特例,
设置2D场景的代码跟3D场景很相似,都是通过node+component组织场景并显示,只不过camera上有一定变化而已。从概念上来说,就是让camera对准3D场景中的一个2D平面,然后我们在该2D平面上布置内容,于是看到的就是2D了。
不过这里有一个坑,就是坐标的问题。先看代码

在private先声明这两个东西,与3D一致:

然后Start()里加入如下代码:

大图点这里

第一部分是准备工作,做好一些数据准备。为什么有屏幕坐标和2D场景坐标呢?稍后解释。在这一节里用不到。
然后是new出来场景,与3D一致。
下面是关键性的设置camera,这个区分了3D和2D。同样是node里面放camera的component,但是把这个camera设置为正交,然后设置正交大小。
怎么理解正交?个人认为就是让camera垂直于一个平面,这样看起来就是2D了,camera距离该平面的远近就是正交大小。

第二部分就是布置2D平面了。
代码书写要比3D简单,因为2D的话只存在纹理。
Urho2D的组织方式如下:使用Sprite2D加载单个图片作为纹理,或者使用SpriteSheet2D一次性读入大图,大图里面有许多小图,这样效率更高。有了纹理后,使用StaticSprite2D来显示纹理,这样屏幕上就看到了那张图片。
在这里例子中,建立一个node,node里有一个component是StaticSprite,然后设置纹理。

第三部分就是渲染,因为2D只是3D的特例,所以这一步也是必须的。

3步后就完成了2D场景设置,运行看效果:

成功显示。

二、坐标问题初探

最开始我试2D的时候总是不显示图片,后来发现是被坐标系坑了。因此这里特别指出。要区分两个概念:屏幕坐标和场景坐标。
屏幕坐标就是屏幕分辨率的对应坐标,我们布局UI时使用的是这个坐标。
场景坐标是对于场景而言的坐标系,与屏幕坐标之间存在差异。

先来看看屏幕坐标,这个app是960*640的分辨率,所以如下图:

我们布局UI时就依靠这个坐标在屏幕上对应位置显示。

我一开始以为2D场景坐标与屏幕坐标一致,然后直接把图片按屏幕坐标放置,结果就是超出camera视野,啥也不显示。
2D场景是3D场景的特例,我们可以忽略z坐标,因为z坐标是向着屏幕里面进去的。只关注x和y,则如下图:

场景坐标系的原点与屏幕坐标系不同,并且数值也是不同的,相差了某个倍数。

回顾之前的代码会发现有一个PIXEL_SIZE,关键就在这里。
对于2D场景而言,场景坐标和屏幕坐标可以实现转换。

只要乘以PIXEL_SIZE,再移动原点,就可以相互转换。
上述代码中scWidth等于4.8,scHeight等于3.2。

于是我们放置图片时要按照场景坐标去做,比如写spriteNode->SetPosition2D(1.0f, 1.0f);
效果如下:

如果你按照屏幕坐标写,就不知道飞到多远了。

另外还引申出一个问题:图片本身的大小是按像素计算的,比如这个花的图片是64*64,放到场景坐标怎么计算呢?
还是乘以PIXEL_SIZE,所以实际上这张图在2D场景中的大小为0.64*0.64,这样一来也就确定了图片之间的大小关系了。

关于坐标还有说道,放到后面。

三、camera移动问题

其实使用2D场景的麻烦还是有点多的,最关键的是node不能像UI那样响应用户操作,Urho3D没有直接提供这种操作。所以在某些场合其实可以考虑直接使用UI来做2D场景,比如做三消游戏。
什么情况下使用2D场景呢?如果游戏涉及到移动场景和缩放,那么就需要用2D场景了。这是因为Urho3D提供了方便的移动camera的功能。

官方示例的24号例子提供了一个思路,做法是在Update()里面显式获取输入,然后根据输入移动camera。换句话说:刷帧判断。
让我们先看一下代码,在Start里注册事件:

大图点这里

第二句是示例代码中出现的,我就照着写了。

大图点这里

然后Update里调一个函数。这是实际上是每一帧都调用一次MoveCamera函数。
MoveCamera函数见下:

基本上就是直接把示例代码拷贝过来的,我感觉就是固定写法。
缩放那个好理解,平移那个有些难懂,涉及到了Node的Translate,我也没太理解,不过这么写确实管用,从语义上来看就是上下左右移动。
看效果:

这是一种思路,不过貌似不太直观,urho3D本身就有按键响应事件,用这个事件岂不更好?尝试一下:

大图点这里

大图点这里

因为E_KEYDOWN事件里没有帧率,所以MOVE_SPEED的设置需要试验,或快或慢。
放在E_KEYDOWN事件逻辑更清晰,不过有一个小弊端:按下键的第一下会卡顿,另外一直按键移动时有小概率卡顿一下。总之就是没有放在Update里流畅,原因的话不明了。
至于哪个好交由读者决定了。

四、动画问题

2D游戏必然是要播放动画的,Urho3D在2D动画方面基本上还是可以的。
播放动画具体而言分成3类。

第一类是整体图片的动画,包括图片平移、旋转、翻转。
旋转和翻转都有直接的方法,因为场景中都是Node,所以调Node的Rotate即可实现。更快速的是roll,pitch,yaw,分别沿z,x,y轴旋转。
平移确实有些不人性化,因为没有所谓Move方法,示例代码中给出的思路是在Update中不断调用SetPosition,每一帧微移,这样看起来就是移动了。虽然能够解决问题,但是并不方便,为之奈何?目前看来只能由程序员在外部包装一下(wrap)。这个具体我也没研究,所以只是提及这个问题。

第二类是帧动画。
可以用骨骼动画实现。具体例子见24_Urho2DSprite的金币动画。这个实际上是一帧。

第三类是骨骼动画
因为帧动画浪费的资源比较多,所以现在骨骼动画是很流行的,urho3d对此提供了很好的支持,具体事例可以参考33_Urho2DSpriterAnimation。
urho3d使用了Spriter来实现动画,具体方法是将精灵(图片)分成各个部分,然后用scml定义各部分和动画,然后代码里读取该scml播放动画。
这样做代码里使用起来会很简单,因为scml中用一个名字指代一段动画,所以在代码里一两行指定scml中的动画名称,就能播放了。

动画部分暂时就这么多了。


上面留了个伏笔,接下来深入探讨坐标问题,见《2D场景-实现鼠标选中场景物体(一)》。