Multiplayer |
Top Previous Next |
This month we are going to discuss more about clients and how we can identify them. Our previous examples worked fine for two players, but how do we set up the things if we want to create a game that can have up to 128 clients? And how do we kick client 123, who is always hiding and shooting the other players in the back? These are just some of the questions that will be answered here.
Let's start by examining the multiplayer12.c script, which can serve as a foundation for any multiplayer project.
#include <acknex.h> #include <default.c>
var server_only = 0; var client_only = 0; var client_server = 0;
First of all note the 3 variable definitions above; server_only will be set to 1 only on a dedicated server, client_only will be set to 1 only on a client and client_server will be set to 1 only if the current instance of the game runs as a client and server at the same time. We won't do anything with these variables, but you could use them to run code that's specific only to the dedicated server, to the client or to the client / server.
The two variables below store the client id value. Why do we need two variables? Well, player_id is a global variable that could be used to detect the number of connected clients as well, while my_id is a local variable that's relevant only for the client. I have used a panel to display my_id for all the clients.
var player_id = 0; var my_id = 0;
PANEL* id_pan = { digits(20, 20, 3 ,* , 1, my_id); flags = visible; }
function server_event() { if (event_type == EVENT_JOIN) { player_id += 1; send_var(player_id); } // take care of the rest of the messages here }
Function server_event will run every time when the server receives a message from one of the clients. If a new client joins the game, player_id is incremented and its value is sent over the network. The server sets player_id to 1, the first client sets player_id to 2, the second client sets player_id to 3 and so on.
Function main is similar with the ones that were used in our previous multiplayer projects; please note that instead of defining strings and texts for the messages, I am using video_window to display the needed text in the window titles.
function main() { on_server = server_event; if (!connection) // can't find a server? (connection = 0) { video_window(NULL, NULL, 16, "Can't find any server. Please try again later."); wait (-4); sys_exit(NULL); }
if (connection == 1) // this instance of the demo runs as a dedicated server? { server_only = 1; video_window(NULL, NULL, 16, "Dedicated server"); // put the rest of your server code here }
if (connection == 3) // this instance of the demo runs as a client and server at the same time? { client_server = 1; player_id += 1; // we set player_id = 1 for this client (the server is also a client) my_id = player_id; // store player_id locally video_window(NULL, NULL, 16, "Client and Server"); // put the rest of your client / server code here } if (connection == 2) // this instance of the demo runs as a client? { client_only = 1; video_window(NULL, NULL, 16, "Client"); while (!player_id) {wait (1);} // wait until you receive the updated player_id value from the server my_id = player_id; // store player_id locally // put the rest of your client code here } }
Run server.bat once, and then run client.bat several times to launch several clients; you will notice that each one of them has a different id number. If you run a dedicated server (you can do that by removing the "-cl" part from inside server.bat) you will see that the first client will have my_id = 1, the second client will have my_id = 2 and so on.
How do we limit the number of clients that can connect to our server? Run the demo from the \multiplayer13 folder and you will notice that you can't have more than 3 clients; all the other clients that try to connect to the server will have their my_id values set to zero.
Let's examine the code that does the job:
#include <acknex.h> #include <default.c>
var server_only = 0; // these variables are defined for further use var client_only = 0; var client_server = 0;
var player_id = 0; var my_id = 0;
PANEL* id_pan = { digits(20, 20, 3 ,* , 1, my_id); flags = visible; }
function main() { on_server = server_event; if (!connection) // can't find a server? (connection = 0) { video_window(NULL, NULL, 16, "Can't find any server. Please try again later."); wait (-4); sys_exit(NULL); } if (connection == 1) // this instance of the demo runs as a dedicated server? { server_only = 1; video_window(NULL, NULL, 16, "Dedicated server"); // put the rest of your server code here } if (connection == 3) // this instance of the demo runs as a client and server at the same time? { client_server = 1; player_id += 1; // we set player_id = 1 for this client (the server is also a client) my_id = player_id; // store player_id locally video_window(NULL, NULL, 16, "Client and Server"); // put the rest of your client / server code here } if (connection == 2) // this instance of the demo runs as a client? { client_only = 1; video_window(NULL, NULL, 16, "Client"); while (!player_id) {wait (1);} // wait until you receive the updated player_id value from the server my_id = player_id; // store player_id locally // put the rest of your client code here } }
The only part of the code that has changed in a significant way is the function below.
function server_event(STRING* client_name) { if (event_type == EVENT_JOIN) // a new client has joined the game? { player_id += 1; // then increment player_id if (player_id > 3) // don't allow more than 3 players to connect to the server { client_drop(client_name); } else { send_var(player_id); // send the new player_id value over the network } } // take care of the rest of the messages here }
Function server_event() is the server event function; it will run every time when the server receives a message from the client. Please note that the function receives the name of the client that tries to connect as a parameter. If a new client joins the game, we increment player_id as usual; however, if player_id exceeds 3 (more than 3 players are trying to play the game) the "client_drop" instruction will drop any player that tries to connect.
The code for the following demo can be found inside the multiplayer14.c file; it allows us to kick the players that bother us with ease.
As you can see, the client / server instance of the game has a special panel with 8 "KICK" buttons. The first button can kick the first client, the second button will kick the second client and so on. Let's take a look at the code inside multiplayer14.c:
#include <acknex.h> #include <default.c>
var server_only = 0; // these variables are defined for further use var client_only = 0; var client_server = 0;
var player_id = 0; var my_id = 0; var kicked_client = 1000; // set kicked_client to a value that's bigger than the maximum number of clients
The variable named kicked_client will be set to a big value initially; later, when the server decides to kick client #3, it will set kicked_client to 3 and so on.
BMAP* kick1_pcx = "kick1.pcx"; BMAP* kick2_pcx = "kick2.pcx"; BMAP* pointer_pcx = "pointer.pcx";
PANEL* id_pan = { digits(20, 20, 3 ,* , 1, my_id); flags = visible; }
PANEL* kick_pan = { pos_x = 20; pos_y = 300; button = 0, 0, kick1_pcx, kick1_pcx, kick2_pcx, kick_clients, null, null; button = 0, 20, kick1_pcx, kick1_pcx, kick2_pcx, kick_clients, null, null; button = 0, 40, kick1_pcx, kick1_pcx, kick2_pcx, kick_clients, null, null; button = 0, 60, kick1_pcx, kick1_pcx, kick2_pcx, kick_clients, null, null; button = 0, 80, kick1_pcx, kick1_pcx, kick2_pcx, kick_clients, null, null; button = 0, 100, kick1_pcx, kick1_pcx, kick2_pcx, kick_clients, null, null; button = 0, 120, kick1_pcx, kick1_pcx, kick2_pcx, kick_clients, null, null; button = 0, 140, kick1_pcx, kick1_pcx, kick2_pcx, kick_clients, null, null; }
Please note that I am using a panel that's made visible only on the server; it contains 8 different buttons that call the same kick_clients(button_number) function when they are clicked:
function kick_clients(button_number) { kicked_client = button_number; // store the button_number value inside kicked_client send_var(kicked_client); // now send kicked_client over the network }
Function kick_clients stores the value of button_number (the number of the button that was clicked) inside the variable named kicked_client, and then sends kicked_client over the network.
function server_event(STRING* client_name) { if (event_type == EVENT_JOIN) // a new client has joined the game? { player_id += 1; // then increment player_id send_var(player_id); // send the new player_id value over the network } // take care of the rest of the messages here }
Function server_event didn't change from our previous demo; however, we are now using a client_event function as well.
function client_event() { if (kicked_client == my_id) { sys_exit(NULL); } // take care of the rest of the client messages here }
Function client_event will run every time when the client receives a message. If the "kicked_client" and "my_id" values match, the server wants to kick that client, so we shut down the engine. I didn't allow the server to kick itself out in this demo (that would be wrong) but if you want to add this, put similar lines of code inside function server_event().
function init_mouse() { mouse_mode = 2; mouse_map = pointer_pcx; while (1) { vec_set(mouse_pos, mouse_cursor); wait(1); } }
function main() { on_server = server_event; on_client = client_event; if (!connection) // can't find a server? (connection = 0) { video_window(NULL, NULL, 16, "Can't find any server. Please try again later."); wait (-4); sys_exit(NULL); } if (connection == 1) // this instance of the demo runs as a dedicated server? { server_only = 1; video_window(NULL, NULL, 16, "Dedicated server"); // put the rest of your server code here } if (connection == 3) // this instance of the demo runs as a client and server at the same time? { client_server = 1; set (kick_pan, VISIBLE); // display kick_pan only on the server init_mouse(); // display the mouse pointer only on the server player_id += 1; // we set player_id = 1 for this client (the server is also a client) my_id = player_id; // store player_id locally video_window(NULL, NULL, 16, "Client and Server"); // put the rest of your client / server code here } if (connection == 2) // this instance of the demo runs as a client? { client_only = 1; video_window(NULL, NULL, 16, "Client"); while (!player_id) {wait (1);} // wait until you receive the updated player_id value from the server my_id = player_id; // store player_id locally // put the rest of your client code here } }
Function main didn't change too much; we make kick_pan visible and we display the mouse pointer only if connection = 3 (in the client / server instance of the game).
So here we are: after 6 multiplayer workshops and 14 playable multiplayer demos, it's time to move on to another chapter of game development. I know that we didn't cover all the aspects of multiplayer programming, but it would have taken us years to do that. Multiplayer programming isn't an easy task, but I hope that the information and the code that I have offered will allow you to create a multiplayer game from ground up.
I'll see you all next month, when we will start another exciting project!
|