text: workshop10_D
  Workshop 10: Aktionen
  Eine Aktion ist einfach nur eine einer Entity zugewiesene Funktion: der Spieler, ein Aufzug, ein Monster, eine Waffe etc. Aktionen lassen sich jeder Art von Entity zuweisen: Modell, Sprite, .wmb oder Terrain. Aktionen sind eine sehr angenehme Art, das Entity-Verhalten zu steuern, ohne einen komplizierten 'Game-Loop' programmieren zu müssen.
  Aktionen lassen sich Ihren Entities mittels zwei verschiedener Methoden zuweisen: per Programmieren oder per WED (letzteres natürlich nur, wenn Ihre Gamestudio-Version WED enthält). Sobald wir unserem Level in WED's Dialog unter Map Properties ein Skript hinzugefügt haben, erscheinen automatisch sämtliche Aktionen in WED's Aktionsliste; siehe Abb. unten. Mit einem einfachen Mausklick lassen sie sich einer Entity zuweisen. (das [?] liefert, falls vorhanden eine Beschreibung der betreffenden Aktion):
  Das Plazieren von Entities im Level per WED und ihnen dann Aktionen zuzuweisen ist denn doch zu einfach. Wir nehmen heute die sportliche Variante und das heißt: wir programmieren das Ganze. Wenn wir im Übrigen nur Lite-C besitzen, können wir WED gar nicht verwenden und haben daher gar keine andere Wahl. Werfen wir einen Blick auf die Unterschiede zwischen Aktionen und Funktionen:
  Aktion und Funktion sehen fast gleich aus. Es gibt lediglich drei Unterschiede. Eine Funktion kann einen Wert zurückliefern, eine Aktion nicht (das Zurückliefern von Werten wird in einem anderen Workshop besprochen). Der zweite Unterschied ist der, dass eine Aktion in WED's Aktionsliste (oben) aufgeführt ist und somit einer Entity zugewiesen werden kann. Drittens beginnt eine Aktion automatisch zu laufen, sobald die Entity, der sie zugewiesen ist, geladen ist. Eine Funktion muß hingegen aufgerufen werden, wenn wir wollen, dass sie startet ((Näheres hierzu finden Sie im dritten Workshop). Eine Aktion sollte nur dann verwendet werden, wenn Sie eine Entity steuern wollen; wenn Sie es nicht mit Entities zu tun haben (z. B. beim Addieren von Zahlen, wie in meinem obigen Beispiel), nehmen Sie Funktionen.
  Bereit in Aktion zu treten? Starten Sie Lite-C und öffnen Sie die Datei namens script10.c im Ordner workshop10:
  vec_set(camera.x,vector(-500, 0, 100)); // plaziere die Kamera bei x = -500, y = 0, z = 100
  Starten wir das Level doch gleich einmal:
  Das Flugzeug dreht sich! Falls Sie den achten Workshop (Position, Winkel und Maßstab) durchgegangen sind, wissen Sie dass das Flugzeug ständig seinen pan-Winkel ändert.
  Einiges an dem Code sollte leicht verständlich sein, denn wir sind bereits einige Male durchgegangen. Konzentrieren wir uns auf die Main-Funktion:
  Zuallererst laden wir das Hangar-Level, das wir bereits im achten Workshop benutzt haben. Diesmal aber werden wir uns nicht einfach zurücklehnen und das Level bewundern, denn das Laden eines Levels ist als ginge man von 2D zu 3D über: plötzlich haben wir viel mehr Funktionen zur Verfügung.
  Die letzte Codezeile innerhalb von function main() setzt die Kameraposition im Level auf x = -500, y = 0, z = 100. Wir benutzen vec_set, eine Anweisung, die die Werte aus den "Vektor"-Klammern auf camera.x, camera.y, camera.z setzt (ein weiteres Beispiel des Verwendens der Punkt-Methode). Natürlich hätte ich den Code auch so schreiben können:
  und es hätte gut funktioniert. Bitte beachten Sie, dass, obwohl wir in unserem Beispiel lediglich camera.x benutzen, vec_set weiß, daß es den entsprechenden Wert in camera.y und camera.z kopieren soll.
  Zeit, die unserem Flugzeug zugewiesene Aktion zu prüfen:
  Das ist nun eine recht kurze und irgendwie unheimliche Aktion! Was hat es mit diesem while(1) auf sich? Einige der Aktionen oder Funktionen unserer Anwendungen und Games müssen für einen längeren Zeitraum laufen - einige könnten etwa durchweg vom ersten Moment, da wir das Spiel starten bis wir es wieder herunterfahren laufen müssen.
  Wenn Sie z. B. ein Spiel erstellen wollen, das, sagen wir, in etwa so aussieht wie Minesweeper und sich auch so spielen lässt, müssen Sie dafür sorgen, dass Sie die Maus ständig bewegen können. Ist dem nicht so, können Sie die Kästchen nicht anklicken und im Übrigen wären Sie auch nicht in der Lage das Spiel per Klick auf das "X" oben rechts im Fenster zu verlassen.
  All die Funktionen und Aktionen, die wir bisher betrachtet haben, machen ihren Job gut, aber sie laufen nicht die ganze Zeit. Sie führen etwas aus, erreichen dann ihr Ende und hören auf zu laufen. Das Flugzeug, das in diesem Workshop als Beispiel dient, dreht sich die ganze Zeit; Sie haben es sich schon gedacht - die Anweisung while (1) sorgt dafür, dass die Aktion ewig läuft.
  Zunächst einmal erstellt while (1) einen Loop, ein Stück Code, das den Satz von Anweisungen, die zwischen einem Paar von geschweiften Klammern steht, wiederholt. Eine typische 'while'-Loop-Definition sieht folgendermaßen aus:
  while (solange eine Bedingung wahr ist)
  // wiederhole die Anweisungen, die zwischen diesen geschweiften Klammern steht
  Unser Loop benutzt eine while (1)-Bedingung. Das ist sozusagen die Kurzform von "solange 1 = 1" was bedeutet, dass die Schleife ewig weiterläuft. Immer wenn Sie eine dauerhaft laufende Aktion brauchen - sei es ein Spieler, ein Aufzug, ein Feind, etc., müssen Sie einen "while (1)"-Loop benutzen. Gut, die erste Zeile innerhalb unserer Aktion rotate_plane weist die Entity an, das was innerhalb des "while"-Loops steht, ständig zu wiederholen. Was aber steht nun innerhalb des "while"-Loops? Was wiederholt die Schleife in einer Tour? Nur zwei Code-Zeilen:
  Die erste Codezeile addiert 0.1 Grad auf den pan-Winkel des Flugzeugs. Das Flugzeug fängt mit einem Anfangs-Panwinkel von 0 Grad an, dann wird sein pan auf 0.1, 0.2, 0.3.... Grad gesetzt und dadurch dreht sich das Flugzeug um seinen pan-Winkel. Falls sie sich das fragen: das Flugzeug wird seinen Pan-Winkel solange erhöhen, bis er 360 Grad (eine volle Umdrehung) erreicht hat und seine Rotation dann fortsetzen.
  Aber was hat es mit dieser "wait (1)"-Anweisung auf sich? Wir wissen, dass diese Zeile die Engine instruiert, exakt einen Frame zu warten und dann, nach dem wait weiterzumachen, was bedeutet, den Loop (die Schleife) zu wiederholen. So können wir sicher sein, dass der Code innerhalb des Loops mit jedem Frame einmal ausgeführt wird - nicht öfter, nicht weniger. In unserer zukünftigen Karriere als Spieleentwickler werden sehr, sehr oft auf Loops wie diese treffen.
  Wait und Multitasking
  Beantworten Sie dazu eine Frage: haben Sie schon mal was von Multitasking gehört? Das bezeichnet die Fähigkeit, verschiedene Programme gleichzeitig laufen zu lassen. Gerade jetzt, wo Sie diesen Workshop lesen, hören Sie vielleicht Musik auf demselben PC, hin und wieder starten Sie die Engine auf diesem Rechner, eventuell haben Sie ein Antivirus-Programm im Hintergrund... all diese Programme scheinen gleichzeitig zu laufen, aber in Wahrheit schaltet der Computer sehr schnell von einem Task (Programm) zum nächsten so dass es den Anschein hat, als liefen sämtliche Anwendungen gleichzeitig.
  Dasselbe passiert bei den while-Loops. Da auch die restlichen Aktionen und Funktionen Ihres Spiels Zeit zur Ausführung brauchen, muss eine "while"-Schleife immer wieder mal stoppen. Tut sie das nicht, verbraucht sie sämtliche Speicherkapazität des Computers, denn sie will ja ununterbrochen ihre Anweisungen wiederholen und nicht einmal die Windows-System-Uhr hat dann eine Chance, ihre Zeit auf Vordermann zu bringen, denn der unablässig laufende Loop lässt nichts anderes zum Zuge kommen!
  Wie unterbrechen wir also einen while-Loop? Dies wird einfach durch "wait (1);" bewerkstelligt; diese kleine Codezeile ermöglicht all den anderen Aktionen und Funktionen, ihren eigenen Code auszuführen.
  Wenden wir uns kurz den Frames und Frame-Raten zu. Was Sie beim Spielen eines Games auf Ihrem Monitor sehen, ist nichts weiter als eine Serie von nacheinander dargestellten Einzelbildern. Wenn Ihr PC 50 Bilder in der Sekunde darstellen kann, läuft das Spiel mit 50 Frames pro Sekunde woraus sich eine Frame-Rate von von eben diesen 50 Frames pro Sekunde (50 fps) ergibt. Eine wait (1)-Anweisung sagt der jeweiligen Aktion oder Funktion, die sie beinhaltet, dass sie für die Dauer eines dieser 50 Frames oder Bilder innerhalb jeder Sekunde aussetzen soll.
  Jeder while (1) -Loop muss eine Anweisung enthalten, die ihn unterbricht, wofür gewöhnlich eine "wait (1);"-Anweisung benutzt wird. Vergessen Sie es, innerhalb Ihres while (1)-Loops eine wait-Anweisung oder sonst eine den Loop unterbrechende oder beendende Anweisung anzubringen, wird die Engine den Loop unendlich und ohne jegliche Pause laufenlassen. Das bedeutet dann, dass Ihr Programm einfriert und Sie keine Chance haben, es anzuhalten oder zu beenden. Das ist kein Witz - probieren Sie es aus, wenn Sie sich trauen! Um mehr über "wait" zu erfahren, lesen Sie bitte das Referenzhandbuch.
  Das Verwenden von time_step
  Wußten Sie, daß Ihre potentiellen Kunden verschiedene PCs haben? Oh, das haben Sie bereits gewußt? Nun, dann haben Sie vielleicht gedacht, Ihr Spiel liefe auf deren PCs mit verschiedenen Frameraten. Wie und weshalb sollte Sie das etwas angehen? Simulieren wir einmal einen sehr langsamen PC und schauen, was passiert. Fügen Sie die folgende (grüne) Zeile in Ihre Main-Funktion ein:
  Diese Codezeile begrenzt die Framerate auf 20 Frames pro Sekunde (fps). Starten Sie die Skriptdatei und Sie werden feststellen, dass sich das Fulgzeug diesmal viel langsamer dreht.
  Jetzt drücken Sie die [Tab]-Taste und holen sich einen alten Freund (die Konsole) herbei. Nun tippen Sie die folgende Codezeile und drücken dann [Eingabe]:
  Die Rotation ist wieder sehr viel schneller! Auf meinem PC hat das Flugzeug bei einer Framerate von 20 fps etwa 100 Sekunden für eine volle Umdrehung gebraucht. Jetzt, bei 60 fps braucht es etwa 30 Sekunden dafür! Normalerweise haben einige (wenn nicht gar die meisten) Ihrer Kunden alte PCs und die Framerate auf ihren Systemen wird geringer sein. Aber denken Sie dran: auch deren Geld hat dieselbe Farbe! Wie können wir alle zufrieden stellen?
  Hier die goldene Regel: auf neuen, schnellen PCs muß Ihr Spiel hervorragend laufen und auf alten, langsamen Rechnern so gut wie möglich. Indem dafür gesorgt wurde, daß es auf jeder anständigen, marktüblichen3D-Karte läuft, hat Conitec mit lite-C wahrlich gute Arbeit geleistet. Mal sehen, was SIE noch dazu beitragen können:
  - Sie können Ihr Spiel als Bundle mit der allerbesten, auf dem Markt verfügbaren 3D-Karte zusammenpacken. Wenn Sie Ihr dann Spiel in hohen Stückzahlen verkaufen, gibt Ihnen der Softwarevertreiber möglicherweise einen großzügigen Rabatt :).
  - Sie programmieren Ihr Spiel klug und sorgen dafür, daß es mit gleicher Geschwindigkeit (nicht mit selber Framerate!) auf dem auf der Verpackung als "Minimale Hardware Voraussetzung" angegebenen PC läuft wie auf dem neuesten PC von 2010.
  Zurück zu unserem Beispiel. Die Framerate wird je nach Konfiguration des betreffenden PCs größer oder kleiner und daran können Sie nichts ändern. Trotzdem sollten Sie dafür sorgen, daß das Flugzeug aus unserem Demo (natürlich nur ein Beispiel) innerhalb derselben Sekundenzahl eine komplette Umdrehung vollführt auch dann, wenn sich die Framerate irgendwo zwischen 10 und 1000 fps bewegt.
  Eine mögliche Methode ist es, fps_max auf einen kleinen Wert zu setzen und die Framerate so auf die auf dem allerältesten PC Mögliche zu begrenzen. Diese Technik einer festgelegten Framerate wurde in alten Spielen und bei alten Engines sehr heftig angewandt, wird heute jedoch als nicht gut genug angesehen. Auf High-End PCs muß Ihr Spiel mit der größtmöglichen Framerate laufen (dafür haben die Leute schließlich bezahlt!) und auf schwachen PCs sollte es immerhin noch eine annehmbare Framerate sein. Sie sollten die Framerate also besser nicht auf einen kleinen Wert begrenzen.
  Wenn ich fps_max auf 20 setze und meine Käufer neue PCs haben, behandle ich sie, als wären sie Kunden mit veralteten PCs, ganz zu schweigen davon, daß das Problem für die Jungs mit Rechnern, die nur 10fps leisten können, damit gar nicht gelöst ist. Ich habe Ihnen gesagt, daß fps_max lediglich die Obergrenze der Framerate festlegt, es gibt keine magische Anweisung zum Verdoppeln oder Verdreifachen einer kleinen Framerate! Die Framerate hängt einzig und allein von CPU und Video-Karte ab, mehr Speicherplatz oder eine größere Festplatte wird die Frames pro Sekunde nicht im geringsten erhöhen.
  Schön, nun wissen Sie alles, was es über dieses Problem zu wissen gibt, betrachten wir uns die Lösung! Sie müssen in Ihrer script10-Datei nur eine einzige Codezeile ändern:
  Speichern Sie das Skript und starten Sie das Level nochmals. Sie werden feststellen, daß das Flugzeug innerhalb von etwa 10 Sekunden eine volle Umdrehung um seinen Pan-Winkel absolviert. Gestatten wir nun, indem wir in der Main-Funktion fps_max auf 200 setzen, eine höhere Framerate (sofern Ihr PC das leisten kann). Editieren Sie also main() dahingehend:
  Hmmm... das Flugzeug braucht für seine volle Umdrehung dieselben 10 Sekunden und das, obwohl die Framerate von 20 auf 200 fps erhöht wurde! Ich weiß was! Machen wir es der Engine mal richtig schwer und setzen fps_max auf 5!
  Okay, ich habe das gemacht und ich muß zugeben, time_step macht was es soll. Die Bewegung ist ruckelig, denn das Flugzeug erneuert seinen Winkel nur 5 mal pro Sekunde, trotzdem braucht es aber für die volle Umdrehung wieder 10 Sekunden...
  Mit der neuen Code-Vatiante hat die Framerate keinerlei Einfluß auf die Drehgeschwindigkeit! Probieren Sie es mit dem Setzen verschiedener Werte für fps_max (5...200) und sie werden feststellen: die Rotationsgeschwindigkeit ist von der Framerate fast gänzlich unabhängig. Was hat es nun mit diesem mysteriösen time_step auf sich? Und wie funktioniert es?
  Die Variable time_step liefert uns die Dauer des letzten Frames. Allerdings nicht in Sekunden, sondern in Ticks. Was ist ein Tick? Ich vermute, es stammt aus der Vergangenheit der Engine und entspricht einer Framedauer auf einem alten 3D-Spiel, das mit etwa 16 Frames pro Sekunde lief. 1 Tick ist genau eine sechzehntel Sekunde, also 1/16 = 0.0625 Sekunden oder 62.5 Millisekunden. Das müssen Sie im Hinterkopf behalten, wenn Sie mit Zeiten in Ihrem Spiel arbeiten.
  Die Relation zwischen time_step und der Framerate (fps) ist folgende:
  Wenn Ihr Spiel also mit 16 Frames pro Sekunde läuft, beträgt die Framedauer genau 1 Tick und somit ist time_step genau 1. Wenn es Framerate 32 fps ist, ist die Framedauer nur halb so lang und es gilt somit time_step = 0.5. Sehen Sie, wie time_step mit dem Vergrößern der Framerate seinen Wert verringert? Wenn wir auf eine Größe, die mit time_step multipliziert wird, addieren oder subtrahieren, werden wir, unabhängig von der gegenwärtigen Framerate, ein ein konstantes Zu- oder Abnehmen erreichen. Zeit, noch mal einen Blick auf unsere time_step-Code-Zeile zu werfen:
  Nehmen wir an, wir haben fps_max auf 200 gesetzt. Das bedeutet, unser Loop, der für unsere Flugzeugumdrehung zuständig ist, wird 200 mal pro Sekunde ausgeführt wird und time_step ist 16 / 200 = 0.08.
  Das Flugzeug beginnt seine Aktion mit einem Initialwert den Pan-Winkels von 0 Grad; einen Frame später hat sich sein Pan-Winkel um 2 * 0.08 = 0.16 Grad erhöht. Einen weiteren Frame später (der zweite Frame), ist pan auf 0.16 + 0.16 = 0.32 Grad weitergewachsen usw. Nach 200 Frames (eine volle Sekunde, wenn das Projekt bei 200 fps läuft), ist der Pan-Winkel auf 0.16 * 200 = 32 Grad angewachsen. Da eine komplette Umdrehung 360 Grad hat, können wir nun die exakte Anzahl der für eine volle Flugzeugumdrehung benötigten Sekunden berechnen: 360 / 32 = 11.25 Sekunden.
  Nehmen wir nun an, wir haben fps_max auf 5 gesetzt. Das bedeutet, unser Loop wird 5 mal pro Sekunde ausgeführt und time_step ist 6 / 5 = 3.2.
  Das Flugzeug beginnt (noch einmal) mit seiner Aktion wieder mit einem Initialwert des Pan-Winkels von 0 Grad; einen Frame später, hat sich sein pan um 2 * 3.2 = 6.4 Grad erhöht. Einen weiteren Frame (der zweite) später, ist pan auf 6.4 + 6.4 = 12.8 Grad angewachsen usw. Nach 5 Frames (einer vollen Sekunde, wenn das Projekt mit 200 fps läuft), ist der Pan-Winkel auf 6.4 * 5 = 32 Grad angewachsen.
  Wie Sie also unschwer sehen, addiert das Flugzeug jede Sekunde 32 Grad auf seinen Pan-Winkel, sogar auch dann, wenn die Framerate von 5 bis 200 fps reicht.
  Wollen Sie in einem PC-Spiel etwas bewegen oder rotieren lassen, müssen Sie die Bewegungs- oder Rotationsgeschwindigkeit mit time_step multiplizieren. Nehmen wir an, wir wollen eine Entity durch Erhöhen ihrer X-Koordinate innerhalb eines while-Loops erhöhen:
  Beachten Sie zu allererst, die kurze Anweisung...
  ist die elegantere und schnellere Form von
  Falls Sie sich wundern, Sie haben recht, wir könnten
  Diese Technik läßt sich für sämtliche mathematischen Operationen benutzen, nicht nur für die Addition. In den folgenden Workshops werden Sie noch viele Beispiele dazu finden.
  Wie auch immer, kehren wir zu unserer Aktion move_entity() zurück und nehmen wir an, mein PC ist eher alt und leistet lediglich 25 fps während Ihr neuer PC 100 fps liefern kann. Das bedeutet, der obige while-Loop läuft auf meinem Computer 25 mal pro Sekunde und auf Ihrem Rechner 100 mal pro Sekunde. In anderen Worten: mein PC addiert jede Sekunde 25 (1 Einheit / Frame) auf die X-Koordinate der Entity, während Ihrer pro Sekunde 100 (1 Einheit / Frame) auf die betreffende X-Koordinate addiert.
  Die in lite-C verwendete Maßeinheit für Raum / Entfernung wird Quant genannt. Sie wissen nicht, wieviele Zentimeter lang ein Quant ist? Nun, das hängt ganz vom Maßstab Ihres Spiels ab. In einem Action-Spiel kann ein Quant ein Zoll (ca. 2.5 cm) lang sein, in einem Stategiespiel hingegen ein Meter. In diesen Workshops habe wir die Modelle so skaliert, dass ein Quant etwa 2-3 cm in der realen Welt entspricht. Dies ist lediglich eine Konvention, die jedoch für die meisten Projekte gut funktioniert.
  Werfen Sie nun einen Blick auf die untenstehende Abbildung und Sie sehen die Entfernungen, die im selben Intervall von 3 Sekunden auf unseren PCs zurückgelegt wurden:
  Und jetzt benutzen wir diese wunderbare Variable time_step:
  Der Loop auf meinem PC läuft weiter mit 25 mal pro Sekunde und mit 100 mal pro Sekunde auf dem Ihren; das können wir nicht ändern. Trotzdem jetzt wird die mit jedem Frame zurückgelegte Entfernung von der Framerate abhängen. Machen wir ein paar einfache mathematische Berechnungen:
  Das heißt, die Entity wird eine Entfernung von 25 Frames * 1 * time_step = 25 * 1 * 0.64 = 16 Quants pro Sekunde auf meinem und 100 Frames * 1 * time_step = 100 * 1 * 0.16 = 16 Quants pro Sekunde auf Ihrem Rechner zurücklegen! Die Entity legt auf jedem PC dieselbe Entfernung zurück, egal wie groß oder klein die Framerate ist.
  Wie verwenden wir time_step in unseren Projekten? Vielleicht haben Sie im Physikunterricht gelernt, dass Entfernung Geschwindigkeit multipliziert mit Zeit ist. Also multiplizieren wir einfach all unsere Geschwindigkeiten und Winkelgeschwindigkeiten (die rechte Seite in unseren Ausdrücken) mit time_step und erhalten so unsere Ergebnisentfernungen und -Winkel. Das war's schon! Hier ein paar Beispiele:
  "my.z += 2;" sollte geändert werden in "my.z += 2 * time_step;"
  "my.roll += 1;" sollte geändert werden in "my.roll += 1 * time_step;"
  Es gibt ein paar wenige weitere Parameter, die time_step beeinflussen können ( wie time_factor, time_smooth, fps_min). Mehr darüber finden Sie im Referenzhandbuch. Machen Sie sich keine Gedanken, wenn Sie sich im Moment noch ein wenig desorientiert vorkommen: wir werden in allen weiteren Workshops mit time_step arbeiten und so werden Sie im Handumdrehen zu einem time_step-Meister!