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

Basic2D    基本的2D元素的绘制

程序运行之后的效果图如下:
Basic2D 虽然不是很炫,但是内容很丰富,一个游戏所需的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的东西)等以后有机会再说吧!

posted on 2010-05-21 14:52  evil_heart  阅读(1216)  评论(0)    收藏  举报