workshop 18

Top  Zurück  Weiter

Workshop 18: Entity-Bewegung

Sobald Ihr Spiel mehr als nur einen statischen Bildschirm darstellt, muss es einen Code zum Bewegen der Entities geben. Sie haben die Wahl zwischen drei verschiedene Methoden zum Bewegen einer (Modell-, Sprite- oder Map)-Entity:

Methode 1: Direkte Positionsänderung in Skript;

Methode 2: Bewegen der Entity mit der c_move-Funktion und Kollisionserkennung;

Methode 3: Bewegen per Physik-Engine mit Kraft und unter dem Gesetz der Schwerkraft.

Physik wird allerdings erst in einem späteren Workshop besprochen. Konzentrieren wir uns hier also auf die beiden ersten Methoden.

1. Ändern der Position einer Entity

Ich möchte zunächst einmal erwähnen, dass diese Methode wirklich einfach ist, allerdings sollte sie nur in einigen wenigen Fällen angewendet werden. Im achten Workshop haben wir bereits die x-, y-,z-Positionen einer Entity kennengelernt und Sie sollten wissen wie man diese verändert. Starten Sie Lite-C und öffnen Sie script18 und starten Sie es.

Das ist ein einfaches Level! Es besteht aus nichts weiter als einem schmalen Gang mit ein paar wenigen kleinen Lichtquellen und einer Modell-Entity, die nach einem Rennauto aussieht.

w18_03

Drücken wir doch gleich mal die Taste [S]!

w18_04

Das Auto hat angefangen sich zu bewegen! Je nach den Lichtverhältnissen im Level verändert er auch seine Helligkeit, was hübsch aussieht...

w18_05

Was geschieht da? Der Rennwagen ist in die Wand eingedrungen und verschwunden! Nun ja, das Leben ist nicht immer fair... betrachten wir uns den dafür zuständigen Teil der Datei script18:

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

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

function main()
{
	level_load ("work18.wmb");
}

action move_me()
{
	while (!key_s) wait (1); // warte, bis der Player die [S]-Tast edrückt
	while (1)
	{
		my.x += 15 * time_step; // bewege die Entity an der x-Achse entlang
		wait (1);
	}
}

Die Main-Funktion ist wahrlich einfach! Ich glaube, es ist sogar die Allereinfachste, die wir bisher zu Gesicht bekommen haben. Augenscheinlich liegt die Magie in der Aktion move_me. Diese Aktion ist dem Auto-Modell bereits im Level-Editor zugewiesen worden und so brauchen wir uns nicht ums Erstellen der Entity und ihre Plazierung im Level zu kümmern.

action move_me()
{
   while (!key_s) wait (1); // warte bis der Spieler die Taste [S] drückt
...

Sie haben gelernt, wie man mit der Tastatur arbeitet! Der obige while-Loop wartet bis der Player die Taste [S] drückt. "!" ist ein logischer Operator, der die Bedeutung des folgenden Vergleichs oder Ausdrucks umkehrt. Er ist wahr, wenn der Ausdruck, der folgt als falsch bewertet wird und umgekehrt. "!key_s" ist solange wahr wie die [S]-Taste nicht gedrückt ist. In diesem Fall würde die Definition des "while"-Loops aus Workshop10:

while (eine Bedingung ist wahr)
  
// wiederhole die folgende Anweisung (oder diverse Anweisungen in geschw. Klammern)

...folgendermaßen übersetzt:

while (key_s ist nicht gedrückt)
   wait (1); // wiederhole diese Anweisung

Wie Sie wissen endet eine Codezeile mit ";". Es spielt also keine Rolle, ob wir das "wait (1);" in die nächste Zeile oder direkt hinter den Vergleich schreiben. Werfen wir einen Blick auf den zweiten Loop innerhalb der Aktion:

while (1)
{
  
my.x += 15 * time_step; // bewege die Auto-Entity entlang der x-Achse
  
wait (1);
}

Diese while-Schleife ändert die Position der Auto-Entity mit einer Geschwindigkeit von 15 Quants per Tick, d.h. 15 Koordinateneinheiten pro Zeiteinheit. Zum Erhöhen bzw. Verringern der Bewegungsgeschwindigkeit können Sie anstelle von "15" auch andere Werte ausprobieren.

Die erste Methode zur Entity-Verschiebung ist wahrlich einfach: In einem while-Loop ändern Sie eine oder mehrere Entity-Koordinaten (x, y, z) und damit hat es sich!

Tun wir einmal so, als wäre unser Auto ein mächtiger Hubschrauber, der am Levelende abhebt. Fügen Sie dazu die folgende (grüne) Codezeile in script18 ein:

function main()
{
	level_load ("work18.wmb");
}

action move_me()
{
	while (!key_s) wait (1); // warte, bis der Player die [S]-Tastedrückt
	while (1)
	{
		my.x += 15 * time_step; // bewege die Entity an der x-Achse entlang
		my.z += 3 * time_step;  // nach oben bewegen
		wait (1);
	}
}

Speichern und starten Sie das Skript. Drücken Sie [S], um das Auto in Bewegung zu bringen. Es bewegt sich weiterhin entlang der x-Achse, dieses Mal aber nimmt es auch an Höhe (z) zu.

w18_07

Die schlechte Nachricht ist die, dass unser Auto nun in die Decke eindringt...Hier zeigt sich der Haupt- und einzige Nachteil der direkten Koordinatenänderung von Entities: Die Entities scheren sich überhaupt nicht um die Levelgeometrie, denn, während sie in Bewegung sind, führen sie keinerlei Kollisionserkennung druch. Entities können einander durchdringen, durch Wände, Böden, Decken, Türen etc. wandern, nichts kann sie aufhalten! Wenn Sie vorhaben, eines Ihrer Monster mit der Fähigkeit durch Wände zu gehen, auszustatten, mag das eine hilfreiche Methode sein. Allerdings ist es ein nutzloses "Feature", wenn Sie ein normales Game erstellen (aber was ist dieser Tage schon "normal"?).

Wichtiger Hinweis: Das Bewegen/Verschieben einer Entity durch Ändern ihrer x-, y-, z-Koordinaten ist absolut einfach, sollte jedoch nicht von Anfängern angewandt werden. Sie müssen dabei sehr genau wissen was Sie tun!

2. Das Verwenden von c_move-Anweisungen

Die c_move-Anweisung bewegt eine Entity und führt währenddessen Kollisionserkennung durch. Das bedeutet, dass unser Auto anhält sobald sie mit den Wänden, der Decke, anderen Entities usw. kollidiert und das ist gut so. Starten Sie Lite-C, laden und starten Sie script18_2:

w18_03

Soweit nichts Neues. Drücken wir jetzt auf [S]!

Das Auto bewegt sich auf der x- und z-Achse - genau wie vorher. Ich glaube, es wird diesmal früher auf die Decke treffen...

w18_11

Erraten? Die Entity ist auf die Decke getroffen aber nicht in sie eingedrungen; ja mehr noch, sie glitt an der Decke entlang weiter bis sie am Ende des Ganges angehalten hat! Ist das echte Entitybewegung oder nicht?

Wenn Sie glauben, der Code, der all diese aufregenden Dinge bewirkt sei kompliziert, haben Sie unrecht. Hier ist der betreffende Teil der Datei script18_2, in dem das Auto unter Verwendung von c_move bewegt wird:

action move_me()
{
	while (!key_s) wait (1); // warte, bis der Player die [S]-Tastedrückt
	while (1)
	{
		c_move(me, nullvector, vector(15 * time_step, 0, 4 * time_step), GLIDE);
		wait (1);
	}
}

Eine einzige Codezeile hat sich verändert - die Zeile, die die neue c_move-Anweisung benutzt:

c_move (me, nullvector, vector(15 * time_step, 0, 4 * time_step), GLIDE);

Die allgemeine Formel von c_move sieht folgendermaßen aus:

c_move (entity_name, relative_speed, absolute_speed, movement_type)

Ich weiß, dass diese Zeile ziemlich kompliziert aussieht aber ich verspreche, jeden Ausdruck zu erklären:

- c_move ist der Name der Anweisung, die irgendeine Entity mit gleichzeitiger Kollisionserkennung bewegen kann. Immer wenn Sie eine Entity, die mit der Levelgeometrie oder anderen Entities kollidieren kann, bewegen wollen, müssen Sie die c_move-Anweisung benutzen. Brauchen Sie Bewegungscode für den Player, ein Monster, irgendein Vehikel, einen Ball usw.? Dann sollten Sie besser c_move verwenden!

- entity ist der Name der Entity, die bewegt werden soll. Ich habe den vordefinierten Pointer me benutzt, denn ich will das Auto bewegen und das ist die Entity, der die Aktion namens move_me zugewiesen ist. Sie können jeden anderen Entity-Pointer, der vor der c_move-Codezeile definiert worden ist, verwenden..

- relative_speed ist die Geschwindigkeit mit der sich die Entity in die ihr per Winkel angegebene Richtung (angles) bewegen wird. In meinem Beispiel aus dem script18_2 habe ich nullvector für relative_speed genommen. Was es mit diesem nullvector auf sich hat, fragen Sie? Weiter nichts, als dass es ein hübscher Name für einen vordefinierten Vector (einem Satz aus drei Variablen) ist, dessen Komponenten sämtlich auf Null gesetzt sind. Immer wenn wir nullvector verwenden, versteht die Engine, dass wir sie mit einer Reihe von Nullen füttern. Sie können hier nullvector oder vector(0,0,0) nehmen, denn die Engine wird dasselbe verstehen: Sie soll die relative Geschwindigkeit der Entity ignorieren.

- absolute_speed ist die Geschwindigkeit, mit der die Entity in eine absolute Richtung unter Weglassen der momentanen Richtung in die die Entity durch die gegebenen Winkel (angles) zeigt, bewegt wird. Ich habe hier vector(15 * time_step, 0, 4 * time_step) verwendet und das bedeutet, das Auto wird sich entlang der x-Achse mit einer Geschwindigkeit von 15 Quants pro Tick bewegen, mit 0 Quants/Tick entlang der y-Achse und mit 4 Quants/Tick an der z-Achse entlang. Wie Sie feststellen, erzeugt vector (15 * time_step, 0, 4 * time_step) einen temporären, namenlosen Vektor. Aber dieser Vektor funktioniert wie 3 Vars, die verschiedene x-, y- und z-Werte haben.

- movement_type setzt den Typus des Bewegungsmodus', der während der Ausführung der c_move-Codezeile durchgeführt wird. Ich habe in meinem Beispiel GLIDE benutzt aber es gibt jede Menge weiterer Möglichkeiten, die im Referenzhandbuch aufgelistet sind. Einige wenige will ich Ihnen gleich jetzt beschreiben:

a) GLIDE - wenn sie mit ihnen kollidiert, gleitet die Entity an Wänden und anderen Entities entlang;

b) IGNORE_PASSABLE - ignoriert (geht hindurch) all die Blocks und Entities, die das Flag PASSABLE gesetzt haben;

c) IGNORE_MODELS - ignoriert (geht hindurch) alle Modelle, auch wenn sie nicht PASSABLE gesetzt haben.

Diese drei Optionen können Sie per "Odern" kombinieren; GLIDE | IGNORE_PASSABLE instruiert die Entity an Wänden und Entities entlangzugleiten aber durch solche Blocks und Entities hindurchzugehen, die ihr Flag PASSABLE gesetzt haben.

Ich habe Ihr Hirn mit einer Menge an Informationen gefüttert und so ist es ziemlich normal, dass Sie ein wenig durcheinander sind. Der üble Teil ist der, in dem es um relative und absolute Geschwindigkeit (relative_speed und absolute_speed) geht, stimmt's? Nehmen wir an, wir wollen ein Rennspiel erstellen:

w18_13

Achten Sie jetzt besonders auf das blaue Auto. Würde ich eine Aktion schreiben, die so aussieht (der Einfachheit halber lasse ich time_step weg):

action blue_car_1()
{
  while (1)
  {
   c_move (me, nullvector, vector(3, 0, 0), GLIDE);
   wait (1);
  }
}

bewegte sich das blaue Auto so, wie im Bild unten:

w18_14

Warum ist das jetzt passiert? Nun, wir verwenden nur "absolute_speed" und vector (3, 0, 0) sagt dem Auto, es solle sich entlang der x-Achse bewegen, denn nur die x-Kompontente des Vektors (3) wurde auf einen Wert ungleich Null gesetzt. Erraten? Die x-Achse weist auf dem Bildschirm nach rechts und also bewegt sich das Auto in diese Richtung.

Nähmen wir eine Aktion, die so aussieht:

action blue_car_2()
{
   while (1)
  {
    c_move (my, vector(3, 0, 0), nullvector, GLIDE);
    wait (1);
  }
}

dann bewegte sich das blaue Auto folgendermaßen:

w18_15

Diesmal bewegt sich das Auto unter Verwendung seiner "relative_speed". Also wird es sich in die Richtung bewegen, die ihm durch seinen pan-Winkel vorgegeben ist. Sie werden in Ihren Projekten meistens "relative_speed" benutzen. Sie müssen den Code für ein den Player jagendes Monster erstellen? Drehen Sie das Monster in Richtung Player (mit ein paar einfachen Codezeilen lässt sich das bewerkstelligen) und setzen Sie es per c_move unter Verwendung seines relative_speed in Bewegung!

Aber warum nur haben die Macher von Lite-C sowohl relative_speed als auch absolute_speed erfunden? Könnten wir nicht ein "irgendein_speed" für alles verwenden?

Nun, das könnten wir machen aber es würde unseren Code manchmal über die Maßen verkomplizieren. Stellen wir uns vor, Sie kreieren einen Shooter und eine Episode spielt in Sibirien, einem kalten, schneereichen Gebiet. Das Level wäre eigentlich recht einfach durchzuspielen, aber da ist dieser kalte Wind, der aus dem Norden bläst und den Player wegschiebt. Wie würden Sie für sowas den Code erstellen? Etwas, das Sie, wenn Sie stillstehen, von Ihrem Ziel wegpustet oder Ihnen beim Zusteuern auf Ihr Ziel (dem Nordpol?) Widerstand leistet? Wie würden Sie das ausdrücken: " da gibt es diese Kraft, die aus dem Norden der Map auf den Player einwirkt und ich bin derjenige der den Code dafür schreiben wird"? Jawohl, hier hilft absolute_speed. Hier ist die Codezeile, die eine Entity in die durch ihren pan-Winkel definierte Richtung bewegt und ihr eine negative, von Norden kommende Kraft (z. B. den Wind) auferlegt:

c_move (my, vector(3, 0, 0), vector(0, -1, 0), GLIDE);

Diese Entity (sei es der Player, ein Vehikel o.ä.) wird sich mit 3 Quants/Frame in die durch seine eigenen Winkel angegebene Richtung bewegen und gleichzeitig die Auswirkungen einer negativen Geschwindigkeit (-1 Quant/Frame) auf der y-Achse zu erleiden haben. Das Ergebnis ist eine Kombination der beiden Geschwindigkeiten (relative_speed und absolute_speed). Sie werden diese Kombination immer dann benutzen wollen, wenn Sie externe Kräfte (wie Schwerkraft, Drift usw.) hinzufügen wollen. Wie halten Sie den Player auf dem Boden? Nehmen Sie einen Vektor mit einer negativen z-Komponente für absolute_speed:

c_move (my, vector(5, 0, 0), vector(0, 0, -2), GLIDE);

Ich denke über c_move habe ich sie nun ausreichend informiert, es ist Zeit für die Hausaufgaben. Ich habe ein nagelneues Projekt für Sie erstellt und dem sollten Sie schon ein wenig Zeit widmen! Starten Sie WED (wenn Sie haben) und öffnen Sie das Level homework18.

w18_16

Es besteht aus einer einfachen Box mit einem Auto-Modell drin. Wenn Sie mit dem rechten Mausknopf auf das Auto klicken und Properties auswählen, stellen Sie vermutlich fest, daß ich seinen Knopf BBOX mit einem Haken versehen habe. Das ist für die Kollisionserkennung wichtig und bedeutet, daß das Auto eine Kollisionshülle in Form eines verlängerten Ellipsoiden von ungefährer Größe des Autos hat. Diese Hülle paßt dem Auto besser als die Default-Hülle, welche kleiner und zylindrisch geformt ist. Wenn Sie zwar kein WED haben, jedoch Lite-C besitzen, können Sie dasselbe mit der Anweisung c_setminmax erreichen. Im Referenzhandbuch finden Sie mehr zum Thema Kollisionserkennung.

Wie auch immer, öffnen Sie Lite-C und starten Sie das Level unter Verwendung der Skriptdatei home18:

w18_17

Das kleine Auto lässt sich mit relative_speed bewegen. Drücken Sie zum Verändern des Pan-Winkels des Autos die Tasten [A] und [S]

und dann die [Leertaste], um zu sehen, wie es sich in Richtung seines pan-Winkels bewegt. Und so sieht das Skript home18 aus:

function main()
{
	level_load ("homework18.wmb");
	vec_set(camera.x,vector(0,-480,1000)); // setze eine statische Kameraposition
	vec_set(camera.pan,vector(90,-68,0)); // setze die korrekte Kameraausrichtung
}

action my_car()
{
	while (1)
	{	
		// bewege d. Auto m. relative_speed
		if (key_a)
			my.pan += 3*time_step; // erhöhe den pan-Winkel des Autos
		if (key_s)
			my.pan -= 3*time_step; // verringere den pan-Winkel des Autos
		if (key_space ) // drücke und halte die "Leertaste", um d. Auto zu bewegen
			c_move (my, vector(15*time_step, 0, 0), nullvector, GLIDE);
		wait (1);
	}
}
In main habe ich gute Anfangspositionen und -Winkel für die Kamera gesetzt und der Rest der Zeilen sorgt für das Bewegen des Autos mithilfe von relative_speed. Wenn Ihnen meine Kameraposition nicht gefällt, können Sie die Kamera mit Hilfe der [0] auf Ihrer Tastatur freisetzen und dahin verschieben, wo Sie sie haben wollen. Das ist eine in der Datei default.c, die wir am Anfang unseres Skriptes per include einbezogen haben, eingebaute Funktion.

Ihre Hausaufgabe besteht darin, solange dieses Level immer wieder laufen zu lassen, bis Sie begriffen haben, wie c_move funktioniert. Was geschieht, wenn Sie die letzte c_move-Anweisung durch die unten ersetzen?

c_move (my, vector(15 * time_step, 0, 4 * time_step), nullvector, GLIDE);

Vielleicht fängt das Auto ja an zu fliegen weil die z-Komponente seiner relative_speed positiv ist? Am besten probieren Sie das selber mal aus. Spielen Sie mit diesem Skript bis sie sicher sind, dass Sie die c_move-Anweisung im Griff haben. Sie werden diese Anweisung in jedem Game anwenden müssen!

Nun werde ich eine Pause machen aber ich erwarte, Sie bei meiner Rückkehr wieder hier vorzufinden..

Weiter: die Physik-Engine


Mehr zum Thema: Gamestudio Handbuch ► c_move