Multiplayer

Top  Previous  Next

In diesem Monat wollen wir ein rundenbasiertes Multplayerspiel schreiben, in dem es um das Rennen zweier Hundewelpen geht, die so schnell wie möglich die Ziellinie erreichen wollen.

 

aum75_puppies1

 

Spiel wird abwechselnd gewürfelt und die Welpen entsprechend bewegt. Ich habe auch die fertige Version des Spiels beigelegt, Sie können also erst server.bat und dann client.bat starten und das Spiel ausprobieren - das wird Ihnen helfen zu verstehen wie es funktioniert.

 

Das Spiel erlaubt es, dass sowohl der Server als auch der Client beginnt, daher sind die Gewinnchancen gleich verteilt, was vor allem wichtig ist, wenn Sie mehrere Spiele nacheinander spielen. Wenden wir uns dem Code zu. Ich werde nicht weiter auf die Definitionen der Variablen, Bitmaps, Panels, Texts usw. eingehen, aber den restlichen Code sehen wir uns im Detail an.

 

function main()

{

       randomize();

       fps_max = 60;

       vec_set(screen_color, vector(10, 10, 10));

       on_server = server_event;

       on_client = client_event;

       if (!connection)

       {

               str_cpy(messages_str, "Can't find the server. Please try again later.");

               wait (-4);

               sys_exit(NULL);

       }        

       if (connection == 2)

       {

               im_client = 1;

               str_cpy(messages_str, "Connected to the server");

       

       else // connection = 3

       {

               im_server = 1;

               str_cpy(messages_str, "Waiting for client to join in...");

       }

}

 

Alles beginnt mit der Funktion main, die mit Hilfe von randomize() jedes Mal eine neue Sequenz von Zufallszahlen erzeugt - die brauchen wir für den Würfel. Wir begrenzen die Framerate auf 60 fps (und limitieren damit auch die über das Netzwerk gesendete Datenmenge auf 60 Pakete pro Sekunde), stellen einen dunklen Hintergrund ein (RGB = 10 10 10) und definieren die Funktion "server_event" als Event Funktion für den Server und entsprechend client_event als Eventfunktion für den Client. Diese werden jedes Mal aufgerufen, wenn Server bzw. Client Daten empfangen.

 

Falls connection gleich 0 ist (!connection), dann kann der Client den Server nicht finden, also zeigen wir die "Can't find server...' Nachricht für 4 Sekunden an, bevor die Engine beendet wird. Steht connection auf 2, dann wird diese Instanz des Programmes vom Client ausgeführt, also setzen wir im_client auf 1 und zeigen die "Connected to server" Nachricht an. Ist Connection schließlich gleich 3 (Server und Client zugleich), setzen wir im_server auf 1 und zeigen "Waiting for client..." an.

 

Die Variable "im_client" ist nur auf dem Client gleich 1, während "im_server" nur auf dem Server den Wert 1 hat; dies hilft uns dabei festzulegen, welche Codeteile nur auf dem Client und welche nur auf dem Server laufen sollen.

 

function server_event()

{

       if (event_type == EVENT_JOIN)

       {

               str_cpy(messages_str, "The client has joined in");

               current_player = integer(random(2)) + 1;

               if (current_player == 1)

               {

                       set(dice_pan, VISIBLE);

               }

               else

               {

                       reset(dice_pan, VISIBLE);

               }

               send_var_to(NULL, current_player);

       }

       if (event_type == EVENT_LEAVE)

       {

               str_cpy(messages_str, "The client has disconnected");

       }

       if (event_type == EVENT_VAR)

       {

               if (current_player == 1)

               {

                       set(dice_pan, VISIBLE);

               }

               else

               {

                       reset(dice_pan, VISIBLE);

               }

               puppy2_pan.pos_x = client_score * 20;

               if (puppy2_pan.pos_x > 600) puppy2_pan.pos_x = 600;

       }

}

 

Die Server Event Funktion startet jedes Mal, wenn der Server Daten vom Client empfängt. Es gibt drei mögliche Server Events, die geschehen können:

1) Der Client ist dem Spiel beigetreten.

2) Der Client hat das Spiel verlassen.

3) Der Client hat dem Server eine Variable geschickt.

 

Sehen wir uns die Codeteile einzeln an:

 

       if (event_type == EVENT_JOIN)

       {

               str_cpy(messages_str, "The client has joined in");

               current_player = integer(random(2)) + 1;

               if (current_player == 1)

               {

                       set(dice_pan, VISIBLE);

               }

               else

               {

                       reset(dice_pan, VISIBLE);

               }

               send_var_to(NULL, current_player);

       }

 

Wenn der Client dem Spiel beitritt, zeigen wir "The client has joined in" an und erzeugen dann zufällig entweder die Zahl 1 oder 2 und speichern diese in "current_player"; dies legt fest, wer beginnt. Falls current_player gleich 1 ist, dann muss der Server beginnen, also zeigen wir das Panel für den Würfel an; andernfalls beginnt der Client, also wird das Panel nicht angezeigt. Schließlich senden wir den Wert der current_player Variable an den Client, der daraufhin festlegt, ob er das Panel anzeigen muss.

 

       if (event_type == EVENT_LEAVE)

       {

               str_cpy(messages_str, "The client has disconnected");

       }

 

Dieser Abschnitt der server_event Funktion startet, wenn der Client das Spiel verlässt. In diesem Fall wird "The client has disconnected" angezeigt.

 

       if (event_type == EVENT_VAR)

       {

               if (current_player == 1)

               {

                       set(dice_pan, VISIBLE);

               }

               else

               {

                       reset(dice_pan, VISIBLE);

               }

               puppy2_pan.pos_x = client_score * 20;

               if (puppy2_pan.pos_x > 600) puppy2_pan.pos_x = 600;

       }

 

Dieser letzte Abschnitt läuft, wenn der Server eine Variable vom Client erhalten hat. Falls current_player gleich 1 ist, dann ist der Server am Zug, also zeigen wir das Panel für den Würfel an. Ist current_player = 2, verbergen wir das Panel.

 

Wir verwenden puppy1_pan (der rote Welpe) als Figur des Servers und puppy2_pan (der blaue Welpe) als Figur für den Client; allerdings bewegt sich puppy2_pan nicht auf dem Server mit, wenn wir es auf dem Client bewegen, wir müssen dies also manuell tun, abhängig von "client_score", eine Variable, die über das Netzwerk gesendet wird.

Ich habe festgelegt, dass client_score mit 20 multipliziert wird; auf diese Weise dauert ein Spiel nicht allzu lange. Sie können diese 20 natürlich überall durch Ihren eigenen Wert ersetzen. Die letzte Codezeile stellt sicher, dass die blaue Figur den Bildschirm nicht verlässt (client_score könnte recht groß sein, wenn der Client gegen Ende zum Beispiel eine "6" würfelt).

 

Sehen wir uns nun die Event Funktion des Clients an:

 

function client_event()

{

       if (event_type == EVENT_LEAVE)

       {

               str_cpy(messages_str, "The server has disconnected");

               wait (-4);

               sys_exit(NULL);

       }

       if (event_type == EVENT_VAR)

       {

               if (current_player == 1)

               {

                       reset(dice_pan, VISIBLE);

               }

               else

               {

                       set(dice_pan, VISIBLE);

               }

               puppy1_pan.pos_x = server_score * 20; 

               if (puppy1_pan.pos_x > 600) puppy1_pan.pos_x = 600;

       }

}

 

Wie Sie sehen besteht diese aus nur zwei Abschnitten: der erste wird ausgeführt, wenn der Server die Verbindung unterbricht, woraufhin "The server has disconnected" 4 Sekunden lang angezeigt wird, bevor die Engine beendet wird.

 

Der zweite Abschnitt des Events startet sobald der Client eine neue Variable vom Server erhält; falls current_player gleich 1 ist (der Server ist am Zug), verbergen wir das Würfelpanel dice_pan, ansonsten zeigen wir es an. Wie schon bei der Serverfunktion muss auch hier puppy1_pan manuell bewegt werden, also tun wir das Entsprechende hier. Wieder müssen wir darauf achten, dass die rote Figur nicht aus dem Bild läuft.

 

Was brauchen wir noch? Eine Funktion für die Maus, die von allein startet, sobald das Spiel läuft.

 

function mouse_startup()

{  

       mouse_mode = 2;

       mouse_map = arrow_tga;

       while (1)

       {  

               vec_set(mouse_pos, mouse_cursor);

               wait(1);

       }

}

 

Daran ist nichts Besonderes, der Mauszeiger wird angezeigt, wodurch wir auf das Panel klicken können. Hier ist die Definition dieses Würfelpanels:

 

PANEL* dice_pan =

{

       bmap = dice1_pcx;

       pos_x = 80;

       pos_y = 130;

       on_click = dice_clicked;

}

 

Wie Sie sehen wird die Funktion dice_clicked() aufgerufen, wenn es angeklickt wird; es ist die letzte Funktion, die wir verstehen müssen, sehen wir sie uns also genau an.

 

function dice_clicked()

{

       no_tricks += 1;

       if (no_tricks > 1) {return;}

       beep();

       dice_value = integer(random(6)) + 1;

       if (dice_value == 1) dice_pan.bmap = dice1_pcx;

       if (dice_value == 2) dice_pan.bmap = dice2_pcx;

       if (dice_value == 3) dice_pan.bmap = dice3_pcx;

       if (dice_value == 4) dice_pan.bmap = dice4_pcx;

       if (dice_value == 5) dice_pan.bmap = dice5_pcx;

       if (dice_value == 6) dice_pan.bmap = dice6_pcx;

 

Diese Funktion startet, sobald der Server oder der Client einen Klick auf das Panel registriert. Ich habe eine Variable namens no_tricks definiert, die auf 0 initialisiert wird; diese Variable erhöht sich jedes Mal um 1, wenn der Spieler das Panel anklickt. Falls no_tricks größer als 1 ist, geschieht nichts; auf diese Weise wird verhindert, dass ein Spieler das Panel schnell mehrmals anklickt und seine Figur weiter bewegt als erlaubt.

 

Ein einfaches beep() lässt den Spieler wissen, dass er den Würfel angeklickt hat; der Rest des Codes generiert eine Zufallszahl zwischen 1 und 6 und ändert das Panel entsprechend (dice1_pcx bis dice6_pcx).

 

       if (im_server)

       {

               server_score += dice_value;

               puppy1_pan.pos_x = server_score * 20;

               if (puppy1_pan.pos_x > 600)

               {

                       puppy1_pan.pos_x = 600;

                       send_var_to(NULL, server_score);

                       str_cpy(game_str, "THE SERVER WINS!");

                       snd_play(victory_wav, 100, 0);

                       send_string(game_str);

                       while (1) {wait (1);}

               }

               current_player = 2;

       }

 

Der "if" Teil oben läuft nur auf dem Server (wo im_server gleich 1 ist). Die erste Zeile addiert den neuen Würfelwurf zu server_score und puppy1_pan.pos_x wird angepasst. Falls dieser Wert die 600 übersteigt, hat die rote Figur (der Server) das Rennen gewonnen, also begrenzen wir puppy1_pan.pos_x auf 600, senden den aktualisierten server_score ein letztes Mal über das Netzwerk, zeigen "THE SERVER WINS!" auf dem Bildschirm an, lassen victory_wav ertönen und senden dann den "THE SERVER WINS!" String auch an den Client.

 

aum75_puppies3

 

Die "while (1) {wait (1);}" Schleife tut nichts Sinnvolles; sie pausiert das Spiel und erlaubt es Ihnen, etwas am Ende des Spiels zu tun: Statistiken anzeigen, das Spiel erneut starten, etc. Falls puppy1_pan.pos_x kleiner als 600 ist (das Spiel ist noch nicht vorbei), wird current_player auf 2 gesetzt, woraufhin der Client am Zug ist.

 

       if (im_client)

       {

               client_score += dice_value;

               puppy2_pan.pos_x = client_score * 20;

               if (puppy2_pan.pos_x > 600)

               {

                       puppy2_pan.pos_x = 600;

                       send_var_to(NULL, client_score);

                       str_cpy(game_str, "THE CLIENT WINS!");

                       snd_play(victory_wav, 100, 0);

                       send_string(game_str);

                       while (1) {wait (1);}

               }

               current_player = 1;

       }

 

Der obige Teil läuft nur auf dem Client. Falls dort das Panel angekllickt wird, addieren wir entsprechend den Wert zu client_score dazu und bewegen puppy2_pan auf die korrekte Position. Wieder wird geprüft, ob der Client dadurch gewonnen hat und falls ja wird wie oben ein String angezeigt und gesendet, nur lautet er diesmal "THE CLIENT WINS!".

 

aum75_puppies2

 

Falls das Spiel noch nicht zu Ende ist, setzen wir current_player auf 1, woraufhin der Server am Zug ist.

 

       send_var(current_player);

       wait (1);

       send_var_to(NULL, client_score);

       send_var_to(NULL, server_score);

       wait (-1);

       reset(dice_pan, VISIBLE);

       no_tricks = 0;

}

 

Diese Zeilen beenden die Funktion dice_clicked() und damit leider auch unseren Workshop; sie werden sowohl auf dem Client als auch auf dem Server ausgeführt. Wir senden die current_player Variable, warten 1 Frame und senden dann die aktualisierten Werte für client_score und server_score. Wir müssen 1 Frame warten, damit current_player korrekt gesetzt ist, wenn die Werte für die Punkte ankommen. Die "wait (-1);" Anweisung zeigt dem Spieler 1 Sekunde lang das Wurfergebnis ehe dice_pan verschwindet. Der Rest des Codes setzt no_tricks wieder zurück, damit das Panel wieder angeklickt werden kann, wenn es erscheint.

 

Es wäre ein leichtes, dieses rundenbasierte Spiel in einen Mausknopffeindlichen Echtzeitactiontitel zu verwandeln. Erhöhen Sie einfach server_score und / oder client_score jedes Mal, wenn der Server bzw. Client die linke Maustaste drückt und der Spieler mit der höchsten Klickrate gewinnt! Vergessen Sie nicht, in diesem Fall no_tricks wieder zu entfernen.

 

Dann bis zum nächsten Monat, wo es um ein weiteres aufregendes Multiplayerprojekt gehen wird!