workshop17

Top  Zurück  Weiter

Workshop 17: Debugging für Fortgeschrittene

Wenn Ihr Spiel mehr als nur ein paar Code-Zeilen hat, wird es wahrscheinlich ein paar Fehler, sogenannte Bugs haben. Sie wissen jetzt, wie man "Watch" benutzt, aber was machen Sie, wenn die Werte der Variablen einfach viel zu schnell wechseln? Und was, wenn Sie nicht den Wert einer Variablen, sondern eine bestimmte Entity überwachen müssen?

Öffnen wir doch gleich mal die Script17-Datei:

///////////////////////////////
#include 
#include 

///////////////////////////////

ENTITY* plane;

action rotate_plane()
{
	plane = me;
	my.ambient = 100;
	while (1)
	{
		my.pan += 3.5 * time_step;
		if (my.pan == 360)
			sys_exit(NULL);
		wait (1);
	}

}

function main()
{
	level_load ("work17.wmb");
	wait(2);	// warte, bis das Level geladen ist
	vec_set(camera.x, vector(-500, 0, 100));
	ent_create("mig.mdl", vector(0, 0, 0), rotate_plane);
}      
Im zehnten Workshop haben wir eine ähnliche Skript-Datei verwendet aber ich habe diesen Code ein klein wenig verändert. Ich habe einen Entity-Pointer namens "plane" hinzugefügt, den ambient des Flugzeugs auf 100 gesetzt und eine Zeile hinzugefügt, die das Spiel sobald der Flieger eine volle Umdrehung (pan == 360) absolviert hat, herunterfährt. Mal sehen, ob der Code das auch tatsächlich macht!  

w17_02

Das Flugzeug dreht sich wie erwartet, allerdings beendet Lite-C das Programm nach einer vollendeten Umdrehung nicht. Tatsache ist, dass die Engine überhaupt nicht herunterfährt! Was könnte an diesen paar Skript-Zeilen falsch sein? Drücken Sie die [Esc]-Taste, um das Programm zu verlassen und lassen Sie uns den Entity-Pointer namens plane überwachen.

Sie kennen das Prozedere bereits aus dem letzten Workshop: Rechtsklick in den Watch-Bereich (oder gehen Sie über Debug / Add Watch) und geben Sie "plane" (ohne Anführungszeichen) ins Feld für den Variablen-Namen ein. Falls noch der alte "energy"-Watch aus unserem vorangegangenen Workshop zu sehen ist, läßt der sich problemlos entfernen, indem man ihn mit rechts anklickt und anschließend auf "Delete watch" geht. Zurück zu unserem Flugzeug: achten Sie darauf, daß Sie auch wirklich denselben Namen wie die Variable oder Entity, die Sie verfolgen wollen, verwenden, sonst nützt Watch leider gar nichts! Der neu erstellte Watch läßt wird in unten abgebildetem Watch-Feld dargestellt.

w17_06

Zeit, die Skript-Datei wieder laufen zu lassen! Sind Sie nicht auch neugierig auf die Werte von plane? Denken Sie dran, DebugRun debugrun zu benutzen.

w17_08

Wie Sie sehen, beobachten wir nun verschiedene Werte: Position, Winkel, Skalierung und noch einige andere Parameter des Entity-Pointers. Ich habe versucht, einen Moment an dem der pan-Winkel nah an 360 Grad liegt, zu erwischen, aber die Schritte sind zu groß, denn die "Watches" werden nur zweimal pro Sekunde (alle 500 ms) erneuert. Versuchen wir, die "Watches" öfter 'up-zu-daten'! Verlassen Sie die Engine und gehen Sie in Lite-C auf Options / Preferences / Environment:

w17_09

Nehmen Sie 200 ms anstelle von 500 ms, klicken Sie auf OK und starten Sie Debug Run debugrun die script17-Datei nochmal.

Es ist genau dasselbe, wenn nicht schlimmer! Die Werte ändern sich viel zu schnell! Was machen wir dann? Es wäre doch nicht schlecht, wenn wir diesen while-Loop wo immer wir wollen anhalten könnten während wir den Code debuggen, oder? Nun... das lässt sich machen!

Verlassen Sie das Spiel. Während eines Debug Run (Debug-Durchlaufs) können Sie das auch per Lite-C's Stop Debugging-Knopf bewerkstelligen. Bewegen Sie den Cursor in die erste Zeile innerhalb des while-Loops und drücken Sie dann [F9]. Die Zeile sollte jetzt rot unterlegt sein:

breakpoint

Wir haben soeben einen Unterbrechungspunkt, (oder neudeutsch: breakpoint) ins Skript eingefügt. Die Engine wird im Debug-Modus immer, wenn sie auf so einen "breakpoint", stößt, eine Pause machen und der Debugger wird aktiv und geht in Einzelschritten durch unser Skript. Bitte haben Sie Geduld, Sie werden recht bald sehen, was 'in Einzelschritten' bedeutet. Übrigens, anstatt einen Breakpoint in Lite-C einzufügen, können Sie den Kommentar "//!" ins Skript schreiben; und zwar hinter die Zeile von der sie wollen, dass die Engine bei ihr stoppt. So setzt man einen permanenten Breakpoint in den Code.

Jetzt starten wir wieder script17:

w17_11

Das Flugzeug ist stehen geblieben und im Titelbalken des Engine-Fensters sehen Sie eine Code-Zeile:

my.pan += 3.5 * time_step;

Drücken Sie einmal auf die [Einzelschritt] -Taste. Normalerweise ist das [F10]. Probieren Sie die Debugging-Tasten aus oder setzen Sie sie in Lite-C unter Options -> Customize Hotkeys so, wie Sie sie haben wollen. Wenn Sie [F10] gedrückt haben, haben Sie bemerkt, daß Einiges verändert ist: das Flugzeug hat sich ein bißchen gedreht und im Titelbalken sehen wir nun eine andere Zeile:

if (my.pan == 360)

Auch im Lite-C-Fenster hat der Cursor sich verändert und zeigt nun die derzeitige Anweisung an. Durch Drücken der [Einzelschritt]-Taste führt der Debugger die Anweisungen Zeile für Zeile aus. Im Watch-Bereich sehen Sie, daß das Flugzeug nun einen pan-Winkel von 10 Grad hat. Drücken wir nochmal [F10] (per Default die [Einzelschritt]-Taste):

wait (1);

Das Ergebnis des Vergleichs (my.pan == 360) war falsch. So soll es auch sein, denn my.pan hat erst 10 Grad. Konsequenterweise wird die nächste Codezeile übersprungen und so befinden wir uns nun bei der "wait (1);"-Anweisung. Drücken Sie solange wieder auf [F10] bis der Cursor einen vollen while-Loop absolviert hat und sich an seiner Eingangszeile befindet.

my.pan += 3.5 * time_step;

Wieder werden 10 Grad auf den pan-Winkel addiert, er ist setzt auf 24 gesetzt. Sie sehen eventuell andere numerische Zahlen auf Ihrem Computer, das macht aber gar nichts. Wenn man mit Einzelschritten durch Code geht, spiegelt time_step nicht die vom letzten Framezyklus genommene Zeit wieder - das wäre ziemlich lange, denn die Engine muß ja warten bis wir eine Taste drücken! Stattdessen wird time_step, während wir den Code debuggen, auf einen fixen Wert gesetzt.

Nun könnten wir, bis wir die benötigten 360 erreicht haben, wieder und wieder [Einzelschritt] drücken. Das würde aber ein bißchen arg lange dauern! Der einfachere Weg, das zu machen, ist es auf den Debug Run debugrun -Knopf zu drücken. Das sorgt nämlich dafür, daß das Programm solange durchläuft, bis es auf den nächsten Breakpoint trifft. Das passiert am Anfang des while-loops, nachdem ein kompletter Loopzyklus ausgeführt wurde. Wiederholen Sie ein paarmal den Klick auf Debug Run debugrun und beobachten Sie den pan-Winkel:

10 - 24 - 38 - 52 - 66 - 80 - 94 - ... - 304 - 317 - 331 - 345 - 359 - 373 !

Ah, ich weiß! Ich habe versucht, die Anwendung herunterzufahren, wenn der pan-Winkel des Flugzeugs 360 Grad entspricht. Das ist aber gar nie geschehen! Jetzt begreife ich, weshalb die Engine nicht herunterfährt: pan springt von 359 auf 373, erreicht also niemals 360 Grad! Da time_step normalerweise einen willkürlichen Wert hat, ist es mehr als unwahrscheinlich, daß pan es jemals schaffen wird, exakt 360 zu sein. Wir wissen genau, wie wir unseren Fehler beheben müssen:

if (my.pan >= 360)
sys_exit(NULL);

Watching Entities

Es gibt noch ein weiteres Debugging-Feature welches Ihnen das Leben erleichtern kann und es nennt sich "watched". Dabei handelt es sich um einen vordefinierten ENTITY*-Pointer, den Lite-C bereits kennt und der permanent auf dem Engine-Bildschirm beobachtet wird. Sie könnten diesen Pointer im Skript setzen, aber es ist viel cooler, ihn zur Laufzeit der Engine einfach per Mausklick zu setzen. Probieren wir das doch gleich mal aus.

Entfernen Sie die Breakpoints und starten Sie wieder das Skript. Diesmal ist es gleichgültig, ob per Debug Run oder Test Run. Drücken Sie nun[Shift-F11]. Das sich drehende Flugzeug stoppt plötzlich als wäre es an einen Breakpoint geraten. Bewegen Sie nun den Mauszeiger über das Flugzeug und klicken Sie. Oben am Bildschirm erscheinen einige seltsame Werte:

mig_watched

Sie können den Entity-Typ erkennen (MDL), ihren internen WED-Namen (mig_mdl_000), den Dateinamen (MIG.MDL), die Position (x, y, z), die Werte der Winkel (pan, tilt, roll), Skalierung, Lichtwerte, Ambient, Farbe, Animation, Flags, Aktion und einiges mehr! Eine detaillierte Beschreibung all dieser Werte finden Sie im Referenzhandbuch.

Jetzt drücken Sie nochmals auf [Shift-F11] und das Flugzeug wird sich weiterdrehen. Seine Werte bleiben aber auf dem Bildschirm und ermöglichen so, sein Schicksal sogar dann noch zu beobachten, wenn es auf dem Bildschirm gar nicht mehr zu sehen ist. Dies ist ein sehr nützliches Feature zum Prüfen was mit Ihren Entities so alles passiert. Wenn Sie z. B.über das ganze Spiel hinweg die Werte einer bestimmten watched-Entity beobachten wollen, fügen Sie die Entity einfach Lite-C's Watch-Liste hinzu.

Ich habe das Gefühl, Sie sind ein bisschen müde... Halten Sie durch, der Workshop ist bald zuende! Einige von Ihnen haben vielleicht bemerkt, dass pan höhere Werte als 360 Grad haben kann. Die Abbildung oben zeigt einen pan-Wert von 641.0 Grad! Könnte das womöglich ein Enginefehler sein? Nein, die Werte der Winkel erhöhen / verringern sich ständig solange die Entity sich in derselben Richtung weiterdreht. Die Engine hat kein Problem mit Winkeln über 360 oder weniger als Null Grad. Das können Sie nutzen, wenn Sie die Anzahl der Umdrehungen für eine Entity festlegen wollen. Interessiert es Sie, wann das Flugzeug sich zwei mal um seinen pan-Winkel gedreht hat? Nun, dann prüfen Sie, ob sein pan-Winkel höher ist als 2 * 360 = 720 Grad! Und im Falle, dass Ihre Winkel im Bereich 0...359 liegen müssen, lässt sich das Problem durch Einfügen einer einzigen Codezeile in den while-Loop lösen:

my.pan %= 360; // begrenze pan auf 0...359 Grad

Setzen Sie diese Zeile in die Datei script17.c, gleich hinter die Anweisung, die 3.5 * time_step auf den pan-Wert addiert. Aber... ob Sie es glauben oder nicht, diese Version des Codes hat auch einen Bug! Starten Sie die überarbeitete Skript-Datei und sehen Sie selbst: die Engine weigert sich (wieder einmal), herunterzufahren. Es ist Ihre Hausaufgabe, diesen widerlichen Bug aufzuspüren und ihm den Garaus zu machen.

Lösung: my.pan %= 360 schränkt den pan-Winkel auf 0...359.999 Grad ein, also wird er niemals gleich 360 Grad sein. Ändern Sie die "if"-Verzweigung so:

f (my.pan >= 359)
   sys_exit(NULL);

Weiter: Entity-Bewegung