This document pertains to pretty much all of glAnts. glAnts started as a simulation for swarm intelligence. It began as a look into how ants interact. I have all but scrapped this aspect and now I am just focusing on the game and the graphics. At present this a partial opengl game. You can tell it is a game, you can move a bot around the 3d world and can collide with other objects and can also shoot. There is a lot left out of course but the basics are there. Hopefully, in the coming months, I will find the time to finish the project that I started. The current line code count is 22,000 lines all C. I would have used C++ and will pretty convert the project to C++, but I began in C, so there.
The game is a battlezone clone, actually I wanted it to resemble SpectreVR, the old mac game. If you dont already know, the premise of these games is to move a mech around and shoot stuff while travelling through a world usually created using big blocks. My game is written in opengl so of course I wanted 3d graphics, but most of the game assumes everything is 2d. All collision detection, ai, movement, etc is 2d. This is easier but it still looks 3d.
I used a linked list for almost every object I used. I did this for several reasons, one it is cool, two If I have a node object and a list object, I have more control on how and where I can create the list. If I have an array, everything is probably global. With a list I can do a number of things. The benefits of a linked-list are in the list not in the node. Of course there are two negatives, one finding a node based on id is kind of difficult, two you have to dynamically allocate and free everything. This is not as bad in C++, but in C it is a pain.
Here is a linked-list object that works for anything.
// plist.h typedef struct tagPtrNode { void *ptr; struct tagPtrNode *next; } PtrNode; typedef struct tagPtrList { PtrNode *head; int items; } PtrList; // plist.cpp PtrNode *CreatePtrNode(void *data) { PtrNode *h = (PtrNode *) malloc(sizeof(PtrNode)); h->ptr = data; h->next = NULL; return h; } // end of the function // // DestroyPtrNode // void DestroyPtrNode(PtrNode *node) { free(node); } // end of the functino // // Create PtrList // PtrList *CreatePtrList() { PtrList *result = (PtrList *) malloc(sizeof(PtrList)); result->head = NULL; result->items = 0; return result; } // end of the function void DestroyPtrList(PtrList *list) { PtrNode *pos, *next; pos = list->head; while(pos != NULL) { next = pos->next; free(pos); pos = next; } // end of the while free(list); } // end of the function void InsertFront(PtrList *list, void *data) { PtrNode *new_node = NULL; new_node = CreatePtrNode(data); if (isempty(list)) list->head = new_node; else { new_node->next = list->head; list->head = new_node; } // end if list->items++; } // end of the function
Objects are fun. I got half-way into this project and I really wanted to go to C++. It really helps to encapsulate everything into a struct and all the code in one particular source file. Since I was using C I tried some tricks for making C look like C++.
// // major struct for driver objects // typedef struct tagDriverObjects { void (*init)(int list_id); void (*compile)(void); void (*draw)(void); // used with compile void (*render)(void); // render object to scenes int call_id; // id used to compile object int visible; } DriverObjects; static void init_object(int list_id); static void compile_object(void); static void draw_object(void); static void render_object(void); static void draw_object(void); // object.cpp DriverObjects object = { init_object, // init, must be called first compile_object, // compile draw_object, // draw render_object, // render to scene 0 // loaded by INIT }; static void init_object(int list_id) { object.visible = 1; // store the id through the function // there is probably a better way to do this object.call_id = list_id; } // end of the functino
Everybody has there own camera interface. I even had a complicated mess
of camera code. I decided to go with the simple third person view
using glulookat. The idea is to follow a point and glulookat is perfect
for this. You can use something a little bit more complicated for first
person views but who has the energy. The code below is worthless, but
hopefully you can take something from the glulookat. I like the up vector
pointing up, so usually the value is 0.0, 1.0, 0.0.
Let me explain, glulookat is simple. Provide a location to look at
, provide a position for the camera and provide the up vector, that is all.
gluLookAt(look_x, look_y, look_z, pos_x, pos_y, pos_z, up_x, up_y, up_z);For me, I extracted the heading of the bot, took a point in front of it, this is the look at point. For the position, I took the same point behind the bot.
void HandleCameraKeys(bool *keys) { float x, y; float cam_x; float cam_y; // process the mouse moves around // our axis Mouse_Movement(); x = camera_bot->look_x; y = camera_bot->look_y; cam_x = camera_bot->cam_x; cam_y = camera_bot->cam_y; Pos_Camera(cam_x, (CAMERA_HEIGHT*CAMERA->zoom_factor), cam_y); // Of course you can replace glulookat // with your own function gluLookAt(CAMERA->position[0], CAMERA->position[1], CAMERA->position[2], x, 0.0f, y, 0.0f, 1.0f, 0.0f); // player control, strange place I know Player_Control(keys); } // end of the functino
I am writing the networking section as we speak. It is simple in theory but pretty rough in practice. The basic idea is for the client to connect to a server, the server updates the position of the clients. Cut-and-dry. In practice, you need multiple threads, mutexes and snapshots of the clients over set intervals. Not easy. See my networking link above for some of the code. Below is the protocol.
Networking Notes: 10/14/2002 1. Get the local ip address 2. Have to use select to swap between the blocking and non-blocking code -- client -- 3. What does the client do? 4. Client connects to server 5. recvfrom - "glAnts v0 Hello" - current mode: running/waiting 6. sendto - Login request 7. recvfrom - ok/err - and id 7a. do ping message 8. if waiting, wait till running 9. if running, start running 10. Use new game to start game 11. Send new position whenever there is a keypress 12. Dont wait for reply. 13. Timeout on snapshots -- server -- - Note: listen for multiple connections 10. what does the server do? 11. sendto - "glAnts v0 Hello" - current mode: running/waiting 12. accept has to queue multiple sock addresses 13. The server has to process accepts as well as send out snapshots of what is going on in the game 14. 1/2/3 threads? decisions? 15. Since I am a brave person, I will use 2 threads as opposed to the 1 thread approach. 2 threads means I will use an accept to gather as many clients as long as the max is not reached. I will use a mutex to make sure we dont send/recv and accept at the same time. The one thread approach just means that I will have a accept and game process thread. 16. Snapshots are 40-60ms, probably 60ms 17. After the accept, send hello, 18. Get login request 18a. Do Ping, get ping 19. Send running or waiting 20. On NewGame, start server 21. At this point, do snapshots of where everybody is. 22. And that is all folks...
Here is some pseudo code for the networking protocol. I made this
simple language up, I will probably be the only one that understands it.
'*Tmp ' client: sendto:login,name,os,v ' server: recvfrom: msg ' server: addq(msg) ' (?sndto can work w others) ' serv:if login then sndto 'g_h:state' else Nothing ' (?if state=rng then sndto valid) ' cli: newgame:(only1thrd!) ' cli: launch thread:recvfr:msgs:buf ' serv: 50ms: send msgq '--LoCaL(server)-- ' loclser: bld msg: ' loclser: keypress: addq(msg) ' loclser: updte pos ' loclser: netscrn ' loclser: netscrn-for clientlist,name,ip,ping?,os '--MISC-- ' locl/serv/cli: msg scrn ' msgscreen: has srvnme, png?, msgsize ' 4bts: 2nets: 2inc=8 ' client: sndto: echbt ' disconnect:? ' interpolation?
Now I need to define the client/server thread. Here is some more funny pseudo-code. There are of course some other things I have to work out, but this is the general idea.
--- ServerThrd-- ' ' Label Srv: ' Sub SrvRecThrd - ' Do ' recvfrom(msg) ' if msg = lgin Then ' get_iaddr ' addq(__client_) ' ELSE ' return # ' addq(__msg) ' End If ' End Do ' ' Label Srv: ' gettimeday, every50ms: ' Do until end of q ' sndto clients ' End DO '------------------------------------- ' ' Label Cli: ' Sub CliRecThrd - ' ' DO ' recvfrom(msgq) ' for msgq, till NULL ' or divide sizeof ' End DO ' Label Cli: ' Sub Main - ' on mv, do ' sendto(mv, pos) ' ' Sub
Please Contact Berlin Brown bigbinc@hotmail.com for any questions.