Zurück: Kollisionen. KI. Zustandsautomaten

Multiplayer- und Online-Spiele

Dieser Workshop befaßt sich mit dem Erstellen von Multiplayer-Spielen. Er basiert auf der Multiplayer-Serie von George Pirvu, die im Gamestudio-Magazin AUM mit Ausgabe 75 beginnt, und beschreibt auch eine Methode zur Verbesserung der Bandbreite, die auf dem User-Forum vorgeschlagen wurde. Während die AUM-Serie sich hauptsächlich mit LAN-Spielen beschäftigte, wird dieser Workshop Ihnen eine Ahnung ernsthaften Online-Gamings mit Berücksichtigung von Bandbreite und Verzögerungen vermitteln. Dies ist wichtig, wenn Sie viele Spieler mit Ihrem Game verbinden, mit Leuten auf der anderen Seite des Globus spielen, oder die nächste Generation eines MMORPG schreiben wollen.

Fangen wir mit einem extrem einfachen Spiel an und konvertieren es in ein Multiplayer-Game. Das Spiel, mit dem wir beginnen, befindet sich in script25.c:

////////////////////////////////////////////////////////////////////////////
// extremely simple single player game
////////////////////////////////////////////////////////////////////////////
#include <acknex.h>
#include <default.c>
 
action player_move() // control the player on the client, move it on the server
{    
  var walk_percentage = 0;
  while (1) 
  {
    my.pan += (key_a - key_d)*5*time_step;   // rotate the entity using [A],[D]
    var distance = (key_w - key_s)*5*time_step;
    c_move(me, vector(distance,0,0), NULL, GLIDE); // move it using [W],[S]
    walk_percentage += distance;
    ent_animate(me,"walk",walk_percentage,ANM_CYCLE); // animate the entity
    wait (1);
  }
}

function main() 
{
  level_load ("multiplayer6.wmb");
  vec_set(camera.x, vector (-600, 0, 100)); // set a proper camera position
  ent_create ("redguard.mdl", vector (100, 50, 40), player_move); // then create the red guard!
}

Starten Sie das Skript und bewegen und drehen Sie den Player mithilfe der Tasten WSAD. Das Laden des Levels sowie die Player-Aktionen sollten Ihnen nun bekannt vorkommen. Ich brauche also nicht viel zum Skript zu erklären. Die main function lädt den Level, setzt eine Kameraposition und erzeugt eine Player-Entity mit der Action player_move. Diese Action rotiert den Player mit den [A] und [D] Tasten, und bewegt ihn vorwärts und rückwärts mit den [W] und [S] Tasten.Das Einzige, was neu sein könnte, ist die Tatsache, dass wir die Bewegungs-Distanz - den Eingabeparameter von c_move - als einen Prozentwert für die Funktion ent_animate verwenden. Auf diese Weise wird die Entity-Animation ihrer Gehgeschwindigkeit angepasst.

Nun, ich muß zugeben, dass ein roter Player, der in einem langweiligen, grauen Raum herumläuft nicht gerade ein sehr aufregendes Spiel darstellt. Der Kerl sieht einsam aus, geben wir ihm etwas Gesellschaft! Für das was jetzt folgt, brauchen Sie die Gamestudio A7.7 Commercial-Edition oder höher, die Multiplayerspiele unterstützt. Öffnen wir nun script25_1.c:

////////////////////////////////////////////////////////////////////////////
// simple lite-C LAN game - naive approach
////////////////////////////////////////////////////////////////////////////
#include <acknex.h>
#include <default.c>
 
action player_move() // control the player on the client, move it on the server
{    
  var walk_percentage = 0;
  while (1) 
  {
    if (my.client_id == dplay_id) { // the following code runs on the player's client only
      my.skill1 = key_w - key_s; // forward/backward
      send_skill(my.skill1,SEND_UNRELIABLE|SEND_RATE); // send movement request to the server
      my.skill2 = key_a - key_d; // left/right rotation
      send_skill(my.skill2,SEND_UNRELIABLE|SEND_RATE); // send rotation request to the server
    }

    if (connection & CONNECT_SERVER) { // the following code runs on the server only
      my.pan += my.skill2*5*time_step;   // rotate the entity using its skill2
      var distance = my.skill1*5*time_step;
      c_move(me, vector(distance,0,0), NULL, GLIDE); // move it using its skill1
      walk_percentage += distance;
      ent_animate(me,"walk",walk_percentage,ANM_CYCLE); // animate the entity
    }
    wait (1);
  }
}

function main() 
{
  if (!connection) 
    error("Start first the server, then the clients!");
  else 
    while (dplay_status < 2) wait(1); // wait until the session is opened or joined

  dplay_localfunction = 2; // run actions both on server and client
  level_load ("multiplayer6.wmb");
  vec_set(camera.x, vector (-600, 0, 100)); // set a proper camera position

  if (connection & CONNECT_SERVER)  // this instance of the game runs on the server
    ent_create ("redguard.mdl", vector (100, 50, 40), player_move); // then create the red guard!
  else // otherwise, it runs on a connected client
    ent_create ("blueguard.mdl", vector (-100,-50, 40),player_move); // create the blue guard
}

Ob Sie es glauben oder nicht, das ist der komplette Code für ein funktionierendes Multiplayer-Game! Er hilft uns, bereits einiges über die Multiplayer-Fähigkeit von Gamestudio zu lernen. Die Acknex-Engine gibt uns die Möglichkeit, Server und Klienten auf ein- und demselben Computer laufen zu lassen. Dies ermöglicht es, ein vollständiges Multiplayer-Game zu erstellen und zu testen, ohne dazu zwei oder mehr Computer zu benötigen.

Diesmal reicht es nicht, zum Starten einfach auf das schwarze Dreieck zu klicken. Sie müssen sich entscheiden, ob Sie die Engine als Server oder als Client (Klient) starten wollen. Um im Server-Modus zu starten, müssen Sie der Engine die Kommandozeilenoption -sv -cl übermitteln. Die einfachste Methode, Kommandozeilenoptionen einzugeben ist: SED Options / Preferences:

Starten Sie zuerst den Server: markieren Sie beides, Client und Server, dann gehen Sie auf Ok und starten das Skript durch Anklicken des schwarzen Dreiecks . Warten Sie bis sich das Engine-Fenster öffnet. Nun gehen Sie wieder auf Options / Preferences und entfernen den Haken bei Server. Und jetzt starten Sie das Skript wieder. Diesmal aber ist nur Client markiert. Wenn Sie einen älteren SED ohne diese Markierungsfensterchen haben, geben Sie einfach manuell im Optionsfeld zuerst -sv -cl ein und dann nur -cl.

Haben Sie alles richtig gemacht, haben Sie nun zwei Engine-Fenster auf ihrem PC und die sehen so aus:

Benutzen Sie zum Bewegen und drehen der Player wieder die Tasten WSAD. Die Tasten funktionieren im aktiven Fenster. Haben Sie ins Server-Fenster geklickt, können Sie den roten Player bewegen, im Klienten-Fenster den Blauen.

Bevor wir nun das Skript untersuchen, haben Sie vermutlich eine wichtige Frage: Was zum Teufel ist dieses Client-Server-Technikgerede? Einen Überblick bietet das Handbuch unter Multiplayer, aber hier kommt die Kurzversion: Ein Server ist sozusagen eine unterwürfige Software-Applikation, die den Wünschen der Klienten nachkommt. Ein Klient ist eine fiese Applikation, die Service vom Server verlangt. Okay, dröseln wir das mal ein wenig auf...

Betrachten Sie die obige Abbildung genau: der Klient stellt ein Gesuch an den Server, fragt nach irgendetwas. Der Server empfängt die Anfrage, berechnet die Information und schickt das Ergebnis an dern Klienten zurück. Im Großen und Ganzen ist das alles was bei einer Klienten-Server-Anwendung passiert. Untersuchen wir noch eine weitere Abbildung:

Wie Sie sehen, können die Klienten nicht direkt miteinander kommunizieren - das ist keine direkte Peer-to-peer-Verbindung. Player1 kann keinerlei Daten direkt an Player2 schicken, sämtliche Klienten müssen, wann immer sie Informationen austauschen wollen, den Server benutzen. Stellen wir uns mal vor, alle Player spielen einen Multiplayer-Shooter: wenn Player1 eine Kugel auf Player3 schießen will, sendet er die Koordinaten seiner Kugel an den Server und der prüft, ob die auf die Modell-Koordinaten von Player3 passen. Kann die Kugel von Player1 mit Player3 kollidieren, zieht der Server etwas vom Gesundheitswert von Player3 ab. Zugegeben, das ist eine vereinfachte Erklärung des Prozesses aber sie funktioniert.

Um zu verhindern, daß wir für ein Zwei-Player-Game einen dritten Computer brauchen, ist die Engine in der Lage, einen Server, der gleichzeitig ein Klient ist, gleichzeitig zu starten. Darum haben wir die Kommandozeilenoptionen -sv -cl, die Server und Klient zusammen in einem einzigen Fenster starten, angegeben .

Werfen Sie nun einen Blick auf den Anfang unseres Skripts:

if (!connection)
  error("Start first the server, then the clients!");
else
  while (dplay_status < 2) wait(1);

Die vordefinierte Variable mit dem Namen connection wird auf 1 gesetzt (als CONNECT_SERVER definiert) wenn die Multiplayer-Applikation als ein Server gestartet wird, auf 2 (CONNECT_CLIENT) wenn die Applikation als Klient gestartet wird und auf 3 wenn sie gleichzeitig als Server und Klient gestartet wird (-sv -cl). Wird kein Server gefunden oder ist das Spiel im Single-Player-Modus, ist "connection" null. Dies geschähe hier, würden Sie den Klienten starten, ehe Sie den Server gestartet haben. Dies gibt uns eine Fehlermeldung.

Ist connection nicht null, wurde der Server gestartet oder der Klient konnte mit ihm Verbindung aufnehmen. In diesem Fall warten wir bis die Variable dplay_status 2 oder höher ist. dplay_status ist sowas wie eine Countdown-Variable, allerdings zählt sie hoch. 2 bedeutet, dass der Server den Klienten für die Session akzeptiert hat.

dplay_localfunction = 2;

Das Setzen von dplay_localfunction bedeutet, dass alle Entity-Aktionen genauso auf den Klienten laufen. Hätten wir diese Variable nicht gesetzt, liefen Entity-aktionen nur auf dem Server.

Danach wird das Level geladen, die Kamera positioniert und - je nachdem ob wir auf dem server oder einem Remote-Klienten sind - erstellen wir einen roten oder einen blauen Spieler:

if (connection & CONNECT_SERVER)
   ent_create ("redguard.mdl", vector (100, 50, 40), player_move);
else
   ent_create ("blueguard.mdl", vector (-100,-50, 40),player_move);

Beachten Sie die Bedingung "connection & CONNECT_SERVER". Sie ist wahr wenn die Engine als alleiniger Server läuft (connection ist 1), sie ist auch wahr, wenn sie gleichzeitig als Server und Klient läuft (connection ist 3). Hätten wir geschrieben "connection == CONNECT_SERVER", wäre die Bedingung nur dann wahr, wenn connection 1 ist. Das '&' ist die gängige Methode, ein einzelnes Flag zu vergleichen, egal, ob andere Flags gesetzt sind oder nicht.

Das Hauptgeheimnis dieses Multiplayer-Spiels ist die Funkton player_move:

action player_move()
{
  var walk_percentage = 0;
  while (1)
  {
      if (my.client_id == dplay_id) {
        my.skill1 = key_w - key_s;
        send_skill(my.skill1,SEND_UNRELIABLE|SEND_RATE);
        my.skill2 = key_a - key_d;
        send_skill(my.skill2,SEND_UNRELIABLE|SEND_RATE);
     }
    ...

Die Funktion player_move läuft auf sämtlichen PCs im Netzwerk, das haben wir per dplay_localfunction = 2 festgelegt. Also müssen wir innerhalb der Funktion entscheiden was wir machen; je nachdem ob die Aktion auf dem Server, einem Klienten oder dem speziellen Klienten, der den Player erstellt hat, läuft. Im letzten Fall ist die client_id des Players (my.client_id) dieselbe wie die ID des Klienten (dplay_id). Ist dem so, kann der Klient Tasteneingaben senden, die den Player steuern und das geschieht in den folgenden Zeilen. Allerdings kann er den Player nicht direkt bewegen - sämtliche Playerbewegungen in einem Multiplayer-Game müssen auf dem Server passieren. Nur so ist garantiert, dass Kollisionserkennung, Treffer etc. für alle verbundenen Klienten konsistent sind.

Regel 1: Bewegungen werden normalerweise auf dem Server durchgeführt.

Dies bedeutet, dass Sie die Klienten zwar eine NPC, also einen Nicht-Player-Entity (einen fliegenden Vogel z. B.), die lediglich einen visuellen Effekt hat, bewegen lassen können, die Bewegungen und vor allem Kollisionen aller Entities aber, die für den Fortgang des Spieles von Bedeutung sind (Player, Kugeln usw.) müssen auf dem Server durchgeführt werden. Der Klient sendet also Bewegungs- und Drehungsanfragen an den Server anstatt seinen Player selbst zu bewegen. Dies geschieht über die Player-Skills skill1 und skill2. Die Enginefunktion send_skill sendet einen (oder mehr) Skill(s) an den Server. Um Bandbreite einzusparen, senden wir im unsicheren Modus (SEND_UNRELIABLE) und nur 32 Mal pro Sekunde (SEND_RATE). Daraufhin nehmen skill1 und skill2 der Player Entity auf dem Server dieselben Werte an wie auf dem Player des Klienten.

Regel 2: Informationen zwischen Klienten und Server werden normalerweise per Updaten von Entities, Skills und Variablen ausgetauscht.

Mit Ausnahme von Positionen und anderen visuellen Parametern von Entities verwenden wir dazu send_funktionen. Erstere werden automatisch von Server an alle Klienten upgedated und so brauchen wir keine send_funktionen, um das Level auf sämtlichen Klienten auf dem neuesten Stand zu halten. Das wird von der Engine automatisch übernommen.

Für Bewegung und Drehung haben wir nur skill1 und skill2 benutzt, Sie könnten jedoch auch andere Skills verwenden, etwa zum Springen, Ausweichen, Schiessen etc., ganz wie in einem "echten" Spiel. Nun schauen wir mal, was der Server mit dieser Information macht:

if (connection & CONNECT_SERVER) {
  my.pan += my.skill2*5*time_step;
  var distance = my.skill1*5*time_step;
  var distance = c_move(me,vector(distance,0,0), NULL, GLIDE);
  walk_percentage += distance;
  ent_animate(me,"walk",walk_percentage,ANM_CYCLE);
}

Der Code prüft, ob er auf dem Server läuft und dreht, bewegt und animiert den Player dann gemäß der vom Klienten des Players empfangenen skill1- und skill2-Werte.

Das ist alles was wir zum Erstellen eines Multiplayer-Games brauchen! Können wir jetzt damit anfangen, die nächste Generation MMORPG zu schreiben? Zum Beantworten dieser Frage starten Sie Server und Klient wie zuvor, drücken aber, zum Aufrufen des Statistik-Panels [F11]. Nun fangen Sie an mehr Klienten zu starten. Beachten Sie, dass für jeden Klienten, den Sie starten ein neuer blauer Player erscheint. Achten Sie darauf, den Player von der Startposition wegzubewegen, ehe Sie einen neuen Klienten starten. Tun Sie das nämlich nicht, wird er sich mit dem Player des nächsten Klienten verheddern und bewegungsunfähig werden.

Beobachten Sie die bps-Anzeige. Sie zeigt die Anzahl der pro Sekunde empfangenen Bytes, während die Anzeigen von rel und unr die Anzahl der im sicheren bzw. unsicheren Modus geseendeten Bytes abbildet. Je mehr Klienten verbunden sind, umso höher wird der bps-Wert auf Klienten und Server. Mit jedem neuen Klienten erhöht sich der ankommende und hinausgehende Datenverkehr. Man kann sich leicht vorstellen, dass dies schlußendlich an eine Grenze stößt und das Spiel dann anfängt verzögert und ruckhaft zu laufen. Was können wir da tun?

Erstellen eines ernsthaften Online-Games

Wir haben ein LAN-Spiel geschrieben, das nicht wirklich dafür geeignet ist, online zu laufen. Was nun ist der Unterschied zwischen einem LAN- und einem Online-Game? Sicher, unser obiges Skript würde auch übers Internet funktionieren, wenn Sie beim Starten des Klilenten die IP-Adresse des Servers per -ip-Kommandozeilenoption angeben würden. Es mit vielen Spielern zu spielen, würde aber nicht so arg viel Spaß machen. Der Grund dafür liegt in Bandbreite und Latenz (Wartezeit). Die Bandbreite bestimmt die maximale Anzahl von Bytes pro Sekunde, die von der Netzwerkverbindung unterstützt wird und die Latenz hängt von der Übertragungszeit eines Datenpakets über das Netzwerk ab. Die Bandbreite beschränkt die zwischen Server und Klienten ausgetauschten Daten und kann - bei einem schlechten Skript - das Spiel wenn das Limit überschritten ist verlangsamen oder gar abwürgen. Latenz fügt eine beträchtliche Verzögerung - im Jargon auch Lag genannt - zwischen dem Drücken einer Taste und dem Sehen einer Reaktion auf dem Bildschirm hinzu. Entgegen anderslautenden Gerüchten hat eine Engine keinerlei Einfluß auf Internet-Bandbreite und Übertragungsgeschwindigkeit von Datenpaketen.

Ihr Skript allerdings hat einen Einfluss auf deren Effekte. Online-Games verwenden viele Tricks, um die übertragenen Daten zu verringern und den Eindruck einer schnellen Reaktion zu vermitteln. Werfen Sie einen Blick auf script25_2.c. Alle Änderungen, die Bandbreite und Verzögerung verbessern, haben wir rot markiert:

////////////////////////////////////////////////////////////////////////////
// simple lite-C online game - serious approach
////////////////////////////////////////////////////////////////////////////
#include <acknex.h>
#include <default.c>

function on_client_event() // terminate client when server disconnects
{ 
   if (event_type == EVENT_LEAVE) sys_exit("Disconnected!"); 
}

function player_remove() // remove player when client disconnects
{ 
   if (event_type == EVENT_DISCONNECT) ent_remove(me); 
}

action player_move() // control the player on its client, move it on the server, animate it on all clients
{ 
  my.event = player_remove;
  my.emask |= ENABLE_DISCONNECT;
  my.smask |= NOSEND_FRAME; // don't send animation
   
  var walk_percentage = 0;
  var skill1_old = 0, skill2_old = 0;
while (1) { if (my.client_id == dplay_id) // player created by this client? { if (key_w-key_s != my.skill1) { // forward/backward key state changed?
my.skill1 = key_w-key_s; send_skill(my.skill1,0); // send the key state in reliable mode } if (key_a-key_d != my.skill2) { // rotation key changed?
my.skill2 = key_a-key_d; send_skill(my.skill2,0); // send rotation state in reliable mode } } if (connection & CONNECT_SERVER) // running on the server? { if (my.skill1 != skill1_old) { // if movement changed
send_skill(my.skill1,SEND_ALL); // send movement to all clients
skill1_old = my.skill1;
}
if (my.skill2 != skill2_old) { // if rotation changed
send_skill(my.skill2,SEND_ALL); // send rotation to all clients
skill2_old = my.skill2;
}
}
my.pan += my.skill2*5*time_step; // rotate the entity using its skill2 var distance = my.skill1*5*time_step; c_move(me, vector(distance,0,0), NULL, GLIDE); // move it using its skill1 walk_percentage += distance; ent_animate(me,"walk",walk_percentage,ANM_CYCLE); // animate the entity wait (1); } } function main() { if (!connection) { // not started with -cl / -sv -cl? if (!session_connect(app_name,"")) // no client found on the localhost? session_open(app_name); // start as server } do { wait(1); } while (dplay_status < 2); // wait until the session is opened or joined dplay_entrate = 4; // 16 ticks/4 = 4 updates per second dplay_smooth = 0; // dead reckoning not needed dplay_localfunction = 2; level_load ("multiplayer6.wmb"); vec_set(camera.x, vector (-600, 0, 100)); // set a proper camera position if (connection & CONNECT_SERVER) { // this instance of the game runs on the server video_window(0,0,0,"Server"); ent_create ("redguard.mdl",vector(100,50,40),player_move); // then create the red guard! } else { // otherwise, it runs on a connected client video_window(0,0,0,player_name); random_seed(0); // allow random player positions ent_create ("blueguard.mdl",vector(-100+random(200),-50+random(100),40),player_move); // create the blue guard } }

Das obige Skript zeigt einige Möglichkeiten auf, ein LAN-Game fürs Online-Spielen zu verbessern. Die generelle Idee ist, dass all Klienten ihre Entities direkt bewegen und rotieren, und dass der Server lediglich die entsprechenden Skills zu den Klienten weiterleitet. Entity-Updates werden dann weniger häufig gesendet, da sie nur noch nötig sind, um Abweichungen bei Entity-Positionen und Winkeln zu korrigieren. Diese Methode hat drei Vorteile:

Die ersten main()-Zeilen sind nur den Eigentümern von Pro-Editionen von Nutzen:

if (!connection) {
  if (!session_connect(app_name,""))
    session_open(app_name);
}

Dies sucht nach einer Session auf dem lokalen Host. Wenn keine gefunden wird, wird eine als Server gestartet. Das erpart uns das dauernde Eingeben der Kommandozeilenoptionen -sv und -cl. Haben Sie aber keine Pro Edition, können Sie diese Methode problemlos ignorieren und die SED-Kommandozeileneigenschaften benutzen wir zuvor.

dplay_entrate = 4;
dplay_smooth = 0;

Es gibt weitere Verbesserungen in der Main-Funktion. Wir reduzieren die Entity-Updaterate - dplay_entrate - auf 4 Updates pro Sekunde, was für das Korrigieren der Entities völlig ausreicht. Aus dem gleichen Grund können und müssen wir nun die "Bewegungsvorhersage" - das Dead Reckoning - per dplay_smooth abschalten, da es der Eigenbewegung dazwischenfunken würde. Der Klientenname (oder einfach "Server") wird nun in der Titelleiste der video_window-Funktion angezeigt. Und der blaue Player wird nun an einer Zufallsposition im Level plaziert, so dass wir ihn nicht dauernd wegbewegen müssen.

Beachten Sie, dass wir jetzt auch eine do...while-Schleife zum Warten bis die Verbindung mit der Session steht verwenden. Das tun wir, um sicherzugehen, dass mindestens ein wait() ausgeführt wird und brauchen es für die video_window-Aufrufe, die nur dann funktionieren, wenn das Engine-Fenster geöffnet ist und das geschieht nach dem ersten Frame.

Werfen wir nun einen Blick auf die zwei neue Event-Funktionen:

function on_client_event()
{
  
if (event_type == EVENT_LEAVE) sys_exit("Disconnected!");
}

Dies ist ein Klienten-Event. Empfängt der Klient nichts mehr vom Server, wird dieser Event ausgelöst. Wir nehmen an, der Server wurde heruntergefahren, das Internet ist zusammengebrochen oder sonst irgendwas und machen einen sauberen Exit.

function player_remove()
{
  
if (event_type == EVENT_DISCONNECT) ent_remove(me);
}

Und das ist ein ähnlicher Event für die Player-Entity. Sendet der Klient, der sie erstellt hat, nicht mehr, wird EVENT_DISCONNECT ausgelöst und der Player wird vom Server entfernt. Würden wir auf diesen Event nicht reagieren, hingen die Player von nicht mehr verbundenen Klienten bis in alle Ewigkeit auf dem Server herum. Diesen Event müssen wir am Anfang der Player-Funktion anbringen.

action player_move()
{
  my.event = player_remove;
  my.emask |= ENABLE_DISCONNECT;
  my.smask |= NOSEND_FRAME;
  ...

Um zu verhindern, dass die Entity-Animation vom Server an die Klienten gesendet wird, haben wir auch das Flag NOSEND_FRAME gesetzt. Dies tun wir, um Bandbreite zu sparen. Animationsdaten betragen etwa 6 Bytes pro Update. Wenn 100 Player verbunden sind, spart uns alleine NOSEND_FRAME - bei 4 Updates pro Sekunde - über 200000 bps an ausgehendem Server-Traffic!

Wenn der Server jedoch keine Animationsupdates mehr sendet, müssen die Klienten die Animationen selbst ausführen. Dazu kommen wir bald, vorher schauen Sie noch auf den Teil, der die Bewegungen steuert:

if (my.client_id == dplay_id) {
  if (key_w-key_s != my.skill1) {
    my.skill1 = key_w-key_s;
    send_skill(my.skill1,0);
  }
}

Wir vergleichen jetzt den Status der Taste mit ihrem früheren Status, der in skill1 gespeichert ist und senden den Tastenzustand nur dann, wenn er sich verändert hat. Weil jeder gesendete Wert jetzt von Bedeutung ist, müssen wir ihn im zuverlässigen Modus und ohne SEND_RATE senden. Auch wenn wir nur die Tastenänderungen senden, nimmt dies immer noch eine Menge an Traffic von den Klienten zum Server weg. Wir tun das gleiche mit der Vorwärts/Rückwärts-Bewegung, für die wir skill2 verwenden. Hier ist übrigens eine Gelegenheit zum Verbessern, wenn wir mehr Tasten benutzen: ein Skill hat 32 Bits, also könnten wir ganz einfach die 2 Tasten in 2 Bits eines einzelnen Skills komprimieren und hätten sogar noch Platz für 30 Weitere.

if (connection & CONNECT_SERVER)
{
  if (my.skill1 != skill1_old) {
    send_skill(my.skill1,SEND_ALL);
    skill1_old = my.skill1;
  }
  if (my.skill2 != skill2_old) {
    send_skill(my.skill2,SEND_ALL);
    skill2_old = my.skill2;
  }
}

Nicht nur der Player-eigene Klient, sondern auch alle anderen Klienten müssen die Bewegung der Player-Entity mitbekommen. Daher sendet der Server die Rotations- und Bewegungsskills, die er vom Player-Klient erhalten hat, weiter an sämtliche verbundenen Klienten. Um Bandbreite zu sparen, werden auch hier die Skills nur gesendet, wenn sie sich verändert haben.

my.pan += my.skill2*5*time_step;
var distance = my.skill1*5*time_step;
c_move(me, vector(distance,0,0), NULL, GLIDE);
...

Was ist nun das? Uns wurde weiter oben gesagt, dass jegliche Bewegung auf dem Server stattfinden muss, aber jetzt wird der Player direkt auf jedem Klienten gedreht und bewegt! Dies geschieht um Verzögerungen zu bekämpfen. In einem FPS ist die Kamera mit dem Player verbunden. Würden wir nur eine Bewegungsanfrage an den Server schicken und darauf warten, bis der Winkel und die Position upgedated wurde, bräuchte die Kamera bis zu einer Drittelsekunde - je nach Internetverzögerung -, um auf die Taste zu reagieren, die wir gedrückt haben. Das ist nicht akzeptabel und so bewegen wir den Player direkt auf seinem Klienten, damit wir eine sofortige Reaktion erhalten.

Mit dem obigen Code brauchen wir weit weniger Bandbreite als mit der vorigen Version und haben keine Drehungsverzögerungen mehr. Haben wir jetzt den perfekten Code für ein MMORPG? Berechnen wir den Traffic unter der Annahme, dass 1000 Spieler aus aller Welt mit unserem Server verbunden sind und sich etwa 300 davon gleichzeitig durch die Gegend bewegen. Ein komprimiertes Positionsdaten-Update ist 8 Bytes lang. Obwohl wir die Update-Rate auf 4 Updates pro Sekunde reduziert haben, führt das zu einer durchschnittlich vom Server rausgehenden Datenrate von 300 Positionen x 8 Bytes x 1000 Klienten x 4 Updates = 9600000 Bytes pro Sekunde. Und das ohne Extra-Entities, Effekte usw. Ihre Internetverbindung zuhause reicht dafür vermutlich nicht aus, aber für eine T3-Verbindung mit 45 Mbps stellt dies noch kein Problem dar. Wenn Sie noch mehr Spieler zulassen wollen, brauchen Sie weitere Tricks zum Reduzieren der Datenrate - beispielsweise können Sie die Welt in Zonen unterteilen und Entity-Updates nur zu den Klienten senden, die sich in der gleichen Zone wie die upgedatete Entity befinden. Für noch riesigere Welten und mehr Spieler verteilen Sie die Welt auf mehrere Server mit eigenen Internetverbindungen. All das können Sie mit Gamestudio machen. Allerdings müssen Sie um so mehr an gründlichem Nachdenken investieren, je mehr Spieler Sie zulassen wollen

Hosting an online game

How can we set up our home PC so that people can play our online game over the internet? There are two problems. First, your PC is probably not directly connected to the Internet, but sits behind a router and a firewall. Second, the internet IP address of your PC will probably change all the time, dependent on your service provider. The solution to the first problem is Port Forwarding, to the second a DynDNS service. Here's a brief step by step instruction to set up an online game server.

========================================================================================

Nun können Sie sich einen erfahrenen Spieleprogrammierer nennen - Sie wissen (zumindest im Prinzip), wie man ein MMOG programmiert, und haben den letzten Absatz des letzten Workshops erreicht! Des letzen Workshops? Nun ja, nicht wirklich. Es gibt noch mehr zu lernen und es gibt weitere Workshops. Im Acknex User Magazine (AUM) auf der Webseite von Gamestudio finden Sie sie alle. Die Workshop-Serie geht in AUM 67 weiter, dort lernen wir alles über die lite-C pure- und legacy mode...