C4Script-Tutorial/3. Variablen
Aus Clonk Wiki
Eine Variable ist ein Platz im Speicher des Computers, an dem man Werte ablegen und später wieder abrufen kann. So kann sich der Zauberer zum Beispiel den Schnellzauber und das Tageszeitenobjekt die aktuelle Tageszeit merken.
Inhaltsverzeichnis |
Variablentypen
In C4Script besitzen alle Werte, d.h. Parameter, Rückgabewerte und eben Variablen, einen von fünf Typen:
| Typ | Beschreibung | Beispiel |
|---|---|---|
| int | int steht für Integer, also Ganzzahl. In Variablen vom Typ int lassen sich vorzeichenbehaftete Ganzzahlen mit 32 Bit speichern, und zwar im Bereich von -231 bis 231-1 (-2147483648 bis 2147483647). | 42 |
| bool | Eine Variable des Typs bool kann nur zwei Werte annehmen: Entweder false (0) oder true (1). | true |
| id | Eine id ist die ID einer Objektdefinition, wie zum Beispiel CLNK, ROCK und GOLD. | CLNK |
| object | Eine Variable dieses Typs "zeigt" auf ein sich im Spiel befindliches Objekt, zum Beispiel auf einen Clonk oder auf eine Hütte. Wichtig ist, object und id voneinder trennen zu können, wofür auch ein eigener Abschnitt reserviert ist. | FindObject(CLNK) |
| string | Beliebiger Text (eine sogenannte Zeichenkette). Wird eine Zeichenkette als Konstante angegeben, so muss diese in Anführungszeichen stehen. | "Dies ist ein String!" |
| array | Ein Feld. Ein Typ, der eine Reihe weiterer durchnummerierter Variablen aller möglichen Typen wild durcheinander enthalten kann, deren Anzahl mit GetLength abgefragt werden kann, und die mit array[index] abgerufen werden können. Die Nummerierung der Indizes der Arrays beginnt mit 0. Ab CR. | Initialisierung:
varZugriff:
|
Dieser Typ wird von der Engine automatisch bestimmt und muss meist nicht explizit angegeben werden. Allerdings wird er von der Engine benutzt, um sicherzustellen, dass Werte nicht an grob unpassenden Stellen übergeben werden (siehe auch Konvertierungen).
Gültigkeitsbereiche
Variablen existieren in drei Gültigkeitsbereichen. Verlassen sie den Gültigkeitsbereich, so werden sie zerstört und der reservierte Speicher freigegeben. Somit sollte man eine Variable nie länger verwenden, als man sie braucht. Diese drei Gültigkeitsbereiche sind die folgenden:
| Gültigkeitsbereich | Beschreibung |
|---|---|
| Threadlokal | Threadlokale Variable existieren für die Dauer des Aufrufes einer Funktion. Wird die Funktion z.B. durch return beendet, wird der Gültigkeitsbereich verlassen und die Variablen hierfür gelöscht. Wird eine Funktion mehrfach aufgerufen, so hat jede Funktion eine eigene Variable. |
| Lokal | Lokale Variablen existieren einmal pro Objekt. Drei Objekte haben daher auch drei unterschiedliche Variablen mit unterschiedlichen Werten. So lassen sich Werte über Funktionsgrenzen hinweg für ein Objekt speichern. |
| Global | Globale Variablen gelten immer und überall und sind somit von überall erreich- und veränderbar. Sie werden erst wieder zurückgesetzt, wenn die Engine beendet wird. |
Noch wichtiger als bei Funktionen ist es hier, den kleinsten möglichen Gültigkeitsbereich zu wählen, da sonst sinnlos Speicherplatz verschwendet wird. Viel schlimmer noch: bei einem Fehler im Code weiß man nicht, wo man anfangen soll ihn zu suchen, da Variablen an allen möglichen Stellen falsche Werte zugewiesen werden könnten.
Die Funktion Log
Bevor wir mit Variablen fortfahren, möchte ich hier noch die Funktion Log vorstellen. Mit Log kann man eine Zeichenfolge in die Log-Datei Clonk4.log schreiben, welche auch im MessageBoard zu sehen ist. Mit einigen Platzhaltern kann man dabei Werte einbinden:
Log("6 + 5 = %d",
Add(6,
5)
);
Der Platzhalter "%d" wird hier mit einer Zahl ersetzt, welche im nächsten Parameter von Log angegeben ist. Dieser Parameter ist in diesem Fall der Rückgabewert der Funktion Add mit den Parameter 6 und 5. Es ist ohne weiteres möglich noch weitere %d in den ersten Parameter einzubauen, die einzusetzenden Werte werden dann aus den weiteren Parametern gelesen. Das geht solange, bis die maximal möglichen 10 Parameter nicht mehr ausreichen.
Weiterhin kann man mit %s Zeichenketten einfügen und mit %i ids. Sollte man jedoch %s angeben, dabei allerdings eine Zahl als entsprechenden Parameter übergeben, so stürzt die Engine ab. Also Vorsicht!
Nummerierte Variablen
Es gibt zwei Arten von Variablen: Nummerierte und Benannte. Zuerst werden wir uns die nummerierten anschauen. Sie werden durch eine Funktion mit einem Index angesprochen, dazu folgendes Beispiel:
- #strict
- protected
func
Initialize() - {
Var(0)
=
7;
Log("Var(0): %d",
Var(0));- }
Innerhalb der Funktion wird der Threadlokale Variable mit dem Index 0 der int-Wert 7 zugewiesen. Würde man stattdessen eine id oder eine Zeichenkette nimmt, ändert sich der Typ der Variablen automatisch. In der fünften Zeile wird mit der oben vorgestellten Funktion Log der Wert der Variablen ausgelesen. Mit der Funktion "Var" kommt man an threadlokale numerische Variablen heran. Für lokale Variablen verwendet man "Local", für globale "Global". Diese funktionieren genauso wie Var, als erster Parameter erwarten sie jeweils den Index der zu bearbeitenden Variable.
Der Wert einer Variablen kann, wie auch der Typ, jederzeit geändert werden. Man kann also in einer Variable zuerst den Wert 7 speichern und später die ID ROCK.
Benannte Variablen
Kommen wir nun zu den benannten Variablen. Wie die Parameter haben sie einen Namen statt, wie bei den numerischen, einen Index. Obiges Beispiel würde mit benannten Variablen so funktionieren:
- #strict
- protected
func
Initialize() - {
var
my_var
=
7;
Log("my_var: %d",
my_var);- }
Mit dem Schlüsselwort "var" (man beachte die Kleinschreibung) wird eine neue threadlokale benannte Variable deklariert. Wie im obigen Beispiel wird ihr hier der Wert 7 zugewiesen, welcher anschließend ausgegeben wird.
Damit kennen wir auch die dritte Möglichkeit Parameter zu übergeben: Wir verwenden eine Variable, der darin gespeicherte Wert wird dann übergeben. Auf dieselbe Art kann man auch Parameter über mehrere Funktionsaufrufe hinweg übergeben.
In der Regel sollte man benannte Variablen den numerischen immer vorziehen, da sie einen Namen haben, der sie einfacher identifizierbar macht und man somit den Code besser lesen und Fehler besser beseitigen kann.
Lokale Variablen werden mit "local", globale mit "static" deklariert. Dies muss jedoch außerhalb von Funktionen geschehen, da sie ja nicht an eine bestimmte Funktion gebunden sind:
- #strict
- local
lifetime; - protected
func
Initialize() - {
lifetime
=
10;- }
Deklariert wird die Variable lifetime außerhalb jeglicher Funktion, in Initialize kann ihr dann ein Startwert zugewiesen werden. Genauso verhält es sich mit globalen benannten Variablen.
Als Name (auch für Funktionen) gültig sind alle Zeichen (außer ß und Umlaute), alle Ziffern und der Unterstrich (_), allerdings darf ein Variablenname (genausowenig wie ein Funktionsname) nicht mit einer Ziffer beginnen.
Operatoren
Sicher fragst du dich nun, was man denn nun mit Variablen so alles anstellen kann außer eine Zahl darin zu speichern und diese dann gleich wieder auszugeben.
Mit Operatoren kann man Variablen nun verarbeiten. Einen Operator haben wir oben bereits kennengelernt: Den Zuweisungsoperator (=). Er weist dem Linken Wert den rechten zu. Dazu muss der linke Wert ein Wert sein, dem man etwas zuweisen kann (ein sogenannter L-value). Welchem Wert kann man denn nichts zuweisen? Die Antwort ist einfach: Einer Konstanten. Folgendes ist also nicht möglich:
7
=
9Man kann der 7 nicht einfach den Wert 9 zuweisen, das verbietet ja auch schon die Mathematik. Wie bei einer Parameterübergabe kann der rechte Wert (R-value) eine Konstante, eine Variable oder der Rückgabewert eines Funktionsaufrufs darstellen:
- var
my_var_1
=
7; - var
my_var_2
=
my_var_1; - var
my_var_3
=
Add(my_var1,
my_var2);
Alle drei Zuweisungen sind legal. Am Ende stehen in my_var_1 und my_var_2 jeweils der Wert 7 und in my_var_3 14 (da in Add my_var_1 (7) und my_var_2 (7) addiert wird).
Variablen müssen nicht zwingend mit einem Wert initialisiert werden. Wird keine Initialisierung (durch den Zuweisungsoperator) durchgeführt, so enthält die Variable den Wert 0. Außerdem ist es mit Hilfe des Zuweisungsoperators möglich, den Wert einer Variablen zu ändern. Dazu ebenfalls ein kleines Beispiel:
- var
my_var_1; - var
my_var_2
=
ROCK; - my_var_1
=
5; - my_var_2
=
Add(my_var_1,
my_var_1);
In Zeile 1 wird die Variable my_var_1 lediglich deklariert, allerdings wird ihr Wert noch nicht initialisiert (und befindet sich auf 0). Die Variable my_var_2 wird in Zeile2 mit der ID ROCK initialisiert. Zeile 3 setzt den Wert von my_var_1, der sich bisher auf 0 befang, auf 5. Zu beachten ist, dass das Schlüsselwort "var" nicht nochmals aufzuführen ist, da keine neue Variable eingeführt wird. Zeile 4 setzt den Wert von my_var_2 (der bisher die ID ROCK enthält) auf die Addition von my_var_1 und my_var_1. Damit ändert sich automatisch der Typ der Variablen von id auf int. Auch möglich ist es, eine Variable mehrmals als Parameter an eine Funktion zu übergeben.
Natürlich gibt es noch weitere Operatoren als den Zuweisungsoperator. Dazu zählen "+=", "-=", "*=", "/=", "%=". Auch diese Operatoren nehmen Zuweisungen vor, auf der linken Seite der Zuweisung muss also ein L-value stehen.
Was diese Operatoren bewirken schauen wir uns anhand des folgenden Beispiels an:
- var
val_1
=
55; - val_1
+=
30; - val_1
*=
Add(val_1,
-80); - val_1
%=
5;
Eine Variable namens val_1 wird mit dem Wert 55 initialisiert. In der zweiten Zeile wird mithilfe des Plusgleich-Operators "+=" 30 dazuaddiert, also steht nun 85 in my_val1. In Zeile 3 wird zuerst mit Hilfe der Funktion Add 80 von val_1 abgezogen (bzw. -80 hinzuaddiert), und das Ergebnis davon (5) wird mit dem Wert durch den Malgleich-Operator mit val_1 malgenommen, in val_1 befindet sich nun also der Wert 425. %= bedeutet ist der Modulogleich-Operator. Modulo ist einfach der Rest einer Division von Ganzzahlen. 425 / 5 gibt 85, Rest 0. Diese 0 wird val_1 durch den Modulogleich-Operator also in Zeile 4 der Variablen my_val zugewiesen.
Das ist natürlich kein sinnvoller Code, aber zur Demonstrierung der Funktionsweise dieser Operatoren eben doch recht nützlich. Zusammenhängenden Code werden wir im sechsten Kapitel betrachten und analysieren.
Weiterhin ist es möglich, komplette Ausdrücke als R-value zu verwenden:
var
val_1
=
(Add(5
-
3,
6
+
4
*
2)
+
5)
/
3;
Ein Ausdruck ist ein zusammenhängender Term, der aus Funktionsaufrufen, Variablen und Konstanten besteht und optional durch Operatoren verbunden ist und einen Wert zurückliefert. Somit ist jeder Funktionsaufruf, jede Zuweisung u.s.w. ein Ausdruck.
Die Operatoren -, * und + (und auch / und %) nehmen dieselben Berechnungen wie +=, -= etc. vor, jedoch ohne das Ergebnis dem linken Wert zuzuweisen (somit können auf beiden Seiten jeweils ein R-value stehen). Das Ergebnis wird vom Operator, wie der Rückgabewert einer Funktion, zurückgeliefert.
Folgende beide Ausdrücke sind daher äquivalent: "val += 5;", "val = val + 5;". "val + 5" liefert einen Wert zurück, der dann an val zugewiesen wird.
Zurück zu obigem Code: Zuerst werden die Parameter für Add berechnet (sie stehen in der innersten Klammer): 5-3 ergibt 2 (und ist somit der erste Add-Parameter), 6 + 4*2 ergibt 14 (C4Script beachtet Punkt-Vor-Strich, dazu später noch mehr). Add addiert diese beiden Werte und gibt das Ergebnis (16) zurück. Die innerste Klammer ist nun abgearbeitet, nun gehts mit Add(...) + 5 weiter. Add hat 16 zurückgegeben, und 16 + 5 ergibt 21. 21 geteilt durch 3 ergibt 7 und ist damit das Ergebnis dieses Ausdruckes und wird so in val_1 gespeichert. Durch Klammersetzung wurde hier übrigens die Strich-Rechnung (Add(...) + 5) vor der Punkt-Rechnung (/3) durchgeführt.
Noch ein kleines Wort zu Divisionen: Eine Division durch 0 gibt ergibt in C4Script immer 0, nicht etwa einen Fehler oder derartiges. Weiterhin wird bei jeder Division immer abgerundet, da es in C4Script keinen Typ für Kommazahlen gibt.
Operatoren haben eine gewisse Priorität. Operatoren mit höherer Priorität werden in einer Operatorenkette zuerst ausgeführt. So haben * und / eine höhere Priorität als + und -, und diese wiederrum eine höhere als =, += u.s.w. Operatoren mit gleicher Priorität werden von links nach rechts ausgeführt. Durch Klammersetzung kann die Priorität beeinflusst werden.
Eine komplette Liste aller verfügbaren Operatoren mit derer Priorität findet sich in der Entwicklermodus-Dokumentation.
Referenzparameter
Um zu verstehen was Referenzparameter sind und wofür man sie benötigt muss erst noch ein wenig zur Funktionsweise der Parameterübergabe gesagt werden.
Werden Werte als Parameter übergeben, so wird für jeden Parameter eine Kopie angelegt. Dies ist an folgendem Beispiel nachvollziehbar:
Als Ausgabe in der Log-Datei Clonk4.log erhalten wir folgendes:
Vor ChangeValue. Wert: 5 In ChangeValue. Wert: 5 In ChangeValue. Wert: 42 Nach ChangeValue. Wert: 5
Wie wir sehen wird der Parameter innerhalb von ChangeValue korrekt verändert, in Initialize ist davon allerdings nichts mehr zu spüren, value hat auch nach dem Aufruf von ChangeValue weiterhin den Wert 5. Damit können wir davon ausgehen, dass die Parameter bei einem Funktionsaufruf kopiert und erst dann übergeben werden.
Mit ein wenig Nachdenken kommen wir auch zu dem Ergebnis, dass dies durchaus sinnvoll ist. Angenommen, die Parameter würden nicht kopiert, so wäre es nicht möglich, R-values als Paramerer zu übergeben:
- #strict
- protected
func
Initialize() - {
ChangeValue(5);- }
- private
func
ChangeValue(iVal) - {
iVal
=
42;- }
Dies wäre also nicht zulässig. Genausowenig könnte man Rückgabewerte von Funktionen direkt als Parameter einer weiteren Funktion (also Sachen wie: Add(Add(5,4),2) ) übergeben.
Wie verhält sich dies nun mit Objekten? Übergibt man ein Objekt an eine Funktion, wird das Objekt dann auch kopiert? Probiers mal hiermit aus:
- #strict
- protected
func
Initialize() - {
var
obj
=
CreateObject(GOLD);
RemoveObj(obj);- }
- private
func
RemoveObj(pObj) - {
RemoveObject(pObj);- }
Die Funktion CreateObject erzeugt ein neues Objekt anhand einer ID, die Funktion RemoveObject löscht eines.
Angenommen, das Objekt würde kopiert werden, so müsste ein Goldklumpen erzeugt worden sein: Durch CreateObject wird einer erzeugt, durch das Kopieren wird ein weiterer erstellt, und nur einer wird durch RemoveObject gelöscht.
Tatsächlich ist dies aber nicht der Fall. Es wird kein Goldklumpen erzeugt (bzw. einer erzeugt und gleich wieder gelöscht). Woran liegt das?
Schauen wir uns nochmal die Beschreibung des Typs object an: Wir haben es hier nur mit einem "Zeiger" zu tun, der auf das Objekt verweist. Tatsächlich wird also nur der Verweis auf das Objekt kopiert - deshalb zeigt pObj in RemoveObj auf dasselbe Objekt wie obj in Initialize.
Was also nicht möglich ist, ist die Adresse des Parameters zu ändern, also auf ein neues oder anderes Objekt zu zeigen (bzw. wird diese Änderung nur lokal in dieser Funktion sein), da man ja nur die Kopie der Adresse bearbeitet hat.
Manchmal will man jedoch den Wert direkt und nicht die Kopie ändern, um zum Beispiel zwei Werte zurückzugeben. Dies ist mit sogenannten Referenzparametern möglich. Dabei wird dann nicht eine Kopie des Parameters übergeben, sondern eine Referenz auf die übergebene Variable - und dadurch kann man sie dann auch ändern. Schauen wir uns mal das erste Beispiel dieses Abschnittes mit einem Referenzparameter an:
Als Ausgabe in der Log-Datei Clonk4.log erhalten wir folgendes:
Vor ChangeValue. Wert: 5 In ChangeValue. Wert: 5 In ChangeValue. Wert: 42 Nach ChangeValue. Wert: 42
Durch ein vorangestelltes "&" im Funktionskopf machen wir also einen Parameter zum Referenzparameter. Und wie wir sehen hat das Script nun die gewünschte Wirkung.
Man beachte, dass für Referenzparameter eine Variable übergeben werden muss - ChangeValue kann keine R-values mehr annehmen. Der Aufruf "ChangeValue(5)" ergibt mit dieser Version der Funktion einen Fehler!
Konvertierungen
Manchmal liegen Variablen nicht in dem Typ vor, in dem man ihn braucht. In diesem Fall die Variable in den neuen Typ konvertiert werden. Einige Konvertierungen nimmt die Engine bei Bedarf automatisch vor, andere kann man manuell konvertieren, und wieder andere lassen sich nicht konvertieren.
Die folgende Tabelle zeigt, welche Typen in welche andere automatisch konvertiert werden können (Diese Tabelle ist etwas erweitert auch in der Dokumentation bei den Typechecks zu finden):
| -> | int | bool | id | object | string | array |
|---|---|---|---|---|---|---|
| int | OK | OK | <= 9999 | Error | Error | Error |
| bool | OK | OK | Error | Error | Error | Error |
| id | #strict | OK | OK | Error | Error | Error |
| object | #strict | OK | Error | OK | Error | Error |
| string | #strict | OK | Error | Error | OK | Error |
| array | Error | OK | Error | Error | Error | OK |
Bei #strict wird nur dann ein Fehler ausgelöst, wenn das Script #strict ist. Dabei handelt es sich um ungefährliche, aber sinnlose Konvertierungen. Die Konvertierung von int nach id ist nur dann erlaubt, wenn der int kleiner oder gleich 9999 ist. Somit werden auch IDs unterstützt, die nur aus Zahlen bestehen (z.B. "1234").
Wird nun an einer Stelle ein anderer Datentyp gegeben als gefordert wird, so versucht die Engine ihn zu konvertieren. Gelingt dies nicht, wird ein Fehler ausgegeben. Dazu folgendes Beispiel:
CreateObject("ROCK");
Hier wird als erster Parameter für CreateObject der String "ROCK" übergeben. Da als Parameter jedoch eine id erwartet wird und string (Zeichenkette) nicht nach id konvertierbar ist, spuckt uns die Engine eine Fehlermeldung aus, wenn wir versuchen, die Anweisung auszuführen:
C4AulExec Initialize(????:NONE)::func "CreateObject" par 0: "string" cannot by converted to "id"
Dies sagt uns, dass der erste Paramter (par 0, die Zählung wird mit 0 anstatt mit 1 begonnen) ein string ist und nicht nach id konvertiert werden kann. Der Fehler kann behoben werden, indem wir die Konvertierung manuell mit der Funktion C4Id durchführen:
CreateObject(C4Id("ROCK"));
oder gleich die korrekte ID angeben:
CreateObject(ROCK);
Auf Fehlermeldungen und effektives Debuggen (Beseitigen von Fehlern) werden wir im fünften Kapitel noch genauer eingehen.
Ein Fall, in dem die Engine eine automatische Konvertierung durchführt, könnte so aussehen:
SetHostility(0,
1,
FindObject(GOLD));SetHostility verändert die Bündnisse zweier Spieler. Die ersten beiden Parameter geben dabei die Spielernummer an (hier 0 und 1, also der 1. und der 2. Spieler - es wird wieder bei 0 zu zählen angefangen). Der dritte Parameter ist ein BOOL, welcher angibt, ob die beiden Spieler verfeindet (true) oder befreundet (false) sein sollen.
Die ersten beiden Parameter sollten schnell klar sein. Anders verhält sich dies eventuell mit dem dritten. Statt einem bool geben wir hier FindObject an, welches ein object anhand einer id sucht und zurückgibt. Wie obige Tabelle zeigt kann object nach bool konvertiert werden - es wird also kein Fehler ausgelöst.
Die Konvertierung sieht dabei so aus, dass alles ungleich 0 (auch negativ) true ergibt, und 0 false ist. FindObject liefert nun einen Zeiger auf einen gefundenen Goldklumpen zurück. Dieser ist, wenn der Goldklumpen gefunden wurde, ungleich 0. Dieser Zeiger wird dann zu true - die beiden Spieler sind verfeindet. Gibt es jedoch keinen Goldklumpen im Spiel, so gibt FindObject 0 zurück (bzw. einen Zeiger, der auf die Adresse 0 zeigt - ein sogenannte Nullzeiger. An und um die Adresse 0 darf nicht zugegriffen werden, deshalb verwendet man Nullzeiger um einen ungültigen Zeiger zu kennzeichnen), welcher bei der Konvertierung zu bool nun false ergibt - die beiden Spieler sind also befreundet.
Zusammengefasst verfeindet dieser Script die Spieler genau dann, wenn Gold vorhanden ist, um das man sich streiten kann, und hebt die Verfeindung sonst auf.
Mehr über id und object
Über Variablen wäre somit alles gesagt, was es zu sagen gibt. Nun möchten wir uns einige Variablentypen näher ansehen, zuerst id und object. Wichtig ist dabei, die beiden Typen nicht zu verwechseln, oder gar keine Unterschiede zwischen ihnen zu machen: id ist die ID einer Objektdefinition, object zeigt auf ein konkretes, sich im Spiel befindliches Objekt.
Sollte ein Objekt gelöscht werden, werden alle Variablen vom Typ object, die auf dieses Objekt zeigen, in einen Nullzeiger umgewandelt.
Um aus einer id ein object zu machen, gibt es zwei Möglichkeiten. Man kann ein neues Objekt mit dieser ID erstellen, oder ein sich im Spiel befindliches Objekt herauszusuchen. Ersteres geht z.B. mit der Funktion CreateObject, wie wir es weiter oben bereits gesehen haben:
CreateObject(ROCK,
10,
10,
-1);Dieser Befehl erstellt ein Objekt vom Typ ROCK (Stein) an der Position 10/10, der niemandem gehört. Somit sehen wir, dass der erste Parameter die zu erstellende Objektdefinition (ID) angibt, die beiden nächsten Parameter die X/Y Koordinaten und der dritte Parameter den Besitzer. Dabei steht -1 für keinen Besitzer, 0 für Spieler 1, 1 für Spieler 2, und so weiter (ja, Spieler werden immer von 0 an gezählt).
CreateObject gibt das neu erstellte Objekt zurück.
Einen weiteren Unterschied zwischen den beiden Typen sehen wir hier auch noch: ids kann man als Konstanten im Code verwenden, wie es eben mit ROCK geschehen ist. Mit object ist dies nicht möglich. Man kann nur auf Objekte im Spiel zugreifen, wenn man sie erstellt oder heraussucht. Ein solches Objekt lässt sich dann in einer Variablen speichern und man kann mit ihm weiterarbeiten.
Ein Objekt suchen kann man mit der Funktion FindObject:
FindObject(ROCK,
10,
10,
50,
50);Der erste Parameter gibt wieder an, welcher Objekttyp gesucht werden soll (man kann auch 0 angeben, dann werden alle Objekte durchsucht). Die vier weiteren Parameter beschreiben ein Rechteck, in dem das Objekt gesucht werden soll: Dabei sind der zweite und dritte Parameter das obere linke Eck des Rechteckes und die darauf folgenden Breite und Höhe (jeweils in Pixeln). FindObject hat noch weitere Parameter um andere Suchkriterien heranzuziehen, doch das würde nun zu weit führen. Diese können bei Bedarf in der Dokumentation nachgeschlagen werden.
Funktionen anderer Objekte aufrufen
Wie man Funktionen im eigenen Script aufruft haben wir bereits im letzten Kapitel gelernt. Mit dem eben erworbenen Wissen über object ist es uns nun auch möglich Funktionen in anderen Objekten aufzurufen (sofern diese public sind, siehe letztes Kapitel).
Um dies zu tun braucht man erstmal einen Zeiger auf das Objekt, in dem man eine Funktion aufrufen will. Anschließend kann mit -> eine Funktion aufgerufen werden:
FindObject(CLNK)->Redefine(ACLK);
Dieser Code ruft beim ersten gefundenen Clonk (CLNK) die Funktion "Redefine" auf und übergibt die ID ACLK als ersten Parametern. Dadurch wird der erste gefundene Clonk zum Aquaclonk gemacht.
Bei -> muss sichergestellt sein, dass die aufzurufende Funktion existiert und dass es sich um keinen Nullpointer handelt, bei dem man eine Funktion aufruft (andererseits gibt es einen Fehler).
Ersteres Problem kann mit einer Tilde (~) nach der Indirektion (->) umgangen werden. Sollte die Funktion nicht existieren wird keine Funktion aufgerufen und 0 zurückgegeben:
FindObject()->~Redefine(ACLK);
Da weggelassene Parameter wie 0 ausgewertet werden findet FindObject nun irgendein Objekt und ruft Redefine auf. Sollte dieses Objekt nun keine Redefine-Methode haben wird der Fehler unterdrückt und 0 zurückgegeben.
Mehr über Zeichenketten
Zeichenketten (vom Typ string) sind ein besonders heikles Thema in C4Script. Mittlerweise lassen sie sich zwar in Variablen speichern, die Verarbeitung funktioniert jedoch nicht einfach über Operatoren. Stattdessen muss die Funktion Format hergenommen werden.
Format ist ähnlich zur weiter oben vorgestellten Funktion Log, jedoch wird der resultierende String nicht in die Log-Datei geschrieben, sondern zurückgegeben. Damit lassen sich zum Beispiel Strings verketten:
Dieses Beispiel stellt dar, wie man Strings verketten (aneinanderhängen kann). In den Variablen string_1 und string_2 werden zwei Zeichenketten gespeichert, welche mit Hilfe der Funktion Format aneinandergebunden und mit einem Leerzeichen getrennt werden. Das Ergebnis wird in der Variable res gespeichert und anschließend mit Log ausgegeben.
Einzelne Teile aus einem String herauszuextrahieren ist etwas komplizierter und kann mit den bisher vorgestellten Mitteln nicht gelöst werden. Glücklicherweise erfordert Clonk-Scripting auch in der Regel keine komplexeren Stringoperationen.
Wie man hier sieht müssen konstante Zeichenketten in Anführungszeichen eingeschlossen werden. Um Anführungszeichen in den String zu bekommen (ohne ihn dabei abzuschließen), muss man es escapen. Das funktioniert, indem man ein Backslash (\) davorschreibt:
Log("Tonk sagte:\"Hallo Welt!\"!");
Um einen Backslash in den String zu bekommen muss man diesen auch escapen - also zwei Backslashes (\\) schreiben.