Intro: understanding network ============================ Use network functions for multiplayer games with UDP or TCP. Up to NW_MAXCLIENT (defined in vgagames2.h, currently 16) players can connect. UDP is for not-synchronized connections (it's the default), that means packets are transmitted without waiting for the clients to read them. TCP is for synchronized connections, that means if one client delays reading a packet, all other clients have to wait, too. There is support for IPv4 and IPv6 and for searching the network-server via broadcast/multicast. How VgaGames-network-games work There are always - one master player, who starts (and connects to) the network-server - one or more client players, who connect to the network-server - perhaps one or more virtual players, who are managed by the master The master does all the work, he - examinates keystrokes and mouse events of the local player and moves him - examinates keystrokes and mouse events of the client players, received by the network-server, and moves them - moves the virtual players, using a moving algorithm - checks for any interaction among all players and objects - sends all data of all objects and players to the network-server The clients only - send their keystrokes and mouse events to the network-server - receive data of all objects from the network-server to draw them on screen The virtual players - are fully controlled by the master - exist only to complete missing players The network-packet To have access to the data of all players and objects, which are sent and received over network, it is important to define at first the network-packet, which holds pointers to the network-data: - network variables which are needed for every player (called player-data) - network variables which are needed for objects not related to players (called common-block) The size of the network-packet is 1152 bytes. The best way to do this, is to define a struct with pointers, which point to the network variables, e.g. struct { int * x_position; // for x-position of player-number 1 to NW_MAXCLIENT int * y_position; // for y-position of player-number 1 to NW_MAXCLIENT } player_data[NW_MAXCLIENT]; struct { char * sillytext; // for a silly text appearing at every players screen } common_block; Overview over network-starting The master has to - define the structs for player-data and common-block (see above) - define the network variables in the network-packet using vg_nw_setvar() and vg_nw_setcommon() - set the minimum and maximum number of players using vg_nw_setplayer() - start the network-server using vg_nw_startserver() - wait for the connects of the clients using vg_nw_waitforconnects() - set the pointers of the structs for player-data and common-block to the network variables, using vg_nw_getvar() and vg_nw_getcommon() - initialize the values of the network variables (using the pointers) - send an initial packet to the network-server using vg_nw_sendinit() Now the master can begin the game loop. The clients have to - define the structs for player-data and common-block as well as the master - define the network variables in the network-packet as well as the master using vg_nw_setvar() and vg_nw_setcommon() - connect to the network-server using vg_nw_connect() - wait for the connects of all other players using vg_nw_waitforconnects() - set the pointers of the structs for player-data and common-block to the network variables, using vg_nw_getvar() and vg_nw_getcommon() as well as the master - receive the initial master packet from the network-server using vg_nw_recvinit() Now the clients can begin the game loop. Overview over the game loop When all connections have been established, the players can go to the game loop. The master has to: - go through all players (1 to vg_nw_maxplayer()) - examinate keystrokes and mouse events for each player: + If the current player-number is equal to vg_nw_myplayer() it's the master's player: use vg_key_update(), vg_key_pressed() and so on + If the current player-number is greater-equal to vg_nw_virtualplayer() it's a virtual player: use don't know which algorithm + else it's a client player: use vg_recvkeys() to get the client's keystrokes and mouse events, use for examination: vg_nw_keypressed(), vg_nw_mousepressed() vg_nw_mousex(), vg_nw_mousey() - update every player's network-data (player-data) and send it to the network-server using vg_nw_senddata() - set common variables of the common-block (non-player related objects) and send them to the network-server using vg_nw_sendcommon() - draw the players on the screen, if they are alive (vg_nw_isalive()) - draw the common objects on the screen The clients have to: - get keystrokes and mouse events, using vg_key_update() (where additional an automatic network-transfer is performed), check for the return value (network could have been closed) - draw the players (1 to vg_nw_maxplayer()) on the screen, if they are alive (vg_nw_isalive()) - draw the common objects on the screen If a player wants to quit, - the function vg_nw_setdead() has to be called to inform all other players of the death of this player - vg_nw_close() closes the network-connection Be careful: As long as any player is still alive, the master must not close the connection, because this exits the network-server, too. Remember the master is responsible for moving the clients and cannot therefore simply exit, when the master's player died. Example A game, where 2 up to 4 players move a little circle around. A silly text appears. See ../tutorial/tut-network0.html At first we have to consider, which network variables are needed: - for the players we need a x-position and a y-position, we want to use two short-integer values - for the silly text we need an array[40] of char So our structs (for master and clients) are: struct { short * xpos; // x-position short * ypos; // y-position } player_data[NW_MAXCLIENT]; // NW_MAXCLIENT is always save struct { char * sillytext; // array for silly text int textsize; // set it later to 40 } common_block; Now both master and clients have to set network variables: // player-data if (vg_nw_setvar(NWVAR_SHORT,1,"xpos") < 1) {goto error;} if (vg_nw_setvar(NWVAR_SHORT,1,"ypos") < 1) {goto error;} // common-block common_block.textsize=40; if (vg_nw_setcommon(NWVAR_CHAR,common_block.textsize,"sillytext") < 1) { goto error; } The master starts the server (and a broadcast-/multicast-server): // set number of players int pl_min=2, pl_max=4; if (vg_nw_setplayer(pl_min,pl_max) < pl_min) {goto error;} // start network-server, UDP, timeout connect=20, IPv4, timeout receiving=30 char serverport[]="1234", mbcastport[]="1235"; if (vg_nw_startserver(PROTO_UDP,serverport,20,4,mbcastport,30) < 0) { goto error; } The clients connect to the server (using the broadcast-/multicast-server): char serverport[]="1234", mbcastport[]="1235"; if (vg_nw_connect(PROTO_UDP,NULL,serverport,mbcastport) < 0) {goto error;} Both master and clients have to wait for all other connects: // the master may end this waiting with space-key if (vg_nw_waitforconnects() < 0) {goto error;} After all connects both master and clients set the struct pointers: // get pointers for player-data int nr; for (nr=1; nr <= vg_nw_maxplayer(); nr++) { // for each player player_data[nr-1].xpos=(short *)vg_nw_getvar("xpos",nr); player_data[nr-1].ypos=(short *)vg_nw_getvar("ypos",nr); } // get pointers for common-block common_block.sillytext=(char *)vg_nw_getcommon("sillytext"); The master initializes the values of the network variables and sends an initial packet to the network-server: // initialize values int nr; for (nr=1; nr <= vg_nw_maxplayer(); nr++) { // for each player player_data[nr-1].xpos[0]=SC_WIDTH/2; // or any other x-position value player_data[nr-1].ypos[0]=SC_HEIGHT/2; // or any other y-position value } snprintf(common_block.sillytext,common_block.textsize,"%s","No way out"); // send initial packet vg_nw_sendinit(); The clients receive the master's initial packet from the network-server: if (vg_nw_recvinit() < 0) {goto error;} Now both are ready for the game-loop. The master's game-loop: for (;;) { // game-loop int alive,nr,x,y; vg_bitmap_clear(NULL,RGB_BLACK); // clear window // move players alive=0; // check for any living real player for (nr=1; nr <= vg_nw_maxplayer(); nr++) { // for each player if (vg_nw_isalive(nr)) { // player is alive // save moving request in x and y x=y=0; if (nr==vg_nw_myplayer()) { // master's player if (vg_key_update() < 0) {goto quitgame;} // network closed if (vg_key_pressed(KEY_DCURS,LONGKEY)) {y=1;} // down if (vg_key_pressed(KEY_UCURS,LONGKEY)) {y=-1;} // up if (vg_key_pressed(KEY_LCURS,LONGKEY)) {x=-1;} // left if (vg_key_pressed(KEY_RCURS,LONGKEY)) {x=1;} // right if (vg_key_pressed(KEY_SPACE,SHORTKEY)) { // player tired vg_nw_setdead(nr); continue; // next player } alive=1; // at least one real player still alive } else if (nr >= vg_nw_virtualplayer()) { // virtual players x=time(NULL)%3-1; // very silly moving algorithm y=time(NULL)%3-1; // very silly moving algorithm } else { // client players if (vg_nw_recvkeys(nr)) { // client has sent a request if (vg_nw_keypressed(KEY_DCURS,LONGKEY)) {y=1;} // down if (vg_nw_keypressed(KEY_UCURS,LONGKEY)) {y=-1;} // up if (vg_nw_keypressed(KEY_LCURS,LONGKEY)) {x=-1;} // left if (vg_nw_keypressed(KEY_RCURS,LONGKEY)) {x=1;} // right } alive=1; // at least one real player still alive } // now move according to x and y player_data[nr-1].xpos[0]+=x; if (player_data[nr-1].xpos[0] < 0) {player_data[nr-1].xpos[0]=0;} if (player_data[nr-1].xpos[0] >= SC_WIDTH) { player_data[nr-1].xpos[0]=SC_WIDTH-1; } player_data[nr-1].ypos[0]+=y; if (player_data[nr-1].ypos[0] < 0) {player_data[nr-1].ypos[0]=0;} if (player_data[nr-1].ypos[0] >= SC_HEIGHT) { player_data[nr-1].ypos[0]=SC_HEIGHT-1; } // send player's data to network-server vg_nw_senddata(nr); } } // end for each player if (alive==0) {break;} // no living player // set silly text (common-block) and send it to network-server switch(time(NULL)%12) { case 0: snprintf(common_block.sillytext,common_block.textsize,"%s","No way out"); break; case 4: snprintf(common_block.sillytext,common_block.textsize,"%s","Check your defaults"); break; case 8: snprintf(common_block.sillytext,common_block.textsize,"%s","Medio tutissime ibis"); break; } vg_nw_sendcommon(); // draw players for (nr=1; nr <= vg_nw_maxplayer(); nr++) { // for each player if (vg_nw_isalive(nr)) { // player is alive vg_draw_circle(NULL,player_data[nr-1].xpos[0],player_data[nr-1].ypos[0],5+nr*2,RGB_WHITE,0); } } // draw silly text vg_draw_text(NULL,RGB_WHITE,0,0,common_block.sillytext,NULL,RGB_TRANS); // do rest vg_window_flush(); vg_wait_time(50); } // end game-loop quitgame: vg_nw_close(); The clients' game-loop: for (;;) { // game-loop int nr; vg_bitmap_clear(NULL,RGB_BLACK); // clear window // get keystrokes and do network-transfer if (vg_key_update() < 0) {break;} // network closed if (vg_key_pressed(KEY_SPACE,SHORTKEY)) { // player tired vg_nw_setdead(vg_nw_myplayer()); break; } // draw players for (nr=1; nr <= vg_nw_maxplayer(); nr++) { // for each player if (vg_nw_isalive(nr)) { // player is alive vg_draw_circle(NULL,player_data[nr-1].xpos[0],player_data[nr-1].ypos[0],5+nr*2,RGB_WHITE,0); } } // draw silly text vg_draw_text(NULL,RGB_WHITE,0,0,common_block.sillytext,NULL,RGB_TRANS); // do rest vg_window_flush(); vg_wait_time(50); } // end game-loop vg_nw_close(); FUNCTIONS vg_nw_setplayer() vg_nw_startserver() vg_nw_connect() vg_nw_waitforconnects() vg_nw_setvar() vg_nw_getvar() vg_nw_setcommon() vg_nw_getcommon() vg_nw_dumppacket() vg_nw_sendinit() vg_nw_recvinit() vg_nw_myplayer() vg_nw_maxplayer() vg_nw_virtualplayer() vg_nw_isalive() vg_nw_recvkeys() vg_nw_keypressed() vg_nw_mousepressed() vg_nw_mousex() vg_nw_mousey() vg_nw_senddata() vg_nw_sendcommon() vg_nw_dumpsprite() vg_nw_undumpsprite() vg_nw_setdead() vg_nw_close() Back to Index