初识子系统与场景模型

DinS          Written on 2017/12/13

一、子系统的概念

我们已经知道了引擎的框架,以及自定义数据类型,那么接下来一个自然的问题就是如何利用引擎来展现场景。
在直接进入场景之前,Urho3D还有一个重要的概念:子系统(subsystem)。
子系统是引擎初始化之后在后台默默运行的系统,我们要显示场景、画面必须通过子系统来完成。至于有哪些子系统可以研读官网,这里仅仅简要介绍重要的子系统。当然随着专题的进展还会深入讲解。

ResourceCache,一个非常重要的子系统,负责读取资源并存入缓存以供后续对象获取
Input,处理用户交互,比如鼠标和键盘的响应。也是不可或缺的
UI,负责界面UI显示,注意UI界面与场景并不是一个概念
Audio,处理声音,相对而言简单点
Graphics,处理窗体和渲染环境和资源
Script,调用AngelScript的接口
Renderer,负责渲染

这几个应该是最常用的了。使用GetSubsystem获取子系统:

获取之后就可以该干嘛干嘛了,至于到底能干什么事慢慢探索。

二、场景模型概念

游戏中都有场景的概念,玩家都是在场景中进行游戏。
场景中有许多对象,如何将场景有效组织起来是每一个游戏都要面临的问题,这个就是场景模型(Scene Model)。
在Urho3D中,场景称为Scene,是惯例。场景由许多节点Node组成,也是惯例。节点下都可以创建子节点Child,这样由许多节点组成了一个等级结构,Scene也就相当于根节点,也是惯例。

除了惯例外,Urho3D有一个特殊的地方。其提供了一个概念叫做Component,这个东西负责处理渲染、音效、逻辑等功能,并且这个Component是创建在Node里面的。这样的设计理念应该是OOP。

创建完Node和Component后,可以很容易地从Scene获取,使用GetNode和GetComponent即可。也可以增加tag,然后通过GetNodesWithTag获取。

三、尝试:放置图片

有了子系统和场景模型的概念,让我们通过实践来学习。
先试试最简单的功能:显示一张图片。
既然是学习,那么应该由浅入深,因此在这里例子里先不管场景模型的概念,只使用子系统来实现,以增加对子系统的理解。

大图点这里

还是在之前的专题建立的自定义项目中做实验。在Start()中加入上述代码。
逻辑应该还是比较清晰的,一共三个部分:
第一个部分与子系统ResourceCache交互,先使用GetSubsystem获得子系统,然后调用成员函数加载资源。这里加载的是png,jpg也是可以的。当然你需要指定Texture2D类型。其他资源以此类推。
第二个部分准备一张图片,这里使用了SharedPtr保证释放资源。然后设置纹理、位置和大小。这三个必须要设置否则图片不会显示。本专题主要讲子系统和场景模型,所以跟图片有关的内容放到后面专题。这里看一个使用套路即可。
第三个部分把图片放到UI系统,这样图片才能够真正在屏幕上显示。建立的sprite仅仅存在于内存中,需要交付UI子系统显示。在这里我们直接加到了UI子系统的根节点,实际使用时不要这么做,但是在这里这样写便于学习理解。

运行看看效果:

成功了!
而且我们知道了原点位于屏幕的左上角,图片的锚点默认在左上角。

让我们再稍稍改进一下,顺便看一看Graphics子系统。
刚才使用的SetPosition参数指定了绝对位置,游戏一般要做不同分辨率,显然写死了不行,那么怎么办?使用Graphics来解决。
把代码改成如下形式:

大图点这里

增加了获取Graphics子系统的部分,目前很简单,就是得到屏幕的高和宽。然后在SetPosition里我们使用相对位置放置对象,这样只要确定比例,不管屏幕分辨率如何都可以自动适应。
运行效果:

这样也获得了居中的效果:

四、尝试:添加场景、节点与组件

有了子系统的概念后,再来看看场景模型。
首先要搞明白UI子系统和场景有什么区别,二者不都是在屏幕上显示内容吗?
个人理解,UI指的是与游戏本身无关的显示,其存在只是为了给玩家提供一个操作的途径。场景指游戏的核心显示,我们玩的是游戏场景而不是游戏UI。

咱们接着上面的代码继续写。先把那个图标放到右下角,以免阻挡场景。
然后增加如下的代码(本专题仅仅是讲述场景模型,至于3D物体是如何显示的、调用了哪些接口可以不必深究,从语义上理解即可)

大图点这里

在最开始增加private部分。
推荐把Scene和Camera在这里声明。Camera也是一个节点,只不过之后会往里放独特的Component。
接下来是Start()部分的代码,紧跟上面试验子系统代码之后:

大图点这里

虽然看起来很复杂,但是每个部分都比较清晰。
套路都是一样的,在场景下创建节点,然后在节点内创建组件。
复杂的部分可能是每个Component的类型都不一样,在这里我们关注场景模型,那些不同组件是为了显示不同的内容而有区别,暂时可以忽略掉。
为了增进理解,再附一张场景模型图:

大图点这里

粗黑框代表节点,所谓场景实际上就是根节点。
节点之间有parent-child关系,用线连接表示。
每个节点内部有若干组件,用绿框表示。在这个例子里每个节点恰好只有一个组件。
请读者结合上面的代码和这张图好好理解Urho3D的场景模型。

注意代码的最后,使用了渲染子系统Renderer,这个是什么?
我们建立的场景只存在于内存中,从内存到界面显示的过程称为渲染。
在概念中场景是3D的,然而计算机屏幕是2D,从3D到2D这个转换过程就是渲染处理的主要问题。这个渲染子系统就是干这个,所以如果我们不处理它屏幕上看不到任何东西。
仔细一想又会引出一个问题:既然是3D的,那么视角不同看到的东西也就不同,因此要让渲染系统成功渲染,我们必须提供一个视角,即Viewport。这个Viewport怎么取?按照惯例我们以camera的方式来看3D场景,镜头看到的就是显示出来的3D场景。
于是我们需要在场景中设置一个镜头,镜头可以随意移动,这个就是Component的Camera。于是看倒数第二行代码:我们建立的Viewport包含了context_,scene_和cameraNode_,这三者一起构成了合法的视窗。然后我们指定视窗,渲染系统就可以正常工作了。

运行代码看效果:

内容都是来自Urho3D初始提供的一些资源。
最后补充一点:创建场景必须使用scene_ = new Scene(context_);
如果尝试使用SharedPtr会无法显示。当然在private部分声明后也就不存在使用SharedPtr构造函数这种写法了。

到此子系统与场景的基本概念就有了,可以显示内容了。接下来是urho3d的又一个关键概念:事件。有了事件游戏才能够运行,见《事件与使用》。