Console

    Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

    • Da hin und wieder mal gefragt wird, wie man nun Cheats für sein Spiel einbauen kann, und ich zur Zeit ein wenig Langeweile habe, dachte ich mir, ich erklär hier mal kurz, wie man sich eine Console zurechtbastelt.
      Da jeder seine Console, wenn er denn eine einbaut, so gestalten sollte, dass sie zum Spiel passt, werde ich hier nicht darauf eingehen, wie man sie auf den Bildschirm zeichnet.

      In diesem Tutorial werden wir am Ende eine Console haben, in der man Kommandos mit bis zu 5 Parametern eingeben kann, einen Speicher der letzten 5 eingegebenen Kommandos mit der möglichkeit zwischen diesen hin und her zu "scrollen", ein paar Beispielkommandos sowie ein sehr kleines "Spiel", um die Wirksamkeit der Befehle an einem praktischen Beispiel zu demonstrieren.

      Fangen wir also an, indem wir eine neue gm6 erstellen. Dort erstellen wir einen neuen Raum, und ein neues Objekt, welches wir obj_console nennen, und in dem neuen Raum platzieren.
      Im Create Event von obj_console werden erstmal alle Variablen, die wir später benutzen werden, initialisiert, um Fehlern vorzubeugen:

      GML-Quellcode

      1. activated=0;
      2. log[0]='';
      3. log[1]='';
      4. log[2]='';
      5. log[3]='';
      6. log[4]='';
      7. log[5]='';

      Die Variable activated gibt an, ob die Console gerade zu sehen und somit benutzbar ist, oder nicht, der Array log[0..5] speichert die letzten 5 Befehle, sowie die aktuelle Eingabe in log[0].

      Im Draw Event stellen wir dann alle Aktionen rein, die die Console zeichnen, wenn activated 1 ist. Dabei ist darauf zu achten, dass log[5] bis log[0] auch irgendwo zu sehen sein sollten.

      Kommen wir also nun zur eigentlichen Technik:
      Wir erstellen ein Press Key Event, und als Taste wählen wir "Any", und legen die Aktion "Execute a piece of Code" rein.

      Als erstes überprüfen wir dort, ob die Console überhaupt an ist. Wenn nicht, so wird überprüft, ob die zuletzt gedrückte Taste, also die, die dieses Event letztendlich ausgelöst hat, die Taste ist, mit der die Console geöffnet werden soll, in diesem Beispiel ist es F1. Falls dem so ist, wird die Console aktiviert, und keyboard_string geleert, da mit der zuletzt eingegebenen Zeichenfolge noch gearbeitet werden soll. Der Code sieht dann so aus:

      GML-Quellcode

      1. switch(activated)
      2. {
      3. case 0:if(keyboard_check_pressed(vk_f1))
      4. {
      5. activated=1;
      6. keyboard_string='';
      7. };break;

      Falls die Console nun schon an sein sollte, geht die eigentliche Arbeit los.
      Es wird überprüft, welche Taste zuletzt gedrückt wurde.
      War es "Esc", soll die Console wieder geschlossen werden, und die bisherige Eingabe gelöscht werden.

      GML-Quellcode

      1. case 1:switch(keyboard_key)
      2. {
      3. case vk_escape:activated=0;log[0]='';break;

      War es die obere Cursortaste, so soll das davor eingegebene Kommando als aktuelles Kommando benutzt werden, dazu wird, bei log[5] beginnend, log[0] mit log[1] bis log[5] verglichen. Falls eine Übereinstimmung existiert, also die Cursortasten bereits benutzt wurden, wird das nächste Kommando in der Liste genommen. Wenn keine übereinstimmung gefunden wird, so sind wir bei der Durchwahl noch ganz am Anfang, und nehmen eben log[1].

      GML-Quellcode

      1. case vk_up:switch(log[0])
      2. {
      3. case log[4]:log[0]=log[5];break;
      4. case log[3]:log[0]=log[4];break;
      5. case log[2]:log[0]=log[3];break;
      6. case log[1]:log[0]=log[2];break;
      7. default: log[0]=log[1];break;
      8. };keyboard_string=log[0];break;

      War die zuletzt gedrückte Taste die untere Cursortaste, so wird, analog zu oberen, nach unten durchgewählt, nur dass, wenn man schon bei log[1] ist, und nochmal nach unten drückt, die Eingabe geleert wird.

      GML-Quellcode

      1. case vk_down:switch(log[0])
      2. {
      3. case log[2]:log[0]=log[1];break;
      4. case log[3]:log[0]=log[2];break;
      5. case log[4]:log[0]=log[3];break;
      6. case log[5]:log[0]=log[4];break;
      7. default: log[0]='';break;
      8. };keyboard_string=log[0];break;

      War die letzte Taste "Enter", so wird die Eingabe vorgenommen. Dazu wandert jeder Logeintrag eine Position nach oben (der 5. geht veloren), und log[0] sowie keyboard_string werden geleert. Anschließend wird log[1] mit einem Skript, das ich als nächstes behandeln werde, in seine Bestandteile zerlegt, und mit einem weiteren Skript, auf das ich auch noch zurück kommen werde, ausgewertet.

      GML-Quellcode

      1. case vk_enter:{
      2. log[5]=log[4]
      3. log[4]=log[3]
      4. log[3]=log[2]
      5. log[2]=log[1]
      6. log[1]=log[0]
      7. log[0]='';
      8. keyboard_string='';
      9. scr_read(log[1]);
      10. scr_execute();
      11. };break;
      Alles anzeigen

      Sollte keine dieser speziellen Tasten gedrückt worden sein, so wird in log[0] die zuletzt eingegebene Tastenfolge (keyboard_string) gespeichert. (Und deshalb sollte auch log[0] irgendwo in der Console, meist in einer extra dafür eingerichteten Eingabezeile, angezeigt werden.) Im Ganzen sieht der Code für obj_console dann so aus:

      GML-Quellcode

      1. switch(activated)
      2. {
      3. case 0:if(keyboard_check_pressed(vk_f1))
      4. {
      5. activated=1;
      6. keyboard_string='';
      7. };break;
      8. case 1:switch(keyboard_key)
      9. {
      10. case vk_escape:activated=0;log[0]='';break;
      11. case vk_up:switch(log[0])
      12. {
      13. case log[4]:log[0]=log[5];break;
      14. case log[3]:log[0]=log[4];break;
      15. case log[2]:log[0]=log[3];break;
      16. case log[1]:log[0]=log[2];break;
      17. default: log[0]=log[1];break;
      18. };keyboard_string=log[0];break;
      19. case vk_down:switch(log[0])
      20. {
      21. case log[2]:log[0]=log[1];break;
      22. case log[3]:log[0]=log[2];break;
      23. case log[4]:log[0]=log[3];break;
      24. case log[5]:log[0]=log[4];break;
      25. default: log[0]='';break;
      26. };keyboard_string=log[0];break;
      27. case vk_enter:{
      28. log[5]=log[4]
      29. log[4]=log[3]
      30. log[3]=log[2]
      31. log[2]=log[1]
      32. log[1]=log[0]
      33. log[0]='';
      34. keyboard_string='';
      35. scr_read(log[1]);
      36. scr_execute();
      37. };break;
      38. default:log[0]=keyboard_string;break;
      39. };break;
      40. }
      Alles anzeigen


      Das folgende Skript, scr_read(),zerlegt die Eingabe der Console in seine Bestandteile. Dazu wird jedes Leerzeichen als Trennzeichen zwischen dem eigentlichen Kommando und jedem Parameter angesehen.
      Bevor es allerdings irgendetwas auswertet, belegt es den Array parameter[1..5]:

      GML-Quellcode

      1. for (i=1; i<=5; i+=1)
      2. {
      3. parameter[i]='';
      4. }

      Wenn ein bestimmtes Zeichen in einem String nicht existiert, so liefert string_pos() als Position für dieses Zeichen in diesem String 0 zurück. In diesem Fall besteht die Eingabe nur aus einem Kommando, ohne parameter.

      GML-Quellcode

      1. if (string_pos(' ',argument0)==0)
      2. {
      3. command=argument0;
      4. }

      Falls aber auch nur ein Leerzeichen im String vorkommt, wird "temp" der Wert von Argument0 übergeben. (Eigentlich könnte man auch diese Zeile sparen, und überall, wo jetzt "temp" folgt "argument0" eingeben, aber zum einen ist das kürzer, und zum anderen mag ich das persönlich lieber, wenn temporäre Variablen, die ich nur zum jonglieren mit Strings brauche, temp heißen.)

      GML-Quellcode

      1. else
      2. {
      3. temp=argument0;
      4. }

      Der Anfang des Strings bis zum ersten Leerzeichen ist das eigentliche Kommando, und wird auch in dieser Variable gespeichert, allerdings ohne das Leerzeichen:

      GML-Quellcode

      1. command=string_copy(temp,1,string_pos(' ',temp)-1);
      2. i=1;

      Übersetzung: "Command sei die Kopie von Temp, Zeichen 1 bis zu dem Zeichen vor dem ersten Leerzeichen." Außerdem wird die Laufvariable i, die gleich benötigt wird schon mal auf 1 gesetzt.

      Dann beginnt eine Schleife, die, solange noch ein Leerzeichen existiert, erstmal das erste Leerzeichen und alle Zeichen davor wegscheidet:

      GML-Quellcode

      1. while (string_pos(' ',temp)!=0)
      2. {
      3. temp=string_copy(temp,string_pos(' ',temp)+1,string_length(temp)-string_pos(' ',temp));

      und danach prüft, ob das erste Zeichen Anführungszeichen sind. Dies wurde eingeführt, damit man auch Strings mit Leerzeichen übergeben kann. Falls dem so ist, wird dieses erstmal aus dem String rausgeschnitten, und dann entweder der ganze restliche String in Argument gespeichert, oder, falls noch irgendwo ein Anführungszeichen existiert, der String bis zu dem Zeichen vor diesem. Im 2. Fall muss dann der ganze String, der in den Anführungszeichen drin stand, aus dem String rausgeschnitten werden, und i wird um 1 erhöht.

      GML-Quellcode

      1. if(string_pos('"',temp)==1)
      2. {
      3. temp=string_copy(temp,2,string_length(temp)-1);
      4. if (string_pos('"',temp)==0)
      5. {
      6. parameter[i]=temp;
      7. temp='';
      8. }
      9. else
      10. {
      11. parameter[i]=string_copy(temp,1,string_pos('"',temp)-1);
      12. temp=string_copy(temp,string_pos('"',temp)+1,string_length(temp)-string_pos('"',temp));
      13. i+=1;
      14. }
      15. }
      Alles anzeigen

      Falls kein Anführungszeichen am Anfang stehen sollte, wird wie vorhin auch überprüft, ob noch ein Leerzeichen vorkommt, und in diesem Fall der String bis zu diesem in Argument[i] kopiert und i um 1 erhöht. Nachdem die Schleife abgearbeitet wurde, wird der letzte Rest auch noch in Argument[i] zugewiesen. Komplett sieht das Skript dann so aus:

      GML-Quellcode

      1. for (i=1; i<=5; i+=1)
      2. {
      3. parameter[i]='';
      4. }
      5. if (string_pos(' ',argument0)==0)
      6. {
      7. command=argument0;
      8. }
      9. else
      10. {
      11. temp=argument0;
      12. command=string_copy(temp,1,string_pos(' ',temp)-1);
      13. i=1;
      14. while (string_pos(' ',temp)!=0)
      15. {
      16. temp=string_copy(temp,string_pos(' ',temp)+1,string_length(temp)-string_pos(' ',temp));
      17. if(string_pos('"',temp)==1)
      18. {
      19. temp=string_copy(temp,2,string_length(temp)-1);
      20. if (string_pos('"',temp)==0)
      21. {
      22. parameter[i]=temp;
      23. temp='';
      24. }
      25. else
      26. {
      27. parameter[i]=string_copy(temp,1,string_pos('"',temp)-1);
      28. temp=string_copy(temp,string_pos('"',temp)+1,string_length(temp)-string_pos('"',temp));
      29. i+=1;
      30. }
      31. }
      32. else
      33. {
      34. if (string_pos(' ',temp)!=0)
      35. {
      36. parameter[i]=string_copy(temp,1,string_pos(' ',temp)-1);
      37. i+=1;
      38. }
      39. }
      40. }
      41. parameter[i]=temp;
      42. }
      Alles anzeigen


      Bis hier hin wurde nur die Eingabe von Kommandos behandelt. Die Anzahl der möglichen Parameter kann man bei bedarf durch modifizieren der Zählschleife erhöhen oder senken, und die Anzahl der Kommandogeschichte kann man durch Hinzufügen oder Löschen von log[]-variablen ändern.
      Das nun folgende Skript, scr_execute(), welches wie scr_read() von obj_console ausgeführt wird, und somit auf die selben lokalen Variablen zugreift, verarbeitet die zerlegte eingabe nun. Was jetzt kommt, ist nur ein Beispiel, und von jedem Programmierer für sein Spiel infividuell zu gestalten.

      Der Aufbau für die Auswertung ist aber immer der selbe: Zuerst wird mit einem Switch "command" überprüft, bei den Kommandos, die einen Parameter benutzen, stehen dann in dem Anweisungsblock für dieses Kommando noch Switches für die einzelnen Parameter, oder Anweisungen, um diese zu verarbeiten.

      Als Beispiel habe ich hier mal die Kommandos "quit" und "exit" zum Beenden des Spiels eingerichtet, "sv_cheats", weil das ziemlich oft vorkommt (auch wenn es hier völlig überflüssig ist), "map" um in einen anderen Raum zu kommen und "bind", um die Tasteenbelegung zu ändern.

      GML-Quellcode

      1. switch(command)
      2. {
      3. //ohne parameter--------------------
      4. case 'quit':game_end();break;
      5. case 'exit':game_end();break;
      6. //mit einem parameter---------------
      7. case 'sv_cheats':{
      8. log[0]='sv_cheats is set to "'+parameter[1]+'"';
      9. switch(parameter[1])
      10. {
      11. case '1':global.cheats=1;break;
      12. case '':log[0]='sv_cheat is "'+string(global.cheats)+'"';break;
      13. default:global.cheats=0;
      14. }
      15. log[5]=log[4];
      16. log[4]=log[3];
      17. log[3]=log[2];
      18. log[2]=log[1];
      19. log[1]=log[0];
      20. log[0]='';
      21. }break;
      22. case 'map':{
      23. switch(parameter[1])
      24. {
      25. case 'room0':room_goto(room0);break;
      26. case 'room1':room_goto(room1);break;
      27. default:{
      28. log[5]=log[4];
      29. log[4]=log[3];
      30. log[3]=log[2];
      31. log[2]=log[1];
      32. log[1]='unknown room: "'+parameter[1]+'"';
      33. }
      34. }
      35. }break;
      36. // mit 2 parametern-------------------
      37. case 'bind':scr_bind(parameter[1],parameter[2]);break;
      38. }
      Alles anzeigen


      Das script scr_bind() selbst sieht dann so aus:

      GML-Quellcode

      1. if(string_length(argument0)=1)
      2. {
      3. //Kleinbuchstaben werden in Großbuchstaben umgewandelt, da für die Tastencodes nur die ASCII Codes der Großbuchstaben benutzt werden.
      4. if (ord(argument0)>=97 && ord(argument0)<=122)
      5. {
      6. argument0=chr(ord(argument0)-32);
      7. }
      8. switch (argument1)
      9. {
      10. case '+move_up':global.move_up=ord(argument0);break;
      11. case '+move_down':global.move_down=ord(argument0);break;
      12. case '+move_right':global.move_right=ord(argument0);break;
      13. case '+move_left':global.move_left=ord(argument0);break;
      14. case 'exit':global.quit=ord(argument0);break;
      15. case 'quit':global.quit=ord(argument0);break;
      16. }
      17. }
      Alles anzeigen


      Ein Objekt namens obj_init wird erstellt, um die Variablen, die mit den Consolenkommandos belegt werden können, zu initialisieren und Fehler zu vermeiden, und sich danach selbst zu zerstören.

      In einem neuen Raum wird ein neues Objekt platziert, welches nur im Press Key (Any) Event folgenden Code bekommt:

      GML-Quellcode

      1. switch(keyboard_key)
      2. {
      3. case global.move_up:{
      4. if(y>20)
      5. {
      6. y-=4;
      7. }
      8. };break;
      9. case global.move_down:{
      10. if(y<460)
      11. {
      12. y+=4;
      13. }
      14. };break;
      15. case global.move_right:{
      16. if(x<340)
      17. {
      18. x+=4;
      19. }
      20. };break;
      21. case global.move_left:{
      22. if(x>20)
      23. {
      24. x-=4;
      25. }
      26. };break;
      27. case global.quit:{
      28. game_end()
      29. };break;
      30. }
      Alles anzeigen
      damit es sich nach den durch die Konsole belegten Tasten bewegt.

      Dies sei nun ein Beispiel, wie man die Konsole benutzen kann.

      Edit:
      Auf trickticklers Hinweis hin sind mir 2 Fehler aufgefallen, dich ich jetzt korrigiert habe. Zum einen, dass die Parameter beim Einlesen nur belegt werden, wenn überhaupt ein Parameter vorkommt, zum anderen, dass einmal ein Datentypkonflikt vorkam.
      Dateien
      • console.rar

        (3,43 kB, 367 mal heruntergeladen, zuletzt: )
    • Benutzer online 1

      1 Besucher