Multiplayer- und KI-Fahrzeuge konfigurieren

  • Multiplayer- und KI-Fahrzeuge werden beim Nutzer dargestellt, aber nicht vom Nutzer gesteuert. Ihr Modell und auch ihr Script sind stark vereinfacht.

    Multiplayer und KI-Fahrzeuge, also Fremdfahrzeuge, werden üblicherweise in der Fahrzeugkonfiguration des "richtigen" Fahrzeugs eingestellt.

    1 Modell

    Visu-Flags blabla.

    2 Script

    Das Prinzip, nach dem die Scripts für die KI und den Multiplayer fit gemacht werden, werde ich zunächst am Beispiel des Multiplayers erklären.


    Man stelle sich zunächst zwei Brüder vor, einen älteren und einen jüngeren. Der Ältere war bei einem Sportkurs und hat genau erklärt bekommen, welche Übungen er zur Stärkung seines Rückens machen soll (weil er den ganzen Tag am PC hockt und nur LOTUS spielt ;) ). Der Jüngere war nicht bei dem Sportkurs, sitzt aber genauso viel am PC und spielt LOTUS. Sie wollen nun die Kosten für einen zweiten Kurs sparen. Weil der Jüngere die Übungen auch kennt, aber nicht weiß, wann er welche wie oft machen soll, machen sie die Übungen einfach zusammen und der Ältere sagt dem Jüngeren immer an, welche Übung sie als nächstes machen, wie lange und wie oft.


    Das Beispiel erklärt meiner Meinung nach sehr gut, wie der Multiplayer in LOTUS intern arbeitet.

    2.1 Problemstellung

    Das Ziel beim Multiplayer ist, dass die Kollegen den eigenen Zug genauso sehen, hören und erleben, wie ich es selbst sehe, höre und erlebe. Das Wichtigste ist selbstverständlich, dass es einerseits der richtige Zug ist (es werden also die ContentIDs der einzelnen Fahrzeuge übertragen) und dass er sich an derselben Stelle befindet (diese wird also ebenfalls übertragen). Bei den Kollegen wird mit den übertragenden ContentIDs nun ein Zug gespawnt, der mit den Positionsdaten immer an die Stelle "geschoben" wird, an der ich mich gerade befinde.


    Diesen Zug bezeichnen wir als den "Multiplayer-(MP)-Zug" oder "empfangenden Zug". Den eigenen Zug bezeichnen wir als "User-Zug" oder "sendenden Zug".


    Allerdings wäre es sehr langweilig, wenn die Kollegen meinen Zug völlig unanimiert, unbeleuchtet und stumm erleben würden. Der MP-Zug soll bei ihnen also möglichst genauso animiert und "versoundet" werden, wie mein User-Zug.

    2.2 Ungeeigneter Lösungsansatz

    Naheliegend wäre nun, wenn sämtliche öffentliche Variablen des User-Zuges einfach an den Kollegen gesendet werden, damit der MP-Zug bei ihm dieselben Animationen und Geräusche macht wie der User-Zug bei mir.


    Dieses Verfahren hat aber mehrere Nachteile:

    • Menge an zu übertragenden Daten: Eine Tram oder ein Bus verfügt - je nach Komplexität - über hunderte Variablen. Fast alle von ihnen haben interne Funktionen und werden für das äußere Erscheinungsbild und die Soundkulisse nicht benötigt.
    • Schlechte Aktualisierungsrate: Die Übertragungsgeschwindigkeit der Datenpakete ist begrenzt und das Aktualisierungsintervall deutlich geringer als die Framerate. Eine beim User-Fahrzeug schnell blinkende Leuchte (z.B. die äußeren Türschließwarnleuchten beim GT6N) wird also beim MP-Fahrzeug nur sehr unregelmäßig und wesentlich langsamer blinken. Animationen würden nur dann aktualisiert werden, wenn wieder ein neuer Variablenwert ankommt, und demzufolge mehr oder weniger ruckartig ablaufen.
    • Inkompatibel zur KI: Soll der Zug statt über den Multiplayer über die KI angesteuert werden, dann müsste diese "raten", welche Variable welche Bedeutung hat. Es wäre ihr also schlichtweg unmöglich, z.B. nachts die Scheinwerfer des Zuges einzuschalten. Über die Türsteuerung brauchen wir gar nicht erst reden...

    Aus den oben genannten Gründen wird deshalb das "Brüder-Modell" verwendet:

    2.3 Geeigneter Lösungsansatz und Vorgehen in LOTUS

    Der User-Zug ist der ältere Bruder. Bei ihm läuft das normale Script mit sämtlichen Variablen und Prozeduren. Der MP-Zug ist der jüngere Bruder. Er verfügt durchaus über Fähigkeiten und weiß, wie die eine oder andere Sache berechnet und im Detail animiert/versoundet wird. Seine Fahrzeuge verfügen also auch über Scripts, jedoch enthalten diese nur die Information, wie bestimmte Dinge (z.B. das Türenöffnen) abgearbeitet werden. Sie können aber nicht entscheiden, wann und warum etwas passiert.


    Die Scripts der User-Fahrzeuge schreiben hierfür in bestimmte standardisierte Variablen, wann sie bestimmte, für die MP-Fahrzeuge wichtige Dinge tun oder welchen Zustand bestimmte Funktionen haben. Dieselben Variablen können nach der Übertragung nun die MP-Fahrzeuge nutzen, damit ihr Script diese "Anweisungen" interpretieren können.


    Die Liste dieser speziellen Variablen ist festgelegt. Deshalb ist einerseits die Übertragung über Internet einheitlich und auf ein Mindestmaß begrenzt und andererseits können sie auch von der KI gesetzt werden, damit die von der KI gesteuerten Fahrzeuge auch entsprechende Animationen und Sounds enthalten.


    Da dieses System einer Fernsteuerung gleicht, wird auch die Bezeichnung RC (engl. remote control) verwendet. Die Liste der RC-Variablen ist hier zu finden: Lexikon-Link.


    Aus dem beschriebenen RC-System ergibt sich, dass sich das Script eines Fahrzeuges abhängig davon, ob es sich im normalen Modus befindet (als User-Fahrzeug) oder im RC-Modus (als MP-/KI-Fahrzeug) unterschiedlich verhält. Deshalb gibt es...

    • ... einen anderen Prozedur-Aufruf, wenn sich das Fahrzeug im Empfangs-/RC-Modus befindet: Statt SimStep wird SimStep_RC aufgerufen (siehe Link)
    • ... eine Variable: RC_Active, welche auf true gesetzt wird, wenn das Fahrzeug im RC-Modus läuft. Ist sie false, dann ist es im Sende-, also normalen Modus.

    Zuletzt ist noch zu beachten, dass die Variablen immer für den ganzen Zug übertragen werden. Hierdurch wird einerseits Übertragungskapazität gespart und andererseits wird es so der KI leichter gemacht. Hierdurch ergeben sich bei bestimmten Variablen einige Kniffe, die in der Liste der RC-Variablen aber je Variable erläutert werden.

    2.4 Empfehlung

    Es sollte eine separate Include-Datei angelegt werden, welche die SimStep_RC-Prozedur abarbeitet. Der zugehörige include-Befehl sollte als letztes nach allen anderen include-Befehlen stehen, damit die SimStep_RC-Prozedur auf alle vorherigen Prozeduren und Variablen zugreifen kann.

    2.5 Beispiel

    Als Beispiel soll hier die Warnglocke vom GT6N dienen:


    In der normalen SimStep-Prozedur wird zunächst ermittelt, ob die entsprechenden Umgebungsbedingungen eintreten. Ist dies der Fall, wird in die vorgesehene Variable RC_Belling eine 1 geschrieben, sonst eine 0:

    Code
    1. if (Cockpit_A.Active and Cockpit_A.Ts_Klingel.Value) or ((TractionAndBrake_FastBrake or (Cockpit_A.Active and Cockpit_A.TS_MgBremse.Value)) and (not TractionAndBrake_VehicleStopped)) then
    2. RC_Belling := 1
    3. else
    4. RC_Belling := 0;

    Anschließend wird die Prozedur SetKlingel aufgerufen, die folgendermaßen aufgebaut ist und dafür zuständig ist, die Soundvariablen anzusteuern:

    Auf diese Weise wird also im User-Fahrzeug-Betrieb beim Drücken des Klingeltasters Cockpit_A.Ts_Klingel.Value der Klingelsound abgearbeitet - die Klingel ertönt beim User - und es wird die Variable RC_Belling auf 1 gesetzt. Dieser Wert wird nun per Netzverbindung an die Kollegen gesendet, wo dasselbe Fahrzeug(script) im MP-Fahrzeug-Betrieb arbeitet. Es wird dort die Variable RC_Belling auf 1 gesetzt und SimStep_RC aufgerufen (welche in der zusätzlichen RC-Include-Datei liegt). Diese sieht schlicht folgendermaßen aus:

    Code
    1. procedure SimStep_RC;
    2. begin
    3. ...
    4. SetKlingel;
    5. ...
    6. end;

    Da SetKlingel RC_Belling prüft, wird der MP-Zug nun ebenfalls klingeln - und das können die Kollegen hören!


    Zusammenfassend kann man anhand dieses Beispiels konkret sehen, dass unsere Ziele erreicht wurden:

    • Minimaler Datenaufwand: Es muss nur eine Variable übertragen werden
    • Kein Synchronisationsproblem: Da SetKlingel auch beim MP-Fahrzeug lokal abgearbeitet wird, werden die zwei zugehörigen Sounds, die zeitkritisch aufeinander abgestimmt sind, dennoch korrekt synchronisiert. Und sollen zusätzlich die Scheinwerfer schnell blinken, würde auch das korrekt wiedergegeben werden (weil auch das ja dann lokal berechnet wird).
    • Kompatibel zur KI: Wenn die KI "entscheidet", dass der Zug klingeln soll, dann setzt sie "RC_Belling" auf 1 und der KI-Zug klingelt - und auf Wunsch blinken die Scheinwerfer! ;-)