Command-Scripte (EM4)

Wir verwenden Cookies, um Inhalte und Anzeigen zu personalisieren, Dienste bereitzustellen und die Zugriffe auf unsere Website zu analysieren. Außerdem werden durch unsere Partner Informationen zu Ihrer Nutzung für soziale Medien, Werbung und Analysen erfasst. Weitere Informationen

  • Informationen zu Command-Scripte für EMERGENCY 4.

    Command-Scripte


    Command-Scripte sind die Scripte, in denen man die Interaktion des Mauszeigers des Spielers in eine Handlung im Spiel umwandelt. Dies ist zum Beispiel der Fall, wenn sich durch das Kommando "Schlauch holen" der Feuerwehrmann im Spiel mit einem Schlauch ausrüstet.

    Aufbau von Command-Scripte

    Ein Command-Script folgt immer einem bestimmten Aufbau:

    Quellcode

    1. object ScriptName : CommandScript
    2. {
    3. ScriptName()
    4. {
    5. }
    6. bool CheckPossible(GameObject *Caller)
    7. {
    8. }
    9. bool CheckTarget(GameObject *Caller, Actor *Target, int childID)
    10. {
    11. }
    12. void PushActions(GameObject *Caller, Actor *Target, int childID)
    13. {
    14. }
    15. };
    Alles anzeigen

    Quellcode

    1. object ScriptName : CommandScript //Erstellt ein Command-Script mit dem Namen //ScriptName//

    Konstruktor

    Quellcode

    1. ScriptName()
    2. {
    3. }

    Dieser Teil des Script ist der sogenannte Konstruktor. Der Name des Konstruktor muss immer identisch mit dem Namen des Command-Script sein. Hier können beispielsweise das Icon oder der Cursor festgelegt werden.

    CheckPossible-Methode

    Quellcode

    1. bool CheckPossible(GameObject *Caller)
    2. {
    3. }

    In diesem Teil des Scripts wird festgelegt, wann das Script ausgeführt werden kann. Da hier das Schlüsselwort bool verwendet wird, muss dem Script ein boolscher Wert innerhalb dieser Methode zurückgegeben werden. Hier könnte man beispielsweise einschränken, dass der Command nur von einer Person ausgeführt kann, wenn die Lebensanzeige der Person komplett voll ist. Der Methode wird auch der Parameter GameObject *Caller übergeben. Die Variable Caller kann also in dieser Methode verwendet werden.

    Der Caller ist das GameObject, dass den Command ausführt. In den meisten Fällen also das selektierte GameObject.



    CheckTarget-Methode

    Quellcode

    1. bool CheckTarget(GameObject *Caller, Actor *Target, int childID)
    2. {
    3. }

    Die CheckTarget-Methode überprüft, ob ein Target für diesen Command geeignet ist. Wenn beispielsweise mit einem Polizisten eine Person festgenommen werden soll, sollte eingeschränkt werden, dass das Script nur auf eine Person und nicht auf Fahrzeuge angewendet werden kann. Auch hier werden wieder Parameter übergeben: GameObject *Caller, Actor *Target und int childID.

    Die Variable Target ist das Object, das Ziel des Command ist. Beispielsweise eine Person, die man festnehmen möchte.



    Der integer-Wert childID ist eine Zahl, die immer einem Command übergeben wird. Wird keine Zahl speziell übergeben, trägt die childID den Wert 0.



    PushActions-Methode

    Quellcode

    1. void PushActions(GameObject *Caller, Actor *Target, int childID)
    2. {
    3. }

    Die PushActions-Methode beinhaltet das eigentliche Script. Hier ist alles enthalten, was am Ende im Spiel wirklich passieren soll. Eben zum Beispiel, dass ein Polizist zu einer Person läuft und diese festnimmt. Liest man also ein Script und möchte wissen was das Script bewirkt, muss man in diesem Teil des Command-Scripts nachlesen.

    Ein Script erstellen

    Planung

    Bevor man anfängt zu scripten, muss man sich überlegen, was das Script können soll. Dies passiert in zwei Schritten:

    Planung InGame
    Zu Beginn muss man sich auf ganz einfacher Ebene überlegen, was beim Ausführen des Scripts im Spiel passieren soll. Hier im Beispiel wird ein einfaches Szenario gewählt: Das Script soll die Person mit dem Command zu einem gewählten Punkt laufen lassen. Dort soll sie eine Flasche auf den Boden legen und danach wieder zum Ausgangspunkt zurück laufen.

    Planung im Script

    An dieser Stelle wird das Ganze schon etwas komplizierter. Nun muss überlegt werden, wie die gewünschte Aktion in EMERGENCY in Form von der Programmiersprache realisiert werden kann. Hierzu tut man sich leichter, wenn man das Script in einem sogenannten Pseudecode verfasst. Bei einem Pseudecode handelt es sich nur um einen Ablaufplan, nicht um etwas, das das Spiel später verarbeiten muss. Somit kann bei diesem Schritt auf jegliche Script-Befehle verzichtet werden.

    Quellcode

    1. object ScriptName : CommandScript
    2. {
    3. ScriptName()
    4. {
    5. Nur Straßen und andere Untergrund als Zielposition
    6. }
    7. bool CheckPossible(GameObject *Caller)
    8. {
    9. keine besonderen Einschränkungen
    10. }
    11. bool CheckTarget(GameObject *Caller, Actor *Target, int childID)
    12. {
    13. keine besonderen Einschränkungen
    14. }
    15. void PushActions(GameObject *Caller, Actor *Target, int childID)
    16. {
    17. Position von Person auslesen
    18. Position von Mausklick auslesen
    19. Person zum Mausklick laufen lassen
    20. Person hinkien lassen (Animation abspielen)
    21. Flasche an Mausklick erstellen
    22. Person aufstehen lassen (Animation abspielen)
    23. Person zurück laufen lassen
    24. }
    25. };
    Alles anzeigen
    Mit diesem Pseudecode haben wir nun eine Bauanleitung für unser Script. Wenn man viel Scripterfahrung hat, reicht es so einen Ablaufplan im Kopf zu haben, jedoch kann so ein Pseudecode zu Beginn seiner Scripterkarriere eine erhebliche Erleichterung darstellen.

    Das SDK durchsuchen

    Nun ist bekannt, dass die Position einer Person ausgelesen werden muss - dies bringt uns im Script aber zunächst nicht weiter. Die gewünschte Aktion muss noch in die Scriptsprache von EMERGENCY umgesetzt werden. Hierzu kann das SDK, quasi ein Wörterbuch für das Scripting, verwendet werden. Im SDK sind alle Befehle aufgelistet, die von EMERGENCY 4 verarbeitet werden können.

    Die Begriffe wie "ActionInsertMode" oder Actor stellenKlassen dar, die die Befehle dieser Klasse beinhalten. Sie sind also sozusagen Überbegriffe für bestimmte Funktionen. Unser Ziel ist es, die Position des sogenannten "Caller" auszulesen. Der "Caller" ist unsere Einheit, die gerade den Befehl ausführt. Unsere Einheit ist eine Person, also suchen wir die Liste der Oberbegriffe nach passenden Klassen durch:
    • ActionInsertModes: Scheint nicht zu passen.
    • Actor: Wahrscheinlich auch nicht.

    • GameObject: Das könnte passen, denn diese Klasse betrifft quasi alle Objekte im Spiel.

    • Person: Das hier könnte auch passen.
    Wir öffnen also die Klasse "GameObject" und sehen alle Befehle/ Funktionen, die auf ein GameObject anwendebar sind. Mit Strg + F öffnen wir das Suchfenster des Browser. Der Befehl, eine Position auszulesen, könnte etwas mit "Pos" oder "Position" beinhalten. Nun wird nach "Pos" gesucht und ein Blick auf die Ergebnisse geworfen - eines davon sieht vielversprechend aus:

    Quellcode

    1. virtual Vector GetPosition() const;
    Würde man diesen Befehl nun aber einfach in das Script kopieren, würde der Compiler einen Fehler ausgeben. Der Compiler weiß zum Beispiel überhaupt nicht, von welchem Objekt die Position ausgelesen werden soll. Steht man noch am Anfang seiner Scripter-Karriere und weiß nicht, wie die einzelnen Befehle zu verwenden sind, kann die Suchfunktion des Forums hilfreich sein. Die Suche nach dem Begriff "GetPosition" bringt zum Beispiel nach kurzer Zeit diesen Script-Schnippsel hervor:

    Quellcode

    1. Vehicle v(Target);
    2. Vector Vec = v.GetPostion();
    Dieses Beispiel zeigt bereits, wie der Befehl verwendet wird. Wird nun die Systematik auf das Script übertragen, sieht der erste Teil in der PushActions-Methode wie nachfolgend aus:


    Quellcode

    1. void PushActions(GameObject *Caller, Actor *Target, int childID)
    2. {
    3. Person p(Caller); //Person = Datentyp, p = Variablenname, Caller = selektierte Person)
    4. Vector Pos = p.GetPosition(); //Vector = Datentyp, Pos = Variablenname
    5. }

    Zusammensetzen des Scripts

    Diese Vorgehensweise gilt für jeden einzelnen Punkt des "Drehbuchs". Beim Durchsuchen des Forums findet man übrigens immer verschiedene Möglichkeiten zur Lösung von Problemen - in der Programmierung gibt es unterschiedliche Wege, die zum gewünschten Ziel führen können. Manche sind einfacher und kürzer, manche etwas umständlicher und ausführlicher. Solange aber die Lösung des Problems erreicht wird, ist es egal, wie man an das Ziel gelangt.

    Im weiteren Verlauf könnte das Script so aussehen:

    Quellcode

    1. void PushActions(GameObject *Caller, Actor *Target, int childID)
    2. {
    3. Person p(Caller);
    4. Vector PosAnfang = p.GetPosition();
    5. Vector Pos = Game::GetCommandPos();
    6. p.PushActionMove(ACTION_NEWLIST, Pos);
    7. p.PushActionSwitchAnim(ACTION_APPEND, "kneeldown");
    8. GameObject flasche = Game::CreatObject(PFAD, "Falsche");
    9. p.PushActionSwitchAnim(ACTION_APPEND, "idle");
    10. p.PushActionMove(ACTION_APPEND, PosAnfang);
    11. }
    Alles anzeigen
    Zunächst muss allerdings noch die Variable "PFAD" deklariert werden:

    Quellcode

    1. const char PFAD[] = "mod:Prototypes/Equipemnt/bottle.e4p;
    2. const char ICON[] = "FlascheHinlegen";
    3. object ScriptName : CommandScript
    4. {
    5. ScriptName()
    6. {
    7. SetIcon(ICON);
    8. SetCursor(ICON);
    9. SetValidTargets(ACTOR_FLOOR);
    10. }
    11. bool CheckTarget(GameObject *Caller, Actor *Target, int childID)
    12. {
    13. return true;
    14. }
    15. void PushActions(GameObject *Caller, Actor *Target, int childID)
    16. {
    17. Person p(Caller);
    18. Vector PosAnfang = p.GetPosition();
    19. Vector Pos = Game::GetCommandPos();
    20. p.PushActionMove(ACTION_NEWLIST, Pos);
    21. p.PushActionSwitchAnim(ACTION_APPEND, "kneeldown");
    22. GameObject flasche = Game::CreatObject(PFAD, "Falsche");
    23. p.PushActionSwitchAnim(ACTION_APPEND, "idle");
    24. p.PushActionMove(ACTION_APPEND, PosAnfang);
    25. }
    26. };
    Alles anzeigen

    Bei dieser Gelegenheit wurde in den Konstruktur noch ein Bild für das Icon und den Cursor definiert. Da der Name der Bilder im Icon-Ordner nicht direkt in Anführungszeichen geschrieben, sondern eine Variable global definiert wurde, muss diese diese natürlich auch noch deklariert werden. Der Befehl "SetValidTargets(ACTOR_FLOOR);" schränkt die möglichen Targets ein, so dass das Script nur auf dem Boden aktiviert werden kann.

    Wird das Script nun ausgeführt, kann festgestellt werden, dass die Person zwar losläuft, aber auch sofort eine Flasche am Boden platziert wird. Der Compiler geht das komplette Script durch und führt die Befehle der Reihe nach aus. Im vorliegenden Fall wurden allerdings PushAction-Befehle verwendet. Diese werden in eine ActionList geschrieben, die nacheinander abgearbeitet wird. Die Person läuft also zunächst zur CommandPos, wenn dies erleigt ist, kniet sie sich hin, danach steht sie wieder auf usw.

    Durch "ACTION_NEWLIST" (ein ActionInsertMode) wird eine neue ActionList erstellt. Sofern es eine alte Liste gab, werden alle darin befindlichen Befehle gelöscht und nicht mehr ausgeführt. "ACTION_APPEND" hängt einfach eine weitere Methode an die Liste an. Um jetzt zu bewirken, dass die Flasche erst erstellt wird, wenn die Person auch an der Stelle ist, müssen wir uns eines kleinen Tricks bedienen, da es keine Möglichkeit gibt, diesen Spawn-Befehl auch direkt in die ActionList einzufügen. Der Trick besteht darin, das Script einfach noch einmal auszuführen und eine andere ChildID zu setzen.

    Quellcode

    1. void PushActions(GameObject *Caller, Actor *Target, int childID)
    2. {
    3. Person p(Caller);
    4. if(childID == 0)
    5. {
    6. Vector PosAnfang = p.GetPosition();
    7. Vector Pos = Game::GetCommandPos();
    8. p.PushActionMove(ACTION_NEWLIST, Pos);
    9. p.PushActionSwitchAnim(ACTION_APPEND, "kneeldown");
    10. p.PushActionExecuteCommand(ACTION_APPEND, "ScriptName");
    11. }
    12. else if(childID == 1)
    13. {
    14. GameObject flasche = Game::CreatObject(PFAD, "Falsche");
    15. p.PushActionSwitchAnim(ACTION_APPEND, "idle");
    16. p.PushActionMove(ACTION_APPEND, PosAnfang);
    17. }
    18. }
    Alles anzeigen
    Beim Ausführen des Scripts würde an dieser Stelle wieder ein Fehler entstehen: Der Vector PosAnfang ist in der if-Schleife mit childID = 1 nicht definiert, da er auf gleicher Instanz definiert wurde, also in einer Klamme, in der die zweite if-Schleife nicht liegt und folglich auch keinen Zugriff hat. Die Lösung ist einfach: Der Vector muss entweder global defniert werden, also außerhalb vom Script, oder er wird über eine Instanz durch die besagte if-Schleife definiert.


    Quellcode

    1. void PushActions(GameObject *Caller, Actor *Target, int childID)
    2. {
    3. Person p(Caller);
    4. Vector PosAnfang;
    5. if(childID == 0)
    6. {
    7. Pos = p.GetPosition();
    8. Vector Pos = Game::GetCommandPos();
    9. p.PushActionMove(ACTION_NEWLIST, Pos);
    10. p.PushActionSwitchAnim(ACTION_APPEND, "kneeldown");
    11. p.PushActionExecuteCommand(ACTION_APPEND, "ScriptName");
    12. }
    13. else if(childID == 1)
    14. {
    15. GameObject flasche = Game::CreatObject(PFAD, "Falsche");
    16. p.PushActionSwitchAnim(ACTION_APPEND, "idle");
    17. p.PushActionMove(ACTION_APPEND, PosAnfang);
    18. }
    19. }
    Alles anzeigen


    Nun ist die PushActions-Methode und der Konstruktor fertig. Infolge müssen nur noch die anderen beiden Methoden erstellt werden. Da zu Beginn beschlossen wurde, den Command nicht weiter einzuschränken, kann man die Methode CheckPossible(GameObject *Caller) einfach weglassen. Der Compiler benötigt diese Methode nicht. Anders sieht es aber mit der CheckTarget-Methode aus. Diese wird vom Compiler benötigt. Da aber keine Einschränkungen des Targets notwendig sind, wird die Methode einfach mit dem Rückgabewert true gefüllt. Nicht vergessen: Methoden mit Bool müssen immer einen Wahrheitswert zurückgeben!

    Quellcode

    1. bool CheckTarget(GameObject *Caller, Actor *Target, int childID)
    2. {
    3. return true;
    4. }

    198 mal gelesen