cegui基础

cegui渲染入门:

 至少需要3,才可以使cegui运行起来

创建CEGUI::Renderer对象

创建CEGUI::System对象

调用渲染函数

of course,实现真正的游戏GUI,你还需要装载相关的数据文件,进行一些最基础的初始话,你也需要处理用户输入。

 

创建 CEGUIRenderer对象

 

这一步相当的直观,对于所支持的渲染组件(Direct3d9 OpenGl Ogre3d etc..),这一步应该没问题

当然要记得包含所使用的渲染组件的头文件。

Direct3D 8.1

 

 CEGUI::Directx81Renderer *myRenderer = new CEGUI::Directx81Renderer (myD3D89Device);

 

Direct3D9

 

 CEGUI::Directx9Renderer *myRenderer = new CEGUI::Directx9Renderer (myD3D9Device,0);

 

OpenGl

 

CEGUI::OpenGLRenderer *myRenderer = new CEGUI::OpenGLRenderer(0);

 

Ogre3d

 

CEGUI::OgreCEGUIRenderer * = new CEGUI::OgreCEGUIRenderer(myRenderWindow)

 

创建CEGUI::System 对象来对系统进行初始化

  this step is simpletoo。 only need  new 一个CEGUI::System 对象。

  并把刚才创建的CEGUI::Renderer对象的指针传送给他。这时整个系统将会自动初始化

  new CEGUI::System(myRenderer)

 

调用渲染函式

  这一步特殊,与所使用的引擎不同。 不过,说白了,你要做的就是每帧渲染之后调用

 CEGUI::System::renderGUI而已。 Ogre3d会自动完成这步。对于其他引擎也很简单。

 Direct3D 81 /9

 

myD3DDevice->BeginScene();

myD3DDevice->Clear(0,0,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,0,0),1.0f,0);

draw3DScene();

CEGUI::System::getSingleton().renderGUI();

myD3DDevice->EndScene();

myD3DDevice->Present(0,0,0,0);

 

总结 这只是使CEGUI 运行的最初步得介绍

cegui 2

资源管理入门

  读了CEGUI渲染入门这一章,你已经知道,要想让CEGUI跑起来,至少对CEGUI进行最基础的

  初始化,并调用System ::renderGUI方法,这看起来不错。可是,你仍然画不出任何东西。

 

为了达成,下一步是设置资源管理组(resource provider groups),我们将用它们装载一些文件供CEGUI在渲染的时候使用。

 

ResourceProvide 是什么?

CEGUI使用了一个工具对象,我们叫它“ResourceProvide”。这个对象提供了一组接口负责CEGUI与其他的文件装载系统通讯。

例如: Ogre Irrlicht 都有他们自己的资源管理/文件装载子系统,通过实现特定的ResourceProvide 对象,CEGUI

的渲染模块就和那些子系统无缝的组合起来。那样CEGUI的数据文件就可以通过那些子系统装载了。但是,更底层的库

Direct3D OpenGL) 没有那样的资源管理系统。所以CEGUI 为他们提供了默认的资源管理系统

default resource provider)。

 

default resource provider 相关说明

 CEGUI::DefaultResourceProvider --------CEGUI 的默认的资源管理系统,是为那些还没有资源管理的库

提供基础帮助的系统。 他不仅提供了CEGUI装载文件 数据时所需要的函数,而且对资源组(resource groups

也提供了初步的支持。 这里的资源组其实只是一个标签,它代表系统的某个文件夹路径。这使得我们可以将文件夹

中的文件按逻辑类型分组,然后可以通过一个人简单的标签而不是硬编码的路径去指定它,也就是说:当数据文件的

路径有改动的时候,只需要更细资源组的路径而不必更改代码和xml文件中的路径信息。

指定资源组和路径

DefaultResourceProvider  允许你定义任意数目的资源组,并为每个资源组指定一个路径。也就是说,你可以

创建一个资源组,比如 “imagesets”,并为它指定一个路径,假设是"./game/datafile/gui/imagesets/".

然后,你通过ImagesetManager装载Imageset的时候,就可以指定"imagesets"为他将要使用的资源组,

这样系统就会在预定义的路径中寻找资源。目前,每个资源组只可以赋予一个路径。

例子说明

   以前,在没有使用资源组的时候,你may be

 

 Imageset *set = ImagesetManager::getSingleton().createImageset("./game/datafile/gui/imagesets/WindowsLook.imageset");

 

  使用了资源组以后,在初始化阶段,你可以使用默认的资源管理器像这样创建资源组

 

DefaultResourceProvider *rp = CEGUI::System::getSingleton().getResourecProvider();

 

rp->setResourcrGroupDirectory("imagesets","("./game/datafile/gui/imagesets/WindowsLook.imageset");

Imageset *set = ImagesetManager::getSingleton().createImageset("WindowsLook.imageset","imagesets")

 

注意 :你不需要提供任何的路径信息,因为在你指定的资源组中已经包含了相关的路径信息。

 

 

 

默认资源组

 

 系统定义的任何代表可以可装载资源的资源类,都有获取和设置默认资源组的静态函数。

当需要载入数据文件的时候,他就用那个默认资源组。比如:对于Imageset类,默认的资源组应该指向

一个存储imageset xml 文件和材质文件的文件夹。

 

对于每个资源类,他们获取 设置资源组的静态函数的名字都是一样的(xerces 是个例外)

const String& getDefaultResourceGroup();

void setDefaultResourceGroup(cosnt String& groupname);

如果是资源类的一个列表,后面是他们各自管理的资源类型

CEGUI::Imageset ------- Imageset xml and texture image files

CEGUI::Font ---------- Font xml and ttf font files.

CEGUI::Scheme -------Scheme xml files

CEGUI::WindowManager      --------- window layout xml files

CEGUI::WidgetLookManager          ---------- LookNFeel xml files

CEGUI::ScriptModule ---------Script files in whichever scripted langauge

 

上面提到过,xerces是个例外,它是“基于Xerces-CXML解释器(Xerces-C based XML parser)”。对此,有一个特殊的资源组设置文件,它指明schema文件在哪里(它们是用来检查xml.xsd文件)。对于这个特殊情况,它的静态成员如下:

const String& XercesParser::getSchemaDefaultResourceGroup(); 

void XercesParser::setSchemaDefaultResourceGroup(const String& groupname);

 

需要注意的最后一点是:资源管理类也有一个默认的资源组。当一个资源类没有显示的指定资源组的时候,

系统就会使用它。当你把所有的数据文件都放到一个文件夹的时候,这点就很方便了。

 


一个完整的例子:配置 DefaultResourceProvider

 

// DefaultResourceProvider设置所需的路径  

CEGUI::DefaultResourceProvider* rp = static_cast  

    (CEGUI::System::getSingleton().getResourceProvider());  

  

rp->setResourceGroupDirectory("schemes""../datafiles/schemes/");  

rp->setResourceGroupDirectory("imagesets",   

                            "../datafiles/imagesets/");  

rp->setResourceGroupDirectory("fonts""../datafiles/fonts/");  

rp->setResourceGroupDirectory("layouts""../datafiles/layouts/");  

10 rp->setResourceGroupDirectory("looknfeels",   

11                             "../datafiles/looknfeel/");  

12 rp->setResourceGroupDirectory("lua_scripts",   

13                             "../datafiles/lua_scripts/");  

14   

15 // 这步仅当你使用Xerces作为XML解析器的时候才需要  

16 rp->setResourceGroupDirectory("schemas""../../XMLRefSchema/");  

  完成这步以后,我们就把资源组和它们的路径都设置好了。最后,为了让系统使用新设置的路径,我们来设置默认资源组:

 

17 // 设置默认资源组  

18 CEGUI::Imageset::setDefaultResourceGroup("imagesets");  

19 CEGUI::Font::setDefaultResourceGroup("fonts");  

20 CEGUI::Scheme::setDefaultResourceGroup("schemes");  

21 CEGUI::WidgetLookManager::setDefaultResourceGroup("looknfeels");  

22 CEGUI::WindowManager::setDefaultResourceGroup("layouts");  

23 CEGUI::ScriptModule::setDefaultResourceGroup("lua_scripts");  

24   

25 // 仅当你用xercesXML解析器,并为它定义了一个资源组的时候使用  

26 CEGUI::XercesParser::setSchemaDefaultResourceGroup("schemas");  

 

总结:

 

目前,你对DefaultResourceProvider 这个类有了大致的了解了。

知道了怎么创建资源组并为他们制定路径。 还学会为CEGUI的使用的每个资源类型设置默认资源组。

cegui 3

数据文件的装载和初始化

目前已经学习了CEGUI的渲染入门 和 资源管理入门,下一步要学习装载数据文件了.只有装载了数据文件,

CEGUI才会产生想要的输出..

 

数据文件概述

CEGUI使用多种类型的数据文件.

xml xsd? 都是xml

CEGUI使用的大多说文件都是xml格式的,除了那些特殊的图片以及可以装载的模块文件(DLL),说到这里

大家可能会想到那个特殊的.xsd文件尽管现在CEGUI的默认xml解析库是Expat XML解析库,可是CEGUI

以前一直使用的是Xerces-C++库作为她的xml解析库.Crazy Eddies 本人也更喜欢这个库,这个库的优势

是它提供模式验证(schema validation).通过模式验证,我们可以在解析期间检测输入文件是否包含需要的

数据以及数据是否正确的设置.系统需要一个额外的文件才可以做到这个.他们就是以.xsd作为后缀的模式文件

CEGUI的模式文件存放在cgui_mk2/XMLRefSchema/目录下.目前,对于.xsd文件,你只需知道,当把Xerce-C++

用作xml解析器的时候,必须让ResourceProvider系统可以找到他们.要做到这一点很简单那,只需要设置一个资源组,

把此资源组关联到一个包含.xsd文件的文件夹,并把这个资源组设置为CEGUI::XeracesParser装载.xsd文件是

所使用的资源组.

 

数据文件

为了清晰的说明数据文件代表什么类型的资源.数据文件并不是都是以.xml作为后缀,而是以更有意义的后缀名.比如

Imageset的后缀名是.imageset,Font的后缀.font等等

 

Imageset

 

为了效率,通常我们把材质文件等图片组合成到一个大的材质图片,在使用的时候在上面截取

各个小的材质.而这个Imageset就是保存各个小材质在整个源图片的区域信息的.源图片也在

里面指定了,每个区域都有一个独一无二的名字,当提到他名字的时候,系统就把他当作图片处理.

所以 ,可以说Imageset定义了一系列图片,通过修改Imageset中的源图片名,各个区域的位置

和大小,就可以轻松的改变所绘制的GUI的外观了.

 

Font

  显然是定义了CEGUI使用的字体所能定义的字体类型有2

FreeType Font

 

这是一种基于true-type (.ttf)的字体文件.CEGUI 0.5.0开始,.font文件中用Type ="FreeType"

指定这种字体类型,在更早的版本中,"Dynamic"指定.

Pixmap  Font

位图字体这种字体基于一个定义了文字图片的Imageset.CEGUI0.5.0 开始,.font文件中

使用Type =" Pixmap"指定该字体,在更早的版本中,使用"Static"指定.

Scheme

该文件是把其他数据文件联系到一起的主要手段,同时他也是装载和定义各种控件最方便的方法.

 

一个Scheme文件可以把下面的一个或者多个文件.

(Scheme文件被载入的时候,他所包含的文件也被载入并且初始化)

Imageset

Font

WindowSet

WindowRendererSet

WindowAlias

FalagardMapping

 

     WindowSet 主要用来指明一个可载入的模块 .dll .so.并列出他所使用的控件中你想注册的

控件的名字.如果没有列出任何空间名,那么模块中的所有控件都将被注册.

   WindowAlias

    提供通过别名可以指定一个窗口/空间类型的方法.用此方法,也可以使用另一个控件类型来"替代"

  一个已经注册的空间类型,这样就达到了隐藏已注册控件的效果.

WindowRendererSet

指明一个可载入模块(.dll等)的名字,并列出它所使用的窗口渲染器(window renderer)中你想注册的窗口渲染器的名字。如果没有列出任何窗口渲染器名,那么模块中所有的窗口渲染器都将被注册。窗口渲染器是一个可以控制基本窗口类型渲染的东西。所有的窗口渲染器都是利用'Falagard'蒙皮系统进行渲染的(尽管这不是非常必须的)。

FalagardMapping

 用来创建一个可用的WindowType类型,包含3个部分.

 TargetType --指定基类(具有相关的功能);Renderer----指定窗口渲染器(可以控制渲染指定的TargetType

 LookNFeel --指定要使用的皮肤(一般这个它们是通过XML格式的looknfeel文件指定)

Layout

文件用xml格式描述了一个窗口的布局每个镶嵌的"Window"元素定义一个想要创建的窗口或者

控件,"Property"元素为每个定义的窗口设置相关属性.

Config

CEGUI文件支持使用配置文i这个文件允许你定义一些默认参数,比如默认载入的Scheme,

默认载入的Layout文件.初始化和终止脚本,以及其他没有提到的东西.

 

载入基本的文件

 

 为了成功的显示CEGUI,你需要载入一些文件,至少是下面这些

Imageset Font Scheme

 

值得高兴的是,Scehme文件可以自动的载入其他的2个文件,基于教程的目的,我们将载入

一个Scheme文件和一个font文件 ------Scheme文件会自动帮我们载入一个Imageset文件.

 

code:

//load Scheme ,他自动load TaharezLook imageset

 

CEGUI::SchemeManger::GetSingleton().loadScheme("TaharezLook.scheme");

载入font文件 .第一个被载入的字体文件将自动成为默认字体.

 

if(!CEGUI::FontManger::GetSingleton().isFontPresent("Commonwealth-10") )

   CEGUI::FontManger::GetSingleton().createFont("Commonwealth-10");

 

 

上面的代码假设资源组以及默认资源组都按照资源管理入门中描述的方法设置好了.

 

简单的默认设置

 

最后需要设置一些默认值,这么做是为了确保系统总是用可用的字体和鼠标指针,以防止某个窗口

或者控件没有特别设置他自己的字体和鼠标指针.

 实际上 我们不需要指定一个默认字体,因为FontManager会自动的设置第一个被载入的字体作为

默认字体,如果他不是你想要的默认字体.你可设置为其他的设置的代码如下:

 

System::getSingleton().setDefaultFont("Commonwealth-10");

 

另一个你需要设置默认值的是鼠标指针。这么做是为了确保当鼠标位于那些没有设置指针的控件上时不至于消失。设置默认鼠标指针的代码如下(本示例使用由上面的scheme载入的TaharezLook imageset):

System::getSingleton().setDefaultMouseCursor(   

                                "TaharezLook""MouseArrow" );  

 

 

如果你打算用工具提示,则需要指明你想用的基于ToolTip的控件类型名。尽管通常情况下是不需要这么做的,这也超出了这个基础教程的范围,其实可以在每个窗口基础上设置它。设置工具提示窗口类型代码如下:

System::getSingleton().setDefaultToolTip( "TaharezLook/Tooltip" ); 

 

总结


目前,我们已经讨论了关于CEGUI所用的数据文件最基础的东西:它们是什么,它们是怎样被载入的,以及最少要做的初始化工作。以后将要对每种数据文件类型做深入的讨论,发掘关于它们更高级的用法。

cegui 4

创建CEGUI窗口入门         讲述如何创建一个简单的CEGUI窗口.

所有的控件都是窗口

这是最重要的概念。所有的控件类都是从Window这个基类派生出来的,所以,在此教程中,每当我提到一个窗口的时候,它可以是一个按钮也可以是一个滚动条控件。

很多的设置都会被继承下去

CEGUI中,窗口的很多的设置和属性都会按窗口父子等级向下传递。比如:如果你将一个窗口的alpha透明度设置为0.5,那么,默认情况下,所有附属于那个窗口的子窗口/组件都将受到影响。同时要注意:子窗口的实际设置并没有改变 ---- 最终的属性值通常是由最顶层到当前窗口所有属性值组合而成的。好多东西都有这个规律,比如窗口的销毁:默认情况下,一个窗口在销毁的时候将销毁它的所有子窗口。这一机制最大的优点是:通过改变根窗口的alpha透明度、显示/隐藏、激活/禁用状态等可以很轻易的控制整个GUI;通过简单的销毁根窗口就可以轻松的清空整个GUI。当个别窗口需要更精确的控制或有更好的管理技术用的时候,可以更改这一默认继承机制。

创建窗口

有两种方法来创建窗口:通过C++硬编码实现和通过XML布局文件实现。下面将讨论这两种方法。

CEGUI的所有窗口都是被WindowManager这一对象创建的。你可以通过getSingleton函数获得这个对象,代码如下:

using namespace CEGUI;  

WindowManager& wmgr = WindowManager::getSingleton();  

一般情况,你将用DefaultWindow(或者它的老名字:DefaultGUISheet)作为GUI的“root”窗口。这点并不是必须的,但它是使用CEGUI很好的习惯,而且可以帮助简化布局。下一步,我们将创建一个DefaultWindow作为GUI的根窗口(这里sheetroot的意思)。

Window* myRoot = wmgr.createWindow( "DefaultWindow""root" );  

System::getSingleton().setGUISheet( myRoot );  

indowManager::createWindow函数有两个string类型的参数。第一个参数,本例中为“DefaultWindow”,告诉系统你要创建的窗口类型或类。通常,你可以使用的窗口类型是那些当你载入scheme文件的时候注册的窗口类。而有些像DefaultWindow这样的是全局类型,它们总是可用的。第二个参数,本例中为“root”,是一个将要赋予这个窗口的独一无二的名字。以后可以通过这个名字获取指向这个窗口的指针。注意:并不是一定要给你的根窗口命名为“root”,但这是通常的命名约定。

 System::setGUISheet函数用来指定一个窗口作为GUI的根窗口。这将替换掉当前的根窗口,但是要注意:之前的一系列窗口/控件并没有被销毁,只是从当前的显示链中摘除 ---- 通过使用setGUISheet函数,你可以轻松的在多个GUI中切换。

现在,你已经创建了第一个窗口并把它附着在GUI系统上,当系统画GUI的时候,它将把这个窗口作为GUI的根。但是,如果你编译运行这些代码,你依然不会看到任何东西。怎么了?你的程序没有任何问题,问题在:我们刚才创建的DefaultWindow是隐藏的!这就是为什么DefaultWindow那么适合做根窗口的原因:它仅仅被用作供其他的窗口和控件依附的空画布。那么,我们再加把劲吧。。。现在,我们将要创建一个框架窗口:这个窗口和你桌面上的窗口很类似,它有一个标题栏,也可以移动和改变大小。代码如下:

FrameWindow* fWnd = (FrameWindow*)wmgr.createWindow(   

                        "TaharezLook/FrameWindow""testWindow" );  

此代码创建了一个“TaharezLook/FrameWindow”窗口。整个系统都使用这种命名约定:窗口类型以组件组名做为前缀(假如你载入了WindowsLook scheme,你就可以创建一个“WindowsLook/FrameWindow”对象)。我们为这个新窗口命名为“testWindow”。需要注意的是:那个类型转换的使用,由于createWindow函数总是返回Window基类指针,尽管对于此例以及其他情况,返回这个Window指针就足够了,但是有时你需要访问子类的方法,所以使用CEGUI的时候示例中的类型转换是很常见的。

为了让系统能够用我们的新窗口做一些有用的事情,我们还需要做一些事情。
首先,我们必须把这个新窗口附着到我们上面指定的根窗口上,代码如下:

myRoot->addChildWindow( fWnd );  

现在,我们可以设置它的位置和大小。CEGUI使用一个统一的(unified坐标系,使得可以同时使用相对部分和绝对部分 ---- 这就是为什么你看到的每个坐标都由两个部分组成。

// 定位在其父窗口左上角开始的1/4位置  

fWnd->setPosition(UVector2( UDim( 0.25f, 0 ), UDim( 0.25f, 0 ) ) );  

10   

11 // 设置其大小为其父窗口的一半  

12 fWnd->setSize( UVector2( UDim( 0.5f, 0 ), UDim( 0.5f, 0 ) ) );  

最后,我们为这个框架窗口的标题栏设置一个标题:

13 fWnd->setText( "Hello World!" );  

XML布局

上面的方法看起来很不错,但是,它有一个很大的弊端:每次你想要调整GUI布局的时候,你就得去修改代码,然后重新编译。这样还不累死了?你真正想要做的是能够把布局保存在文件中,然后在代码中调用那个布局文件。

系统支持XML格式的布局文件,可以通过WindowManager::loadWindowLayout函数载入此文件。此函数为你创建所有的窗口并返回一个指向根窗口的指针。调用setGUISheet设置GUI的根窗口的时候用此指针再适合不过了。
所以,首先我们需要一个布局文件。下面这个XML文件所描述的窗口和我们上面用C++创建的窗口是一样的:

14 <guilayout>  

15   <window type="DefaultWindow" name="root">  

16     <window type="TaharezLook/FrameWindow" name="testWindow">  

17       <property name="UnifiedPosition" value="{{0.25,0},{0.25,0}}">  

18       <property name="UnifiedSize" value="{{0.5,0},{0.5,0}}">  

19       <property name="Text" value="Hello World!">  

20     </property></property></property></window>  

21   </window>  

22 </guilayout>  

Window元素的属性和WindowManager::createWindow函数的参数是一一对应的。参见上面讨论过的createWindow 函数
镶嵌的Window元素用来表明窗口的父子关系。注意:在一个布局文件中,你仅可以拥有一个级别的窗口,这也是通常情况下把DefaultWindow用作根窗口的另一个原因。

Property元素是用来设置当前窗口的属性的。每种窗口/控件类都有很多属性,而且每个类都从它的父类中继承所有的属性。可以在这里查看所有硬编码属性以及它们的格式。由于Falagard蒙皮('Falagard' skins)可以创建自己的属性,你所使用的窗口可能会有更多的属性 ---- 对于那些软编码属性,你需要参阅你所使用的蒙皮的相关文档(参看样品蒙皮TaharezLookWindowsLook)。

如果保存此布局文件为test.layout,你可以按如下方法加载并设置GUI根窗口:

23 using namespace CEGUI;  

24 Window* myRoot = WindowManager::getSingleton().loadWindowLayout(   

25                                             "test.layout" );  

26 System::getSingleton().setGUISheet( myRoot );  

编译运行后得到的结果和前面用C++写的程序是一样的。不过,现在你可以轻松修改、增强GUI布局而不用修改、重新编译代码啦。

总结

现在,你已经学习了:基本窗口的创建,多窗口的创建,以及如何修改窗口设置。你学习了如何通过C++硬编码实现和如何通过XML布局文件实现。有很多更先进的方法可以同时使用那两种方式,但这最好留给更高级的教程去讲解。

cegui 5

输入输出入门

 通过前面的教程,你现在可以渲染CEGUI,甚至是创建窗口,这时,你也许想给你的GUI添加用户交互了.

 

CEGUI输入处理简介

坏消息

 令人吃惊的是 ,CEGUI不会自动捕获任何的用户输入,由程序决定CEGUI需要处理哪些输入.这意味着:

 每当产生案件或者鼠标移动等消息时,你就得把它门传递给CEGUI,尽管这看起来很奇怪,这其实也给你更多

的控制权我们并没有把所有的输入绑定到特定的系统,你可以筛选传递CEGUI需要获得的输入.

 

传递你的输入

我们创建了一个接口用来向CEGUI传递输入信息.它由CEGUI::System类的一组成员函数组成--------

对于每种基本的输入类型都有一个函数与之对应

 

bool iniectMouseMove(float delta_x ,float delta_y);

bool injectMousePosition(float x_pos,float y_pos);

bool injectMouseLeaves(void);

bool injectMouseButtonDown(MouseButton button);

bool injectMouseButtonUp(MouseButton button);

bool injectKeyDown(unit key_code);

bool injectKeyUp(unit key_code);

bool injectChar(utf 32 code_point);

bool injectMouseWheelChange(float delta);

bool injectTimePulse(float timeElapsed);

是的,看起来很多你可能注意到有点重复了,比如"mouse move""mouse position"有点重复.

"key down""char"其实这么做是为了方便使用,对于鼠标,你可以传递相对位移(injectMouseMove)

或绝对坐标(injectMousePosition),具体选择哪种很大程度上取决于使用的输入库支持的类型对于按键,

你可以传递按下.抬起(injectKeyDown/injectKeyUp)以及字符(injectChar)消息.这样做有2个原因:第一,

不是所有的按键都产生字符消息(比如shift alt)等等;2,允许你自定义按键映射和自动重复按键(CEGUI目前不提供那些函数)

 

另外要说明的是函数的布尔返回值.他是用来告诉程序CEGUI是否处理了此消息.如果返回false,说明CEGUI没有处理此消息,

此时你的程序需要做另外一些处理.

那些函数是用来干什么的

下面,我们将要简要的讨论每个函数的作用,它需要的参数,它用输入做了什么。

bool injectMouseMove( float delta_x, float delta_y )  

此函数用来传递鼠标移动信息。参数delta_xdelta_y分别用来指明鼠标在X轴和Y轴上所移动的位移(有方向)。这将导致鼠标做相应的移动(具体移动的大小可以通过CEGUI::System::setMouseMoveScaling设置比例因子改变)。如果你用此函数,基本上就不需要使用injectMousePosition函数了。

bool injectMousePosition( float x_pos, float y_pos )  

此函数用来传递鼠标当前的绝对坐标。参数x_posy_pos用来指明鼠标的绝对坐标,坐标系的原点(0, 0)CEGUI窗口的左上角(所以,在窗口模式下,原点在窗口左上角而不是整个桌面的左上角)。这将把鼠标移动到新的位置。如果你用此函数,基本上就不需要使用injectMouseMove函数了。

bool injectMouseLeaves( void )  

此函数用来告诉系统鼠标已经离开程序窗口了。在窗口模式下,此方法很有用。

bool injectMouseButtonDown( MouseButton button )  

此函数用来告诉系统某个鼠标按键被按下了。参数button可以取如下所示的CEGUI::MouseButton枚举值:

enum MouseButton  

{  

   LeftButton,  

   RightButton,  

   MiddleButton,  

10    X1Button,  

11    X2Button,  

12    MouseButtonCount,  

13    NoButton  

14 };  

如果你的输入库和上面的不吻合,你必须自己转化。另外,要注意:NoButton的值不是0

15 bool injectMouseButtonUp( MouseButton button )  

此函数用来告诉系统某个鼠标按键被释放了。它的参数和injectMouseButtonDown是一样的。

 

16 bool injectKeyDown( uint key_code )  

此函数用来告诉系统某个按键被按下了。它的参数key_code是那个按键的扫描码(不是ASCII码或其他的字符编码)。所有的扫描码在CEGUI::Key::Scan枚举变量中有定义。微软的DirectInput库输出的扫描码和我们的是一样的,如果使用其他的库,你可能需要做一些转换。目前看来,并不需要传递所有的按键按下/释放消息,一般需要这些消息的是:backspacedelete,回车,shift以及方向箭头按键。

目前没有实现按键到字符的映射,也没有提供自动重复按键的函数(这些也许在以后会实现)。如果你需要自动重复按键功能,你可以使用输入库提供的函数或者自己编码实现。你肯定需要输入字符,请看下面的injectChar函数。

17 bool injectKeyUp( uint key_code )  

此函数用来告诉系统某个按键被释放了。和injectKeyDown的参数一样,key_code是按键的扫描码。

 

 

18 bool injectChar( utf32 code_point )  

此函数用来告诉系统一个字符按键被按下。在输入文本的时候,你需要用此函数。参数code_point是一个字符的Unicode UTF32编码(欲了解Unicode请参看此 Unicode 网站)。怎样获得这个值取决于你所使用的输入库。对于ASCII字符,你只需直接以ASCII码的形式传递给它即可,因为Unicode码和ASCII码的0127部分是相同的。对于其他字符,你使用的输入库应该会有相关的API(比如:通过微软的Windows消息泵可以获得字符的UTF32编码),具体怎么做不属于本教程的范围。

 

19 bool injectMouseWheelChange( float delta )  

此函数用来告诉系统鼠标滚轮的使用情况。正数表示向前滚动(远离用户方向),负数表示向后滚动(接近用户方向)。

20 bool injectTimePulse( float timeElapsed )  

此函数用来告诉系统相关时间信息。参数timeElapsed表示自上次调用此函数或运行CEGUI以来所经过的秒数。此函数越来越重要了,它被用来控制控件的消失,为工具提示、菜单以及鼠标按键的自动重复提供计时等。

总结

目前,我们学习了CEGUI获取输入的函数,以及那些函数的作用、参数等。

与前面几篇教程不同,此篇并未提供具体的示例代码。主要是想使教程适当的简短,以防因对特定的输入库编码而杂乱无章。将特定输入库的使用放在单独的文章中讲述更合理。

如果想看一些具体的例子,这里提供两个:

我们在wiki上有一些很牛的例子

    o Using CEGUI with SDL and OpenGL

    o Using CEGUI with Producer and OpenGL 

使用示例框架代码写成的例子被保存在cegui_mk2/Samples/common/src

    o Win32AppHelper.cpp - contains a windows message procedure injecting

         mouse and character inputs into CEGUI, and using DirectInput 

         for keyboard up and down messages.

    o CEGuiOgreBaseApplication.cpp - contains an example Ogre::FrameListener 

         class which takes inputs generated by the OIS Library and 

         injects these into CEGUI.

    o CEGuiOpenGLBaseApplication.cpp - registers callback functions with glut

         to process inputs, and also shows the use of a key 

         translation table. 

 

此外,使用Irrlicht的同学可以使用Irrlicht自带的event pusher ---- 每当一个输入产生的时候你可以使用Irrlicht RendererOnEvent函数处理它(你只需用injectTimePulse处理时间)

 

 

统一度量系统使用指南

注意:此教程仅适用于CEGUI >= 0.4.0

统一度量系统使得我们可以用相对部分和绝对部分共同来表示一个坐标或大小。这给窗口布局带来了极大的方便。比如:你可以用相对尺寸来表示高度,使用绝对大小表示宽度,或者混合使用。


统一度量系统共有三种形式

    * UDim      :  简单的一维

    * UVector2  :  由两个UDim组成的二维向量

    * URect     :  用四个UDim表示一个矩形,依次为:左,上,右,下

UDim

UDim是最简单的了

格式为 :{scale, offset}  

例如  :{1,0}  

父窗口的值乘以scale然后加上offset就是最后的结果(单位都是像素)。例如:假如上面的例子是窗口的UnifiedWidth属性值,我们将得到和其父窗口一样宽的窗口。
另一个例子:

{0.5, 25}

这将使得到的窗口宽度为其父窗口的一半加上25像素。
使用单一UDim作为其值的属性有:

    * UnifiedXPosition

    * UnifiedYPosition

    * UnifiedWidth

    * UnifiedHeight

UVector2

UVector2是用来表示位置和大小的。

格式为 :{{x-scale, x-offset}, {y-scale, y-offset}}  

例如  :{{1, 0}, {1, 0}}  

UVector2中包含的两个UDim很像。还是用例子说明吧:假如上面的例子代表窗口的UnifiedSize属性,我们将得到和它的父窗口一样大小的窗口。

{{1,0},{0,100}}  

上例将产生一个和其父窗口一样宽,但是高度固定为100像素的窗口。

使用Uvector2作为其值的属性有:

    * UnifiedPosition

    * UnifiedSize

    * UnifiedMinSize

    * UnifiedMaxSize

URect

最后的一种是URect。它有点特殊,它定义了左,上,右,下四个坐标,而不是大小或位置。由于参数很多,我将用ls代替left-scale,用to代替top-offset等等。

格式为 :{{ls,lo},{ts,to},{rs,ro},{bs,bo}}  

例如  :{{0,0},{0,0},{1,0},{1,0}}  

上述代码是DefaultWindow类型窗口的默认矩形。它将覆盖其父窗口的整个区域。只有一个属性使用URect值 ---- UnifiedAreaRect
我们定义矩形区域而不是其大小的做法是很聪明的。比如:假如我们想使一个窗口覆盖它的父窗口,但要为父窗口的四边分别留出10像素的大小,代码可以这样写:

{{0,10},{0,10},{1,-10},{1,-10}}  

此例可以看出,绝对部分可以取负数。

XML中的应用举例

<property name="UnifiedPosition" value="{{0.1,10},{1.0,-30}}">  

10 </property>  

X-position:父窗口宽度的10% + 10像素
Y-position:父窗口的高度 - 30像素

11 <property name="UnifiedSize" value="{{0.6,5},{0.3,20}}">  

12 </property>  

Width:父窗口宽度的60% + 5像素
Height:父窗口高度的30% + 20像素

13 <property name="UnifiedXPosition" value="{0.25,-5}">  

14 </property>  

X-position:父窗口宽度的25% - 5像素

15 <property name="UnifiedAreaRect" value="{{0.1,0},{0.1,0},{0.9,0},{0.9,0}}">  

16 </property>  

X-position:父窗口宽度的10%
Y-position:父窗口高度的10%
Width:父窗口宽度的80%
Height:父窗口高度的80%

cegui 7

CEGUI中使用Lua脚本入门

CEGUI所使用的脚本是基于Lua5.0.2 tolua++ 1.06pre2-1.

界面的编写很大一部分可以通过脚本来实现.我们可以修改脚本而不必重新编译整个程序,

这就为我们省去了好多时间去设计界面.

目前的Lua脚本模块仍然处于初期的开发阶段。它支持绝大多数的内核系统、基层的窗口类,不过,对于某些特殊的控件目前只能通过属性系统(properties system)来设置。

Ok,我们开始吧!你需要有一定的CEGUI基础,比如初始化、创建窗口等,当然,还要有一定的Lua基础。

初始化

Lua脚本模块囊括了所有的管理类(manager classes),所以可以通过Lua脚本对CEGUI进行简单的初始化.例如:

#include "CEGUILua.h"

CEGUI::YourRendererOfChoice* renderer = new YourRendererOfChoice;  

CEGUI::LuaScriptModule* script_module = new CEGUI::LuaScriptModule();  

  

// 第二个参数设置xml解析器,0代表默认解析器  

new CEGUI::System( renderer, 0, script_module );  

现在CEGUI::System已经被创建,脚本模块也被指定了。此时,LuaScriptModule的构造函数自动为我们创建了一个lua_State。你也可以传递一个lua_State*LuaScriptModule的构造函数中来使用自己的lua_State
如果你在初始化脚本中用到自定义函数,你就需要这么做。代码如下:

...  

lua_State* s = your_lua_state;  

CEGUI::LuaScriptModule* script_module = new CEGUI::LuaScriptModule(s);  

...  

初始化/退出 脚本

CEGUI支持一个配置文件。它的文件名是CEGUI::System的构造函数的一个可选参数。默认为cegui.config

通过设置此配置文件,你可以控制在系统创建和销毁的时候是否执行一个脚本。配置文件的内容类似这样:

10 <?xml version="1.0" ?>  

11 <CEGUIConfig  

12     InitScript="../datafiles/scripts/init_script.lua"  

13     TerminateScript="../datafiles/scripts/exit_script.lua"  

14 />  

init_script.lua是在系统初始化时候将要被执行的Lua脚本文件。内容可以是这样:

15 -- 获取CEGUI singletons  

16 local logger = CEGUI.Logger:getSingleton()  

17 logger:logEvent( ">>> Init script says hello" )  

18 --logger:setLoggingLevel( CEGUI.Informative )  

19   

20 -- 为我们要使用的singletons创建相应的局部变量(非必须)  

21 local system    = CEGUI.System:getSingleton()  

22 local fontman   = CEGUI.FontManager:getSingleton()  

23 local schememan = CEGUI.SchemeManager:getSingleton()  

24   

25 -- 载入schemes  

26 schememan:loadScheme( "../datafiles/schemes/TaharezLook.scheme" )  

27 schememan:loadScheme( "../datafiles/schemes/WindowsLook.scheme" )  

28   

29 -- 载入默认字体  

30 local font = fontman:createFont( "../datafiles/fonts/Commonwealth-10.font" )  

31   

32 -- 设置默认鼠标光标  

33 system:setDefaultMouseCursor( "TaharezLook","MouseArrow" )  

34   

35 logger:logEvent( "<<< Init script says goodbye" )  

并不一定要同时提供初始化和退出脚本,但是,假如你在初始化脚本中申请了全局使用的存储空间,那你就要在退出脚本中释放它(或者在其它合适的地方)。


现在,你学习了怎样用Lua脚本初始化CEGUI。后续教程将更深入的讲解。

cegui 8 Lua消息处理入门 收藏

 

cegui 8

Lua消息处理入门

GUI的消息处理从代码中分离出来,并交给lua脚本处理,这样可以给你的界面带来很大的灵活性.GUI

的相关地东西可以在测试期间很轻松的修改.

Lua中的负责处理消息的东西其实只不过是普普通通的只带一个参数的Lua函数而已.若想用它作事件

处理函数,你必须先在系统中注册它.所以除非你在初始化脚本中载入他们,你必须在处理相应事件之前

载入相关脚本文件.

 载入脚本文件

2种载入脚本文件的方法:使用c++代码或者在初始化脚本中使用Lua代码,由于Lua函数和相应的c++

函数一一对应,2种方法看起来很像.

CEGUI::System::executeScriptFile(const CEGUI::String &filename,const CEGUI::String& resourceGroup="");

显然这个函数有两个参数:文件名和资源组。大多数情况下,可以不用管最后一个参数。
通过CEGUI指定的Lua脚本模块调用此函数就可以执行指定的Lua脚本文件。这意味着:你的程序可以访问脚本中文件定义的函数等所有东西了,当然,被定义为局部范围的除外。

例如,用C++可以这么写:

CEGUI::System::getSingleton().executeScriptFile("../datafiles/scripts/guiscript.lua");  

如果有错误产生,它会抛出一个异常。
Lua可以这么写:

CEGUI.System:getSingleton():executeScriptFile("../datafiles/scripts/guiscript.lua")  

脚本文件中的全局代码也将被执行,所以要注意对每次执行进行必要的处理(使用一个计数器fx)。

注册事件到Lua函数

既然我们已经载入了脚本文件,下一步就可以绑定事件到脚本处理函数上了.

绑定Lua函数的函数与绑定C++函数的函数名称不同。

Event::Connection subscribeScriptedEvent(const String& name,   

                                const String& subscriber_name);  

参数name是你要绑定的事件。参数subscriber_name为处理此事件的Lua函数的函数名。

 

 

调用完此函数后,指定的Lua函数就将成为那个事件的处理函数。而且它运行起来和C++版本的函数几乎一模一样(当然,不同的是:它是Lua脚本)。

如下是绑定PushButton单击事件到一个Lua函数上的代码片段:

CEGUI::PushButton* pb = (CEGUI::PushButton*)CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button","lua_powered_button");  

pb->setSize(CEGUI::Size(0.1f,0.1f));  

pb->setPosition(CEGUI::Point(0.1f,0.1f));  

pb->subscribeScriptedEvent("Clicked","luabtn_clicked");  

CEGUI::System::getSingleton().getGUISheet()->addChildWindow(pb); 

 

这段代码将创建一个简单的TaharezLook按钮,把它的Clicked事件绑定到Lua函数luabtn_clicked中,然后把它添加到当前的GUI

现在,我们看一下那个Lua事件处理函数:

10 function luabtn_clicked(e)  

11   local we = CEGUI.toWindowEventArgs(e)  

12   we.window:setText("handled from Lua");  

13 end  

它使得当按钮被按下时,它的文本会变成handled from Lua
我们使用了一个公用的函数:

14 CEGUI.toWindowEventArgs(e)  

从它的名字可以看出:它把EventArgs参数转换为WindowEventArgs类型。其他的EventArgs类型也有类似的转换函数。 

Layout文件中注册Lua事件处理函数
Layout文件中绑定用Lua写的事件处理函数是很简单的。看下这个例子吧:

15 <?xml version="1.0"?>  

16 <GUILayout>  

17 <Window Type="TaharezLook/Button" Name="lua_powered_button">  

18 <Property Name="Width" Value="0.1" />  

19 <Property Name="Height" Value="0.1" />  

20 <Property Name="XPosition" Value="0.1" />  

21 <Property Name="YPosition" Value="0.1" />  

22 <Event Name="Clicked" Function="luabtn_clicked" />  

23 </Window>  

24 </GUILayout>  

这个简单的layout文件所做的事和上面的C++代码是一样的。


cegui 9 编写CEGUI脚本入门

本教程所列的代码是Lua脚本,它用到了CEGUI0.4版本起所绑定的CEGUILua模块。这些代码片段可能没有多少实际用途,但它们足以展示CEGUILua结合起来使用的可能性。

更改记录级别

 

 local logger = CEGUI.Logger:getSingleton()--获取logger

 local lvl = logger.getLoggingLevel()  --获取记录级别

 

 if    lvl < CEGUI.Insane then

       logger:setLoggingLevel(lvl +1)

 end

此代码:每次增加记录级别一个等级直到到达Insane级别.

载入一个Scheme

CEGUI.SchemeManager:getSingleton():loadScheme("../datafiles/scheme/TaharezLook.scheme")

此代码:载入了TaharezLook.scheme.

 

简单的界面

-- 创建GUI sheet  

local sheet = CEGUI.WindowManager:getSingleton():createWindow(  

                                    "DefaultGUISheet","root");  

CEGUI.System:getSingleton():setGUISheet(sheet) -- 然后,把它附着到系统中  

-- 创建一个FrameWindow  

local fw = CEGUI.WindowManager:getSingleton():createWindow(  

                            "TaharezLook/FrameWindow","framewnd");  

-- 把它附着到sheet上  

sheet:addChildWindow(fw)  

10   

11 -- 设置它的大小和位置  

12 local sz = CEGUI.Size(0.5,0.5)  

13 local pos = CEGUI.Point(0.2,0.1)  

14 fw:setSize(sz)  

15 fw:setPosition(pos)  

16 -- 禁止用户改变大小  

17 fw:setProperty("SizingEnabled","False")  

18   

19 -- 使关闭按钮生效  

20 fw:subscribeEvent("CloseClicked","fwCloseClicked")  

21   

22 -- CloseClicked事件的处理函数  

23 function fwCloseClicked(eventArgs)  

24     local we = CEGUI.toWindowEventArgs(eventArgs)  

25     CEGUI.WindowManager:getSingleton():destroyWindow(we.window) -- 销毁frame窗口  

26 end  

此代码:创建一个GUISheet,把它附着到System上。然后,创建一个FrameWindow,设置它的大小和位置,禁用更改大小属性并为CloseClicked事件绑定了事件处理函数。

类型转换的另一种方法

27 -- CloseClicked事件的处理函数  

28 function fwCloseClicked(eventArgs)  

29     local we = tolua.cast(eventArgs,"CEGUI::WindowEventArgs")  

30     CEGUI.WindowManager:getSingleton():destroyWindow(we.window) -- 销毁frame窗口  

31 end  

此代码:展示了把EventArgs对象转换成WindowEventArgs类型的另一种方法。

载入一个layout

32 local w = CEGUI.WindowManager:getSingleton():loadWindowLayout(  

33                         "../datafiles/layouts/test.layout")  

34 CEGUI.System:getSingleton():getGUISheet():addChildWindow(w)  

此代码:载入了一个layout并把返回的窗口添加到当前的GUISheet中。


弹出菜单

35 -- 我们将多次使用WindowManager对象  

36 local wmgr = CEGUI.WindowManager:getSingleton()  

37   

38 -- 设置菜单  

39 local bar = wmgr:createWindow("WindowsLook/Menubar","the_menu_bar")  

40 bar:setSize(CEGUI.Size(1,0.1))  

41 CEGUI.System:getSingleton():getGUISheet():addChildWindow(bar)  

42   

43 -- 添加一个菜单项  

44 local item = wmgr:createWindow("WindowsLook/MenubarItem","the_menu_bar_item")  

45 item:setText("Bar item")  

46 bar:addChildWindow(item)  

47   

48 -- 添加一个弹出菜单到菜单项中  

49 local pop = wmgr:createWindow("WindowsLook/PopupMenu","the_popup_menu")  

50 item:addChildWindow(pop)  

51   

52 -- 添加一些菜单项到弹出菜单中  

53 item = wmgr:createWindow("WindowsLook/PopupMenuItem","the_popup_menu_item_1")  

54 item:setText("Popup item 1")  

55 pop:addChildWindow(item)  

56   

57 item = wmgr:createWindow("WindowsLook/PopupMenuItem","the_popup_menu_item_2")  

58 item:setText("Popup item 2")  

59 pop:addChildWindow(item)  

此代码:创建了一个简单的菜单,并添加了一个包含两个子菜单的弹出式菜单到它上面。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CEGUI(Crazy Eddie’s GUI http://www.cegui.org.uk)是一个自由免费的GUI库,基于LGPL协议,使用C++实现,完全面向对象设计。CEGUI开发者的目的是希望能够让游戏开发人员从繁琐的GUI实现细节中抽身出来,以便有更多的开发时间可以放在游戏性上。 CEGUI的渲染需要3D图形API的支持,如OpenGL或Direct3D。另外,使用更高级的图形库也是可以的,比如OGRE、Irrlicht和RenderWare等,关键需求可以简化为二点: 纹理(Texture)的支持直接写屏(RHW的顶点格式、正交投影、或者使用shader实现) 本书截止日时,CEGUI的最新版本是0.6.0(本书的讨论也是基于此版本),本书光盘提供了SDK和全部源码的下载。 除此之外,CEGUI还同步提供了官方界面编辑器LayoutEditor和ImageSet编辑器,以方便UI和图像集的制作。作为界面编辑器,它需要系统级界面以提供编辑器操作,0.3.0版是基于MFC实现的;而在0.4.0版本以后,改为基于wxWidgets(跨平台的本地UI框架,这里的UI指Window操作系统底层,如:Windows、Unix和Mac,详见http://www.wxwidgets.org)实现。 目前将CEGUI作为游戏界面库开发的游戏已经有好多种,国内的天龙八部,巨人等游戏就是很好的例子。 CEGUI的功能是非常强大的,而且使用也非常的灵活,可以和脚本配合。可以通过配置文件自定义窗口外观。通过布局文件实现窗口布局等等特性,使得游戏的界面开发更加方便。
CEGUI的详细笔记和文档,学习游戏编程的朋友一定要看看 第1章 CEGUI的简介 - 5 - 1.1CEGUI历史和本书使用的版本 - 5 - 1.2 CEGUI的编译和例子介绍 - 5 - 1.2.1CEGUI源代码的简介 - 5 - 1.2.2CEGUI源代码编译 - 7 - 1.3 CEGUI官方编辑器的介绍 - 10 - 1.3.1 CEGUI布局编辑器 - 10 - 1.3.2 图像集编辑器 - 12 - 1.4 CEGUI总体架构 - 13 - 第2章 CEGUI事件系统和属性系统 - 15 - 2.1 CEGUI的事件系统 - 16 - 2.2 CEGUI的属性系统 - 29 - 2.3 属性事件与布局文件 - 32 - 第3章 CEGUI基类的实现 - 39 - 3.1 窗口设计原理 - 39 - 3.2 Window类 - 40 - 3.2.1 Window类的继承关系以及与其相关的函数。 - 40 - 3.2.2 窗口的组织结构 - 41 - 3.2.3 窗口位置和大小 - 45 - 3.2.4窗口渲染 - 51 - 3.2.5 事件响应与处理 - 59 - 3.2.6 窗口状态 - 60 - 3.2.7 窗口与输入系统 - 61 - 3.2.8 窗口的其他功能 - 63 - 3.3 窗口类厂和类厂管理 - 64 - 3.3.1 窗口的类厂和类厂管理 - 64 - 3.3.2 渲染窗口的类厂和类厂管理 - 72 - 3.4 窗口管理系统 - 76 - 第4章 CEGUI核心控制体系 - 80 - 4.1 系统控制 - 80 - 4.1.1 系统变量 - 80 - 4.1.2 初始化和退出流程 - 82 - 4.1.3 输入系统的事件派遣流程 - 87 - 4.2 资源管理 - 96 - 4.2.1资源管理模式 - 96 - 4.2.2 图像集 - 102 - 4.3 系统接口 - 106 - 4.3.1 脚本接口 - 107 - 4.3.2 XML相关接口 - 107 - 4.3.3 图像解码接口 - 108 - 4.3.4 资源提供接口 - 109 - 4.4渲染机制 - 109 - 第5章 CEGUI应用程序框架 - 118 - 5.1 OpenGL程序框架 - 118 - 5.1.1 WIN32应用程序框架 - 118 - 5.1.2 OpenGL应用程序框架 - 122 - 5.1.3 CEGUI应用程序框架 - 124 - 5.2 CEGUI例子程序 - 125 - 5.2.1 加载资源和创建窗口 - 126 - 5.2.2 窗口的逻辑处理 - 127 - 第6章 外观系统 - 131 - .........

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值