Network functions
Networking is implemented using one network server, which coordinates the clients, using UDP.
A client sends key- or gamecontroller-strokes to the server and gets back strokes from all clients including its own strokes.
There is a state-counter taking care of resending packets which are not acknowledged by the clients.
A client on delay should skip its game-loop-waiting function VG3_wait_time() until it is in time again.
This will be determined by the function VG3_nw_update() which must be used instead of VG3_inputevent_update().
VgaGames3 network functions are designed to keep it relative simple to migrate a game from one-player to multi-player.
There are no deliberations needed which data had be to transferred to the other clients, because the only data are input-strokes.
But so there are some limitations:
- the number of clients is limited somehow
- no float variables should be used to keep and update their value for a longer time, because of the inaccuracy in computing
- random numbers must be the same on every client, e.g. using VG3_nw_get_random()
- the same code must be executed in the same order on every client
- Structures and Enumerations
- Network server
- VG3_nw_server()
Start the network server in the background.
There is no stop-function, as the server exits when it idles too long.
Then network-server (and multicast-/broadcast-server) can also be started manually
with the command vg3-nw-server,
useful if they shall run on a separate computer in the LAN. - Multicast/broadcast server
- VG3_nw_mbcast_start()
Start the multicast/broadcast server in the background.
It has to be started in the LAN on the computer where the network server is running so that the clients are able to find the ip-address of the server (IPv4 or IPv6). - VG3_nw_mbcast_stop()
Stop the multicast/broadcast server when all clients has been connected. - VG3_nw_mbcast_getip()
Client-function, to obtain the ip-address of the network server via multicast/broadcast. - Client functions
- Connecting/disconnecting
- VG3_nw_open()
Return an initialized network structure. - VG3_nw_connect()
Connect to network server. - VG3_nw_close()
Disconnect from network server and destroy the network structure. - Defining and using key strokes for network
- VG3_nw_addkey()
Add a keyboard- or gamecontroller-key to the network structure to be sent over network, defining it as a network-key.
It is important that all clients add their keys in the same order.
This function has to be called before VG3_nw_connect(). - VG3_nw_haskey()
Check whether a keyboard- or gamecontroller-key is a network-key. - VG3_nw_changekey()
Change the key of a keyboard- or gamecontroller-key which is a network-key.
This is only needed if a player changes during the game his key-definition e.g. via the system-menu. - VG3_nw_key_ispressed()
Check for a key (added with VG3_nw_addkey()) of a specific client whether it is pressed or not.
This function replaces VG3_key_ispressed() and VG3_gamecontroller_ispressed() but only for network-keys. - Activating and using mouse-data for network
On default, mouse-data is not sent via network, because it requires 3 additional bytes for each client.
- VG3_nw_addmouse()
Activate sending mouse-data via network. - VG3_nw_mouse_ispressed()
Return which mouse-buttons are pressed for a specific client.
This function replaces VG3_mouse_ispressed(). - VG3_nw_mouse_position()
Return mouse-position of a specific client.
This function replaces VG3_mouse_position(). - Querying client properties
- VG3_nw_numberofclients()
Return the total number of clients including already disconnected clients. - VG3_nw_getclientinfo()
Return the name of a client and whether it is still connected. - Sending and retrieving network data
- VG3_nw_update()
Update input-strokes sending and retrieving network data.
This has to be done once every game-loop.
There is also a return info whether the own client is on delay.
This function is a complete network-replacement for VG3_inputevent_update(). - Sending and retrieving additional data-packets
- VG3_nw_send_data()
Send a data-packet to all connected clients. - VG3_nw_recv_data()
Receive data-packets if available.
(Be aware that the function VG3_nw_update() also receives data-packets). - VG3_nw_fetch_data()
Return a new received data-packet. - Miscellaneous network functions
- VG3_nw_pause()
Pause the network game for all clients. - VG3_nw_get_random()
Get random numbers which are equal to all clients.
This function must be used since at least after VG3_nw_connect(). - VG3_nw_random_getnext()
Get random numbers which are equal to all clients and limited to indivdual code-blocks.
This function must be used since at least after VG3_nw_connect(). - VG3_nw_random_initseed()
Get initial seed-number for VG3_nw_random_getnext().
Example
/* A network game where clients * move their sunnyboy with cursor keys, * Quit game with "Q" */master-client.c
/* client who starts network */ #include <vgagames3.h> extern void do_game(char *, int); int main(int argc, char **argv) { do_game(argv[0], 1); }client.c
/* normal clients */ #include <vgagames3.h> extern void do_game(char *, int); int main(int argc, char **argv) { do_game(argv[0], 0); }game.c
/* common code for all clients */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <errno.h> #include <vgagames3.h> /* connect to network-server, return client-number or 0 = exit */ static int connect_to_server(struct vg3_window *wstruct, struct vg3_nwclient **nwclnt, int is_master) { char nwservername[64], *nwserverip; int clnr, clmax, clx; /* if master-client, start multicast-/broadcast-server and network-server */ if (is_master) { VG3_nw_mbcast_start(); VG3_nw_server(NULL); } /* clear window, there could also be put a background image */ VG3_draw_clear(wstruct, NULL, VGAG3_COLOR_BLACK); VG3_window_update(wstruct, 0, 0); /* get ip-address of network-server via multicast/broadcast */ nwserverip = VG3_nw_mbcast_getip(nwservername, sizeof(nwservername)); if (nwserverip == NULL) { fprintf(stderr, "%s\n", VG3_error()); return 0; } printf("\n"); printf("Network-server: name=%s, ip-address=%s\n", nwservername, nwserverip); /* open network */ *nwclnt = VG3_nw_open(wstruct); /* define network-keys: cursor keys of keyboard */ VG3_nw_addkey(*nwclnt, 0, VGAG3_KEY_LCURS); VG3_nw_addkey(*nwclnt, 0, VGAG3_KEY_RCURS); VG3_nw_addkey(*nwclnt, 0, VGAG3_KEY_UCURS); VG3_nw_addkey(*nwclnt, 0, VGAG3_KEY_DCURS); /* connect to network-server with default port and name = hostname * and return own client-number */ clnr = VG3_nw_connect(*nwclnt, nwserverip, NULL, NULL, 0, &is_master); if (clnr < 0) { fprintf(stderr, "%s\n", VG3_error()); return 0; } /* error */ if (clnr == 0) { return 0; } /* close window */ /* print out names of connected clients */ printf("\n"); printf("Connected clients:\n"); clmax = VG3_nw_numberofclients(*nwclnt); for (clx = 1; clx <= clmax; clx++) { const char *name; if (VG3_nw_getclientinfo(*nwclnt, clx, &name)) { printf("- %s\n", name); } } free(nwserverip); /* if master-client, stop multicast-/broadcast-server */ if (is_master) { VG3_nw_mbcast_stop(); } return clnr; } /* game-loop */ static void game_loop(struct vg3_window *wstruct, struct vg3_nwclient *nwclnt, int clnr, int winw, int winh) { struct vg3_image *imgptr; struct vg3_rect *imgpos; int clmax, clx; int inr1, inr2, dowait; /* get highest client-number */ clmax = VG3_nw_numberofclients(nwclnt); if (clmax < 1) { return; } /* load sunnyboy image used for all clients and get width and height */ imgptr = VG3_image_load(wstruct, "sunnyboy.bmp", 0); if (imgptr == NULL) { fprintf(stderr, "%s\n", VG3_error()); return; } VG3_image_getsize(wstruct, imgptr, NULL, &inr1, &inr2); /* create positions of all sunnyboys (left upper corner) */ imgpos = (struct vg3_rect *)calloc(clmax, sizeof(*imgpos)); if (imgpos == NULL) { fprintf(stderr, "%s\n", strerror(errno)); return; } for (clx = 1; clx <= clmax; clx++) { imgpos[clx - 1].x = imgpos[clx - 1].y = 0; imgpos[clx - 1].w = inr1; imgpos[clx - 1].h = inr2; } /* real game-loop */ VG3_discard_input(wstruct); for (;;) { VG3_draw_clear(wstruct, NULL, VGAG3_COLOR_BLACK); /* update network data (including own key-strokes) */ if (VG3_nw_update(nwclnt, &dowait)) { break; } /* close window and exit game */ /* process key-strokes of all clients */ for (clx = 1; clx <= clmax; clx++) { /* check whether client is still connected */ if (VG3_nw_getclientinfo(nwclnt, clx, NULL) == 0) { continue; } /* cursor left: go left, respect window border */ if (VG3_nw_key_ispressed(nwclnt, clx, 0, VGAG3_KEY_LCURS, VGAG3_IS_PRESSED)) { imgpos[clx - 1].x--; if (imgpos[clx - 1].x < 0) { imgpos[clx - 1].x = 0; } } /* cursor right: go right, respect window border */ if (VG3_nw_key_ispressed(nwclnt, clx, 0, VGAG3_KEY_RCURS, VGAG3_IS_PRESSED)) { imgpos[clx - 1].x++; if (imgpos[clx - 1].x > winw - imgpos[clx - 1].w) { imgpos[clx - 1].x = winw - imgpos[clx - 1].w; } } /* cursor up: go up, respect window border */ if (VG3_nw_key_ispressed(nwclnt, clx, 0, VGAG3_KEY_UCURS, VGAG3_IS_PRESSED)) { imgpos[clx - 1].y--; if (imgpos[clx - 1].y < 0) { imgpos[clx - 1].y = 0; } } /* cursor down: go down, respect window border */ if (VG3_nw_key_ispressed(nwclnt, clx, 0, VGAG3_KEY_DCURS, VGAG3_IS_PRESSED)) { imgpos[clx - 1].y++; if (imgpos[clx - 1].y > winh - imgpos[clx - 1].h) { imgpos[clx - 1].y = winh - imgpos[clx - 1].h; } } } /* quit if Q-key is pressed (not a network-key) */ if (VG3_key_ispressed(wstruct, VGAG3_KEY_Q, VGAG3_IS_NEW_PRESSED)) { break; } /* draw sunnyboy for each client */ for (clx = 1; clx <= clmax; clx++) { /* check whether client is still connected */ if (VG3_nw_getclientinfo(nwclnt, clx, NULL) == 0) { continue; } /* draw sunnyboy */ inr1 = imgpos[clx - 1].x + (imgpos[clx - 1].w / 2); inr2 = imgpos[clx - 1].y + (imgpos[clx - 1].h / 2); VG3_image_copy(wstruct, NULL, imgptr, inr1, inr2, NULL, 0); } /* update window */ VG3_window_update(wstruct, 0, 0); /* if not on delay, sleep rest of wait-time */ if (dowait) { VG3_wait_time(30); } } VG3_discard_input(wstruct); /* free sunnyboy image and positions */ VG3_image_unload(wstruct, imgptr); free(imgpos); (void)clnr; /* we don't need own client-number here! */ } /* game */ void do_game(char *argv0, int is_master) { struct vg3_window *wstruct; int winw, winh; struct vg3_nwclient *nwclnt; int clnr; /* open window */ wstruct = VG3_window_new(argv0, VGAG3_VGAVERSION_LOW, VGAG3_WINSCALE_BESTSCALE); if (wstruct == NULL) { fprintf(stderr, "%s\n", VG3_error()); return; } /* get the size of the window */ VG3_window_getsize(wstruct, &winw, &winh); /* (start and) connect to network-server */ if (connect_to_server(wstruct, &nwclnt, is_master) == 0) { goto byebye; } /* game-loop */ game_loop(wstruct, nwclnt, clnr, winw, winh); /* close network connection */ VG3_nw_close(nwclnt); /* if an error occurred after creating the window, close it before exiting */ byebye: /* close window */ VG3_window_free(wstruct); }Makefile
CFLAGS = -W -Wall -O2 VGAG_CFLAGS = `vgagames3-config --cflags` VGAG_LIBS = `vgagames3-config --libs` all: master-client client master-client: master-client.o game.o $(CC) $(CFLAGS) master-client.o game.o $(VGAG_LIBS) -o master-client client: client.o game.o $(CC) $(CFLAGS) client.o game.o $(VGAG_LIBS) -o client master-client.o: master-client.c $(CC) $(CFLAGS) $(VGAG_CFLAGS) -c master-client.c client.o: client.c $(CC) $(CFLAGS) $(VGAG_CFLAGS) -c client.c game.o: game.c $(CC) $(CFLAGS) $(VGAG_CFLAGS) -c game.c clean: rm -f master-client client master-client.o client.o game.o