您的位置:首页 > 其它

ClanLib开源游戏引擎探索(1):Basic2D

2010-05-21 14:52 423 查看
研究ClanLib已经有一个多月了,心中若有所得,感觉还是有必要写点东西。如果有研究ClanLib的同学,请不用客气的来指正吧!

Basic2D 基本的2D元素的绘制

程序运行之后的效果图如下:


虽然不是很炫,但是内容很丰富,一个游戏所需的2D元素都包含在这里边了。下面慢慢分解这个程序:

第一步,让程序跑起来!

ClanLib是一个基于C++的游戏引擎,而一个C++程序要运行起来,就必须指定一个main入口点函数。而在ClanLib中我们除了可以用自定义的main函数——就像hello world那样,也可以使用CL_ClanApplication来获得跨平台的支持。

使用CL_ClanApplication时,我们只需要声明一个全局的对象就可以了,在声明时需要向构造函数传递一个指向型别为int fun(const std::vector<CL_string> &args)的函数指针。参照帮助说明中的,“To use this class, create a static main function in your application class, then make a single global instance of CL_ClanApplication。”并结合XNA的模式,我是如此设计程序结构的:

#include <ClanLib/core.h>
#include <ClanLib/application.h>
#include "Game.h"

class app
{
public:
static int main(std::vector<CL_String> const&args)
{
Game game;

return game.run(args);
}
};

CL_ClanApplication application(&app::main);

在app::main中首先实例化一个Game的对象,然后执行Game::run,这样我们就将程序的控制流程转移到Game类中。注意到,Game类是用Template Methods模式来设计的,它定义了一个游戏的基本框架,并提供一系列的Hack接入点。对于任一个特定的游戏,我们只需从Game类继承得到一个子类,然后扩展相应的接入点,就可以定制游戏的行为了。继承之后,注意更改这里的Game为你定义的类如myGame等。

第二步,搭建游戏架构!

游戏运行的最初任务是要创建一个游戏窗口,有了窗口之后才能加载游戏中的图片、Sprites、材质、贴图等asserts。在ClanLib中使用CL_DisplayWindow来创建和管理游戏窗口,并且能向外发出一些事件信号,以供用户监听相应。在实例化CL_DisplayWindow是我们往往向构造器传递一个CL_DisplayWindowDescription对象来个性化窗口,包括设置窗口的大小、title、是否可以缩放等。实例化完毕之后,窗口就创建完成并显示出来。

之后,就进入游戏循环了。如win32中的消息循环一样,我们使用一个while——当while循环跳出的时候,游戏就结束了。在每次循环中,我们要更新游戏逻辑,绘制图像要素,处理输入等等,值得注意的是,在循环最好要调用CL_DisplayWindow::flip来交换前后缓冲区,是绘制的内容显示出来,然后调用 CL_KeepAlive::process来更新ClanLib状态。

下面的代码是我参照XNA的相应结构设计的Game类实现:

#include <ClanLib/core.h>
#include <ClanLib/display.h>
#include <ClanLib/gl.h>

class Game
{
public:

int run( const std::vector<CL_String> & args )
{
try
{
desc = CL_DisplayWindowDescription(cl_text("游戏"));
ModifyDisplayWindowDescription();

window = CL_DisplayWindow(desc);
ModifyDisplayWindow();

CL_GraphicContext gc = window.get_gc();
LoadContent(gc);

quit = false;
while (!quit)
{
gc.clear(CL_Colorf::cornflowerblue);

Update();
Draw(gc);

window.flip(1);
CL_KeepAlive::process();
}
}
catch (CL_Exception &e)
{
CL_ConsoleWindow console("Exceptions: ", 160, 80);
CL_Console::write_line(e.message);
console.display_close_message();

UnLoadContent();
return -1;
}

UnLoadContent();
return 0;
}

virtual void Update() {}
virtual void Draw(CL_GraphicContext &gc) {}
virtual void LoadContent(CL_GraphicContext &gc) {}
virtual void UnLoadContent() {}
virtual void ModifyDisplayWindow() {}

virtual void ModifyDisplayWindowDescription()
{
desc.set_allow_resize(false);
desc.set_size(CL_Size(800, 600), true);
}

protected:

CL_SetupCore core;
CL_SetupDisplay display;
CL_SetupGL gl;

CL_DisplayWindow window;
CL_DisplayWindowDescription desc;

bool quit;
};

关于成员变量中的几个Setup的说明: 在使用相应的ClanLib功能前我们要先初始化相应的库,这只需实例化相应库的Setup类对象就可以了。CL_SetupCore——对系统库的支持,CL_SetupDisplay——对窗口和绘图CL_Draw的支持,CL_SetupGL——对OpenGl的支持。

第三步,让游戏要素登台!

对于图中的各个效果,这里简单说明一下,具体的内容还是直接参照代码吧!

1,渐变填充的背景 通过CL_Draw::gradient_fill完成,在参数中指定CL_Gradient(CL_Colorf::blue, CL_Colorf::greedyellow)就可以了

2,旋转的Sprite 使用一副图像来构造一个CL_Sprite对象,设置rotation_hotspot为origin_center即图像中点,在Update中设置旋转角度angle

3,旋转的半透明方块 用CL_Draw的fill和box可以画出带边框的方块,指定fill用的颜色为CL_Colorf(1.0f, 0.0f, 0.0f, 0.5f)就可以达到红色半透明的效果

4,显示文字 使用CL_Font创建一个字体,然后使用draw_text来绘制要显示的文字

5,移动的剪切区 通过push_cliprect可以添加一个剪切区,通过pop_cliprect来移除一个剪切区,在每次绘制前先push,然后开始绘制,绘制的内容会被剪切,然后pop就可以达到动态剪切区的效果了

#include "game.h"

class myGame :
public Game
{
public:

void Draw(CL_GraphicContext &gc)
{
CL_Draw::gradient_fill(gc,
CL_Rectf(window.get_viewport()),
CL_Gradient(CL_Colorf::blue, CL_Colorf::greenyellow));

CL_Draw::line(gc,
clip_rect.left, clip_rect.top - 1,
clip_rect.right, clip_rect.top - 1,
CL_Colorf::navy);
CL_Draw::line(gc,
clip_rect.left, clip_rect.bottom,
clip_rect.right, clip_rect.bottom,
CL_Colorf::navy);

gc.push_cliprect(clip_rect);

spr_card.draw(gc,
400.0f - spr_card.get_width() / 2,
300.0f - spr_card.get_height() / 2);

CL_Draw::fill(gc, box_rect1, CL_Colorf(1.0f, 0.0f, 0.0f, 0.6f));
CL_Draw::box(gc, box_rect1, CL_Colorf::black);

CL_Draw::fill(gc, box_rect2, CL_Colorf(0.0f, 1.0f, 0.0f, 0.6f));
CL_Draw::box(gc, box_rect2, CL_Colorf::black);

font.draw_text(gc, 50.0f, 300.0f + 8.0f, cl_text("ClanLib游戏演示1: Basic2D"));

gc.pop_cliprect();
}

void LoadContent(CL_GraphicContext &gc)
{
spr_card = CL_Sprite(gc, cl_text("EE1-JP169.jpg"));
spr_card.set_rotation_hotspot(origin_center);

rotation_angle = 0.0f;

clip_rect = CL_Rect(0, 0, CL_Size(800, 300));
speed = 3;

font = CL_Font(gc, cl_text("宋体"), 18.0f);
}

void Update()
{
rotation_angle += 1.0f;
spr_card.set_angle(CL_Angle(rotation_angle, cl_degrees));

if (clip_rect.top < window.get_viewport().top
|| clip_rect.bottom > window.get_viewport().bottom)
{
speed *= -1;
}

clip_rect.translate(0, speed);

float radioX = sin(rotation_angle / 15) * 100.0f;
float radioY = cos(rotation_angle / 15) * 100.0f;

box_rect1 = CL_Rectf(400.0f - radioX,
300.0f - 12.5f - radioY,
CL_Sizef(25, 25));

box_rect2 = CL_Rectf(400.0f + radioX,
300.0f - 12.5f + radioY,
CL_Sizef(25, 25));
}

void ModifyDisplayWindow()
{
slot_window_close = window.sig_window_close().connect(this, &myGame::onWndClose);

CL_InputDevice keyboard = window.get_ic().get_keyboard();
slot_keydown = keyboard.sig_key_down().connect(this, &myGame::onKeyDown);
}

void onKeyDown(const CL_InputEvent &input, const CL_InputState &state)
{
if (input.id == CL_KEY_ESCAPE)
{
quit = true;
}
}
void onWndClose()
{
quit = true;
}

private:

CL_Slot slot_window_close;
CL_Slot slot_keydown;

CL_Sprite spr_card;

float rotation_angle;
CL_Rect clip_rect;
int speed;

CL_Rectf box_rect1;
CL_Rectf box_rect2;

CL_Font font;

};

总结

这个例子并不复杂,关键的东西就是CL_Draw。例子就说到这里,其他有趣的东西如signal—slot模型(模拟C#中event的东西)等以后有机会再说吧!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: