
在 ClanLib 教程的这個部分,我们将学习ClanDisplay 是如何处理图像的。研究范围将包括使用基本操作(primitives)、字体和动画精灵(sprites)来进行绘图。还會说说ClanLib 资源系统。
ClanDisplay是一個用来在屏幕上处理图像的模块。在这個模块中有很多功能,例如字体、图片、精灵、帧缓冲、混合、基本操作、渲染器程序(shaders)、材质,另外还有输入和碰撞检测。ClanDisplay只是一個抽象的概念,妳需要使用若干個显示目标以及渲染器(renderers)。ClanLib支持OpenGL 2/3、OpenGL1 和一個多核的软件渲染器。
在这個教程中的示例里,我们會使用ClanGL 目标,它需要一個OpenGL 2 或更高级的显卡才能正常工作。如果妳的硬件配置不满足条件的话,也只需要改动几行代码来换用另一個兼容性更好的显示目标。在官方的ClanLib 示例里,还能找到一個例子,它让妳能够支持所有的显示目标,并且让用户来选择自己想要使用哪一個。
要将显示模块初始化,就包含display.h,并且实例化一個配置对象。
#include <ClanLib/display.h>
...
CL_SetupDisplay setup_display ;
选择显示目标的过程也是类似的。在我们的例子中,我们使用OpenGL 目标:
#include <ClanLib/gl.h>
...
CL_SetupGL setup_opengl ;
如果妳想使用OpenGL 1 目标或者软件渲染器目标的话,就像下面这样:
#include <ClanLib/gl1.h>
...
CL_SetupGL1 setup_opengl1 ;
#include <ClanLib/swrender.h>
...
CL_SetupSWRender setup_swrender ;
要显示东西,我们首先需要一個窗口。在ClanLib 中,它叫做CL_DisplayWindow。妳想创建多少就可以创建多少,但是在游戏里通常只创建一個。
以下代码會创建一個640×480 的窗口,标题为Hello ClanLib!
CL_DisplayWindow window ( "Hello ClanLib!", 640, 480 );
上面显示的CL_DisplayWindow 會为创建的窗口假设一系列的默认参数,如果妳不想采用这些默认参数,那么就需要先创建一個显示窗口描述信息。在下面的例子里,我们先描述窗口的一些属性,再创建窗口:
CL_DisplayWindowDescription description ;
description.set_size ( CL_Size ( 640,480 ), true );
description.set_title ( "Hello ClanLib!" );
description.set_allow_resize ( false );
CL_DisplayWindow window ( description );
如果妳想了解更多细节,就读一下 CL_DisplayWindowDescription的参考文档。
操作系统的窗口系统會通过一個消息队列来向程序发送消息,以这种方式与程序和它的窗口通信。为咯处理这些消息,ClanLib 程序需要调用CL_KeepAlive::process()。这個函数會读取并处理那些消息、更新输入上下文、在发生一些事件时发送信号,例如窗口被改变大小。
一個ClanLib 程序的主循环大概是这样的:
CL_SetupCore setup_core ;
CL_SetupDisplay setup_display ;
CL_SetupGL setup_gl ;
CL_DisplayWindow window ( "Hello ClanLib!", 640, 480 );
CL_GraphicContext gc = window.get_gc ();
CL_InputContext ic = window.get_ic ();
while ( ic.get_keyboard ().get_keycode ( CL_KEY_ESCAPE ) == false )
{
draw_gfx ( gc );
handle_input ( ic );
window.flip ();
CL_KeepAlive :: process ();
CL_System :: sleep ( 10 );
}
这里我们还调用CL_System::sleep,以避免导致CPU 过忙。
现在让我们把已经提到的代码组合到一個例子里吧,它會初始化一個显示界面、一個窗口、一個主循环,并且會绘制一些基本图案;一個典型的程序猿式艺术例子!注意,我们将它分割成两个类 – 一個静态的Program 类,它将ClanLib 初始化,并且处理异常。如果发生咯异常,那么我们就创建一個终端窗口来显示错误信息以及完整的调用栈信息。
例子中剩下的部分是一個独立的非静态类,叫做PrimitivesExample,我们在其中用基本绘图操作绘制一些东西。ClanLib有一個叫做CL_Draw的静态类,它是一個便利类,可用来绘制点、线、框、填充框、圆和渐变。
注意,我们使用ClanLib 的信号/信号槽系统来监听窗口关闭事件,以便在用户点击窗口的关闭按钮时退出程序。信号/信号槽系统是一种强大的用来向 ClanLib 里挂入回调函数的方式。它的语法也许看起来有点2(weird),但是不久妳就會习惯的。在使用信号时,有一点非常重要 – 确保从连接函数中返回的CL_Slot 对象的存活。如果妳忘记咯这一点,那么,那個信号槽會立即断开,于是妳的回调函数从此再也不會被调用咯!
#include <ClanLib/core.h>
#include <ClanLib/display.h>
#include <ClanLib/gl.h>
#include <ClanLib/application.h>
class PrimitivesExample
{
private :
bool quit ;
public :
void run ()
{
quit = false ;
CL_DisplayWindow window ( "Sunset", 640, 480 );
CL_Slot slot_quit = window.sig_window_close ().connect ( this, & PrimitivesExample :: on_window_close );
CL_GraphicContext gc = window.get_gc ();
CL_InputDevice keyboard = window.get_ic ().get_keyboard ();
while (! quit )
{
if ( keyboard.get_keycode ( CL_KEY_ESCAPE ) == true )
quit = true ;
draw_sunset ( gc );
window.flip ();
CL_KeepAlive :: process ();
CL_System :: sleep ( 10 );
}
}
void draw_sunset ( CL_GraphicContext & gc )
{
CL_Colorf red ( 155/255.0f, 60/255.0f, 68/255.0f );
CL_Colorf yellow ( 255/255.0f, 234/255.0f, 117/255.0f );
CL_Colorf blue ( 13/255.0f, 75/255.0f, 74/255.0f );
CL_Colorf lightblue ( 16/255.0f, 91/255.0f, 90/255.0f );
// 绘制落日场景的顶部
CL_Gradient gradient1 ( CL_Colorf :: black, red );
CL_Draw :: gradient_fill ( gc, CL_Rectf ( 0,0,640,160 ), gradient1 );
// 绘制落日场景的第二部分
CL_Gradient gradient2 ( red, yellow );
CL_Draw :: gradient_fill ( gc, CL_Rectf ( 0,160,640,240 ), gradient2 );
// 绘制太阳
CL_Draw :: circle ( gc, CL_Pointf ( 320, 240 ), 15, CL_Colorf :: white );
// 绘制大地
CL_Draw :: fill ( gc, CL_Rectf ( 0, 240, 640, 470 ), blue );
// 绘制距离线
for ( int y = 241, ydelta = 2 ; y < 480 ; y += ydelta, ydelta += ydelta )
{
CL_Draw :: line ( gc, 0, y, 640, y, lightblue );
}
}
void on_window_close ()
{
quit = true ;
}
} ;
class Program
{
public :
static int main ( const std :: vector < CL_String > & args )
{
CL_SetupCore setup_core ;
CL_SetupDisplay setup_display ;
CL_SetupGL setup_gl ;
try
{
PrimitivesExample example ;
example.run ();
}
catch ( CL_Exception & exception )
{
// 如果出错,则创建一個终端窗口,输出文字
CL_ConsoleWindow console ( "Console", 80, 160 );
CL_Console :: write_line ( "Error: " + exception.get_message_and_stack_trace ());
console.display_close_message ();
return - 1 ;
}
return 0 ;
}
} ;
CL_ClanApplication app (& Program :: main );
ClanLib拥有很多字体来源 – 系统字体、FreeType字体、矢量字体和精灵(Sprite)字体。它们各有优劣,但最简单的还是使用系统字体。妳使用CL_FontDescription来创建一個字体描述对象,其中描述的是妳想创建的字体,再用它来将那个字体类型实例化。
这是個简单的例子,初始化并且绘制一些文字:
// 初始化一個系统字体
CL_FontDescription system_font_desc ;
system_font_desc.set_typeface_name ( "tahoma" );
CL_Font_System system_font ( gc, system_font_desc );
// 初始化一個freetype 字体
CL_FontDescription freetype_font_desc ;
freetype_font_desc.set_typeface_name ( "myfont.ttf" );
CL_Font_Freetype freetype_font ( gc, freetype_font_desc );
...
// 用这些字体来绘制一些文字
system_font.draw_text ( gc, 100, 100, "Hello World using Tahoma" );
freetype_font.draw_text ( gc, 100, 200, "Hello World using a freetype font" );
注意,妳可通过ClanLib 中那个叫Font的示例来体验不同的字体源。
要在ClanLib中载入资源的话,常规做法是使用资源文件。这是一個XML 文件,以其专有语法来描述各种各样的资源,例如图片、精灵、声音和自定义的数据。
一个资源文件例子:
< ?xml version= "1.0" encoding= "iso-8859-1" ? >
< resources >
< sprite name= "WalkingMan" base_angle= "180" >
< image file= "Gfx/walkingman1.png" / >
< image file= "Gfx/walkingman2.png" / >
< image file= "Gfx/walkingman3.png" / >
< translation origin= "center" / >
< rotation origin= "center" / >
< animation pingpong= "no" speed= "100" / >
< /sprite >
< section name= "sounds" >
< sample name= "MissileHit" file= "Sound/missilehit.wav" / >
< /section >
< section name= "stuff" >
< my-type name= "my-resource1" >
< text > Hello World! < /text >
< /my-type >
< /section >
< /resources >
要载入资源,妳需要先搞到一個CL_ResourceManager。ClanLib 中的每种资源都有对应的构造函数,可用来从一個资源管理器中初始化自己。
CL_ResourceManager resources ( "resources.xml" );
CL_SoundBuffer sample ("sounds/MissileHit”, & resources );
CL_Sprite sprite ( gc, “WalkingMan”, & resources );
妳还可以定义非标准的标签,并且用 XML DOM API 来载入它们:
CL_String load_my_type ( const CL_String & res_id, CL_ResourceManager & resources )
{
CL_Resource resource = resources.get_resource ( res_id );
if ( resource.get_type () != "my-type" )
throw CL_Exception ( "Resource not of expected type!" );
CL_DomElement text_element = resource.get_element ().named_item ( "text" ).to_element ();
return text_element.get_text ();
}
CL_ResourceManager resources ( "resources.xml" );
CL_Console :: write_line ( load_my_type ( "stuff/my-resource1", resources );
可能大部分人都已经知道咯,一個精灵其实就是一组2D 图片(叫做幀),按顺序显示,每幀之前都插入一定的延迟。精灵被用于很多游戏对象中,比如移动人物、飞船、椅子、机器启动(powerups)、导弹、动画鼠标……。
CL_Sprite为妳实现这些东西,而且非常简单但强大。最简单的精灵就是一组幀,它们一個接一個地显示在屏幕上的某個角落。如果妳想使用更高级的特性的话,那麽可以这样:精灵可以旋转,可以变成半透明的,可以调整单个幀的动画速度,设置幀的位移,设置绘制及旋转时的对齐方式,可以将它们倒放,可以乒乓式播放,还可以单幀播放……
现在让我们用这些新学到的技术来为日落程序润色吧。在互联网上做咯一下快速搜索之后,我找到咯一些精灵,可以用在这里。这是一個4 幀的动画;它被放置在一個精灵單中。每一幀的尺寸是159×91像素。
在资源中配置精灵是非常灵活的,精灵单是以<grid>标签来表示的。注意,在ClanLib 中还有一個叫做材质打包器的工具,它能将一堆图片打包到精灵单中并且自动生成资源文件。另外,看看Clanlib 文档,了解一下精灵 和精灵资源的完整功能。
在资源文件resources.xml 中定义这個船:
< ?xml version= "1.0" encoding= "iso-8859-1" ? >
< resources >
< sprite name= "Boat" >
< image file= "boat.png" >
< grid size= "159,91" array= "4,1" / >
< /image >
< animation speed= "200" loop= "yes" pingpong= "no" / >
< /sprite >
< /resources >
再添加一些代码来载入、显示及动画:
void run ()
{
quit = false ;
CL_DisplayWindow window ( "Sunset", 640, 480 );
CL_Slot slot_quit = window.sig_window_close ().connect ( this, & SpritesExample :: on_window_close );
CL_GraphicContext gc = window.get_gc ();
CL_InputDevice keyboard = window.get_ic ().get_keyboard ();
CL_ResourceManager resources ( "resources.xml" );
CL_Sprite boat_sprite ( gc, "Boat", & resources );
CL_FontDescription font_desc ;
font_desc.set_typeface_name ( "tahoma" );
font_desc.set_height ( 30 );
CL_Font_System font ( gc, font_desc );
while (! quit )
{
if ( keyboard.get_keycode ( CL_KEY_ESCAPE ) == true )
quit = true ;
draw_sunset ( gc );
boat_sprite.draw ( gc, 70, 252 );
font.draw_text ( gc, 146, 50, "A quiet evening in the pacific..." );
boat_sprite.update ();
window.flip ();
CL_KeepAlive :: process ();
CL_System :: sleep ( 10 );
}
}
ClanLib 中的基本绘图部分结束咯。实际上在这一块还有很多东西,但是在这個教程中不再多说。现在妳应当可以开始做2D 的图形界面的咯,妳还可以随意看看,研究更多东西。在ClanLib 中有一大堆值得一看的例子,另外还可以阅读ClanLib 文档以了解更多细节。
在下一部分,我们會学习一下网络通信方面的东西 – 创建一個服务器和一個客户端,并且互相发送消息。来读第3部分!
HxLauncher: Launch Android applications by voice commands