Funktionen

Eine Funktion besteht aus einer Liste von Anweisungen für die Engine. Wird die Funktion ausgeführt, werden die Anweisungen eine nach der anderen ausgeführt. Eine solche Aweisung kann das Zuweisen eines Wertes an eine Variable sein oder eine andere Anwender-definierte Funktion bzw. bzw. auch eine interne Engine-Funktion aufrufen. Eine Funktion wird folgendermaßen definiert:

function name (Parameter1, Parameter2...) { instruction1; instruction2; ... }

Dies erstellt eine Funktion mit dem angegebenen Namen (Name). Um ihr Information zu übermitteln, lassen sich optionale Parameter - wie etwa Variablen, Vektoren, numerische Ausdrücke oder Pointer - beim Aufrufen der Funktion übergeben. Die Funktion greift auf die Parameter unter dem in Klammern angegebenen Namen wie auf eine normale Variable zu. Wie bei allen Variablen muss darauf geachtet werden, keine reservierten Namen, wie x, y, z, my, you etc., für die Parameter zu verwenden. Die Funktionen werden zwischen die geschweiften Klammern geschrieben. Die Definition einer einfachen Funktion ohne Parameter sieht so aus:

function beep3() 
{ 
  beep();
  beep();
  beep();
} 
...
beep3(); // beeps 3 times

Diese Funktion wird immer dann ausgeführt, wenn die Engine einer Zeile begegnet, die beep3() enthält. Das nächste Beispiel ist eine funktion die ein var als Parameter nimmt:

function beeps(times)
{
  while (times > 0) { 
    beep(); 
    times -= 1; 
    wait(1); 
  }
}
...
beeps(7); // beeps seven times

Sollte außerhalb der Funktion eine Variable oder ein anderes Objekt mit demselben Namen existieren, beziehen sich die Anweisungen der Funktion auf den 'lokalen' Parameter und nicht auf die 'globale' Variable. Die Funktion kann, wie im Beispiel 'times', jedweden Parameter ändern. Der Originalparameter in der aufrufenden Funktion bleibt aber unverändert. Die Parameter können weggelassen werden, in dem Fall bleibt der Platz innerhalb der Klammern leer.

 LC  In lite-C kann eine Funktion eine beliebige Anzahl an Parametern eines jeden Typs haben. Handelt es sich bei dem Typen nicht um ein var, muß er, wie in "function beeps (int times,float loudness)", vor dem Namen des Parameters angegeben werden. In C-Skript kann eine Funktion jedoch nur bis zu 4 Parameter beinhalten und dabei muß es ich entweder um ein var, oder einen var-Pointer handeln. Da ein var auch Handles oder Objekt-Pointer speichern kann, läßt sich auch ein Handle oder ein Pointer an ein beliebiges Objekt einer Skript-Funktion übergeben. Dann müssen Sie es lediglich einem 'wirklichen' Pointer zuweisen ehe direkt auf seine Parameter zugreifen können.

Beispiel (C-Script):

// this function takes a var set to an entity pointer as argument.
function ent_init(ent)
{
   my = ent; // necessary, because my.bright = on works, but ent.bright = on won't work!
   my.bright = on;
   my.abient = 100;
   ent_animate(ent,"walk",50,0); // it does not matter whether you pass my or ent here
}
...
ent_init(you);
...

Beispiel (lite-C):

// this function takes an entity pointer as argument.
function ent_init(ENTITY* ent)
{
   set(ent,BRIGHT);
   ent.ambient(100);
   ent_animate(ent,"walk",50,0);
}
...
ent_init(you);
...

Funktionen akzeptieren auch Pointer auf Arrays oder Structs als Parameter. Um anzuzeigen, dass es sich bei einem Parameter um einen ein var-Pointer und nicht um eine einzelne Zahl handelt, schreiben wir In C-Skript ein "&" vor den Namen des Parameters. In Lite-C kennzeichnen wir dies hingegen dadurch, dass wir jeden anderen nicht-var-Parameternamen mit seinem var* Typus einleiten. Wird ein Array-Pointer an eine Funktion übergeben, kann auf seine Elemente wie üblich durch [..] -Indices zugegriffen werden:

Beispiel (C-Script):

function vector_add2(&sum,&v1,&v2)
{ // calculate the sum of two vectors
   sum[0] = v1[0] + v2[0];
   sum[1] = v1[1] + v2[1];
   sum[2] = v1[2] + v2[2];
} 	

var vector1[3] = 1,2,3;
var vector2[3] = 4,5,6;
var vector3[3];
...
vector_add2(vector3,vector1,vector2); // vector3 now contains 5,7,9

Beispiel (lite-C):

function vector_add2(var* sum,var* v1,var* v2)
{ // calculate the sum of two vectors
   sum[0] = v1[0] + v2[0];
   sum[1] = v1[1] + v2[1];
   sum[2] = v1[2] + v2[2];
} 	

var vector1[3] = { 1,2,3 };
var vector2[3] = { 4,5,6 };
var vector3[3];
...
vector_add2(vector3,vector1,vector2); // vector3 now contains 5,7,9

An Funktionen, die var-Pointer erwarten, lassen sich auch Struct-Parameter übergeben. Das ist oftmals zum Übergeben von Entity- oder View-Positionen, von Winkeln oder Skills an eine Funktion nützlich:

Beispiel (C-Script):

function vector_direction(&result,&from,&to)
{ // calculate the direction angles between two positions
    result[0] = to[0] - from[0];
    result[1] = to[1] - from[1];
    result[2] = to[2] - from[2];
    vec_to_angle(result,result);
} 		

var direction[3];
...
vector_direction(direction,my.x,your.x);
// calculates the angle pointing from my to you
vector_direction(direction,camera.x,nullvector);
// calculates the angle from the camera to the level origin

Beispiel (lite-C):

function vector_direction(var* result, var* from, var* to)
{ // calculate the direction angles between two positions
    result[0] = to[0] - from[0];
    result[1] = to[1] - from[1];
    result[2] = to[2] - from[2];
    vec_to_angle(result,result);
} 		

var direction[3];
...
vector_direction(direction,my.x,your.x);
// calculates the angle pointing from my to you
vector_direction(direction,camera.x,nullvector);
// calculates the angle from the camera to the level origin

Sie können sowohl eine Variable, als auch einen Variablen-Parameter an eine Funktion übergeben oder auch einen Variablen-Pointer. Allerdings gibt es einen feinen Unterschied. Wenn die Funktion die Variable verändert, operiert sie im ersten Fall lediglich mit einer Kopie des Variablen-Inhalts, d.h., die Veränderung geschieht lediglich intern, im Rahmen der Funktion. Die übergebene Variable selbst bleibt unverändert. Wird aber ein Variablen-Pointer übergeben, operiert die Funktion direkt mit dem Inhalt und daher wird die Variable 'global' modifiziert.

Speichern, Laden, wait

Eine Spiele-Engine ist für gewöhnlich eine Multitasking-Engine. Das bedeutet, dass viele Funktionenoder einige Instanzen ein und derselben Funktion gleichzeitig laufen können. In manchen Fällen, wie Speichern und Laden eines Spieles oder dem Leerlauf während eines wait-Zyklus, bedarf dies spezieller Vorsorgemaßnahmen.

Der momentate Zustand einer Funktion, ihre lokalen Variablen und die vordefinierten my- und you-Pointer werden während eines wait()-Aufrufs konserviert und mit der game_save function gespeichert. Dadurch wird sichergestellt, dass, wenn Sie ein vorher gespeichertes Game laden, sämtliche Funktionen auch wieder an dem Punkt weiterlaufen, an dem sie beim Speichern waren. Naja, meistens. Im Gegensatz zu my und you werden Pointer nicht gespeichert und daher nach Laden eines Spiels innerhalb von Funktionen ungültig. Das kann aber nur während wait() time geschehen und so sollten Sie sich der Tatsache bewußt sein, daß Ihre Pointer beim Laden eines Spiels nach wait() ungültig werden können. Brauchen Sie Pointer, die in Ihrer Funktion während wait() time gültig bleiben, konvertieren Sie diese vor wait() in ein Handle und danach zurück in einen Pointer.

Warte-Funktionen werden in einer Scheduler-Liste gespeichert und in der Reihenfolge ihrer letzten wait()-Ausführung aufgerufen. Durch die Variable proc_mode (lite-C) bzw. die Funktion proc_late (C-Skript) lässt sich diese Reihenfolge verändern.

Funktionsprototypen und -Pointer

Wie alle anderen Objekte auch, müssen Funktionen definiert werden bevor sie einem anderen Objekt oder einer anderen Funktion zugewiesen werden. Manchmal ist dies ungünstig, z.B. wenn zwei Funktionen einander aufrufen. In diesem Fall läßt sich eine Funkton durch einen Prototypen (C++ -Programmierer kennen das) vordefinieren. Der Prototyp besteht aus dem Funktionstitel mit den Parametern, gefolgt von einem Semikolon, aber ohne die Anweisungsliste in {...}. Beispiel:

function beep_sum(beep1,beep2); // prototype of function beep_sum        

function use_beep_sum()
{
   beep_sum(3,4); // beep_sum is used here, and needs the prototype defined before
}

function beep_sum(beep1,beep2) // this is the real function
{
   beep1 += beep2;
   while (beep1 > 0)
   {
      beep();
      beep1 -= 1;
      wait(1);
   }
}

 LC  In Lite-C kann ein Prototyp auch als Funktionspointer verwendet werden. Beispiel:

long MessageBox(HWND,char *,char *,long); // prototype of function MessageBox
...
MessageBox = DefineApi("user32!MessageBoxA"); // set the MessageBox pointer to a function in the user32.dll

Rückgabewerte

Durch die return-Anweisung kann eine Funktion einen Wert an ihren Aufrufer zurückgeben. Beispiel:
function average(a,b) 
{ 
  return((a+b)/2); 
} 


// this is called this way:
x = average(123,456); // assign the average of 123 and 456 to x
 LC  Während C-Skript lediglich ein var zurückliefern kann, können Lite-C, C, oder C++ jede Variable und jeden Pointer-Typen zurückliefern. Anstelle von "function" wird die Funktion dann aber mit dem zurückgebenen Typus, wie etwa float, ENTITY* oder was auch immer, definiert. Beispiele (Lite-C):
PANEL* emptypanel(layer) 
{ 
  return(pan_create("",layer)); 
}

float fAverage(float a, float b) 
{ 
  return((a+b)/2.0); 
} 
...
float x = fAverage(123.0,456.0); // assign the average of 123 and 456 to x

Um anzuzeigen, dass eine Funktion nichts zurückliefert, kann Sie mit dem Typus void definiert werden:

void beep_twice() 
{
  beep(); 
  beep(); 
} 

Das ist der Grund weshalb Sie die Main-Funktion manchmal als void main() anstelle von function main() definiert vorfinden. Trotzdem kann der Typ function auch zum Zürückliefern von nichts bentutzt werden, beides ist gleichwertig.

Rekursive Funktionen

Eine Funktion kann sich selbst aufrufen - das nennt man eine rekursive Funktion. Rekursive Funktione sind zur Lösung bestimmter mathematischer Probleme sowie von Problemen mit künstilicher Intelligenz (K I) hilfreich. Dies ist ein Beispiel:

function factorial(x)
{
  if (x <= 1) { return(1); }
  if (x >= 10) { return(0); } // number becomes too big...
  return (x*factorial(x-1));
}

Rekursive Funktionen müssen mit Bedacht benutzt werden und sind nur etwas für fortgeschrittene Anwender. Wenn Sie einen Fehler machen, z.B. eine Funktion, die sich unendlich selbst aufruft, kann es Ihnen leicht passieren, dass Sie einen sogenannten 'stack overflow' produzieren und der bringt den Computer zum Absturz. Die Stack-Grösse ermöglicht eine Rekursionstiefe von etwa 10.000 ineinander verschachtelte Funktionsaufrufe.

Überladenen Funktionen

 LC  In Lite-C oder C++ können Funktionen überladen sein. Das bedeutet, dass Sie verschiedene Funktionen mit demselben Namen aber unterschiedlichen Argumenten definieren können. Der Kompiler weiß anhand der übergebenen Typen und der jeweiligen Anzahl an Argumenten, welche Funktion benutzt werden muß. Beispiel:

double square(double x) 
{ 
  return(x*x); 
}
int square(int x) 
{ 
  return(x*x); 
}
...
double x = square(3.0); // calls the first square function
int i = square(3);      // calls the second square function 

 !! Gehen Sie beim Aufrufen überladener Funktionen sicher, dass die Argument-Typen exakt auf eine der definierten Funktionen passt. Die automatische Typenkonvertierung und Pointererkennung des lite-C-Kompilierers greifen hier nicht. Das bedeutet, dass Sie tatsächlich den C++ &-Operatoren für Pointer benutzen müssen. Will die Funktion einen STRING*, reicht es nicht, ein char* zu übergeben. Behalten Sie dies im Kopf beim Übergeben von Konstanten an überladene Funitionen, die möglicherweise von anderem Typus sind als Sie das erwarten. Textkonstanten wie "dies ist ein Text" sind vom Typ char* (nicht STRING*), ganze Zahlen wie 12345 sind vom Typ long (nicht var), Konstanten mit Dezimalen wie 123,456 sind vom Typ double (nicht float) und Einzelbuchstaben-Konstanten wie 'A' or 'B' gehören ebenfalls zum Typus long (nicht char). Sorgen Sie also dafür, dass für alle Argumententypen, die vorkommen können überladene Funktionen existieren. Oder Sie verwenden zum Konvertieren der Argumente in den richtigen Typus Typecasting. Beispiel:

int square(int x) 
{ 
  return(x*x); 
}
var square(var x) 
{ 
  return(x*x); 
}
...
int i = square(3); 		// calls the first square function
var a = square((var)3);   // calls the second square function 
A7.75 Wird trotzdem kein exakt passender numerischer Parameter gefunden, konvertiert der Kompilierer numerische Typen zum Finden der nächstbesten Übereinstimmung unter den überladenen Funktionen. Wird überhaupt keine Übereinstimmung gefunden, gibt der Kompilierer eine Fehlermeldung aus.

Aufrufkonventionen

 LC  In Lite-C, C,oder C++ wird zum Aufruf der Win32-API-Funktionen die __stdcall-Aufrufkonvention verwendet. In C-Skript, Lite-C und C++-Programmen ist die __cdecl-Aufrufkonvention default. Weil der Stack vom Aufrufer gelöscht wird, sind (vararg-) Funktionen mit einer unterschiedlichen Anzahl von Argumenten möglich (z.B. printf(...)).

Spezielle Funktionen

Eine Funktion namens main() wird bei beim Starten des Programms automatisch ausgeführt.  LC In Lite-C wird main() vor dem Initialisieren von Engine und Video ausgeführt. Das ermöglicht in der Main-Funktion neu definierbare Variablen wie video_mode oder dplay_maxclients vor dem ersten wait()-Aufruf.

Direkt nach der main()-Funktion werden die Funktionen, die mit ..._startup enden ausgeführt und  LC  vor dem Initialisieren von Engine und Video. Auf diese Weise kann jedes Skript seine eigene Startup-Funktion haben, die seine Variablen oder Objekte initialisiert. Beispiel:

function debug_startup() // automatically started if this script is included
{
  set(debug_panel,SHOW);
  while (1) 
  { // run forever
    debug_panel.pos_y = screen_size.y - 15;
    fps = 0.9*fps + 0.1/time_step;
    wait(1);
  }
}

 LC  Funktionen, die mit ..._event enden, werden dem entsprechenden Event automatisch zugewiesen. Eine Event-Funktion wird durch einen bestimmten Auslöser, - etwa eine Entity-Kollision, einen Knopf- oder Mausklick oder bei Spielstart beim Laden des Levels - gestartet. Eine Liste von Events findet sich im Kapitel Vordefinierte Variablen. Beispiel:

// the following function is automatically assigned to the on_x event
function on_x_event()
{
   printf("[X] key pressed!");
}

 !!  Eine Event-Funktion kann während der Ausführung einer jeglichen Engine-Funktion, die Events erlaubt - etwa einer Kollisions- oder wait()-Anweisung -, starten. Sie kann starten, wenn Sie es gar nicht erwarten. Daher muß darauf geachtet werden, daß Event-Funktionen keine globalen Variablen oder andere Funktionen, die sich darauf beziehen verändern.

Aktionen stellen eine weitere spezielle Art von Funktionen dar. Sie haben keine Parameter und liefern keinen Wert zurück, erscheinen aber in der Action-Pop-Up-Liste in WEDs Eeigenschaftspanel. Aktionen lassen sich also per WED Entities zuweisen und werden nach Laden des Levels automatisch ausgeführt. Die Namen von Aktionen sind auf 20 Zeichen begrenzt. Außer diesen gibt es keine weiteren Unterschiede zwischen Funktionen und Aktionen. Hiert unser übliches Beispiel einer Entity-Aktion, welche lediglich aus drei Anweisungen besteht und die Entity rotieren läst. Beispiel:

action ent_rotate()
{
   while (1) 
   {
     my.pan += 3*time_step;
     wait(1);
   }
}

In einer Multiplayerumgebung laufen Aktionen normalerweise nur auf dem Server; so beeinflußen sie die gesamte Spiele-Welt. Alle anderen Funktionen, die üblicherweise dem Benutzer-Interface zugewiesen sind - also Funktionen, die an Tasten oder Panel-Buttons gebunden sind - laufen dort, wo sie ausgelöst werden. in der Regel auf einem Client. In einer Ein-Spieler-Umgebung sind Server und Client derselbe PC und daher braucht es Sie nicht zu kümmern, wo Ihre Funktionen laufen.

Siehe auch:

Variables, Pointers, Structs

► Aktuelle Version Online