Konstruktoren im GM

    • GM 7

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

    • Konstruktoren im GM

      Ein kurzer Tip am Rande mit ein wenig Bla Bla drum rum:

      Was ist ein Konstruktor?
      In objektorientierten Sprachen (C++, Delphi) gibt es Objekte. Diese müssen irgendwo im Speicher des Programms abgelegt werden, d.h. Speicher muss reserviert und ggf. initialisiert werden, und genau das macht der Konstruktor.

      Ein Konstruktor ist eine Funktion eines Objekts, die geufen wird, wenn ein Objekt erzeugt werden soll, da das häufig etwas komplizierter ist, als nur zu sagen "diese paar Byte gehören nun zu diesem Objekt".

      Warum erzähle ich das in einem Tutorialforum für den GM?
      Weil der GM auch sowas wie einen Konstruktor bereit stellt: Das Create Event.
      Wenn man die Funktion

      GML-Quellcode

      1. instance_create(x, y, obj);

      ruft, wird zunächst die Instanz von obj erzeugt, dann das Create Event ausgeführt, und erst dann wird wieder zu der Funktion zurückgekehrt, die instance_create() gerufen hat.

      Und wo ist nun das Problem?
      Der Unterschied zwischen einem Konstruktor in objektorientierten Programmiersprachen und dem Create Event im GM ist nun, dass man den Konstruktoren auch Parameter mitgeben kann, die sie dann verwenden können. Beim Create Event ist dies nicht möglich, da muss man, wenn man variable Werte von außen mitgeben möchte, erst das Create Event ablaufen lassen, und dann über

      GML-Quellcode

      1. obj_foo.bar := 42;

      oder

      GML-Quellcode

      1. with(instance_create(x,y,obj_foo))
      2. {
      3. //...
      4. }

      herumtricksen.

      Was ist nun aber, wenn das Objekt so entworfen wurde, dass das Create Event unbedingt einen oder mehrere bestimmte Parameter braucht? Und was, wenn dieses Create Event nicht gerade kurz ist?

      Jedes Mal, wenn man eine Instanz dieses Objekts erzeugen will,

      GML-Quellcode

      1. with(instance_create(x,y,obj_foo))
      2. {
      3. // unglaublich langes Create Event mit zu dieser Situation passenden Aktualparametern
      4. }

      zu rufen ist recht umständlich, und bläht den Code nur auf.

      Besser ist es zwar schon, dies alles in ein Skript auszulagern, aber auch diese Methode hat den Nachteil, dass erst das Create Event ausgeführt wird, und dann mit Variablen jongliert wird.

      Und nu?
      In gewissen Programmiersprachen gibt es immer eine Funktion mit dem Namen "main", die automatisch als erste bei Programmstart gerufen wird, und die das eigentliche Programm ist. Diese Funktion hat dann üblicherweise zwei Parameter: "argc" und "argv" (argument counter und argument vector). Die erste Variable gibt an, wieviele Parameter dem Programm übergeben wurden, der zweite enthält dann die Parameter in einem Array aus Strings (oder so ähnlich). Der GM hat das mit parameter_count() und parameter_string() übrigens auch.

      Auf dieses Prinzip kann man nun jedenfalls bei dem Problem mit dem Konstruktor zurückgreifen:

      Man richtet sich eine globale Variable "argv" ein,

      GML-Quellcode

      1. globalvar argv;

      und benutzt diese als Ablage für die Parameter.

      Nun kann man sich einen "Konstruktor" wie folgt schreiben:

      GML-Quellcode

      1. // argument0: x
      2. // argument1: y
      3. // argument2: parameter 1
      4. // argument3: parameter 2
      5. // ...
      6. argv[0] := argument2;
      7. argv[1] := argument3;
      8. // ...
      9. return instance_create(argument0, argument1, obj_foo);
      Alles anzeigen

      und im Create Event dann die Parameter auslesen:

      GML-Quellcode

      1. param1 := argv[0];
      2. param2 := argv[1];
      3. // ...
      4. // Eigentliches Create Event unter Verwendung der Parameter


      Und schon kann man über

      GML-Quellcode

      1. myObj = obj_foo_create(x,y,param1, param2, ...);

      Instanzen erzeugen und gleichzeitig Parameter übergeben.

      Ich möchte nun aber in unterschiedlichen Situationen unterschiedliche Parameter übergeben.
      Kein Problem. Entweder man reserviert sich auch "argc" (oder eine andere Variable) als weitere globale Variable oder nutzt "argv[0]", um eine Zahl quasi als "Konstruktor-ID" zu übergeben. D.h. für jede unterschiedliche Situation, in der man einen anderen Konstruktor braucht, schreibt man sich eine eigene Funktion "obj_foo_createN()", und jede schreibt dann eine eigene Zahl in argc oder argv[0]. Dann prüft man im Create Event diese Zahl, und je nach dem, welche da steht, wertet man die übrigen Werte von argv aus.

      Oder man bleibt bei einem Konstruktor, und nutzt argument2, um die ID des Konstruktors anzugeben, den man möchte.

      Ich mag aber keine globalen Variablen
      Geht auch ohne, aber die Methode mag ich nicht, deswegen reiße ich sie nur mal kurz an:
      Man erstellt sich ein Objekt "obj_Konstruktor" oder "obj_fooK", erzeugt davon im Konstruktor eine Instanz, schreibt die übergebenen Parameter dort rein, und erstellt dann das eigentliche Objekt. Dieses holt sich nun seine Parameter aus dem Konstruktor-Objekt, welches im Anschluss wieder gelöscht wird.

      Da das nur gut geht, wenn ein Objekt in seinem Create Event keine anderen Instanzen von sich selbst erzeugen soll, und weil dabei ständig temporäre Instanzen erzeugt und wieder gelöscht werden, präferiere ich doch eher die Lösung mit der einen globalen Variable.
    • CAS schrieb:

      Man richtet sich eine globale Variable "argv" ein,
      Zufälligerweise hab ich gerade heute im Informatik-Unterricht die Anwendung von "argv" in C gelernt, da kenne ich auch gleich den Hintergrund dazu ^^
      Zum Tut: Danke, sowas hab ich schon gesucht! Hab das bis jetzt viel umständlicher (und auch für den Spieler umständlicher) gelöst.
    • Öh, das mit dem hintergrund dazu ist nett zu wissen (:)), aber die Durchführung ist doch echt simpel.
      Denke fast jeder der es wirklich braucht würde es schon selber rauskriegen wie mans auf diese weise macht.
      ich habs noch nie benötigt, von daher kann ichs nicht beurteilen xD

      Und was meinst du mit "unglaublich langes Create Event mit zu dieser Situation passenden Aktualparametern"?
      Mit deiner methode ist es irgendwie umständlicher, denn man hat eine lange unübersichtliche Zeile code anstelle.
      Außerdem kann man durch diese methode kaum Funktionen anwenden und man "verschwendet" zusätzlich speicherplatz für den globalen Array :/

      Aber trotzdem ein gut geschriebenes tut :thumbsup:

      Willst du auf diese Drachen und -eier klicken?
      Sie werden sich freuen ;)
    • Naja Konstruktoren sind schon ziemlich hilfreich, aber finde ich das Create Event reicht in den meisten Fällen und wenns nicht reicht kann man das Problem leicht umgehen.
      Manchmal wäre es wirklich gut Parameter übergeben zu können, aber da wäre es besser und übersichtlicher, wenn das direkt in den GM integriert wäre.

      Achja:
      Ich warne hier einmal vor "globalvar".
      Da man kein global.* braucht um solche Variablen anzusprechen kann es leicht zu einem Variablensalat mit lokalen Variablen kommen. Vor allem bei einem großen Projekt kanns da zu seltsamen Fehlern kommen, die man nur schwer findet... Stimmts MasterXY? :P
      Daher verwende ich nur mehr "normale" globale Variablen.

      © 2008 by Teamgrill Productions
    • DragonGamer schrieb:

      Öh, das mit dem hintergrund dazu ist nett zu wissen (:)), aber die Durchführung ist doch echt simpel.
      Denke fast jeder der es wirklich braucht würde es schon selber rauskriegen wie mans auf diese weise macht.
      ich habs noch nie benötigt, von daher kann ichs nicht beurteilen xD

      Und was meinst du mit "unglaublich langes Create Event mit zu dieser Situation passenden Aktualparametern"?
      Mit deiner methode ist es irgendwie umständlicher, denn man hat eine lange unübersichtliche Zeile code anstelle.
      Außerdem kann man durch diese methode kaum Funktionen anwenden und man "verschwendet" zusätzlich speicherplatz für den globalen Array :/

      Aber trotzdem ein gut geschriebenes tut :thumbsup:


      Wer mit dem GM programmiert, der 16 Byte für einen boolschen verbraucht, sollte bei einem Array nicht von verschwendetem Speicher sprechen.

      Ein konkretes Beispiel, was ich meine, kann ich leider nicht liefern, da ich die genauen Details nicht kenne (dieses Tutorial entstand, weil sich jemand beschwerte, dass das Create Event ausgeführt wird, bevor man Variablen übergeben kann).

      Aber wenn man ein Create Event hat, das unbeding einen Wert für eine gewisse Variable (z.b. foo) braucht, um zu funktionieren, dann kann man auch statt meiner Krücke schreiben:

      GML-Quellcode

      1. with(instance_create(x,y,obj))
      2. {
      3. foo := 42;
      4. // Code, der eigentlich im Create Event stehen sollte
      5. }


      Und wie gesagt: Dies überall hinzuschreiben, wo man das betreffende Objekt erzeugen will, bläht den Code auf. Es komplett in ein Skript auszulagern macht den Code unwartbarer, weil man dann nicht nur das Objekt selbst im Auge behalten muss, sondern auch jedes Skript, das man für so eine Kapselung geschrieben hat. Wenn nun der größte Teil des Codes in jedem Create Event gleich ist, hat der auch in jedem Skript drin zu stehen (und sei es durch auslagern in eine weitere Funktion). Dann muss man entweder bei Änderungen durch alle Skripte durchgehen oder den Überblick über weitere Skripte behalten.
      Wenn der GM schon objektorientierte Ansätze besitzt, sollte man diese auch nutzen, und nicht dagegen arbeiten; Code, der nur von einem Objekt an einer Stelle ausgeführt wird, gehört in dieses Objekt an genau diese Stelle, und nicht in ein Skript.

      Dagegen ist oben beschriebene Methode ein generischer Wrapper, der nur dazu dient, Parameter an das Create Event zu übergeben. Wenn man einen Parameter zur Angabe des Objektes nutzt, kann man auch mit einem einzigen Skript auskommen:

      GML-Quellcode

      1. var i;
      2. for(i:=3; i<16; i+=1)
      3. {
      4. argv[i-3] := argument[i];
      5. }
      6. instance_create(argument0, argument1, argument2);

      das man dann mit beliebig vielen Argumenten rufen kann

      GML-Quellcode

      1. my_instance_create(x,y,obj);
      2. my_instance_create(x,y,obj, param1);
      3. my_instance_create(x,y,obj, param1, param2);
      4. // ...


      Dadurch wird nichts anderes getan, als Argumente an das Create Event weiterzureichen; das eigentliche Create Event vergleibt im Objekt.

      Und wieso eine Zeile unübersichtlicher sein soll, als so ein riesiger Codeblock, musst du mir noch erklären.

      Im übrigen verlierst du durch oben beschriebene Alternative die Möglichkeit, das Create Event des Parents zu erben, da dieses lange Skript erst gerufen wird, nachdem das tatsächliche Create Event vorbei ist.

      Edit: @Soul Reaver:
      Ich ging davon aus, dass diese globale Variable nur für diesen Zweck verwendet wird, und dass auch sonst keine Variable so heißt. In diesem Fall wäre ein global.* nur nervend, globalvar dürfte keine Fehler verursachen.
    • CAS schrieb:


      Und wieso eine Zeile unübersichtlicher sein soll, als so ein riesiger Codeblock, musst du mir noch erklären.


      ich meine wenn man sagen wir mal 4 Sachen dem Objekt übergeben will, muss man mit deinem Skript sowas schreiben:



      obj_foo_create(x,y,22,195,"Blalbla",17);



      Ich bin mir ziemlich sicher das ich spätestens nach einer Stunde nicht mehr wüsste was diese Zahlen bedeutet haben und welcher variable sie zugeordnet wurden-.-

      Willst du auf diese Drachen und -eier klicken?
      Sie werden sich freuen ;)
    • Dieses Problem löst man ganz leicht, indem man als Kommentar oben ins Skript schreibt, was jeder Parameter macht, als Kommentar direkt über den Aufruf schreibt, wozu welcher Parameter war, oder ab GM7 noch eleganter: Man schiebt die Konstruktor-Skripte in eine Extension, und hat dann den Konstruktor sowie die Bezeichnung der Parameter in der Vervollständigungsliste.
    • Soul Reaver schrieb:

      Achja:
      Ich warne hier einmal vor "globalvar".
      Da man kein global.* braucht um solche Variablen anzusprechen kann es leicht zu einem Variablensalat mit lokalen Variablen kommen. Vor allem bei einem großen Projekt kanns da zu seltsamen Fehlern kommen, die man nur schwer findet... Stimmts MasterXY? :P
      Daher verwende ich nur mehr "normale" globale Variablen.
      Pff^^

      Achja. An sich ein nettes Tutorial, ich werd trotzdem bei meinem:

      GML-Quellcode

      1. var inst;
      2. inst=instance_create()
      3. inst.bla=0
      4. usw

      bleiben. Hat für mich keinen Nachteil, ist sogar relativ nützlich, da ich so Standardparameter im Objekt festlegen kann, indem ich das Create-Event verwende, die dann durch das zuweisen überschrieben bzw geändert werden. Braucht das Objekt wirklich mal spezielle Parameter bereits im Create-Event, umgehe ich das entweder indem ich die Aktion mit alarm[0]=1 um einen step verschiebe oder Eigens dafür ein Skript anlege welches ich dann in oben angeführten Code verwende.

      © 2008 by Teamgrill Productions
    • MasterXY schrieb:

      Achja. An sich ein nettes Tutorial, ich werd trotzdem bei meinem:

      GML-Quellcode

      1. var inst;
      2. inst=instance_create()
      3. inst.bla=0
      4. usw

      bleiben. Hat für mich keinen Nachteil, ist sogar relativ nützlich, da ich so Standardparameter im Objekt festlegen kann, indem ich das Create-Event verwende, die dann durch das zuweisen überschrieben bzw geändert werden.

      Ich verfahre genauso. Es ist praktisch und bisher war ich noch in keiner Situation, die eine andere Variante benötigt hätte. Nichtsdestotrotz ein ordentliches Tutorial.
      █████ ██ █ ████ everything ███ █████ is █████ ████ ████ fine ████ ███ █ ██████ love.
      █████ ███████ ███ your █████ ████ government.
    • Da in diesem gesamten Thread immer noch kein einziges konkretes Szenario für diesen Anwendungszweck genannt wurde, und viele den Nutzen anzweifeln, möchte ich mal ein Beispiel geben, in dem Konstruktoren meiner nach die eleganteste Lösung sind, damit etwas Konkretes im Raum steht zum diskutieren:

      Stellt euch vor, ihr entwickelt ein RPG. Dabei erstellt ihr einzelne Objekte für Gegner, allerdings sollen alle Gegner ein Level haben (1 - n). Von diesem Level hängen nun sämtliche Eigenschaften des Gegners ab (Trefferpunkte, Stärke, Ausrüstung, ...). Natürlich sind dies keine einfachen lineaerer Zusammenhänge, und Zufallsfaktoren spielen auch eine Rolle. Alle diese Werte müssen also genau einmal errechnet werden, nachdem die Instanz erschaffen wurde. Wie löst man soetwas mit der "Variablen-Setz-Methode" elegant? Soll man im Step Event prüfen, ob die schoneinmal gesetzt wurden, oder wie?
    • ganz einfach (glaub die ideew urde vorhin schon genannt):
      Ins creation event des gegner schreibt man alarm[0] = 1;
      Dann wird in Alarm 0 die ganze Berechnungssache gemacht.
      Somit kann man trotzdem per with-statement oder mit adressing den level nach dem Erzeugen der Einheit einstellen.

      Willst du auf diese Drachen und -eier klicken?
      Sie werden sich freuen ;)
    • DragonGamer schrieb:

      ganz einfach (glaub die ideew urde vorhin schon genannt):
      Ins creation event des gegner schreibt man alarm[0] = 1;
      Dann wird in Alarm 0 die ganze Berechnungssache gemacht.
      Somit kann man trotzdem per with-statement oder mit adressing den level nach dem Erzeugen der Einheit einstellen.

      Führt aber dazu, dass die Instanz sich zeitweise in einem nicht vollständig initialisieren Zustand befindet. Wenn man also irgendwo später, unabhängig von der Erstellung der Instanz, schreibt:

      GML-Quellcode

      1. with (obj_gegner) {
      2. // ...
      3. }

      Dann kann das zu sehr seltsamen und eventuell schwer findbaren Problemen führen.
    • Naja, dann muss man eben drauf achten das man nicht im selben Step sowas macht, aber hast recht.

      Eine todsichere methode, die allerdings ein wenig länger ist, wäre das creation event des Objektes in ein nicht benutzes user-event zu verlagern, und dann nach dem initialisieren des Objeckte mit event_perform dieses user event aus zuführen, um das Objekt zu Ende zu initialisieren.

      Willst du auf diese Drachen und -eier klicken?
      Sie werden sich freuen ;)
    • Ich werde CAS Methode in Zukunft sicherlich nutzen. Noch vor kurzem hätte ich Konstruktoren bestens gebrauchen können, habe es jedoch bereits auf anderen Wegen gelöst. Dafür musste ein Objekt, welches für das Laden von Leveln zuständig ist, bestimmte Parameter bereits im Create Event verarbeiten.
      Solche Fälle treten bei mir äußerst oft auf, und da kommen mir Konstruktoren wirklich gelegen. Mir kommt es ein bisschen so vor, als wolle man die Methode zu unrecht schlecht reden (?).