17.1 UI库
Ogre中提供给我们的二维处理能力还不足以方便我们构建出一套完美的UI界面,而目前很多成熟的GUI库都可以很好的整合到Ogre3D中,这样很大程度上节省了我们定制UI界面的时间,每一种GUI库都有它存在的理由,我们不可能很明确的说那一个最好,或者说哪一个最适合用于Ogre3D中,我们只能根据自己的所求在适当时候选择适合自己所需的GUI库。
目前存在着很多流行的GUI库,下面我们来简单了解一下:
(1)CEGUI
CEGUI是一个免费的GUI库,它基于LGPL协议,使用C++实现,采用完全面向对象的方式设计。在作者写这本书期间,CEGUI的版本已经由0.7.5过渡到了0.7.6。CEGUI开发者的目的是希望能够让游戏开发人员从繁琐的GUI实现细节中抽身出来,以便有更多的开发时间可以放在游戏性上。CEGUI也是官方Ogre官方推荐使用的界面库,Ogre1.6及以前的版本都是内置支持的。使用它的商业游戏业非常多,比如天龙八部、火炬之光、仙剑四等。
(2)Ogre SDKTray
从Ogre1.7开始,Ogre3D就不再内置支持CEGUI了,转而使用Ogre自己的SDKTray,这也是为什么我们使用1.6以前的版本不需要配置CEGUI,而使用1.7之后的版本还需要配置CEGUI环境的原因。SDKTray是在Ogre的Overlay和material的基础上实现的,很容易理解和使用,不过目前来说它还是个半成品,无法应用到商业游戏中。虽然Ogre3D不再内置CEGUI了,不过Ogre3D和CEGUI的整合很简单,这不是我们学习CEGUI的障碍。
(3)QuickGUI
QuickGUI的最新版本是10.01,专为Ogre3D写的UI库,它被设计一个易于使用、有效且强大的GUI库,QuickGUI的更新速度很快,每个几个月就会发布一个新的版本。
(4)Hikari
Hikari也是完全免费的,而且允许用户使用目前广泛流行且方便使用的Flash来制作界面,它通过Flash.ocx将swf渲染成Ogre的Texture,然后我们就可以任意操纵这个Texture了。另外,Hikari支持Flash的所有版本,不存在兼容性问题,由于Flash本身就支持多国语言(包括中文),因此使用Hikari来实现中文的显示自然是轻而易举的一件事,不过Hikari效率比较低,而且它使用的是Flash的 ocx,先将动画内容渲染到DC上,然后拷到Texture中,所以很运行速度会比较慢,而且将动画内容渲染到DC上这一步是造成其运行速度比较慢的主要原因,这使得Flash优秀的脏矩形优势无法发挥,根本无法应用到商业游戏中。
(5)Scaleform
Scaleform跟Hikari有类似之处,都是用Flash来制作游戏界面,不过它的效率要比Hikari高很多,可以说是游戏界面的最强工具了,而且它对亚洲语言显示和输入的支持都堪称完美。据统计,有超过600多款的游戏使用Scaleform制作界面,比如:StarCraft II,Crysis,Fable II,CivilizationIV等。唯一遗憾的是, Scaleform是收费的,且费用很高。
(6)MyGUI
MyGUI的最新版本是3.2.0,它的优点是:高效、轻便、灵活等;它的缺点是:注释和文档不是很完善,相关资料比较缺乏,效果有时不太理想,目前还没有太多商业游戏使用这个库,但是MyGUI目前越来越受到广大游戏开发者的关注,相信不久后也会有良好的发展。
了解了这么多UI库,其实就像笔者前面说的,每一种库都有各自的优缺点,我们在选择的时候只有选择适合自己的才是对的,由于我们本书是一本介绍Ogre3D知识的书,所以不可能过多的把重点放到每个GUI的具体讲解上,因为上面我们提到的每一款GUI库都完全可以当做一本书来写,所以如果读者朋友想深入了解某一款GUI,还需多搜集一些它们相关的资料。
由于Ogre3D和CEGUI的结合已经算是老搭档了,而且CEGUI在各方面的使用不管是从个人还是商业引擎方面都已经比较成熟,学习的人比较多,而且读者朋友在学习的时候遇到麻烦也会有很多热心朋友的帮助,所以本书主要采用CEGUI作为我们的界面库。本章我们主要讲解一些CEGUI的知识,让大家对CEGUI有个逐步的了解,希望在本章结束的时候读者朋友能做出一些比较复杂的界面来。
17.2 CEGUI环境的配置
在Ogre中使用CEGUI的第一步当然是需要我们首先配置CEGUI环境。
笔者提示: CEGUI下载地址: https://sourceforge.net/projects/crayzedsgui/files/CEGUI%20Mk-2/0.7.5/CEGUI-0.7.5.zip/download CEGUI依赖库下载地址: https://sourceforge.net/projects/crayzedsgui/files/CEGUI%20Mk-2%20MSVC%2B%2B%20Dependencies/0.7.x/CEGUI-DEPS-0.7.x-r2-vc9.zip/download |
解压CEGUI压缩文件到任意目录,如我们这里:
然后进行CEGUI依赖库的添加,添加到如下目录:
然后进入到如下目录:
用文本编辑器(为了更好的显示,建议用UltraEdit等功能比较强的文本编辑器打开)打开目录下的config.lua,找到下面几行:
改为:(注意:具体目录根据自己Ogre安装目录确定)
找到CEGUI_OLD_OIS_API变量,设置为false(我们这里用的这个版本默认已经是false了)
找到OGRE_RENDERER变量,设置为true
找到SAMPLES_OGRE变量,设置为true
如下所示:
保存此文件!
然后进入目录D:\CEGUI-0.7.5\projects\premake,运行build_vs2008.bat(或对应的其他版本),你将看到CEGUI.sln,继续运行build_samples_vs2008.bat(或对应的其他版本),你将看到CEGUISamples.sln;
然后向我们以前添加OGRE_HOME环境变量一样,这里我们可以添加一个环境变量CEGUI_HOME用来代替CEGUI根目录。
打开VS2008,选择菜单栏:工具---选项(当然,我们也可以在每个工程属性页中进行包含路径的添加,具体请参看Ogre环境配置一节)
在 包含文件 一项中,配置CEGUI相关的如下一些路径:
(1)$(CEGUI_HOME)\cegui\include
(2)$(CEGUI_HOME)\Samples\common\include
(3)$(CEGUI_HOME)\dependencies\include
同理,在 库文件 一项中,配置如下:
(1)$(CEGUI_HOME)\lib
(2)$(CEGUI_HOME)\dependencies\lib\dynamic
然后到如下目录(根据自己CEGUI安装目录而定)打开CEGUI.sln选择Debug和Release两种模型,分别编译
编译成功后,再对CEGUI Samples.sln的Debug和Release分别编译,编译成功后,可以查看CEGUI.sln里面的实例,可以选取其中一个工程,点击右键设为启动项目,如下所示:
到D:\CEGUI-0.7.5\dependencies\bin(根据自己CEGUI的安装目录决定) 这个目录会看到如下一些文件:
为了使用方便,可以把这个目录下的所有dll文件拷贝到:D:\CEGUI-0.7.5\bin(根据自己CEGUI的安装目录决定)这个目录下,同时到D:\ogre\ogre_src_v1-7-2\bin\debug和D:\ogre\ogre_src_v1-7-2\bin\release(根据自己Ogre的安装目录决定)两个这个目录下,分别拷贝:OgreMain_d.dll和OIS_d.dll以及OgreMain.dll和OIS.dll,到D:\CEGUI-0.7.5\bin(根据自己CEGUI的安装目录决定)这个目录下,然后再次运行示例工程,可以看到如下启动界面,选择OpenGLRenderer或者DirectX运行:
到了这里还是会弹出无法运行的对话框,如下:
我们需要到解决方案一栏全部选择所有工程,点击右键----属性:
然后选择配置属性----调试:
在工作目录一栏中选择下拉菜单,选择浏览,找到CEGUI对应的datafiles文件夹:
点击确定,重新运行,如果按照上述方式,应该可以成功运行了~~~~
效果如下:
这样总算是把CEGUI也算是配置好了。
17.2 CEGUI简介及入门
前面我们已经简单介绍过CEGUI,它的功能是非常强大的,而且使用也非常的灵活,可以和脚本配合。可以通过配置文件自定义窗口外观。通过布局文件实现窗口布局等等特性,使得游戏的界面开发更加方便。另外,和Ogre3D一样,CEGUI的开源特性让我们在开发过程中可以随时查看源代码,更好的了解整个软件的工作流程。
CEGUI的担当的职责是UI功能,它在软件中提供一些用户界面的组件,如按钮、列表框、文本框、滚动条等等。
CEGUI设计了许多接口,程序可以通过实现接口来为CEGUI提供服务。比如说渲染接口定义好了以后,就使得CEGUI与具体的渲染平台无关。不管是OpenGL还是Direct3D,无论是Windows还是Linux,只要在那个平台上实现了CEGUI的渲染接口。当然CEGUI库也必须在那个平台下编译才行。
本章假设你已经编译好了CEGUI库,如果读者还没有成功编译CEGUI,请参照CEGUI配置的相关部分编译配置成功再继续往下学习。好了,下面我们就通过一个例子来看一下如何编写CEGUI程序。
第一步,我们新建一个Win32控制台应用程序的空项目,名叫CEGUITest(名字任意);
第二步,新建一个cpp文件,名叫Main(名字任意);
记得我们在写Ogre3D程序的时候为了方便,我们经常继承一个ExampleApplication的类,CEGUI也一样,为我们提供了一个这样的类,省去了我们不少工作,不过如果读者朋友想一探究竟,看看这个类到底为我们做了一些什么工作的话,就需要查看实现代码了;
第三步,加入我们需要的头文件:
#include "CEGuiSample.h" #include "CEGUI.h" |
第四步,加入对应的库文件:CEGUISampleHelper.libCEGUIBase.lib,并设置工作目录为(你的CEGUI根目录)\bin:
笔者注: 如果你在编译程序之前,请确保你正确配置了CEGUI的各个包含目录和库目录,否则会遇到找不到头文件或者其它类似的错误提示,具体配置方法请参看CEGUI配置一节。 |
第五步,定义一个新的类MyFirstWindowExample,让它继承自CEGuiSample类:
class MyFirstWindowSample : public CEGuiSample { |
第六步,重新CEGuiSample中的两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
bool initialiseSample() { using namespace CEGUI; SchemeManager::getSingleton().create("TaharezLook.scheme"); System::getSingleton().setDefaultMouseCursor("TaharezLook","MouseArrow"); WindowManager& winMgr = WindowManager::getSingleton(); DefaultWindow* root = (DefaultWindow*)winMgr.createWindow("DefaultWindow","Root"); System::getSingleton().setGUISheet(root); FrameWindow* wnd = (FrameWindow*)winMgr.createWindow("TaharezLook/FrameWindow","Demo Window"); root->addChildWindow(wnd); wnd->setPosition(UVector2(UDim(0.25f,0),UDim( 0.25f,0))); wnd->setSize(UVector2(UDim(0.5f,0),UDim( 0.5f,0))); wnd->setText("Hello IMedia!"); return true; } void cleanupSample() { } |
第七步,编写main函数:
void main() { MyFirstWindowSample app; app.run(); } |
到此,我们的所有工作都做完了,编译并运行我们的程序,你将会看到如下效果:
选择Direct3D或者OpenGL渲染(不要选择Ogre),你将会看到如下效果:
代码分析:
我们可以看到,通过简单的几行代码,我们就可以实现这么棒的效果,其实大部分工作都在CEGuiSample这个类里给我们封装好了。
不管我们使用的是什么引擎,在使用CEGUI时,至少要做如下所示的最基础的三部,才能使CEGUI运行起来。
第一步,创建CEGUI::Render对象。这其中会根据我们选择的是OpenGL还是Direct3D或者是Ogre渲染,选择我们最终要使用的渲染选择器,比如说这里我们选择了Direct3D方式渲染,那么在CEGuiSample中会为我们生成一个CEGuiD3D9BaseApplication对象的实例,在这个类的构造函数中会创建对应的Direct3D9Renderer渲染器然后这个实例对象会通过它的一个execute函数反过来调用我们重写的这个initialiseSample函数,这样我们的代码就能被调用到了,初始化工作也就完成了;
第二步,创建CEGUI::System对象。在一些初始化工作完成之后,CEGUI会调用下面这样的代码来完成System对象的创建:
CEGUI::System&guiSystem =CEGUI::System::getSingleton(); |
第三步,调用渲染函数进行渲染。现在我们有了System对象,因此就可以开始进行渲染了,方法很简单,我们可以调用System的renderGUI函数开始我们的渲染,这样我们的程序就真正工作起来了。
虽然上面仅仅是简单的三步,其实要想是CEGUI真正运作起来还需要一些其它的操作,但是上面三步是一个主要的流程,详细读者朋友理解了这三步,查看源代码的时候就会很容量理解整个流程了。下面我们通过一个简单的流程图再来理一下我们的思路。
17.3 CEGUI中的资源管理及其相应组件的创建
理解了基本的渲染流程,现在让我们来看一下CEGUI中的资源管理及其相应组件的创建。
17.3.1 资源管理
CEGUI中用资源组管理器装载一些文件共其在渲染的时候使用,它使用到了一个工具对象,我们把它叫做“ResourceProvider”,此对象提供了一组接口负责CEGUI与其它的文件装载系统通信。例如,我们知道Ogre也有自己的资源管理/文件装载子系统,通过实习特定的ResourceProvider对象,CEGUI的渲染模块就可以和那些子系统无缝的组合起来,那样CEGUI的数据文件就可以通过那些子系统装载了。但是我们知道更底层的库(Direct3D和OpenGL)没有那样的资源管理系统,所以,CEGUI会为它们提供默认的资源管理系统(Default Resource Provider)。
由于现在我们还没有把CEGUI和Ogre结合,所以我们先来了解一下CEGUI的默认资源管理系统:CEGUI::DefaultResourceProvider,它是为那些目前还没有的库提供基础帮助的系统。它不仅提供了CEGUI装载文件、数据时所需的函数,而且对“资源组(Resource Groups)”也提供了初步的支持。这里的“资源组”其实是一个标签,它代表系统的某个文件夹路径,这就使得我们可以将文件夹中的文件按其逻辑类型进行分组,然后可以通过一个简单的标签而不是硬编码的路径去指定它。也就是说,当数据文件的路径有改动的时候,只需要更新资源组的路径而不必更改代码和XML文件中的路径信息。
DefaultResourceProvider允许你定义任意数目的资源组,并为每个资源组指定一个路径。也就是说:你可以创建一个资源组,比如“imagesets”,并为它指定一个路径,假设是“datafiles/imagesets”,然后,当你通过ImagesetManager装载Imageset的时候,就可以指定“imagesets”为它将要使用的资源组,这样,系统就会再预定义的路径中寻找资源。目前,每个资源组只能被赋予一个路径。
这里,我们举一个小例子来说明,以前,如果我们在没使用资源组的时候,你可能这么做:
Imageset* imset = ImagesetManager::getSingleton().createImageset("datafiles/imagesets/TaharezLook.imageset"); |
用了资源组以后,在初始化阶段,我们可以用默认的资源管理器像下面这样创建资源组:
CEGUI::DefaultResourceProvider*rp =static_cast<CEGUI::DefaultResourceProvider*>(CEGUI::System::getSingleton().getResourceProvider()); rp->setResourceGroupDirectory("imagesets","datafiles/imagesets/"); |
然后当你需要载入imageset的时候,你可以这样指定要使用的资源组:
Imageset* imset = ImagesetManager::getSingleton().createImageset("TaharezLook.imageset", "imagesets"); |
这里你不需要提供任何路径信息,因为在你指定的资源组中已经包含了相关的路径信息,一般它们只包含文件名。
系统定义的任何代表可装载资源的资源类,都有获取和设置默认资源组的静态函数,当需要载入数据文件的时候,它就用那个默认资源组。比如,对于Imageset类,默认的资源组应该指向一个存储imageset xml文件和材质文件的文件夹。
让我们回过头来,看一下前面我们画的CEGUI运行流程图,图中有两个地方或许我们应该注意一下:initialiseResourceGroupDirectories()和initialiseDefaultResourceGroups(),这两个函数就是我们的CEGUI程序在运行的时候初始化资源组已经设置默认资源组的地方。我们来看一下这两个函数中的部分代码:
void initialiseResourceGroupDirectories() { // initialise the required dirs for the DefaultResourceProvider CEGUI::DefaultResourceProvider*rp = static_cast<CEGUI::DefaultResourceProvider*> (CEGUI::System::getSingleton().getResourceProvider()); ………… // for each resource type, set a resource group directory rp->setResourceGroupDirectory("schemes",resourcePath); rp->setResourceGroupDirectory("imagesets",resourcePath); rp->setResourceGroupDirectory("fonts",resourcePath); rp->setResourceGroupDirectory("layouts",resourcePath); rp->setResourceGroupDirectory("looknfeels",resourcePath); rp->setResourceGroupDirectory("lua_scripts",resourcePath); rp->setResourceGroupDirectory("schemas",resourcePath); rp->setResourceGroupDirectory("animations",resourcePath); ………… } |
这个函数就像前面一下,是设置我们的资源组目录;
void initialiseDefaultResourceGroups() { // set the default resource groups to be used CEGUI::Imageset::setDefaultResourceGroup("imagesets"); CEGUI::Font::setDefaultResourceGroup("fonts"); CEGUI::Scheme::setDefaultResourceGroup("schemes"); CEGUI::WidgetLookManager::setDefaultResourceGroup("looknfeels"); CEGUI::WindowManager::setDefaultResourceGroup("layouts"); CEGUI::ScriptModule::setDefaultResourceGroup("lua_scripts"); CEGUI::AnimationManager::setDefaultResourceGroup("animations"); ………… } |
这个函数就是设置默认的资源组函数,我们可以看到这里可以设置的不仅是前面我们提到的imagesets(图片集),还有fonts(字体)、lookfeels(外观)、layouts(布局)等等文件。
17.3.2 组件的创建
CEGUI中所有的空间都是窗口,它们都是从Window这个基类派生出来的。CEGUI中有两种方法来创建窗口:通过C++硬编码实现和通过XML布局文件实现。通过C++硬编码实现的所有窗口都是由名字空间CEGUI中WindowManager类的一个实例通过成员函数createWindow (constString & type, const String &name = "")来创建。
组件通过自己的成员函数设置自己的属性以及事件响应等。
各种组件是以树形结构组合在一起的。组件通过各自的成员函数addChildWindow()来添加自己的子组件。例如:
一般情况下,你将用DefaultWindow(或者它的老名字:DefaultGUISheet)作为GUI的“root”窗口,这点并不是必须的,但它是使用CEGUI很好的习惯,而且可以帮助简化布局。如,在我们前面第一个示例中,我们创建了一个DefaultWindow作为GUI的根窗口:
DefaultWindow* root = (DefaultWindow*)winMgr.createWindow("DefaultWindow","Root"); System::getSingleton().setGUISheet(root); |
这里createWindow的第一个参数“DefaultWindow”,它是告诉系统你要创建的窗口类型或类。通常,你可以使用的窗口类型是那些当你载入scheme文件的时候注册的窗口类,而有些像DefaultWindow这样的是全局类型,它们总是可用的。第二个参数是组件的名字,名字不能重复。
setGUISheet函数用来指定一个窗口作为GUI的根窗口,这将替换掉你当前的根窗口,但是要注意:之前的一系列窗口/控件并没有被销毁,只是从当前的显示链中摘除——通过使用setGUISheet函数,你可以轻松的在多个GUI中切换。
现在,你已经创建了第一个窗口并把它附着在GUI系统上,当系统画GUI的时候,它将把这个窗口作为GUI的根。但是,如果你编译运行这些代码,不会看到任何东西,因为我们刚才创建的DefaultWindow是隐藏的,这也是为什么DefaultWindow那么适合做根窗口的原因:它仅仅被用作供其它的窗口和控件依附的空画布。
那么,我们再来看下面的代码,我们创建了一个框架窗口,这个窗口有一个标题栏,也可以移动和改变大小:
FrameWindow* wnd = (FrameWindow*)winMgr.createWindow("TaharezLook/FrameWindow","Demo Window"); |
上面的代码创建了一个"TaharezLook/FrameWindow"窗口,整个系统都是用这种命名约定:窗口类型以组件组名做为前缀(假如你载入了WindowsLook scheme,你就可以创建一个"WindowsLook/FrameWindow"对象),这里我们为这个新窗口命名为"Demo Window",需要注意的是前面强制类型转换的使用,由于createWindow函数总是返回Window基类指针,所以使用CEGUI的时候示例中的类型转换是很常见的。
为了让系统能够用我们的新窗口做一些有用的事情,我们还需要做一些事情。首先,我们必须把这个新窗口附着到我们上面指定的根窗口上:
root->addChildWindow(wnd); |
现在,我们可以设置它的位置和大小。CEGUI使用一个“统一的(unified)”坐标系,使得可以同时使用相对部分和绝对部分——这就是为什么你看到的每个坐标都由两部分组成。
设置位置在其父窗口左上角的1/4位置:
wnd->setPosition(UVector2(UDim(0.25f,0),UDim( 0.25f,0))); |
设置其大小为父窗口的一半:
wnd->setSize(UVector2(UDim(0.5f,0),UDim( 0.5f,0))); |
最后,我们为这个框架窗口的标题栏设置一个标题:
wnd->setText("Hello IMedia!"); |
就这样,这就是我们前面一个第一个示例的整个分析过程。虽然看起来简单,不过理解了整个的过程你的CEGUI就算入门了。
17.3.3 XML布局文件
我们前面提到过,除了采用硬编码的方式,我们还有一种方式来进行组件的创建。虽然通过硬编码的方式已经看起来很不错了,但是,不难发现,它有一个很大的弊端,每次你想要调整GUI布局的时候,你就得去修改代码,然后重新编译,这浪费了我们很多宝贵的时间。而使用XML布局的方式,我们只需把布局保存在文件中,然后在代码中调用那个布局文件。
系统支持XML格式的布局文件,可以通过WindowManager:: loadWindowLayout函数载入此文件。此函数为你创建所有的窗口并返回一个指向根窗口的指针。调用setGUISheet设置GUI的根窗口的时候用此指针再合适不过了。
所以,首先我们需要一个布局文件。下面这个XML文件所描述的窗口和我们前面第一个示例中用C++创建的窗口是一样的。
到“CEGUI根目录/datafiles/layouts”目录下,新建一个文件名为:MyLayout1.layout,然后填入以下内容:
<?xml version="1.0" ?> <GUILayout> <Window Type="DefaultGUISheet" Name="root"> <Window Type="TaharezLook/FrameWindow" Name="Demo Window"> <Property Name="UnifiedPosition" Value="{{0.25,0},{0.25,0}}"> </Property> <Property Name="UnifiedSize" Value="{{0.5,0},{0.5,0}}"> </Property> <Property Name="Text" Value="Hello IMdedia"> </Property> </Window> </Window> </GUILayout> |
这个文件中的内容(如:Window元素的属性)和前面我们通过硬编码创建的内容时一一对应的。镶嵌的Window元素用来表明窗口的父子关系。注意:在一个布局文件中,你仅可以拥有一个“根”级别的窗口,这也是通常情况下把DefaultWindow用作根窗口的另一个原因。Property元素是用来设置当前窗口的属性的,每种窗口/控件类都有很多属性,而且每个类都从它的父类中继承所有的属性。虽然我们把这个文件名命名为MyLayout.layout,但是大家应该可以看出这其实是一个XML文件,如果大家对XML有所了解,详细里面的格式应该非常熟悉。
文件定义完之后,我们需要在程序代码中调用这个文件,修改前面示例中initialiseSample函数体中的内容如下:
bool initialiseSample() { using namespaceCEGUI; SchemeManager::getSingleton().create("TaharezLook.scheme"); System::getSingleton().setDefaultMouseCursor("TaharezLook","MouseArrow"); WindowManager& winMgr = WindowManager::getSingleton(); Window* root = WindowManager::getSingleton().loadWindowLayout("MyLayout1.layout"); System::getSingleton().setGUISheet(root); return true; } |
编译并运行程序,你将会发现最终显示的效果和我们前面通过硬编码写的效果是一样的。不过,现在我们可以轻松修改、增强GUI布局而不用修改、重新编译代码了。
17.4 Ogre和CEGUI的整合
前面我们已经了解了关于CEGUI的很多基础知识,不过我们这章讨论CEGUI的目的在于把它整合到Ogre3D中,让它作为Ogre3D系统的GUI,之所以在前面花这么大工夫介绍CEGUI的基础知识,就是为这一节准备的。这一节我们就来介绍怎样把CEGUI整合到Ogre3D中。
笔者注: 由于我们现在需要进行Ogre和CEGUI的整合,在编写我们程序之前,请确保你正确配置了Ogre3D和CEGUI的各个包含目录和库目录,否则会遇到找不到头文件或者其它类似的错误提示,具体配置方法请参看Ogre3D和CEGUI相关的配置介绍。 |
首先,这一章我们需要在《Ogre启动序列》一章我们自己手动编写的代码(当然也可以继承ExampleApplication,不过需要重写帧监听):
新建一个Win32项目名为OgreCEGUITest,然后新建一个头文件和一个cpp文件,内容如下:
OgreCEGUITest.h
#include <windows.h> #include "Ogre.h" #include "OIS\OIS.h"
class MyApplication:public Ogre::FrameListener, public OIS::KeyListener,publicOIS::MouseListener { public: MyApplication(); ~MyApplication(); void go(); protected: bool setup(); void setupResources(); bool configure(); void chooseSceneManager(); void createCamera(); void createViewports(); void loadResources(); void createScene(); void createFrameListener(void); // Ogre::FrameListener virtual bool frameRenderingQueued(constOgre::FrameEvent&evt); // OIS::KeyListener virtual bool keyPressed( const OIS::KeyEvent &arg ); virtual bool keyReleased( const OIS::KeyEvent &arg ); // OIS::MouseListener virtual bool mouseMoved( const OIS::MouseEvent &arg ); virtual bool mousePressed( const OIS::MouseEvent &arg,OIS::MouseButtonIDid ); virtual bool mouseReleased( const OIS::MouseEvent &arg,OIS::MouseButtonIDid ); private: Ogre::Root*mRoot; Ogre::RenderWindow*mWindow; Ogre::SceneManager*mSceneMgr; Ogre::Camera*mCamera; bool mShutDown; //OIS Input devices OIS::InputManager*mInputManager; OIS::Mouse* mMouse; OIS::Keyboard*mKeyboard; }; |
OgreCEGUITest.cpp
#include "OgreCEGUITest.h"
MyApplication::MyApplication() { mRoot = NULL; mWindow = NULL; mSceneMgr = NULL; mCamera = NULL; mWindow = NULL; mShutDown = false; mInputManager = NULL; mMouse = NULL; mKeyboard = NULL; } MyApplication::~MyApplication() { if( mInputManager ) { mInputManager->destroyInputObject(mMouse ); mInputManager->destroyInputObject(mKeyboard ); OIS::InputManager::destroyInputSystem(mInputManager); mInputManager = 0; } if (mRoot) { delete mRoot; } } bool MyApplication::setup() { mRoot = new Ogre::Root("plugins_d.cfg"); setupResources(); if (!configure()) { return false; } chooseSceneManager(); createCamera(); createViewports(); loadResources(); createScene(); createFrameListener(); return true; } void MyApplication::setupResources() { // Load resource paths from config file Ogre::ConfigFilecf; cf.load("resources_d.cfg"); // Go through all sections & settings in the file Ogre::ConfigFile::SectionIteratorseci =cf.getSectionIterator(); Ogre::StringsecName,typeName,archName; while (seci.hasMoreElements()) { secName = seci.peekNextKey(); Ogre::ConfigFile::SettingsMultiMap *settings =seci.getNext(); Ogre::ConfigFile::SettingsMultiMap::iteratori; for (i =settings->begin();i !=settings->end(); ++i) { typeName = i->first; archName = i->second; Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName,typeName,secName); } } } bool MyApplication::configure() { if (!mRoot->showConfigDialog()) { return false; } mWindow = mRoot->initialise(true,"IMedia Project"); return true; } void MyApplication::chooseSceneManager() { mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC); } void MyApplication::createCamera() { mCamera = mSceneMgr->createCamera("MyCamera"); mCamera->setPosition(Ogre::Vector3(0,0,80)); mCamera->lookAt(Ogre::Vector3(0,0,-300)); mCamera->setNearClipDistance(5); } void MyApplication::createViewports() { Ogre::Viewport*vp =mWindow->addViewport(mCamera); vp->setBackgroundColour(Ogre::ColourValue(0,0,0)); mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth())/Ogre::Real(vp->getActualHeight())); } void MyApplication::loadResources() { Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); }
void MyApplication::createFrameListener(void) { OIS::ParamListpl; size_t windowHnd = 0; std::ostringstreamwindowHndStr; mWindow->getCustomAttribute("WINDOW", &windowHnd); windowHndStr << windowHnd; pl.insert(std::make_pair(std::string("WINDOW"),windowHndStr.str())); mInputManager = OIS::InputManager::createInputSystem(pl ); mKeyboard=static_cast<OIS::Keyboard*>(mInputManager->createInputObject(OIS::OISKeyboard,true )); mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject(OIS::OISMouse,true )); mMouse->setEventCallback(this); mKeyboard->setEventCallback(this); mRoot->addFrameListener(this); } bool MyApplication::frameRenderingQueued(constOgre::FrameEvent&evt) { if(mWindow->isClosed()) return false; if(mShutDown) return false; //Need to capture/update each device mKeyboard->capture(); mMouse->capture(); return true; } bool MyApplication::keyPressed( const OIS::KeyEvent &arg ) { if (arg.key ==OIS::KC_ESCAPE) { mShutDown = true; } return true; } bool MyApplication::keyReleased( const OIS::KeyEvent &arg ) { return true; } bool MyApplication::mouseMoved( const OIS::MouseEvent &arg ) { return true; } bool MyApplication::mousePressed( const OIS::MouseEvent &arg,OIS::MouseButtonIDid ) { return true; } bool MyApplication::mouseReleased( const OIS::MouseEvent &arg,OIS::MouseButtonIDid ) { return true; } void MyApplication::createScene() { Ogre::Entity*ent =mSceneMgr->createEntity("Sinbad.mesh"); mSceneMgr->getRootSceneNode()->attachObject(ent); } void MyApplication::go() { if (!setup()) return; mRoot->startRendering(); }
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) { MyApplication app; app.go(); return 0; } |
这就是我们Ogre3D的基本模板,下面我们开始把CEGUI的内容整合进来:
第一步,添加CEGUI相应的头文件到OgreCEGUITest.h中:
#include <CEGUI.h> #include <RendererModules/Ogre/CEGUIOgreRenderer.h> |
第二步,在OgreCEGUITest.h头文件中,为MyApplication类添加一个private类型的成员变量mRenderer和一个protected类型的成员函数quit:
CEGUI::OgreRenderer*mRenderer; |
bool quit(const CEGUI::EventArgs &e); |
第三步,在OgreCEGUI.cpp文件中,添加quit成员函数的实现:
bool MyApplication::quit(constCEGUI::EventArgs &e) { return true; } |
第四步,增加CEGUI所需的依赖库文件:CEGUIBase_d.lib和CEGUIOgreRenderer_d.lib
如果是在Release模式下加入:CEGUIBase.lib和CEGUIOgreRenderer.lib,如下所示:
第五步,我们需要把下面的一些DLLs复制到你的(Ogre3D根目录)/bin/debug目录下,或者Release目录下:
Debug模式下:
(1)CEGUIBase_d.dll
(2)CEGUIFalagardWRBase_d.dll
(3)CEGUIOgreRenderer_d.dll
(4)CEGUIExpatParser_d.dll
(5)CEGUIOgreRenderer_d.dll
Release模式下:
(1)CEGUIBase.dll
(2)CEGUIFalagardWRBase.dll
(3)CEGUIOgreRenderer.dll
(4)CEGUIExpatParser.dll
(5)CEGUIOgreRenderer.dll
按要求做完这些之后你可以编译运行一下程序,不过这是只会显示和以前一样的画面,还看不到CEGUI的东西。
第六步,我们在前面介绍CEGUI第一个示例的时候介绍过CEGUI中也有资源管理的概念,由于现在我们需要把CEGUI整合到Ogre3D中,所以我们可以把CEGUI中所需的资源组定义在Ogre3D的配置文件中,因此你需要在resources_d.cfg(Debug模式下)中或者resources.cfg(Release模式下)定义它所需要的资源组和它们对应的位置目录。
这里有一点需要注意:用CEGUI根目录下的datafiles文件夹路径代替下面的path_to_cegui路径:
[Imagesets]
FileSystem=path_to_cegui/imagesets
[Fonts]
FileSystem=path_to_cegui/fonts
[Schemes]
FileSystem=path_to_cegui/schemes
[LookNFeel]
FileSystem=path_to_cegui/looknfeel
[Layouts]
FileSystem=path_to_cegui/layouts
第七步,在createScene函数体中继续增加如下内容:
mRenderer = &CEGUI::OgreRenderer::bootstrapSystem(); |
虽然这只是简单的一行代码,但是它是联系Ogre和CEGUI的纽带。bootstrapSystem这个函数创建了所有Ogre指定的对象,然后通过它们初始化CEGUI系统,这里CEGUI将会使用默认的Ogre渲染窗口进行渲染,作为输出表面。
第八步,我们现在需要为CEGUI资源管理设置每一个默认的资源组,接着刚才的代码增加下面这些内容(其实就是设置刚才我们在resources.cfg配置文件中的那些):
CEGUI::Imageset::setDefaultResourceGroup("Imagesets"); CEGUI::Font::setDefaultResourceGroup("Fonts"); CEGUI::Scheme::setDefaultResourceGroup("Schemes"); CEGUI::WidgetLookManager::setDefaultResourceGroup("LookNFeel"); CEGUI::WindowManager::setDefaultResourceGroup("Layouts"); CEGUI::SchemeManager::getSingleton().create("TaharezLook.scheme"); CEGUI::System::getSingleton().setDefaultMouseCursor("TaharezLook","MouseArrow"); |
第九步,注入键事件。
CEGUI不以任何方式处理输入,它不读取鼠标运动或者键盘输入。相反,它依赖用户去注入键盘和鼠标事件到系统中。因此,下面我们需要做的就是处理键盘事件。
1.找到keyPressed函数,修改其内容,如下:
bool MyApplication::keyPressed( const OIS::KeyEvent &arg ) { if (arg.key ==OIS::KC_ESCAPE) { mShutDown = true; } CEGUI::System &sys =CEGUI::System::getSingleton(); sys.injectKeyDown(arg.key); sys.injectChar(arg.text); return true; } |
2.找到keyReleased函数,修改其内容,如下:
bool MyApplication::keyReleased( const OIS::KeyEvent &arg ) { CEGUI::System::getSingleton().injectKeyUp(arg.key); return true; } |
第十步,转换和注入鼠标事件。
既然我们完成了键盘事件的输入工作,我们需要关注鼠标的输入。
不过,我们现在有个小的问题需要描述。
当我们向CEGUI注入键盘按下和释放的事件时,我们从来不需要转换键,因为OIS和CEGUI使用的是同样的键盘输入键码,但是鼠标却不一样。
在我们注入鼠标按钮进入CEGUI之前,我们需要写一个函数用到进行OIS按钮ID到CEGUI按钮ID的转换工作:
增加下面的代码到OgreCEGUITest.cpp文件中(由于这是一个全局函数,所以可以写在这个文件的MyApplication构造函数的上面即可):
CEGUI::MouseButtonconvertButton(OIS::MouseButtonIDbuttonID) { switch (buttonID) { case OIS::MB_Left: return CEGUI::LeftButton; case OIS::MB_Right: return CEGUI::RightButton; case OIS::MB_Middle: return CEGUI::MiddleButton; default: return CEGUI::LeftButton; } } |
现在我们可以注入鼠标事件了。
1.找到mousePressed函数,修改器内容,如下:
bool MyApplication::mousePressed( const OIS::MouseEvent &arg,OIS::MouseButtonIDid ) { CEGUI::System::getSingleton().injectMouseButtonDown(convertButton(id)); return true; } |
2.找到mouseReleased函数,修改其内容,如下:
bool MyApplication::mouseReleased( const OIS::MouseEvent &arg,OIS::MouseButtonIDid ) { CEGUI::System::getSingleton().injectMouseButtonUp(convertButton(id)); return true; } |
3.最后,我们需要输入鼠标运动(mouse motion)到CEGUI中。
CEGUI::System对象有一个injectMouseMove函数,代表的是鼠标的相对运动。
OIS::mouseMoved句柄在state.X.rel和state.Y.rel变量中给我们提供了那些相对运动。
找到mouseMoved函数,修改器内容,如下:
bool MyApplication::mouseMoved( const OIS::MouseEvent &arg ) { CEGUI::System &sys =CEGUI::System::getSingleton(); sys.injectMouseMove(arg.state.X.rel,arg.state.Y.rel); // Scroll wheel. if (arg.state.Z.rel) sys.injectMouseWheelChange(arg.state.Z.rel / 120.0f); return true; } |
这里,120是以前微软用的一种魔术数字,它在现代系统中很常见。OIS使用同样的魔术数字,其它的,像GLUT不是。
到此为止,CEGUI就被设置好了接受鼠标和键盘事件。
第十一步,我们想在界面上增加一个离开按钮,因此,在createScene函数里继续接着增加如下代码:
CEGUI::WindowManager &wmgr =CEGUI::WindowManager::getSingleton(); CEGUI::Window *sheet =wmgr.createWindow("DefaultWindow","CEGUIDemo/Sheet"); |
下面我们要做的就是去创建离开按钮并且设置它的大小:(接着上面的代码继续添加)
CEGUI::Window *quit =wmgr.createWindow("TaharezLook/Button","CEGUIDemo/QuitButton"); quit->setText("Quit"); quit->setSize(CEGUI::UVector2(CEGUI::UDim(0.15, 0), CEGUI::UDim(0.05, 0))); |
我们要做的另一件事是把这个离开按钮增加到我们创建的那个sheet上,然后把那个sheet置入当前GUI sheet。继续添加如下代码:
sheet->addChildWindow(quit); CEGUI::System::getSingleton().setGUISheet(sheet); |
现在如果你编译运行你的应用程序你将会看到一个Quit按钮在屏幕的左上角,但是当年点击它的时候它还不能做任何事。
第十二步,事件处理。事件在CEGUI中是非常灵活的。不是使用一个你需要实现的接口去接受事件,它使用的是一个回调原理来绑定一个公共函数,用来进行事件处理。因此,我们现在将在程序中给Quit按钮在按下的时候注册一个点击事件。
增加下面的代码到MyApplication::createScene中,放置到创建quit按钮的代码之后:
quit->subscribeEvent(CEGUI::PushButton::EventClicked,CEGUI::Event::Subscriber(&MyApplication::quit,this)); |
这里将签署点击事件。每一个在CEGUI中的组件有一系列它支持的事件。它们都以“Event”开始。subscribeEvent的第一个参数是事件本身;第二个参数是一个Event::Subscriber对象;
当我们创建一个Subscriber对象的时候,我们首先需要做的传递给一个函数指针(注意,符号‘&’指的是传递一个函数指针),第二个我们需要传递给Subscriber对象的是MyApplication对象。
因此,我们创建的MyApplication::quit函数将会处理鼠标点击事件来终止程序。
增加下面的代码到MyApplication::quit中:
bool MyApplication::quit(constCEGUI::EventArgs &e) { mShutDown = true; return true; } |
编译运行程序,并测试它,应该能成功运行并终止:
需要注意的是,我们能为处理CEGUI事件创建许多函数;
唯一的限制就是这些函数必须返回一个bool值,而且它们必须采取一个单一的参数类型“const CEGUI::EventArgs &”。
到此,我们第一个Ogre和CEGUI结合的示例就完成了,纵观整个过程,其实大部分内容都是我们以前涉及到的,大家可以回过头来好好回顾一下,虽然这只是一个简单的Ogre与CEGUI结合的示例,但是我们的确完成了最重要的一步,因为以后我们的工作都是在此基础上进行的,而且理解了这个过程,相信我们再扩展我们的应用也不会是件难事了。
17.5 CEGUI中文的显示与中文的输入
默认情况下,当我们通过CEGUI在屏幕上添加一些窗口、按钮或者编辑框的时候只能显示英文,也只能输入英文,因此要想实现中文的显示与输入需要做一些配置工作。
17.5.1 中文显示
第一步:首先拷贝C:/WINDOWS/Fonts目录下面的一个字体文件(例如simfang.ttf)到datafiles\fonts目录下(datafiles目录根据你的设置而定),随便打开一个.font文件(记事本或UltraEdit打开),复制里面的格式,自己创建一个.font文件,拷贝进去(例如:<?xml version="1.0" ?>
<FontName="simfang" Filename="simfang.ttf"Type="FreeType" Size="10" NativeHorzRes="800"NativeVertRes="600" AutoScaled="true" />)保存起来,名字最好和FontName里面一样(其实没太大需要,如果你理解的话)。
第二步:修改datafiles\schemes下面的文件TaharezLook.scheme的内容,方法,记事本打开。在第四行和第五行之间加入新的一行:<Font Name="simfang" Filename="simfang.font"/>,保存即可。
第三步:然后在createScene函数最后函数中加入下面一行代码:
CEGUI::System::getSingleton().setDefaultFont((CEGUI::utf8*)"simfang"); |
在程序中如下图所示:
第四步: 然后你可以在你程序中使用中文了。不过setText等里面的参数要稍加加工。参数为:
quit->setText((CEGUI::utf8*)Ogre::UTFString(L"中文退出").asUTF8_c_str()); |
结果如下图所示:
到此,CEGUI的中文显示功能完全实现了。
17.5.2 中文输入
第一步:在程序最后中添加如下两个文件:
CEGUICHSSupport.h
#pragma once
#include <OgreRenderWindow.h> #include <CEGUI.h>
class CEGUICHSSupport { public: static bool injectChar( Ogre::RenderWindow* pWin, CEGUI::utf32code_point ); static bool initTest(Ogre::RenderWindow*pWin); public: inline static bool IsLocked() { return m_bIsLock; } protected: static bool MakeWChar( unsigned char code_point ); static LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); protected: static bool m_bIsOk; static bool m_bIsLock; static WNDPROC m_funcOldWndProc; };
|
CEGUICHSSupport.cpp
#include "CEGUICHSSupport.h"
#include <Imm.h> #pragma comment( lib, "Imm32.lib" )
#include <cassert> #ifndef AssertEx #define AssertEx( exp, msg ) assert( exp && msg ) #endif
bool CEGUICHSSupport::m_bIsOk = false; bool CEGUICHSSupport::m_bIsLock = false; WNDPROC CEGUICHSSupport::m_funcOldWndProc = NULL; UINT VirtualKeyToScanCode(WPARAM wParam, LPARAM lParam) { if(HIWORD(lParam) & 0x0F00) { UINT scancode = MapVirtualKey(wParam, 0); return scancode | 0x80; } else { return HIWORD(lParam) & 0x00FF; } } LRESULT CEGUICHSSupport::WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch( uMsg ) { case WM_IME_COMPOSITION: { if( lParam & GCS_COMPSTR ) { HIMC hIMC = ImmGetContext( hWnd ); TCHAR szBuf[1280] = {0}; DWORD dwSize = ImmGetCompositionString( hIMC, GCS_COMPSTR, (void*)szBuf, 1280 ); if( dwSize != 0 ) { m_bIsLock =true; } else { m_bIsLock =false; } } } break; case WM_CHAR : { MakeWChar( (CEGUI::utf32)wParam ); } break; //响应键盘连续按下的情况 case WM_KEYDOWN: { //输入法状态时,输入不传递到UI系统中去。 UINT vk = (UINT)ImmGetVirtualKey(hWnd); if(vk ==wParam) break; } return CEGUI::System::getSingleton().injectKeyDown((CEGUI::utf32)(VirtualKeyToScanCode(wParam,lParam))); break; case WM_KEYUP: return CEGUI::System::getSingleton().injectKeyUp((CEGUI::utf32)(VirtualKeyToScanCode(wParam,lParam))); break; } AssertEx( m_funcOldWndProc != NULL, "不可为空" ); return CallWindowProc( m_funcOldWndProc , hWnd , uMsg , wParam , lParam ); } bool CEGUICHSSupport::MakeWChar( unsigned char code_point ) { using namespace CEGUI; static char s_tempChar[3] = ""; static wchar_t s_tempWchar[2] = L""; static bool s_flag = false; unsigned char uch = code_point; if( uch >= 0x81 || s_flag ) { if( !s_flag ) { s_tempChar[0] = (char)uch; s_flag = true; return true; } else if( uch >= 0x40 ) { s_tempChar[1] = (char)uch; s_flag = false; MultiByteToWideChar( 0, 0,s_tempChar, 2,s_tempWchar, 1); s_tempWchar[1] =L'\0'; utf32 code = (utf32)s_tempWchar[0]; return CEGUI::System::getSingleton().injectChar(code ); } else { if ( !ImmIsIME(GetKeyboardLayout(0))) { return CEGUI::System::getSingleton().injectChar(code_point); } } } else { s_flag = false; return CEGUI::System::getSingleton().injectChar(code_point); } return false; } bool CEGUICHSSupport::initTest(Ogre::RenderWindow*pWin) { AssertEx( pWin != NULL, "RenderWindow不能为空..." ); if( !m_bIsOk ) { HWND hWnd = NULL; pWin->getCustomAttribute("WINDOW", &hWnd ); if( NULL == hWnd ) { throw std::runtime_error( "获取系统句柄失败..." ); } m_funcOldWndProc = (WNDPROC)::GetWindowLong(hWnd,GWL_WNDPROC ); ::SetWindowLong( hWnd, GWL_WNDPROC, (LONG)WndProc ); m_bIsOk = true; return false; } return true; } bool CEGUICHSSupport::injectChar( Ogre::RenderWindow* pWin, CEGUI::utf32code_point ) { AssertEx( pWin != NULL, "RenderWindow不能为空..." ); if( !m_bIsOk ) { HWND hWnd = NULL; pWin->getCustomAttribute("WINDOW", &hWnd ); if( NULL == hWnd ) { throw std::runtime_error( "获取系统句柄失败..." ); } m_funcOldWndProc = (WNDPROC)::GetWindowLong(hWnd,GWL_WNDPROC ); ::SetWindowLong( hWnd, GWL_WNDPROC, (LONG)WndProc ); m_bIsOk = true; return false; } return true; } |
第二步:然后你在OgreCEGUITest.h头文件中引入头文件“CEGUICHSSupport.h”。修改keyPressed函数为如下内容:
bool MyApplication::keyPressed( const OIS::KeyEvent &arg ) { if (arg.key ==OIS::KC_ESCAPE) { mShutDown = true; } if( !CEGUICHSSupport::IsLocked() ) { CEGUI::System::getSingleton().injectKeyDown(arg.key ); } //下面这句,处理中文 CEGUICHSSupport::injectChar( mWindow , (CEGUI::utf32)arg.text ); return true; } |
第三步:在createScene中增加一个编辑框测试一下即可,如(接着该函数中原来的代码增加即可):
CEGUI::Window *edit1 =wmgr.createWindow("TaharezLook/Editbox","CEGUIDemo/Edit1"); edit1->setPosition(CEGUI::UVector2(CEGUI::UDim(0.15, 0), CEGUI::UDim(0.35, 0))); edit1->setSize(CEGUI::UVector2(CEGUI::UDim(0.5, 0), CEGUI::UDim(0.05, 0))); sheet->addChildWindow(edit1); |
第四步:如果现在我们运行程序会发现我们可以像编辑框中输入内容了,但是当输入中文时会出现乱码,如下所示:
这是由于我们使用的字符集的原因,打开项目——属性页,把原来的Unicode字符集修改为多字节字符集即可:
结果如下如所示:
虽然这样算是比较好的实现了所需要的功能,但是测试发现当你点击一次BackSpace按钮时其实一次删除的是两个字,其实原因是我们对原来网友提供的程序代码进行了修改的缘故,但是未修改之前的源程序中按住BackSpace不放却只能删除一个字,只有抬起这个键再按一次才能删除另一个字符,也就是说不能连续删除字符,因此,针对这两种矛盾,我们的程序对代码进行了三次修改,其实上面提供的就是我们第三次修改之后的最新代码,只是有些功能还没有调用罢了,我们可以采取这样的操作:首先,注释掉keyPressed中除最后一句的所有代码,如下所示:
bool MyApplication::keyPressed( const OIS::KeyEvent &arg ) { //if( !CEGUICHSSupport::IsLocked() ) //{ // CEGUI::System::getSingleton().injectKeyDown( arg.key ); //}
////下面这句,处理中文 //CEGUICHSSupport::injectChar( mWindow , (CEGUI::utf32)arg.text ); return true; }
|
然后在其他地方,如在createScene中增加一句代码:
CEGUICHSSupport::initTest(mWindow); |
现在,你如果再测试的话应该就可以正常删除字符了。
到此,我们对Ogre和CEGUI结合的内容就告一段落了,毕竟本书不是一本介绍CEGUI的书籍,我们只是想通过本章的介绍让大家对怎样在Ogre中使用CEGUI有一个比较全面的认识,至于读者朋友最终选择的GUI库是否是CEGUI,或者说读者朋友更愿意使用其它的GUI库,都是可以的,适合的才是最好的。如果读者朋友对CEGUI的知识很感兴趣,欢迎随时交流,而且关于CEGUI的网络资源也非常多,希望读者朋友们学习顺利。
PS:很久以前就打算把学习Ogre中遇到的知识分享给大家(虽然只是一些皮毛),但是说来惭愧,一直很懒,直到最近才抽出点时间写一些自己的理解(Ogre入门级的东西),所以难免会有很多不足之处,分享是一种快乐,同时也希望和大家多多交流!
(由于在Word中写好的东西发布到CSDN页面需要重新排版(特别是有很多图片时),所以以后更新进度可能会比较慢,同时每章节发布的时间可能不一样(比如说我首选发布的是第二章,其实第一章就是介绍下Ogre的前世今生神马的,相信读者早就了解过~~~),但是我会尽量做到不影响大家阅读,还望大家谅解。)
上述内容很多引用了网上现有的翻译或者内容,在此一并谢过(个人感觉自己有些地方写得或者翻译的不好),还望见谅,转载请注明此项!