Programmierschwierigkeiten: FAQ

In jeder Programmiersprache kann man alles auf wenigstens drei Arten in Code fassen: richtig, falsch und hässlich. Im Folgenden haben wir einige sehr häufig auftretende Probleme gesammelt. Meist betreffen sie Anfänger, manchmal aber durchaus auch Experten.

Das Setzen von Events

F: Ich habe eine sehr einfache Event-Funktion wie diese geschrieben:
function my_collision_event() { beep(); }
und habe Sie folgendermaßen der my-Entity zugewiesen:
my.emask |= ENABLE_IMPACT;
my.event = my_collision_event();

Aber wenn die my-Entity getroffen wird, macht es nicht beep, stattdessen erscheint eine Fehlermeldung! Was ist hier los?

A: Funkitionen werden aufgerufen, indem man sie in den Code schreibt und Klammern hinzufügt. Sie haben aber die Funktion my_collision_event aufgerufen und den zurückgelieferten Wert - welcher undefiniert ist - dem Event-Pointer zugewiesen. Dies setzt den Event auf Null, oder noch schlimmer: auf einen Zufallswert, der beim Auslösen des Events aufgerufen wird und verursacht einen Absturz. Das ist nun mit Sicherheit nicht das, was Sie beabsichtigt hatten. Vermutlich meinten Sie:

my.event = my_collision_event;

Vergleichen von Variablen und Strings

F: Ich möchte prüfen ob meine Variable den Wert 0 hat oder nicht, also:

if (my_var = 0) { do something; } 

Hey, my_var erweist sich immer als 0! Was ist falsch?

A: Sie haben '=' und '==' verwechselt. Anstatt zu vergleichen, setzen Sie Ihre Variable auf 0. Dies ist gültige C-Syntax, denn in einem Vergleich lassen sich Variablen setzen. Selbst erfahrenen Programmierern passiert das gelegentlich. Der Programmierer dieser Engine hat daher die Gewohnheit angenommen, in einem Vergleich zuerst die Konstante zu plazieren. Wird nun das zweite '=' weggelassen, beschwert sich der Compiler:

if (0 == my_var) { do something; } 

F. Ok, nun habe ich ein anderes Problem und ich bin sicher, diesmal '==' benutzt zu haben! Warum ist der folgende Vergleich stets falsch?

var x = 0.6;
x += 0.1;
if (x == 0.7) { do something; } 

A: Auf einem Computer werden alle Variablen samt und sonders mit einer begrenzten Präzision gespeichert. Aus diesem Grund unterscheidet sich der tatsächliche Wert von float, double oder nicht-integrer var-Variablen geringfügig von 0.6, 0.1 oder worauf auch immer Sie sie setzen wollen. Daher lassen diese sich normalerweise nicht per '==' mit einer Zahl vergleichen. Für Fließkomma-Variablen oder nicht-integre var-Variablen funktionieren ausschließlich größer- oder kleiner-Vergleiche (<, <=, >, >=).

Q. Warum geht mein Stiring-Vergleich daneben?

STRING* string1 = "The same";
STRING* string2 = "The same";

...
if (string1 == string2) { do something; } 

A. Sie vergleichen keine Strings sondern String-Pointer. string1 und string2 sind verschiedene Pointer und daher geht der Vergleich immer daneben, egal ob sie denselben Text enthalten. Um die Inhalte von Strings zu vergleichen, nehmen Sie str_cmp:

if (str_cmp(string1,string2)) { do something; }   

Verwirrung mit globalen und lokalen Variablen

F: Der folgende Code, der sieben Sounds produzieren sollte, funktioniert nicht. Wenn ich aber die wait(1)-Zeile lösche, funktioniert er! Warum?

var i;
...

function seven_chimes()
{
  i = 0;
  while (i < 7) // repeat seven times
  {
     beep(); // play a chime
     i += 1; // increase the counter variable 
     wait(-0.5); // wait half a second
  }
}

A: Die globale i-Variable wird auch von anderen Funktionen Ihres Codes zum Zählen benutzt. All diese anderen Funktionen laufen während er wait-Zeit. Tritt i das erste mal auf wait(-0.5), hat es mit Sicherheit einen Wert von 1 - danach aber kann es irgendeinen x-beliebigen Wert haben, je nachdem, was andere Actionen damit angestellt haben. Mit einer lokalen Variablen wird das nicht passieren:

function seven_chimes()
{
  var i = 0;
  while (i < 7) // repeat seven times
  {
     beep(); // play a chime
     i += 1; // increase the counter variable 
     wait(-0.5); // wait half a second
  }
}

Behandeln Sie wait() als eine Funktion, die definitiv sämtliche globalen Variablen und Pointer verändern kann. Nur die my- und you-Entity-Pointer sowie lokale Variablen behalten garantiert ihren Wert während der wait-Zeit.


Verwirrung mit Pointern

F. Meine Funktion unten soll eigentlich zwei Vektoren miteinander multiplizieren und das Ergebnis zurückliefern. Aber alles was sie zurückliefert ist eine Absturz-Fehlermeldung! Wie kommt das?

function vec_multiply(VECTOR* a, VECTOR* b)
{
  VECTOR* c;
  c.x = a.x * b.x;
  c.y = a.y * b.y;
  c.z = a.z * b.z;
  return c;
}

A. Sie haben einen lokalen VECTOR-Pointer definiert, aber vergessen, diesem Pointer etwas zuzuweisen - er zeigt also einfach auf gar nichts. Alle Versuche, auf die nicht-existenten Komponenten dieses VECTORs zuzugreifen, enden in einem Absturz.

F. Ok, hier ist mein nächster Versuch. Diesmal habe ich dafür gesorgt, wirklich einen tatsächlichen VECTORen zu definieren und nicht einen bloßen Pointer darauf. Diesmal kompiliert es aber noch nicht einmal!

function vec_mul(VECTOR* a, VECTOR* b)
{
  VECTOR c;
  c.x = a.x * b.x;
  c.y = a.y * b.y;
  c.z = a.z * b.z;
  return c;
}

A. Eine Funktion kann nur einen einzelnen Wert zurückliefern. Es können also Variablen zurückgeliefert werden, oder Pointer auf Strukts, ganze Strukts können Funktionen aber nicht zurückgeben

F: Ok, nächster Versuch: Ich weiß, daß ich den Pointer von etwas durch ein vorgestelltes '&' bekommen kann. Nun kompilliert es endlich und ich bekomme noch nicht einmal einen Absturz... einen gültigen Wert erhalte ich allerdings auch nicht. Wenn ich den zurückgelieferten VECTORen prüfe, sieht es nicht so aus, als enthielte er Multiplikationsergebnisse, sondern einfach nur Müll!

function vec_mul(VECTOR* a, VECTOR* b)
{
  VECTOR c;
  c.x = a.x * b.x;
  c.y = a.y * b.y;
  c.z = a.z * b.z;
  return &c;
}

A: Sie haben einen lokalen VECTORen definiert was bedeutet, dass er nur innerhalb einer Funktion gültig ist. Sobald die Funktion endet, verliert der VECTOR seinen Inhalt. Um dies zu beheben, könnte ich nun ausserhalb meiner Funktion einen globalen VECTOR c definieren. Aber was, wenn ich alles drinbehalten will? Ich übergebe den Ergebnisvektor einfach in der Funktions-Argumentenliste:

function vec_mul(VECTOR* a, VECTOR* b, VECTOR* c)
{
  c.x = a.x * b.x;
  c.y = a.y * b.y;
  c.z = a.z * b.z;
  return c;
}

Alternativ gibt es noch einen Trick zum Zurückliefern eines nicht-lokalen Vektors auch in einer lokalen Funktion: das Verwenden der vector()-Funktion, welche einen temporären VECTOR*-Pointer zurückliefert, der seine Werte auch außerhalb der Funktion behält. Vergessen Sie dabei aber nicht, daß dieser Pointer temporär ist - er wird aus einem internen Speicher-Pool genommen und seinen Inhalt nach etwa 60 weiteren vector()-Aufrufen verlieren.

function vec_mul(VECTOR* a, VECTOR* b)
{
  VECTOR* c = vector(0,0,0);
  c.x = a.x * b.x;
  c.y = a.y * b.y;
  c.z = a.z * b.z;
  return c;
}

Bad timing

F: Die folgenden zwei Aktionen sind zwei Entities im Level zugewiesen. Gestern haben sie perfekt funktioniert. Heute aber, nachdem etwas an einer völlig anderen Stelle geändert wurde, spuckt die zweite Aktion diese höllische Absturz-Fehlermeldung aus!

ENTITY* monster;

action become_monster() { monster = my; // set a global pointer, that can be used by other functions }

action get_monster_height() { my.z = monster.z; // use the pointer to set my vertical position to the monster's }

A: Die zweite Aktion verlangt, daß der Monster-Pointer bereits gesetzt ist. Dies jedoch hängt davon ab, ob die erste Aktion vor der zweiten gestartet wurde - und dafür gibt es lediglich eine Chance von 50%. Es ist nicht definiert, in welcher Reihenfolge die den Entities zugewiesenen Aktionen beim Laden des Levels gestartet werden. Ersetzen Sie Ihre zweite Aktion durch:

action set_monster_height()
{
  while (!monster) { wait(1); } // wait until there is a monster
  my.z = monster.z;
}

Beim Verwenden eines Pointers - selbst eines einfachen wie my oder you - denken Sie imer an die Möglichkeit, daß er zu dieser Zeit undefiniert sein könnte.

F: Ich will einen blauen und einen grünen Partikel aussenden aber ich kriege dauernd zwei grüne Partikel! Was stimmt am folgenden Code nicht?

var particle_color = 0;
... 
particle_color = 2; // this should be a blue particle
effect(particle_start,1,my.x,nullvector);
particle_color = 1; // this should be a green particle
effect(particle_start,1,my.x,nullvector);
...
function particle_start(PARTICLE* p)
{
  if (particle_color = 0) { p.red = 255; p.green = 0; p.blue = 0; }
  else if (particle_color = 1) { p.red = 0; p.green = 255; p.blue = 0; }
  else if (particle_color = 2) { p.red = 0; p.green = 0; p.blue = 255; }
  ...
}

A: Anders als beim direkten Aufrufen einer Funktion, gibt es beim indirekten Starten von Funktionen durch Anweisungen wie effect, ent_create, game_load etc. keine Garantie, daß diese sofort nach dieser Anweisung starten. Sie können bis zu zwei Frame-Zyklen später starten und bei Multiplayer-Spielen manchmal noch nicht einmal auf demselben PC! Partikelfunktionen z. B. laufen auf den Clienten wenn auf dem Server effect() ausgeführt wird. In obigem Beispie, starten beide Partikel-Aktionen mit auf 1 gesetztem particle_color (oder irgendeinem anderen Wert, den particle_color später kriegen kann). Die Lösung wäre es, entweder verschiedene Partikel-Funktionen zu benutzen oder die Partikelfarbe im 4. Parameter der effect-Funktion zu übergeben.

Schlechtes Rechnen

F: In meinem Mathematikbuch habe ich die folgende Formel zum Berechnen einer zweidimensionalen Distanz zum Ursprung gefunden - in meinem Skript aber liefert sie manchmal ein falsches Ergebnis!

dist2d = sqrt(my.x*my.x + my.y*my.y);

Die var-Variable mit all ihren Zwischenergebnissen liegt im Bereich von -9999999.999 und +999999.999. Die obige Formel wird also ungültig, sobald my.x oder my.y 1000 überschreitet (das Zwischenergebnis my.x*my.x überschreitet dann nämlich 1000000). Eine Lösung liegt darin, für solche Berechnungen die Variablen vorübergehend in float oder double zu konvertieren und das Ergebnis dann wieder zu var zurückzukonvertieren:

double fx = my.x;
double fy = my.y;
dist2d = sqrt(fx*fx + fy*fy);

Alternativ können Sie, wenn Sie erwarten, daß die Zwischenergebnisse von Formeln den Bereich überschreiten, diese mittels Multiplikation mit einem sorgfältig ausgewählten Faktor begrenzen und das Ergebnis dann mit einem Korrekturfaktoren multiplizieren:

dist2d = 10000 * sqrt((0.01*my.x)*(0.01*my.x) + (0.01*my.y)*(0.01*my.y)); // 10000 = 1/(0.01*0.01)

Diese rechnerisch identische Formel gibt my.x und my.y einen gültigen Bereich zwischen 10 und 100000 und liefert Null wenn sie unterhalb von 10 liegen. Im Normalfall überschreiten die Game-Variablen die kritischen Grenzen nicht, die einzige Ausnahmen, daß es passieren kann, ist beim Multiplizieren zweier Distanzen.

Nochmal schlechtes Timing

F: Wir haben eine Modell-Entity mit einem hellen roten Licht genau in ihrem Zentrum. Um den Lichteffekt zu verbessern, haben wir einen TGA-Sprite an dieses Modell angebracht:

action attach_sprite()
{
  set(my,BRIGHT); // make light more brilliant
  my.roll = 1;    // always face the camera
  while (1)
  {
    vec_set(my.x,you.x); // place the sprite at the xyz position of the entity that created it
    wait(1);
  }
}

action red_model()	
{
  ent_create("light.tga", my.x, attach_sprite); // attach light sprite
  ...
  patrol() // some movement action
}

Wir haben erwartet, daß das Licht-Sprite im Zentrum des Modells fixiert ist und sich mit ihm mitbewegt. Es scheint aber dauernd um einen Frame hinterherzuhinken. Und noch schlimmer: das Modell bewegt sich jetzt überhaupt nicht mehr! Was ist da los?

A: Das zweite Problem ist leicht zu beheben: wir haben vergessen, das Licht passierbar zu machen, also kollidiert das Modell dauernd mit seinem eigenen Licht. Entities, die mit anderen verbunden werden, sollten PASSABLE sein.

action attach_sprite()
{
  set(my,BRIGHT|PASSABLE); // make light more brilliant and passable
	... // etc.

Was aber sorgt für dies seltsame Zeitverzögerung? Wenn sich zwei Entities gegenseitig beeinflussen, ist es wichtig, die Reihenfolge ihrer zwei gleichzeitig laufenden Aktionen im Gedächtnis zu behalten. Sie laufen nicht wirklich gleichzeitig. Im Beispiel startet die Funktion scheduler während eines Frames erst die Aktion attach_sprite und dann die Bewegungs-Aktion des red_models. Funktionen, die zuerst gestartet werden, laufen auch zuerst. Das Sprite wird also stets auf die Position platziert, die red_model im vorangegangenen Frame innehatte. Die Lösung ist einfach: indem man die Reihenfolge der Aktionen verändert...

action red_model()
{
  patrol(); // perform move behavior first
  ent_create ("light.pcx", my.x, flare_light); // attach light sprite and start it's action after that
}

...das Licht bleibt perfekt im Zentrum der Entity. Alternativ könnten wir zum Ändern der Reihenfolge der Aktionen auch PROC_LATE verwenden:

action attach_sprite()
{
  set(my,BRIGHT|PASSABLE); // make light more brilliant and passable
  my.roll = 1;    // always face the camera
  while (1)
  {
    proc_mode = PROC_LATE;
    vec_set(my.x,you.x); // place the sprite at the xyz position of the entity that created it
 	  wait(1);
  }
}

 

► Aktuelle Version Online