C4Script-Tutorial/2. Funktionen

Aus Clonk Wiki
Wechseln zu: Navigation, Suche

Die Clonk-Scriptsprache C4Script ist funktionsbasiert. Das heißt, dass man den Code nicht einfach irgendwie untereinander schreibt, sondern in verschiedene Funktionen unterteilt, die jeweils verschiedene, in sich geschlossene Aufgaben übernehmen. Funktionen ist es möglich andere Funktionen aufzurufen, damit wird der Code innerhalb der aufgerufenen Funktion ausgeführt. Ist die Funktion fertig, liefert sie einen Wert zurück und die Codeausführung geht an der Stelle weiter, an der der Funktionsaufruf stattfand.

Syntax[Bearbeiten]

Funktionskopf[Bearbeiten]

Den Funktionskopf haben wir bereits im letzten Kapitel kennen gelernt. Er leitet eine neue Funktion ein und besteht aus der Zugriffsberechtigung, dem Schlüsselwort 'func', dem Namen der Funktion und einem Klammernpaar. Die Zugriffsberechtigung bestimmt, wer die Funktion aufrufen darf:

Zugriffsberechtigung Beschreibung
private Eine private Funktion darf nur vom eigenen Objekt (also dem Objekt, das das Script ausführt) aufgerufen werden. Dazu zählen Timer- und Actioncalls und manuelle Aufrufe vom eigenen Script aus, wie wir sie in Funktionsaufrufe besprechen.
protected Eine Funktion mit der Zugriffsberechtigung protected erhält dieselben Rechte wie eine private Funktion, allerdings darf sie zusätzlich von der Engine aufgerufen werden, was wir bei den Funktionsaufrufen der Engine behandeln werden.
public Eine Funktion, die public ist, darf, zusätzlich zu protected, auch von anderen Objekten aufgerufen werden. Wie das konkret funktioniert, wirst du aber erst im nächsten Kapitel erfahren.

Vielleicht fragst du dich nun, wo der Sinn liegt, die Zugriffsrechte auf eine Funktion einzuschränken. Die Antwort ist einfach: Wenn eine Funktion nur objektinterne Aufgaben übernimmt und von außen nicht aufgerufen werden soll, so kann man sie als privat deklarieren. Geschieht nun doch ein Zugriff von außen, so reagiert die Engine mit einer Fehlermeldung. Somit lassen sich Fehler schneller aufspüren und beseitigen.

Die Angabe einer Zugriffsberechtigung ist optional, also nicht notwendig. Wird keine Zugriffsberechtigung angegeben, so wird public angenommen. Trotzdem gehört es zum sauberen Scriptstil, immer eine Zugriffsberechtigung festzulegen.

Wie ein Funktionskopf genau aussieht, haben wir ja im letzten Kapitel bereits gesehen. Trotzdem, hier ist er nochmal:

C4Script
protected
 
func
 
Initialize()

Funktionsrumpf[Bearbeiten]

Der Funktionsrumpf besteht aus einem Block, welcher von zwei geschweiften Klammern begrenzt wird. All das, was passiert, wenn die Funktion aufgerufen wird, wird in den Funktionsrumpf geschrieben. Eine komplette Funktion könnte also so aussehen:

C4Script
protected
 
func
 
Initialize()
{
}

Wird die Funktion Initialize nun aufgerufen, wird das ausgeführt, was im Funktionsrumpf steht. Hier ist das gar nichts, also beendet sich die Funktion nach Aufruf sofort wieder und die Codeausführung wird beim Aufrufer fortgesetzt.

Funktionen aufrufen[Bearbeiten]

Parameter[Bearbeiten]

Bevor wir lernen, wie man Funktionen aufruft, müssen wir noch wissen, was Parameter sind. Parameter, auch als Argumente bekannt, sind Informationen, die man an eine Funktion übergibt, damit diese etwas damit anfangen kann. So kann man einer Funktion, die ein Objekt löschen soll, als Parameter das zu löschende Objekt übergeben.

Pro Funktion sind in C4Script maximal 10 Parameter erlaubt. Jeder Parameter hat dabei einen Namen, mit dem man ihn innerhalb der Funktion ansprechen kann. Da die Benutzung der Parameter mit denen der Variablen gleichkommt, werden wir uns hier lediglich dafür interessieren, wie man Parameter deklariert und sie in der Funktion bekannt macht.

Jeder der bis zu 10 Parameter wird dabei, durch ein Komma getrennt, in das Klammenpaar des Funktionskopfes geschrieben. Der Funktionskopf einer Funktion namens "Add", die zwei Zahlen miteinander addieren soll, könnte also so aussehen:

C4Script
protected
 
func
 
Add(Sum1,
 
Sum2)

Sum1 ist hier der Name des ersten Parameters, Sum2 der des zweiten. Unter diesen beiden Namen kann man die beiden Parameter dann im Funktionsrumpf ansprechen.

Funktionsaufrufe[Bearbeiten]

Innerhalb einer Funktion kann man nun andere Funktionen aufrufen. Ist die Funktion fertig, so wird die Codeausführung wieder dort fortgesetzt, wo die Funktion aufgerufen wurde. Und so sieht ein Funktionsaufruf aus:

C4Script
  1. protected
     
    func
     
    Initialize()
  2. {
  3.   
    Add(4,
     
    7);
  4. }

Wie unschwer zu erkennen ist, rufen wir die Funktion auf, indem wir ihren Namen, gefolgt von einem Klammernpaar schreiben. Die beiden Zahlen, die hier innerhalb des Klammernpaares stehen, sind die Parameter, die an die Funktion Add übergeben werden. Auch wenn keine Parameter übergeben werden, muss das Klammernpaar vorhanden sein. Innerhalb von Add kann man also mit Sum1 die Zahl 4 ansprechen, mit Sum2 7. Würde man andere Parameter übergeben, würde sich das natürlich anders verhalten.

Es sei hinzuzufügen, dass obiges Beispiel ziemlich sinnlos ist, da wir mit dem errechneten Wert (selbst wenn wir die Add-Funktion hätten), nichts anfangen können, da wir nicht wissen, wie wir etwas mit diesem Wert anfangen bzw. ihn verarbeiten. Das schauen wir uns erst im nächsten Kapitel genauer an.

Somit haben wir hier eine von drei Möglichkeiten kennengelernt, Parameter an eine Funktion zu übergeben: Als Konstante. Wir übergeben die Konstante Zahl 4 als ersten Parameter und die Zahl 7 als zweiten. Eine weitere Möglichkeit lernen wir in Rückgabewerte, und eine dritte im nächsten Kapitel.

Rückgabewerte[Bearbeiten]

Ist eine Funktion zu einem Ergebnis gekommen, so kann sie dem Aufrufer dieses Ergebnis zurückgeben. So kann unsere Add Funktion die Summe der beiden Parameter zurückgeben. Dazu werfe ich einfach mal folgenden Code in den Raum:

C4Script
  1. public
     
    func
     
    Add(iSum1,
     
    iSum2)
  2. {
  3.   
    return
     
    iSum1
     
    +
     
    iSum2;
  4. }

Halten wir einfach fest, dass wir mit dem Schlüsselwort "return" einen Wert zurückgeben können. return kann man wie eine Funktion betrachten, deren erster Parameter zurückgegeben wird. Was hier genau geschieht und was es mit dem "+" auf sich hat, werden wir im nächsten Kapitel näher behandeln.

Wenn eine Funktion returnt, so wird die Codeausführung beim Aufrufer fortgesetzt. Das heißt, dass jeglicher Code, der nach dem return steht, nicht mehr ausgeführt wird. Wird eine Funktion ohne return beendet, so wird automatisch 0 zurückgegeben

Spätestens hier ist wohl noch zu erwähnen, dass jede Anweisung in C4Script mit einem ";" abgeschlossen wird (Der Funktionskopf nicht, er ist ja auch keine Anweisung). Somit ist es möglich, mehrere Anweisungen in eine Zeile zu schreiben oder eine Anweisung auf mehrere Zeilen aufzuteilen. Theoretisch wäre es sogar möglich, das komplette Skript in nur eine Zeile zu schreiben.

Mit dem hier zurückgegebenen Wert kann der Aufrufer nun weiterarbeiten. Welche Möglichkeiten sich dadurch ergeben wird hauptsächlich in den nächsten Kapiteln eine wichtige Rollte spielen, doch eine sei hier noch vorgestellt: Statt eine konstante Zahl als Parameter anzugeben, kann man erneut eine Funktion aufrufen. Das, was diese Funktion zurückgibt, wird dann an die erste Funktion übergeben. So können wir zum Beispiel drei Zahlen miteinander addieren:

C4Script
  1. protected
     
    func
     
    Initialize()
  2. {
  3.   
    Add(4,
     
    Add(5,
     
    8)
     
    );
  4. }

Zuerst wird die Funktion Add(5,8) aufgerufen. Das ist eigentlich wie in der Mathematik, die innersten Klammern werden zuerst abgearbeitet. Add(5,8) gibt nun 13 zurück, dann wird erneut Add (das äußere) mit den Parametern 4 und 13 aufgerufen, was 17 zurückgibt. Das lässt sich beliebig schachteln:

C4Script
  1. protected
     
    func
     
    Initialize()
  2. {
  3.   
    Add(4,
     
    Add(Add(10,
     
    5),
     
    8)
     
    );
  4. }

Zuerst wird Add(10+5) berechnet, das Ergebnis dann mit 8 addiert, und das wiederrum mit 4.

Damit kennen wir auch schon die zweite Möglichkeit, Parameter an Funktionen zu übergeben: Wir rufen eine weitere Funktion auf, deren Rückgabewert dann als Parameter verwendet wird.

Funktionsaufrufe der Engine[Bearbeiten]

An bestimmten Ereignissen ruft die Engine Funktionen im Script auf (wenn diese mindestens als protected deklariert sind). So lässt sich auf diese Ereignisse reagieren. Damit kann der Ritterclonk zum Beispiel 10 Pfeile zu einem Paket zusammenpacken, sobald er einen Pfeil aufnimmt. Unter anderem wird auch die Funktion "Initialize" aufgerufen, wenn das Objekt erstellt wird, oder in einem Szenarioscript, wenn das Szenario gestartet wird.

Nun ist auch leicht ersichtlich, wie das Hello World Script funktionierte: Zu Beginn des Szenarios wurde von der Engine die Funktion Initialize aufgrufen, welche den ScriptCounter gestartet und die Nachricht ausgegeben hat. Zehn Frames später wurde ebenfalls von der Engine durch den gestarteten ScriptCounter die Funktion Script1 aufgerufen, welche das Spiel beendet.

Funktionsaufrufe der Engine können auch Parameter erhalten. Bei der Funktion Collection zum Beispiel, welche aufgerufen wird, wenn ein Objekt eingesammelt wurde, wird das eingesammelte Objekt als erster Parameter übergeben. Wie man diesen Parameter dann nennt bleibt dem Script überlassen, das spielt keine Rolle. Eine komplette Auflistung aller Funktionsaufrufe mit ihren Parametern und wann sie aufgerufen werden, findet sich in der Entwicklermodus Dokumentation.

Timer- und Actioncalls[Bearbeiten]

Timer- und ActionCalls stellen Funktionsaufrufe da, die regelmäßig aufgerufen werden. Für den TimerCall lässt sich dazu in der DefCore einen TimerCall festlegen (TimerCall=Funktion), welcher standardmäßig alle 35 Frames (Ungefähr einmal pro Sekunde) aufgerufen wird. Dieser Wert lässt sich mit einer Eintragung wie Timer=50 in der DefCore ändern (hier würde der TimerCall nur alle 50 Frames aufgerufen werden).

ActionCalls sind Start-, End- und PhaseCalls von Actions. Innerhalb einer Action kann jeweils ein Start-, End- und PhaseCall (StartCall=, EndCall=, PhaseCall=) definiert werden. Wenn die Action startet, wird der StartCall im Script ausgeführt, wenn die Action endet der EndCall und zwischen jedem Animationsbildwechsel (Delay) der PhaseCall.

Parameter werden an Timer- und ActionCalls nicht übergeben. Zudem sollten sie nur dann eingesetzt werden, wenn sie wirklich gebraucht werden, da sie oft aufgerufen werden und daher rechenintensiv sind. In vielen Fällen lassen sie sich durch Funktionsaufrufe der Engine ersetzen.

Funktionsüberladung[Bearbeiten]

Funktionen überladen sich, wenn sie den gleichen Namen haben. Bei einem Funktionsaufruf wird dann immer die Funktion aufgerufen, welche weiter unten im Script steht. Dieser Funktion ist es dann möglich, die Funktion, die sie überlädt (also diejenige, die weiter oben steht) mit der Funktion "inherited" aufzurufen. inherited ist nur dann verfügbar, wenn das Script #strict ist.

Das macht vor allem mit #appendto Sinn. Mit #appendto kann man das eigene Script an das eines anderen Objektes "untendran anhängen". Dazu ein Beispiel:

C4Script
  1. #strict 2
  2. #appendto CLNK
  3. protected
     
    func
     
    Initialize()
  4. {
  5.   
    CreateContents(ROCK);
  6.   
    return
     
    inherited();
  7. }

Dieses Script wird mit #appendto an das Script des Clonkes (ID: CLNK) angehängt. Die Funktion Initialize wird so überladen, dass, wenn der Clonk erzeugt wird, er einen Stein (ROCK) im Inventar erhält, was mit der Funktion CreateContents realisiert wird (CreateContents erwartet einen Parameter: Die ID des Objektes, welches zu erstellen ist). Anschließend wird mit inherited die normale Initialize-Funktion des Clonkes aufgerufen, damit er seine eigenen Initialisierungsroutinen durchführt.

Zu erwähnen wäre eventuell noch, dass Parameter durch inherited nicht automatisch mitübergeben werden, dies muss manuell geschehen:

C4Script
  1. #strict 2
  2. #appendto CLNK
  3. protected
     
    func
     
    Collection(pObj)
  4. {
  5.   
    return
     
    inherited(pObj,
     
    ...);
  6. }

Bei dem Aufruf von inherited muss der Parameter der Funktion Collection (pObj) mitübergeben werden, damit er der überladenen Funktion (die von inherited aufgerufen wird) zur Verfügung steht. Über die Ellipse (...) werden außerdem alle zusätzlichen, nicht in der eigenen Funktionsdeklaration angegebenen Parameter nach pObj mit übergeben. Dies ist notwendig, da man nicht weiß, ob die Funktion nicht in zukünftigen Versionen zusätzliche Parameter bekommt.

inherited verursacht einen Fehler, wenn gar keine überladene Funktion existiert. Soll dieser Fehler vermieden werden, kann _inherited verwendet werden, welches sonst genauso wie inherited funktioniert.


[ Zurück (Hello World) - Inhalt - Vor (Variablen) ]