I. Introduction
Clanlib is a cross-platform C ++ framework mainly for game developers. Although the API is mainly designed for game development, you can easily use clanlib to develop a scientific 3D visualization tool or multimedia application (such as gecko multimedia system ).
Clanlib has various API-2D and 3D graphics, sound, networking, I/O, input, Gui as well as resource management. It also provides transparent OpenGL support, so you can use the local OpenGL command to let clanlib handle window management and everything else dependent on the operating system. Clanlib uses DirectX or a simple direct media layer (one independent multi-media library) to generate 2D images. The clanlib game homepage lists more than 50 games that have been well developed, including 2D and 3D puzzles, strategies, and shooter games. For example, asteroid arena (see Figure 1) uses clanlib and OpenGL technologies to implement a superior classic arcade game.
Figure 1. Asteroid arena screen snapshot |
Clanlib can work on Windows, Linux, and MACOs operating systems, and provides source code-level ZIP or tar file support. Windows developers can use Microsoft Visual Studio, Borland C ++, or mingw (small GNU for Windows) compilers and environments. Third-party support for Ruby and Perl binding is also available. The optional special effect programs include a Lua plug-in (popular little scripting language) and FreeType (a free TrueType library ).
Ii. clanlib feature set
Before using the API, let's take a look at the main features of clanlib:
· Basic cross-platform runtime libraries (GUI, multithreading, file I/O, etc)
· Template-Based C ++ signal/slot Library (Type-safe callback/Proxy)
· Comprehensive resource management
· Sound Mixer support. WAV Files, Ogg Vorbis, and any types of files supported by mikmod Library (mod, s3m, XM, etc.)
· Support for Document Object Model (DOM) XML Analyzer
· Advanced 2D graphics API, supporting OpenGL, DirectX, and SDL as coloring targets
· High-Performance batch coloring engine, when using OpenGL to color 2D
· 2D Collision Detection
· 2D genie animation support
· Highly customizable GUI framework
· From low-level to advanced network library Interfaces
Iii. clanlib basic game model
Now, let's analyze the clanlib API model carefully. I found that the best tutorial is a fully self-explanatory sample program. Specifically, let's analyze Luke Worth's box game, a paper and pencil game with two players (see figure 2 ). This box game contains some lattice points. Players can draw lines between any two points. Whoever draws an encapsulated rectangle with the last line gets a point and enters the next round.
Figure 2. In an ongoing box game, the score is Blue 8/Red 3 |
I specifically kept the main function of the program as short as possible, so that we may focus on the highlighted "game loop ":
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} |
The first thing to note about this application is that the main () function (see row 41) is not a top-level function, but is embedded into an object derived from cl_clanapplication. This object encapsulates a lot of unavoidable platform dependencies-this may include a traditional: Main () implementation (for example, winmain () must be used in Win32 applications ()).
It should also be noted that in fact all executable code (lines 43-58) is encapsulated in a try {}/catch {} exception processor block. If necessary, clanlib will cause exceptions. You can restart a game and so on. Basically, all game logic is included in the init (), drawboard (), endofgame (), and inputhandler () methods. If the Board is no longer moved (fullup = true), exit the game loop (line 48 ). Cl_system: keep_alive () update all input and system events (such as closing a window or moving it ). This will release the CPU cycle in the old Win16 API: yield () or sleep () on Linux.
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); // Select 44 kHz for sampling 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 |
The init () method is used to initialize most games. Of course, here we need the clanlib subsystem to process graphics and sound (rows 68-72), and then build a window to display all graphics (rows 75 ).
Cl_surface (row 77-87) is a 2D bitmap class used to draw a cursor, a square filled with blue and a square filled with red.
A tga file is a bitmap file format. Clanlib has an integrated PNG Library, so it can read and write the most popular bitmap file formatting.
Next, you must initialize the Board into an empty state (line 87-103) and execute other cleanup jobs similar to this to implement a new game counter.
89 90 redturn = true; 91 curs. Vert = false; 92 fullup = false; 93 curs. x = curs. y = 1; 94 95 srand (cl_system: get_time (); // start the random number generator. 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 |
A particularly prominent aspect of clanlib is that it avoids callback models that are traditionally used in many frameworks and introduces the "signal and slot" model. This model is widely used in boost C ++ libraries and implemented in QT. A signal represents a callback function with multiple targets, which is also called "publisher" or "Event" in some similar systems ". Signals are connected to some slots, which are callback function receivers (also called event targets or subscribers) and called when a signal is "sent. The signal has the advantages of type security, which avoids the inevitable cast operation in the traditional framework.
Signals and slots are centrally managed. Tracking all connections in the signal and slot (or more accurately, objects that appear as part of the slot, when any of them is damaged, the signal/slot connection can be automatically disconnected. This allows you to establish a signal/slot connection without having to spend much time managing the lifecycles of those connections and all objects contained in them. In row 105, you only need to capture all the key-down events and make sure your own inputhandler () is used (see row 168-216 ).
105 cl_slot keypress = Cl_keyboard: sig_key_down (). Connect (this, & Boxes: inputhandler ); |
Now, you will start initializing the music part of the program. First, you load a cl_soundbuffer with a .wav ("binary") music file, and then prepare a session handle to play the game. Next, you apply a fade-in and fade-out filter to asynchronously adjust the volume-from zero to 108 of the maximum volume within five seconds (rows 112-60%.
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} |
The drawboard () method draws the dot-drawn grid pattern of the line segment, such as the square in the Red Tomato and blue cornflower boxes won by each player, as well as the imitation cursor. The most important line of code is 165th. Cl_display: Flip () swap the front and back buffer. The background buffer is the place where you draw all the images in the frame, and the foreground buffer is the content displayed on the screen.
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 // draw a box 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 // draw line 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 // draw a grid 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 // draw the cursor 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} |
The inputhandler () function you installed is used to observe the key signal of line 105. This function is used to handle details-turning a key-click into a game movement, and the most important space or enter key-is used to indicate a choice (row 200-210) for the current player ). Then, you need to check whether a "square" has been completed and return the control to the original player.
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 (! Vers [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} |
Finally, the final score is calculated by the endofgame () method. Remember that the game is not over until the board is full (see Row 48) or someone quits by pressing the ESC key (see row 46. Finally, you can fade out the volume to 0 in about 1 second.
217 void boxes: endofgame () 218 { 219 // count score 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} |