workshop 23

Top  Zurück  Weiter

Weiterführende Modell-Animation. Der Knochensammler

Falls Sie den vorangegangenen Workshop gelesen haben, wissen Sie, dass Modelle mit Hilfe von Frames animiert werden können. Sie brauchen also nur die entsprechenden Frames für Stehen, Gehen, Laufen, Schießen, Springen, Ducken, Sterben usw. zu erstellen. Was aber machen Sie, wenn Sie die Möglichkeit haben wollen, gleichzeitig zu laufen UND zu schießen? Nun, Sie könnten eine weitere Animation erstellen, die beides macht und sie "lauf schieß" oder so ähnlich nennen. Oder aber, Sie nehmen Bones und generieren ausschließlich per lite-C eine beliebige Kombination aus den vorhandenen Animationen wie gleichzeitiges Gehen + Hochsehen oder vielleicht Ducken + Schießen oder... nun, ich habe mich verständlich gemacht.

Starten Sie SED, öffnen Sie workshop23 und starten Sie script23.c. Sie werden so eine seltsame Kreatur wie in der Abbildung unten zu sehen kriegen:

w22_01

Sie merken vermutlich, daß dieser Roboter ganz offensichtlich Produkt der Kunstfertigkeit eines Programmierers ist. Der Roboter spielt seine "walk"-Animation in einer while-Schleife ab. Denken Sie daran, dass Sie, wenn Sie die Taste [0] drücken, die Kamera mit Hilfe von [Home], [End], [PgUp], [PgDn], [<] und [>].bewegen und drehen können. Drücken und halten Sie nun eine Weile lang die [Leertaste]:

w22_02

Der Roboter hat seine Waffen in Richtung der linken Bildschirmseite gedreht. Lassen Sie die [Leertaste] los und die Waffen werden in ihre ursprüngliche Position zurückkehren. Wenn Sie robot.mdl in MED öffnen und seine Animation starten, werden Sie feststellen, daß es sich bei den beiden Animationen ("walk" und "turn") zwei verschiedenen Animationsszenen handelt und unsere Skriptdatei hat sie harmonisch miteinander verbunden. Der Roboter kann gehen und gleichzeitig seine Waffen drehen und das, obwohl wir keine spezielle "walkturn"-Animation benutzt haben!

Die Skript-Datei ist klein genug - sehen Sie selbst:

function bones_test()
{
    my.pan -= 180; // wende dich dem Player zu
    var walk_speed = 0;
    var turn_speed = 0;
    while(1)
    {
       ent_animate(my, NULL, 0, 0); // setze alle Animationen zurück
       walk_speed += 1.5 * time_step;  //erhöhe walk_speed; 1.5 setzt die Geschwindigkeit
       ent_animate(my, "walk", walk_speed, ANM_CYCLE); // animiere das Modell (benutze "walk")
       if (key_space && (turn_speed < 100)) // wurde Leertaste gedrückt?
          turn_speed += 10 * time_step; // dann erhöhe turn_speed
       if (!key_space && (turn_speed > 0)) // Leertaste ist nicht gedrückt?
          turn_speed -= 10 * time_step; // dann verringere turn_speed
       if (turn_speed > 0) // gedreht?
          ent_animate(my, "turn", turn_speed, ANM_ADD); // animiere das Modell (benutze "turn")
   	  wait(1);
   }
}

function main()
{
  level_load ("");	// lade ein leeres Level
	// und erstelle dann jetzt das Roboter-Modell bei x = 400, y = 0, z = -50 Quants
  ent_create("robot.mdl", vector(400, 0, -50), bones_test); 
}

Die Main-Funktion lädt ein leeres Level, wartet bis es geladen ist und erstellt dann ein Roboter-Modell welches es an seine festgesetzte Position im Level gestellt wird und weist ihm die Funktion mit Namen bones_test zu. Diese Funktion zieht 180 Grad vom pan-Winkel des Roboters ab, sodass er in die Kamera schaut und dann startet sie einen while (1)-Loop. Wir wollen den Inhalt dieser Schleife einmal gründlich betrachten:

while(1)
{
 
ent_animate(my, NULL, 0, 0);
  
walk_speed += 1.5 * time_step;
  
ent_animate(my, "walk", walk_speed, ANM_CYCLE);

Im vorangegangenen Workshop haben wir ent_animate besprochen. Die erste Codezeile innerhalb der "while"-Schleife setzt sämtliche Knochen unseres Modells zurück und stellt so ihre Originalposition her. Es ist nichts magisches an dieser Codezeile: den Animationsnamen auf NULL zu setzten funktioniert immer, WENN Ihr Modell Bones verwendet. Vergessen Sie nicht, das das MED-Kapitel im Game-Design-Tutorial zu lesen, damit Sie mehr über das Erstellen von Bones und Bone-Animationen erfahren. Zur Steuerung der Animationsgeschwindigkeit haben wir eine lokale Variable namens walk_speed definiert. Spielen Sie mit der 1.5, wenn Sie die "walk"-Animation für unseren Roboter schneller haben wollen.

Die folgende Codezeile bringt den Roboter dazu seine "walk"- Animation so auszuführen, wie wir das im vorhergehenden Workshop besprochen haben. Ok, betrachten wir uns diese ent_animate-Definition noch einmal, wir brauchen sie ja eh als Vorlage:

ent_animate (entity_name, animation_name, animation_percentage, animation_mode);

In dieser ent_animate-Codezeile ist nichts Ungewöhnliches zu entdecken: sie animiert unser Modell mithilfe seiner "walk"-Animation und mit der von walk_speed gegebenen Geschwindigkeit und aufgrund des Parameters ANM_CYCLE wird die Animation in einer Schleife abgespielt (neudeutsch: 'gelooped'). Besprechen wir nun die nächsten paar Zeilen unsrer Aktion:

if (key_space && (turn_speed < 100))
  
turn_speed += 10 * time_step;
if (!key_space && (turn_speed > 0))
   
turn_speed -= 10 * time_step;

Die obigen Zeilen prüfen, ob wir die [Leertaste] gedrückt haben oder nicht. Wenn wir sie drücken und turn_speed einen Wert unter 100 hat, wird sich sein Wert erhöhen und zwar mit der Geschwindigkeit von ""10 * time_step". Wird die [Leertaste] andererseits nicht gedrückt und ist turn_speed größer als Null, wird sein Wert aufgrund von "10 * time_step" verringert. Diese vier Codezeilen setzen also die Variable namens turn_speed auf einen Wert, der, wenn wir die [Leertaste] drücken und halten, von 0 auf 100 anwachsen kann und verringert turn_speed langsam, wenn wir die [Leertaste] loslassen. Zur Verdeutlichung hier ein Bild:

w22_04

Der Spieler wartet (in diesem Beispiel) eine Sekunde lang und drückt dann die [Leertaste]: "turn_speed" fängt an zu wachsen, indem mit jedem Frame 10 * time_step auf seinen Ausgangswert addiert wird und stoppt, wenn der Wert 100 erreicht, selbst, wenn der Player weiterhin die [Leertaste] gedrückt hält. Nun sind 5 Sekunden vergangen und der Player beschließt, die [Leertaste] loszulassen. Daraufhin fängt "turn_speed" an seinen Wert solange um 10 * time_step pro Frame zu verringern, bis er Null erreicht.

ent_animate(my, "turn", turn_speed, ANM_ADD);
wait(1);
}

Der letzte Teil der Schleife "ent-animiert" (ent_animate) das Modell unter Verwendung dessen "turn"-Animation und dem Modus ANM_ADD.

Dieser Animationsmodus kann nicht für auf Frames basierende Animationen (`Vertex-Animationen´) benutzt werden und daher habe ich mich dazu entschieden, ihn im vorangegangenen Workshop nicht zu erwähnen. Anstatt die älteren Bone-Winkel komplett zu ersetzen, addiert diese Methode die neuen Bone-Winkel auf die alten auf und das bedeutet, dass das Modell auch dann weitergeht, wenn wir zum Auslösen seiner "turn"-Animation die [Leertaste] drücken.

Schauen wir uns die komplette Schleife kurz noch einmal an:

while(1)
{
  ent_animate(my, NULL, 0, 0); // setze alle Animationen zurück
  walk_speed += 1.5 * time_step; // erhöhe walk_speed; 1.5 setzt die Geschwindigkeit
  ent_animate(my, "walk", walk_speed, ANM_CYCLE); // animiere das Modell (benutze "walk")
  if (key_space && (turn_speed < 100)) // wurde Leertaste gedrückt?
      turn_speed += 10 * time_step; // dann erhöhe turn_speed
  if (!key_space && (turn_speed > 0)) // Leertaste ist nicht gedrückt?
      turn_speed -= 10 * time_step; // dann verringere turn_speed
  if (turn_speed > 0) // gedreht?
      ent_animate(my, "turn", turn_speed, ANM_ADD); // animiere das Modell (benutze "turn")
  wait(1);
}

Es gibt in dieser Aktion drei ent_animate-Anweisungen:

- die erste setzt die Animationen (die Bone-Positionen) mit jedem Frame zurück. Achten Sie darauf, dies am Anfang des Loops zu tun

- die zweite Anweisung animiert den Roboter und verwendet dabei seine "walk"-Animation.

- die dritte Anweisung spielt die "turn"-Animation ab indem die neuen Bone-Winkel auf die alten aufaddiert werden.

Der Rest des Codes müsste eigentlich sehr einfach zu verstehen sein.

Der Knochensammler

Jawohl, dieser Abschnitt ist für Sie, den Knochensammler, geschrieben. Sie sind kein normaler Mensch, Sie wollen ein Meister im Lite-C-Programmieren werden und deshalb wollen Sie noch mehr über Bone-Animationen wissen. Wir werden über "ent_bonerotate" und "pose", zwei wichtige Features im Zusammenhang mit Bones, sprechen.

Starten Sie SED und öffnen Sie die Datei namens script23_2.c:

BMAP* crosshair_pcx = "crosshair.pcx"; // die für's Fadenkreuz benutzte bitmap

function rotate_bone()
{
  my.pan = -180; // wende dich der Kamera zu
  var walk_speed = 0; // speichert d. Gehgeschwindigkeit von Robot
  ANGLE bone_angle; // ein Vektor, der die pan-, tilt- und roll-Winkel beinhaltet
  while(1)
  {
     walk_speed += 2 * time_step; // erhöhe walk_speed, "2" setzt die Animationsgeschwindigkeit
     ent_animate(my, "walk", walk_speed, ANM_CYCLE); // animiere das Modell (benutze "walk")
     bone_angle.pan = (mouse_pos.x - screen_size.x / 2) / 10; // ändert pan von -40 zu +40 Grad
     bone_angle.tilt = (screen_size.y / 2 - mouse_pos.y) / 1; 0// ändert tilt von -30 zu +30 Grad
     bone_angle.roll = 0; // für diesen Bone ist ein Ändern d. roll-Winkels nicht nötig
     ent_bonereset(my,"CA4");
     ent_bonerotate(my,"CA4", bone_angle); // drehe den Bone namens "CA4"
     wait(1);	
  }
}

function main()
{
	level_load (""); // lade ein leeres Level
	ent_create("robot.mdl", vector(400, 0, -50), rotate_bone); // und erstelle dann das Roboter-Modell
	mouse_map = crosshair_pcx; // setze die bitmap, die vom Mauszeiger verwendet werden wird
	mouse_mode = 4; // nun zeige den Zeiger
}

Die Funktion main() lädt ein leeres Level, erstellt das Roboter-Modell, setzt die Bitmap für den Maus-Pointer auf crosshair_pcx und macht den Cursor sichtbar. . Die "while (1)"-Schleife updated mit jedem Frame die Position des Pointers. Wenn Sie den 14. Workshop durchgearbeitet haben, wissen Sie wie die Maus funktioniert.

Untersuchen wir die neue Bone-Funktion:

  while(1)
  {
    walk_speed += 2 * time_step;
    ent_animate(my, "walk", walk_speed, ANM_CYCLE);
    bone_angle.pan = (mouse_pos.x - screen_size.x/2) / 10;
    bone_angle.tilt = (screen_size.y/2 - mouse_pos.y) / 10;
    bone_angle.roll = 0;
    ent_bonereset(my,"CA4");
    ent_bonerotate(my,"CA4", bone_angle);
    wait(1);
 }

Der Code beginnt genauso wie im vorherigen Workshop: wir sorgen dafür, daß der Roboter in Richtung Kamera schaut und dann spielen wir seine "walk"-Animation ab. Da unser Animationsmodus nicht ANM_ADD sondern ANM_CYCLE ist, brauchen wir die Bones dieses Mal nicht zurückzusetzen. Die folgenden 3 Zeilen berechnen die benötigten Winkel wozu sie die Mausposition benutzen:

bone_angle.pan = (mouse_pos.x - screen_size.x / 2) / 10;
bone_angle.tilt = (screen_size.y / 2 - mouse_pos.y) / 10;
bone_angle.roll = 0;

Wir haben einen ANGLE-(Winkel)Vektor namens bone_angle definiert. Ein ANGLE ist lediglich ein normaler Verktor, seine 3 Komponenten heißen aber pan, tilt, roll und nicht x, y, z. Betrachten wir einmal wie sie berechnet werden.

w23_03

Wir haben die Video-Auflösung für dieses Demo auf 800 x 600 Pixel gesetzt. Die x-Position des Mauszeigers (mouse_pos.x) kann also einen Wert zwischen 0 (linke Bildschirmseite) und 800 Pixel (rechte Bildschirmseite) haben. Unsere Formel zieht von mouse_pos.x die Hälfte des horizontalen Bildschirms (400) ab und setzt damit einen Wert, der irgendwo zwischen - 400 und 400 liegen kann. Dann dividiert sie das Ergebnis durch 10. Das heißt, unser bone_angle (Knochenwinkel) kann, wenn wir die Maus an der x-Achse entlang bewegen, seinen pan-Winkel im Bereich von (-400... +400) / 10 = (-40... +40) Grad verändern.

Die Formel zum Berechnen des tilt-Winkels ist ein wenig anders, denn die y-Koordinate ist umkehrbar (s. Abb. oben). Wir müssen mouse_pos.y von der Hälfte der vertikalen Bildschirmgröße abziehen, damit wir einen Wert zwischen -300 (Grundlinie des Bildschirms) und +300 (oberer Bildschirmrand) erhalten. Das Ergebnis wird durch 10 geteilt und so erhält der tilt-Winkel einen Wert zwischen -30 und +30 Grad, wenn der Player die Maus an der y-Achse entlang bewegt.

Ich habe beschlossen für die beiden Winkel, pan auf -40...+40 Grad zu begrenzen und tilt auf -30... +30, Sie können jedoch irgendwelche anderen Werte nehmen indem Sie mit der "10" herumspielen. Sind Sie nicht auch neugierig darauf, den Roboter in Aktion zu sehen? Starten Sie die Skriptdatei und Sie werden sehen, er folgt mit seinen Kanonen dem Mauszeiger.

Nun, da wir die benötigten pan- und tilt-Winkel berechnet haben, können wir sie zum Drehen des Geschützturms verwenden:

ent_bonereset(my,"CA4");
ent_bonerotate(my,"CA4", bone_angle);

Die erste Codezeile setzt den Gewehr-Bone mit dem in MED erhaltenen Namen "CA4" auf seine Default-Ausrichtung und -Winkel in Relation zu seinem Parent-Bone zurück. Die nächste Zeile dreht den Bone, wozu sie unsere vorher berechneten, in bone_angle gespeicherten pan- und tilt-Winkel benutzt. Hier die Default-Position des Bones "CA4" für unser robot.mdl in MED:

w23_02

 

Werfen wir einen Blick auf die allgemeine Formel der Anweisung:

ent_bonerotate(entity_name, bone_name, rotation_angle);

- "entity_name" ist der Name der Entity;

- "bone_name" ist der Name des Bones, der gedreht werden wird;

- "rotation_angle" ist ein Vektor, der die pan-, tilt- und roll-Winkel beinhaltet.

Warum müssen wir die Bones zuerst zurücksetzen? Nehmen wir an, daß bone_angle.pan in einem bestimmten Moment auf 30 Grad gesetzt ist. Dann, stellen wir uns vor, der Player bewegt die Maus ein wenig weiter in den folgenden Frame bis bone_angle.pan 35 Grad erreicht hat. Die Kanone war aber schon im vorangegangenen Frame um 30 Grad gedreht worden, so dass er sich nun um weitere 35 Grad drehen und den pan-Winkel auf 30 + 35 = 65 Grad setzen würde!

Dies geschähe, würden wir diese "ent_bonereset"-Anweisung nicht innerhalb der while-Schleife anwenden. Indem wir ent_bonereset benutzen, entledigen wir uns jedoch der alten Winkel mit jedem Frame und so bekommt der Bone nur die neu berechneten Winkel (in meinem Beispiel pan = 35 Grad). Nun, wir brauchen ent_bonereset hier nicht wirklich, denn sie "walk"-Animation setzt ja schon den Bone der Kanone zurück. Benutzen Sie aber andere Entities mit anderen Animationen, könnte dies jedoch nicht der Fall sein!

Nun, da Sie wissen wie man mit ent_bonerotate arbeitet, bin ich mir sicher, Sie werden diese Anweisung als sehr hilfreich für Ihre Projekte erleben: Ihre Gegner können ihre Gewehre auf Sie richten, Ihre Ninja-Krieger werden sich bewegen und ihre Körper drehen sich, wenn Sie getroffen sind etc.

Weiter: Die duellierenden Zauberer