<b>一、 簡介</b>
ClanLib是一個主要針對遊戲開發者的跨平台C++架構。盡管API主要為遊戲開發設計,你照樣可以容易地使用ClanLib來開發一個科學的3D可視化工具或多媒體應用程式(例如Gecko多媒體系統)。
ClanLib擁有各種API-2D和3D圖形,聲音,網絡,I/O,輸入,GUI以及資源管理。它還提供透明的OpenGL支援,是以你可以使用本機OpenGL指令而讓ClanLib處理依賴于作業系統的
視窗管理和其它一切事情。ClanLib通過DirectX或簡單的Direct Media
Layer(一平台獨立的多媒體庫)生成2D圖形。ClanLib遊戲首頁上列舉了約50多個開發非常成功的遊戲,包括以2D和3D形式完成的難題、政策
以及射手類遊戲。例如,Asteroid Arena(見圖1)使用了ClanLib和OpenGL技術,實作了勝人一籌的經典街機遊戲。
圖1.Asteroid Arena螢幕快照
ClanLib可以工作在Windows,Linux和MacOS作業系統之上,并且提供源碼級的zip或tar檔案支援。Windows開發者可以使用微軟Visual Studio,Borland C++或者MinGW(小型GNU for Windows)編譯器和環境。第三方的對于Ruby和Perl語言的綁定支援也是可用的。可選的特效程式包括一個Lua插件(流行的小腳本程式設計語言)和FreeType(一個免費的TrueType字型庫)。
<b>二、 ClanLib特征集</b>
在具體使用API之前,讓我們看一下ClanLib的主要特征:
·基本跨平台運作時刻庫(GUI,多線程,檔案I/O,等等)
·基于模闆的C++信号/槽庫(類型安全的回調/代理)
·綜合的資源管理
·聲音混合器支援。WAV檔案,Ogg Vorbis,以及由MikMod庫(MOD,S3M,XM,等等)支援的任何類型檔案
·文檔對象模型(DOM)XML分析器支援
·進階2D圖形API,支援OpenGL,DirectX和SDL作為着色目标
·高性能的批量着色引擎,當用OpenGL着色2D時
·2D碰撞檢測
·2D精靈動畫支援
·高度可定制的GUI架構
·從低級到進階的網絡庫接口
<b>三、 ClanLib基本的遊戲模型</b>
現在,讓我們仔細分析一下ClanLib API模型。我發現最好的教程是一個完全自解釋的示例程式。具體地,讓我們分析一下Luke
Worth的盒子遊戲,這是一個有兩個玩家的紙和鉛筆遊戲(見圖2)。這個盒子遊戲包含一些格子點,在任意兩點間玩家都可以畫線。誰用最後一條線畫成一個
封裝的矩形,誰就得一分,并進入到下一輪中。
圖2.一個進行中的盒子遊戲,得分情況是藍8/紅3
我特意使程式的main函數盡可能簡短,這樣我們可能集中注意力于高亮處的"遊戲循環":
1 #include <iostream>
2 #include <ClanLib/application.h>
3 #include <ClanLib/core.h>
4 #include <ClanLib/display.h>
5 #include <ClanLib/gl.h>
6 #include <ClanLib/sound.h>
7 #include <ClanLib/vorbis.h>
8
9 const int boardsize = 6, spacing = 50, border = 20;
10 const int numsquares = int(pow(float(boardsize - 1), 2));
11
12 enum coloursquare { off, blue, red };
13 struct cursor {
14 int x, y;
15 bool vert;
16 };
17
18 class Boxes: public CL_ClanApplication {
19 bool ver[boardsize][boardsize - 1];
20 bool hor[boardsize - 1][boardsize];
21 coloursquare squares[boardsize - 1][boardsize - 1];
22 bool redturn;
23 bool fullup;
24 cursor curs;
25
26 void inputHandler(const CL_InputEvent &i);
27 bool findsquares(void);
28 inline int numaroundsquare(int x, int y);
29 void init();
30 void drawBoard();
31 void endOfGame();
32
33 public:
34 virtual int Boxes::main(int, char **);
35 } app;
36
37 using namespace std;
40
41 int Boxes::main(int, char **)
42 {
43 int winsize = spacing * (boardsize - 1) + border * 2;
44 try {
45 Boxes::init();
46 while (!CL_Keyboard::get_keycode(CL_KEY_ESCAPE)) {
47 Boxes::drawBoard();
48 if (fullup) break;
49 CL_System::keep_alive(20);
50 }
51 Boxes::endOfGame();
52
53 CL_SetupVorbis::deinit();
54 CL_SetupSound::deinit();
55 CL_SetupGL::deinit();
56 CL_SetupDisplay::deinit();
57 CL_SetupCore::deinit();
58 }
59 catch (CL_Error err) {
60 std::cout << "Exception caught: "<< err.message.c_str() << std::endl;
61 }
62
63 return 0;
64 }
關于這個應用程式,應注意的第一事情是main()函數(見行41)并不是一個最頂層的函數,而是嵌入到一個從CL_ClanApplication派
生的對象中。該對象封裝了不少難以避免的平台依賴性-這可能包含一個傳統的::main()實作(例如在Win32應用程式中必須使用WinMain
())。
而且還應注意,事實上所有的可執行的代碼(行43-58)被封裝在一個try{}/catch{}異常處理器塊中。如果需要
的話,ClanLib将引發異常,你可以重新開機一遊戲,等等。基本上,所有的遊戲邏輯包含在init(),drawBoard(),endOfGame()
和inputHandler()這幾個方法中。如果board不再移動(fullup==true),則退出遊戲循環(行48)。CL_System::
keep_alive()更新所有的輸入和系統事件(象關閉視窗或者移動它)。這在老式的Win16 API
::Yield()或者Linux上的sleep()中将會釋放CPU周期。
66 void Boxes::init()
67 {
68 CL_SetupCore::init();
69 CL_SetupDisplay::init();
70 CL_SetupGL::init();
71 CL_SetupSound::init();
72 CL_SetupVorbis::init();
73
74 CL_DisplayWindow window("Boxes", winsize, winsize);
75 CL_SoundOutput output(44100); //選擇44Khz采樣
76
77 CL_Surface *cursimg = new CL_Surface("cursor.tga");
78 cursimg->set_alignment(origin_center);
79 CL_Surface *redpict = new CL_Surface("handtransp.tga");
80 redpict->set_alignment(origin_center);
81 redpict->set_scale(float(spacing)/float(redpict->get_width()),
82 float(spacing)/float(redpict->get_height()));
83 CL_Surface *bluepict = new CL_Surface("circlehandtransp.tga");
84 bluepict->set_alignment(origin_center);
85 bluepict->set_scale(float(spacing) / float(bluepict->get_width()),
86 float(spacing) / float(bluepict->get_height()));
87
這裡的init()方法完成大部分的遊戲初始化工作。當然,在此需要ClanLib子系統以用于處理圖形和聲音(行68-72),然後建構一個視窗用于顯示所有的圖形(行75)。
CL_Surface(行77-87)是一個2D位圖類,用于繪制光标,用藍色填充的方格和用紅色填充的方格。
TGA檔案是一種位圖檔案格式。ClanLib有一個內建的PNG庫,是以它可以讀寫最流行的位圖檔案格式化。
下一步,你必須把闆子初始化成一個空狀态(行87-103)并執行類似的其它的清理工作以實作新的遊戲計數器。
89
90 redturn = true;
91 curs.vert = false;
92 fullup = false;
93 curs.x = curs.y = 1;
94
95 srand(CL_System::get_time()); //啟動随機數字生成器
96
97 for (int x = 0; x < boardsize - 1; x++) {
98 for (int y = 0; y < boardsize; y++)
99 hor[x][y] = ver[y][x] = false;
100
101 for (int y = 0; y < boardsize - 1; y++)
102 squares[x][y] = off;
103
104
ClanLib的一個特别突出的方面是它避開傳統型應用于許多架構中的回調模型,而引入了"信号和槽"模型。這種模型廣泛應用于Boost
C++庫中,并在QT中得到實作。信号代表具有多個目标的回調函數,又在一些類似的系統中稱作"出版者"或者"事件"。信号被連接配接到一些槽上,它們是回調
函數接收器(也稱作事件目标或者訂戶),當信号被"發出"時即被調用。信号具有類型安全的優點,它們避開了在傳統型的架構中的不可避免的cast操作。
信号和槽被統一管理。在信号和槽中(或者更準确些說是,作為槽的一部分出現的對象)跟蹤所有的連接配接,并當任何其一被破壞時能夠自動地斷開信号/槽連接配接。
這能夠使使用者建立信号/槽連接配接而不需要花費多大的代價來管理那些連接配接以及所有包含于其中的對象的生命周期。在行105中,你隻要捕獲所有的鍵擊
("down")事件并確定使用了你自己的inputHandler()(見行168-216)。
105 CL_Slot keypress =
CL_Keyboard::sig_key_down().connect(this,
&Boxes::inputHandler);
現在,你将開始初始化程式的音樂部分。首先,你用一個.wav格式的("binary")音樂檔案裝載一個CL_SoundBuffer,然後準備一個
會話句柄以為玩遊戲之用。下一步,你應用一個淡入淡出過濾器來異步地調整音量-在五秒(行 108-112)内把音量從零變化到最大音量的百分之六十。
106 CL_SoundBuffer *music = new CL_SoundBuffer("linemusic.ogg");
107 CL_SoundBuffer_Session session = music->prepare();
108 CL_FadeFilter *fade = new CL_FadeFilter(0.0f);
109 session.add_filter(fade);
110 session.set_looping(true);
111 session.play();
112 fade->fade_to_volume(0.6f, 5000);
113 }
drawBoard()方法繪制線段所在的點畫格子圖案,如,每個玩家赢得的紅色的蕃茄和藍色的矢車菊框出的方格,還有模仿的光标。而最重要的代碼行
是第165行。CL_Display::flip()交換前背景緩沖區。背景緩沖區是在該幀中你繪制所有圖形的地方,而前台緩沖區是顯示在螢幕上的内容。
115 void Boxes::drawBoard()
116 {
117 CL_Display::clear(redturn ? CL_Color::red : CL_Color::blue);
118 CL_Display::fill_rect(CL_Rect(border/2, border/2,
119 winsize - border/2, winsize - border/2),CL_Color::black);
120
121 //畫方框
122 for (int x = 0; x < boardsize - 1; x++)
123 for (int y = 0; y < boardsize - 1; y++) {
124 if (squares[x][y] == red) {
125 CL_Display::fill_rect(CL_Rect(x * spacing + border,y * spacing + border, x * spacing + border +
spacing,
127 y * spacing + border + spacing),CL_Gradient(CL_Color::red,
128 CL_Color::red, CL_Color::tomato, CL_Color::tomato));
129 redpict->draw(x * spacing + border + spacing / 2,
130 y * spacing + border + spacing / 2);
131 }
132 else if (squares[x][y] == blue) {
133 CL_Display::fill_rect(CL_Rect(x * spacing + border,
134 y * spacing + border,x * spacing + border +spacing,
135 y * spacing + border +spacing),CL_Gradient(CL_Color::blue,
136 CL_Color::blue, CL_Color::cornflowerblue,CL_Color::cornflowerblue));
137 bluepict->draw(x * spacing + border + spacing / 2,y * spacing + border + spacing / 2);
139 }
140 }
141
142 //畫線
143 for (int x = 0; x < boardsize; x++) {
144 for (int y = 0; y < boardsize - 1; y++) {
145 if (ver[x][y]) CL_Display::draw_line(x * spacing + border,
146 y * spacing + border,x * spacing + border,
147 y * spacing + border+ spacing,CL_Color::yellow);
148 if (hor[y][x]) CL_Display::draw_line(y * spacing + border,
149 x * spacing + border,y * spacing + border+ spacing,x * spacing + border,CL_Color::yellow);
151 }
152 }
153
154 //畫格子
155 for (int x = 0; x < boardsize; x++)
156 for (int y = 0; y < boardsize; y++)
157 CL_Display::draw_rect(CL_Rect(x * spacing + border,
158 y * spacing + border,x * spacing + border + 2,159 y * spacing + border + 2),CL_Color::white);
160
161 //畫光标
162 if (curs.vert) cursimg->draw((curs.x - 1) * spacing + border,int((curs.y - 0.5) * spacing + border));
163 else cursimg->draw(int((curs.x - 0.5) * spacing + border),(curs.y - 1) * spacing + border);
164
165 CL_Display::flip();
166 }
你安裝的inputHandler()函數用于觀察在行105的按鍵信号。這個函數負責處理細節問題-把鍵擊變成遊戲運動,還有最重要的空格或者Enter鍵
-用于訓示目前玩家的一個選擇(行200-210)。然後,你要檢查一下是否已完成了一個"方形"并把控制傳回到原來的玩家。
168 void Boxes::inputHandler(const CL_InputEvent &i)
169 {
170 if (redturn) {
171 switch(i.id) {
172 case CL_KEY_LEFT:
173 case CL_KEY_G:
174 if (curs.x > 1) curs.x--;
175 break;
176 case CL_KEY_RIGHT:
177 case CL_KEY_J:
178 if (curs.x < boardsize) curs.x++;
179 break;
180 case CL_KEY_UP:
181 case CL_KEY_Y:
182 if (!curs.vert && curs.y > 1) {
183 curs.y--;
184 curs.vert = !curs.vert;
185 }
186 else if (curs.vert) curs.vert = false;
187 break;
188 case CL_KEY_DOWN:
189 case CL_KEY_H:
190 if (curs.vert && curs.y < boardsize) {
191 curs.y++;
192 curs.vert = !curs.vert;
193 }
194 else if (!curs.vert) curs.vert = true;
195 break;
196 }
197 if (curs.x == boardsize && !curs.vert) curs.x--;
198 if (curs.y == boardsize && curs.vert)
curs.vert = false;
199
200 if (i.id == CL_KEY_SPACE || i.id == CL_KEY_ENTER) {
201 if (curs.vert) {
202 if (!ver[curs.x-1][curs.y-1]) {
203 ver[curs.x-1][curs.y-1] = true;
204 if (!findsquares()) redturn = !redturn;
205 }
206 }
207 else {
208 if (!hor[curs.x-1][curs.y-1]) {
209 hor[curs.x-1][curs.y-1] = true;
210 if (!findsquares()) redturn = !redturn;
211 }
212 }
213 }
214 }
215 }
最後,由endOfGame()方法計算最後的得分。記住遊戲還沒有結束,直到闆子滿了為止(見行48)或者某人通過按下ESC鍵(見行46)退出。最後,你用大約1秒的時間把音量淡出到0。
217 void Boxes::endOfGame()
218 {
219 // 計數得分
220 int redscore, bluescore;
221 redscore = bluescore = 0;
222 for (int x = 0; x < boardsize - 1; x++)
223 for (int y = 0; y < boardsize - 1; y++) {
224 if (squares[x][y] == red) redscore++;
225 else if (squares[x][y] == blue) bluescore++;
226 }
227
228 cout << "Red: " << redscore << "\nBlue: " << bluescore << endl;
229 if (bluescore != redscore)
230 cout << (bluescore > redscore ? "Blue" : "Red") << " player wins\n";
231 else cout << "It was a tie\n";
232
233 if (fullup) {
234 fade->fade_to_volume(0.0f, 1000);
235 CL_System::sleep(1000);
236 }
237 }